diff --git a/.hgtags b/.hgtags index 67d680e02..69c60352e 100644 --- a/.hgtags +++ b/.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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 2386770d7..b1d3a3f39 100755 --- a/CMakeLists.txt +++ b/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) diff --git a/changes.txt b/changes.txt index 6d91ec59a..ca9c2f561 100644 --- a/changes.txt +++ b/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"). diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index 70c7975b7..914319054 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/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 = ""; }; FA41A3C71C0A1F950084430C /* ASTCHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTCHandler.h; sourceTree = ""; }; FA4B66C81ABBCF1900558F15 /* Timer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Timer.cpp; sourceTree = ""; }; + FA4F2B771DE0125B00CA37D7 /* xxhash.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xxhash.c; sourceTree = ""; }; + FA4F2B781DE0125B00CA37D7 /* xxhash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xxhash.h; sourceTree = ""; }; + FA4F2BA21DE1E36400CA37D7 /* RecordingDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecordingDevice.cpp; sourceTree = ""; }; + FA4F2BA31DE1E36400CA37D7 /* RecordingDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecordingDevice.h; sourceTree = ""; }; + FA4F2BA41DE1E36400CA37D7 /* wrap_RecordingDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_RecordingDevice.cpp; sourceTree = ""; }; + FA4F2BA51DE1E36400CA37D7 /* wrap_RecordingDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_RecordingDevice.h; sourceTree = ""; }; + FA4F2BAA1DE1E37000CA37D7 /* RecordingDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecordingDevice.cpp; sourceTree = ""; }; + FA4F2BAB1DE1E37000CA37D7 /* RecordingDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecordingDevice.h; sourceTree = ""; }; + FA4F2BAE1DE1E37B00CA37D7 /* RecordingDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecordingDevice.cpp; sourceTree = ""; }; + FA4F2BAF1DE1E37B00CA37D7 /* RecordingDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecordingDevice.h; sourceTree = ""; }; + FA4F2BDF1DE6650600CA37D7 /* Transform.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Transform.cpp; sourceTree = ""; }; + FA4F2BE01DE6650600CA37D7 /* Transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Transform.h; sourceTree = ""; }; + FA4F2BE11DE6650600CA37D7 /* wrap_Transform.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Transform.cpp; sourceTree = ""; }; + FA4F2BE21DE6650600CA37D7 /* wrap_Transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Transform.h; sourceTree = ""; }; FA56D9BA1C2089EE00D8D3C7 /* libmodplug.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libmodplug.a; sourceTree = ""; }; FA577A6716C719D900860150 /* FreeType.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FreeType.framework; path = /Library/Frameworks/FreeType.framework; sourceTree = ""; }; FA577A6D16C719EA00860150 /* Lua.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Lua.framework; path = /Library/Frameworks/Lua.framework; sourceTree = ""; }; @@ -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 = ""; @@ -2890,6 +2956,15 @@ path = theora; sourceTree = ""; }; + FA4F2B761DE0125B00CA37D7 /* xxHash */ = { + isa = PBXGroup; + children = ( + FA4F2B771DE0125B00CA37D7 /* xxhash.c */, + FA4F2B781DE0125B00CA37D7 /* xxhash.h */, + ); + path = xxHash; + sourceTree = ""; + }; 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, diff --git a/platform/xcode/love.xcodeproj/project.pbxproj b/platform/xcode/love.xcodeproj/project.pbxproj index 496c34afe..bc38e00b6 100644 --- a/platform/xcode/love.xcodeproj/project.pbxproj +++ b/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, ); diff --git a/src/common/Matrix.cpp b/src/common/Matrix.cpp index 9e6077fa9..3cb9b3013 100644 --- a/src/common/Matrix.cpp +++ b/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) { diff --git a/src/common/Matrix.h b/src/common/Matrix.h index 98945adc8..2e62b12b8 100644 --- a/src/common/Matrix.h +++ b/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 void transform(V *dst, const V *src, int size) const; + /** + * Computes and returns the inverse of the matrix. + **/ Matrix4 inverse() const; /** diff --git a/src/libraries/xxHash/xxhash.c b/src/libraries/xxHash/xxhash.c new file mode 100755 index 000000000..f74880b0d --- /dev/null +++ b/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 +static void* XXH_malloc(size_t s) { return malloc(s); } +static void XXH_free (void* p) { free(p); } +/*! and for memcpy() */ +#include +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 + 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> 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> 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 + 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> 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> 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 */ diff --git a/src/libraries/xxHash/xxhash.h b/src/libraries/xxHash/xxhash.h new file mode 100755 index 000000000..9d831e03b --- /dev/null +++ b/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 /* 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 */ diff --git a/src/modules/audio/Audio.h b/src/modules/audio/Audio.h index 0a114a8ad..811845bd4 100644 --- a/src/modules/audio/Audio.h +++ b/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 &getRecordingDevices() = 0; /** * Gets the distance model used for attenuation. diff --git a/src/modules/audio/RecordingDevice.cpp b/src/modules/audio/RecordingDevice.cpp new file mode 100644 index 000000000..4f61f1b84 --- /dev/null +++ b/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 diff --git a/src/modules/audio/RecordingDevice.h b/src/modules/audio/RecordingDevice.h new file mode 100644 index 000000000..95a61b00b --- /dev/null +++ b/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 + +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 diff --git a/src/modules/audio/null/Audio.cpp b/src/modules/audio/null/Audio.cpp index b2a0ffe4a..9db2e67b1 100644 --- a/src/modules/audio/null/Audio.cpp +++ b/src/modules/audio/null/Audio.cpp @@ -144,23 +144,9 @@ float Audio::getDopplerScale() const return 1.0f; } -void Audio::record() +const std::vector &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 diff --git a/src/modules/audio/null/Audio.h b/src/modules/audio/null/Audio.h index e4143d447..fdd8325fc 100644 --- a/src/modules/audio/null/Audio.h +++ b/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 &getRecordingDevices(); DistanceModel getDistanceModel() const; void setDistanceModel(DistanceModel distanceModel); @@ -81,6 +79,7 @@ public: private: float volume; DistanceModel distanceModel; + std::vector capture; }; // Audio diff --git a/src/modules/audio/null/RecordingDevice.cpp b/src/modules/audio/null/RecordingDevice.cpp new file mode 100644 index 000000000..94af141e7 --- /dev/null +++ b/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 diff --git a/src/modules/audio/null/RecordingDevice.h b/src/modules/audio/null/RecordingDevice.h new file mode 100644 index 000000000..11294d75c --- /dev/null +++ b/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 diff --git a/src/modules/audio/openal/Audio.cpp b/src/modules/audio/openal/Audio.cpp index c3b1bd05e..920197d05 100644 --- a/src/modules/audio/openal/Audio.cpp +++ b/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 +#include 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 &Audio::getRecordingDevices() +{ + std::vector devnames; + std::vector 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 diff --git a/src/modules/audio/openal/Audio.h b/src/modules/audio/openal/Audio.h index b676abf8c..aaa675fdc 100644 --- a/src/modules/audio/openal/Audio.h +++ b/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 &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 capture; // The OpenAL context. ALCcontext *context; diff --git a/src/modules/audio/openal/RecordingDevice.cpp b/src/modules/audio/openal/RecordingDevice.cpp new file mode 100644 index 000000000..db59f4621 --- /dev/null +++ b/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(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 diff --git a/src/modules/audio/openal/RecordingDevice.h b/src/modules/audio/openal/RecordingDevice.h new file mode 100644 index 000000000..4092d1f21 --- /dev/null +++ b/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 +#include +#else +#include +#include +#endif +#else +#include +#include +#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 diff --git a/src/modules/audio/openal/Source.cpp b/src/modules/audio/openal/Source.cpp index 0b904d87a..448661d0c 100644 --- a/src/modules/audio/openal/Source.cpp +++ b/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 &sources, const std::vector &ids, const std::vector &wasPlaying) @@ -941,7 +968,7 @@ void Source::pauseAtomic(const std::vector &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; diff --git a/src/modules/audio/openal/Source.h b/src/modules/audio/openal/Source.h index 9bddfb73d..680cf5ddb 100644 --- a/src/modules/audio/openal/Source.h +++ b/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(); diff --git a/src/modules/audio/wrap_Audio.cpp b/src/modules/audio/wrap_Audio.cpp index 3bbd5b974..46ff0169c 100644 --- a/src/modules/audio/wrap_Audio.cpp +++ b/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 &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 }; diff --git a/src/modules/audio/wrap_Audio.h b/src/modules/audio/wrap_Audio.h index 0f358517d..3990f29d6 100644 --- a/src/modules/audio/wrap_Audio.h +++ b/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 { diff --git a/src/modules/audio/wrap_RecordingDevice.cpp b/src/modules/audio/wrap_RecordingDevice.cpp new file mode 100644 index 000000000..7450b1f64 --- /dev/null +++ b/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(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 diff --git a/src/modules/audio/wrap_RecordingDevice.h b/src/modules/audio/wrap_RecordingDevice.h new file mode 100644 index 000000000..d9dc9c2e4 --- /dev/null +++ b/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 + diff --git a/src/modules/event/sdl/Event.cpp b/src/modules/event/sdl/Event.cpp index 15c23db59..2b602ad62 100644 --- a/src/modules/event/sdl/Event.cpp +++ b/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(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; diff --git a/src/modules/event/sdl/Event.h b/src/modules/event/sdl/Event.h index 9ab44174e..978dd4566 100644 --- a/src/modules/event/sdl/Event.h +++ b/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; diff --git a/src/modules/event/wrap_Event.cpp b/src/modules/event/wrap_Event.cpp index f8b6f2402..eec4e1998 100644 --- a/src/modules/event/wrap_Event.cpp +++ b/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; } diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 253bc13b3..c2271be8a 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -260,6 +260,8 @@ StringMap::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::features(Graphics::featureEntries, sizeof(Graphics::featureEntries)); @@ -270,6 +272,7 @@ StringMap::Entry Graphics::syst { "texturesize", LIMIT_TEXTURE_SIZE }, { "multicanvas", LIMIT_MULTI_CANVAS }, { "canvasmsaa", LIMIT_CANVAS_MSAA }, + { "anisotropy", LIMIT_ANISOTROPY }, }; StringMap Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries)); diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 6c59d1271..cb0cac409 100644 --- a/src/modules/graphics/Graphics.h +++ b/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); diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index e2a986fba..e6dccdba5 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/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, ¤t_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(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 &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 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 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); diff --git a/src/modules/graphics/opengl/Canvas.h b/src/modules/graphics/opengl/Canvas.h index da6f63547..ab81057d9 100644 --- a/src/modules/graphics/opengl/Canvas.h +++ b/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 &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 &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 attachedCanvases; - int requested_samples; int actual_samples; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 7635853cf..1408e3373 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -27,6 +27,9 @@ #include "font/Font.h" #include "Polyline.h" #include "math/MathModule.h" +#include "window/Window.h" + +#include "libraries/xxHash/xxhash.h" // C++ #include @@ -50,28 +53,34 @@ namespace opengl { Graphics::Graphics() - : currentWindow(Module::getInstance(Module::M_WINDOW)) - , quadIndices(nullptr) + : quadIndices(nullptr) , width(0) , height(0) , created(false) , active(true) + , canCaptureScreenshot(true) + , currentPass() , writingToStencil(false) + , renderPassCount(0) { gl = OpenGL(); states.reserve(10); states.push_back(DisplayState()); - if (currentWindow.get()) + auto window = getInstance(M_WINDOW); + + if (window != nullptr) { - int w, h; - love::window::WindowSettings wsettings; + window->setGraphics(this); - currentWindow->getWindow(w, h, wsettings); + if (window->isOpen()) + { + int w = 0, h = 0; + window->getPixelDimensions(w, h); - if (currentWindow->isOpen()) setMode(w, h); + } } } @@ -123,7 +132,6 @@ void Graphics::restoreState(const DisplayState &s) setFont(s.font.get()); setShader(s.shader.get()); - setCanvas(s.canvases); setColorMask(s.colorMask); setWireframe(s.wireframe); @@ -166,20 +174,6 @@ void Graphics::restoreStateChecked(const DisplayState &s) setFont(s.font.get()); setShader(s.shader.get()); - bool canvaseschanged = s.canvases.size() != cur.canvases.size(); - - for (size_t i = 0; i < s.canvases.size() && i < cur.canvases.size(); i++) - { - if (s.canvases[i].get() != cur.canvases[i].get()) - { - canvaseschanged = true; - break; - } - } - - if (canvaseschanged) - setCanvas(s.canvases); - if (s.colorMask != cur.colorMask) setColorMask(s.colorMask); @@ -217,32 +211,18 @@ void Graphics::setViewportSize(int width, int height) this->width = width; this->height = height; - if (!isCreated()) - return; + if (currentPass.active && currentPass.info.colorAttachmentCount == 0) + { + // Set the viewport to top-left corner. + gl.setViewport({0, 0, width, height}, false); - // We want to affect the main screen, not any Canvas that's currently active - // (not that any *should* be active when this is called.) - std::vector> canvases = states.back().canvases; - setCanvas(); - - // Set the viewport to top-left corner. - gl.setViewport({0, 0, width, height}); - - // If a canvas was bound before this function was called, it needs to be - // made aware of the new system viewport size. - Canvas::systemViewport = gl.getViewport(); - - // Set up the projection matrix - gl.matrices.projection.back() = Matrix4::ortho(0.0, (float) width, (float) height, 0.0); - - // Restore the previously active Canvas. - setCanvas(canvases); + // Set up the projection matrix + gl.matrices.projection = Matrix4::ortho(0.0, (float) width, (float) height, 0.0); + } } bool Graphics::setMode(int width, int height) { - currentWindow.set(Module::getInstance(Module::M_WINDOW)); - this->width = width; this->height = height; @@ -282,8 +262,6 @@ bool Graphics::setMode(int width, int height) else setGammaCorrect(false); - Canvas::screenHasSRGB = isGammaCorrect(); - bool enabledebug = false; if (GLAD_VERSION_3_0) @@ -313,9 +291,9 @@ bool Graphics::setMode(int width, int height) // Restore the graphics state. restoreState(states.back()); - pixelSizeStack.clear(); - pixelSizeStack.reserve(5); - pixelSizeStack.push_back(1); + pixelScaleStack.clear(); + pixelScaleStack.reserve(5); + pixelScaleStack.push_back(1); int gammacorrect = isGammaCorrect() ? 1 : 0; @@ -350,6 +328,15 @@ void Graphics::unSetMode() // mode change. Volatile::unloadAll(); + for (const auto &pair : framebufferObjects) + gl.deleteFramebuffer(pair.second); + + for (const CachedRenderbuffer &rb : stencilBuffers) + glDeleteRenderbuffers(1, &rb.renderbuffer); + + framebufferObjects.clear(); + stencilBuffers.clear(); + gl.deInitContext(); created = false; @@ -369,7 +356,8 @@ bool Graphics::isActive() const { // The graphics module is only completely 'active' if there's a window, a // context, and the active variable is set. - return active && isCreated() && currentWindow.get() && currentWindow->isOpen(); + auto window = getInstance(M_WINDOW); + return active && isCreated() && window != nullptr && window->isOpen(); } static void APIENTRY debugCB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei /*len*/, const GLchar *msg, const GLvoid* /*usr*/) @@ -439,91 +427,181 @@ void Graphics::reset() origin(); } -void Graphics::clear(Colorf c) +void Graphics::beginPass(PassInfo::BeginAction beginAction, Colorf clearColor) { - gammaCorrectColor(c); + if (currentPass.active) + throw love::Exception("Cannot call beginPass while another render pass is active!"); - glClearColor(c.r, c.g, c.b, c.a); - glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + currentPass.active = true; - if (gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current) + OpenGL::TempDebugGroup debuggroup("Render Pass begin"); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); + + gl.setViewport({0, 0, width, height}, false); + + // The projection matrix is flipped compared to rendering to a canvas, due + // to OpenGL considering (0,0) bottom-left instead of top-left. + gl.matrices.projection = Matrix4::ortho(0.0, (float) width, (float) height, 0.0); + + if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control) { - // This seems to be enough to fix the bug for me. Other methods I've - // tried (e.g. dummy draws) don't work in all cases. - gl.useProgram(0); - gl.useProgram(Shader::current->getProgram()); + if (isGammaCorrect() && !gl.hasFramebufferSRGB()) + gl.setFramebufferSRGB(true); + else if (!isGammaCorrect() && gl.hasFramebufferSRGB()) + gl.setFramebufferSRGB(false); } + + // Always clear the stencil buffer, for now. + GLbitfield clearflags = GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT; + + if (beginAction == PassInfo::BEGIN_CLEAR) + { + Colorf c = clearColor; + gammaCorrectColor(c); + + glClearColor(c.r, c.g, c.b, c.a); + clearflags |= GL_COLOR_BUFFER_BIT; + } + + if (clearflags != 0) + glClear(clearflags); + + PassInfo info; + info.colorAttachmentCount = 0; + info.stencil = true; + + currentPass.info = info; + + renderPassCount++; + + canCaptureScreenshot = false; } -void Graphics::clear(const std::vector &colors) +void Graphics::beginPass(const PassInfo &info) { - if (colors.size() == 0) - return; + if (info.colorAttachmentCount == 0) + throw love::Exception("At least one Canvas must be specified for an off-screen render pass."); - size_t numcanvases = states.back().canvases.size(); + if (currentPass.active) + throw love::Exception("Cannot call beginPass while another render pass is active!"); - if (numcanvases > 0 && colors.size() != numcanvases) - throw love::Exception("Number of clear colors must match the number of active canvases (%ld)", states.back().canvases.size()); + if (info.colorAttachmentCount > gl.getMaxRenderTargets()) + throw love::Exception("This system can't simultaneously render to %d canvases.", info.colorAttachmentCount); - // We want to take the single-color codepath if there's no active Canvas, or - // if there's only one active Canvas. The multi-color codepath (in the loop - // below) assumes MRT functions are available, and also may call more - // expensive GL functions which are unnecessary if only one Canvas is active. - if (numcanvases <= 1) + Canvas *firstcanvas = info.colorAttachments[0].canvas; + + bool multiformatsupported = isSupported(Feature::FEATURE_MULTI_CANVAS_FORMATS); + Canvas::Format firstformat = firstcanvas->getTextureFormat(); + + bool hasSRGBcanvas = Canvas::getSizedFormat(firstformat) == Canvas::FORMAT_SRGB; + + for (int i = 1; i < info.colorAttachmentCount; i++) { - if (colors[0].enabled) - clear(colors[0].toColor()); + Canvas *c = info.colorAttachments[i].canvas; - return; + if (c->getWidth() != firstcanvas->getWidth() || c->getHeight() != firstcanvas->getHeight()) + throw love::Exception("All canvases in a render pass must have the same dimensions."); + + if (!multiformatsupported && c->getTextureFormat() != firstformat) + throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats."); + + if (c->getRequestedMSAA() != firstcanvas->getRequestedMSAA()) + throw love::Exception("All Canvases in a render pass must have the same requested MSAA value."); + + if (Canvas::getSizedFormat(c->getTextureFormat()) == Canvas::FORMAT_SRGB) + hasSRGBcanvas = true; } - bool drawbuffermodified = false; + OpenGL::TempDebugGroup debuggroup("Render Pass begin"); - for (int i = 0; i < (int) colors.size(); i++) + bindCachedFBOForPass(info); + + currentPass.active = true; + + int w = firstcanvas->getWidth(); + int h = firstcanvas->getHeight(); + + gl.setViewport({0, 0, w, h}, true); + gl.matrices.projection = Matrix4::ortho(0.0, (float) w, 0.0, (float) h); + + // Make sure the correct sRGB setting is used when drawing to the canvases. + if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control) { - if (!colors[i].enabled) - continue; - - GLfloat c[] = {colors[i].r, colors[i].g, colors[i].b, colors[i].a}; - - // TODO: Investigate a potential bug on AMD drivers in Windows/Linux - // which apparently causes the clear color to be incorrect when mixed - // sRGB and linear render targets are active. - if (isGammaCorrect()) - { - for (int i = 0; i < 3; i++) - c[i] = math::gammaToLinear(c[i]); - } - - if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0) - glClearBufferfv(GL_COLOR, i, c); - else - { - glDrawBuffer(GL_COLOR_ATTACHMENT0 + i); - glClearColor(c[0], c[1], c[2], c[3]); - glClear(GL_COLOR_BUFFER_BIT); - - drawbuffermodified = true; - } + if (hasSRGBcanvas && !gl.hasFramebufferSRGB()) + gl.setFramebufferSRGB(true); + else if (!hasSRGBcanvas && gl.hasFramebufferSRGB()) + gl.setFramebufferSRGB(false); } - glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + GLbitfield clearflags = 0; - // Revert to the expected draw buffers once we're done, if glClearBuffer - // wasn't supported. - if (drawbuffermodified) + // Take a single-color clear codepath if there's only one specified Canvas. + // The multi-color codepath (in the loop below) assumes MRT functions are + // available, and also may call more expensive GL functions which are + // unnecessary if only one Canvas is used. + if (info.colorAttachmentCount <= 1) { - std::vector bufs; + if (info.colorAttachmentCount > 0 && info.colorAttachments[0].beginAction == PassInfo::BEGIN_CLEAR) + { + clearflags |= GL_COLOR_BUFFER_BIT; + Colorf c = info.colorAttachments[0].clearColor; + gammaCorrectColor(c); + glClearColor(c.r, c.g, c.b, c.a); + } + } + else + { + bool drawbuffermodified = false; - for (int i = 0; i < (int) states.back().canvases.size(); i++) - bufs.push_back(GL_COLOR_ATTACHMENT0 + i); + for (int i = 0; i < info.colorAttachmentCount; i++) + { + if (info.colorAttachments[i].beginAction == PassInfo::BEGIN_CLEAR) + { + Colorf c = info.colorAttachments[i].clearColor; + gammaCorrectColor(c); + + if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0) + { + const GLfloat carray[] = {c.r, c.g, c.b, c.a}; + glClearBufferfv(GL_COLOR, i, carray); + } + else + { + glDrawBuffer(GL_COLOR_ATTACHMENT0 + i); + glClearColor(c.r, c.g, c.b, c.a); + glClear(GL_COLOR_BUFFER_BIT); + + drawbuffermodified = true; + } + } + } + + // Revert to the expected draw buffers once we're done, if glClearBuffer + // wasn't supported. + if (drawbuffermodified) + { + std::vector bufs; + + for (int i = 0; i < info.colorAttachmentCount; i++) + bufs.push_back(GL_COLOR_ATTACHMENT0 + i); - if (bufs.size() > 1) glDrawBuffers((int) bufs.size(), &bufs[0]); - else - glDrawBuffer(GL_COLOR_ATTACHMENT0); + } } + if (info.stencil) + clearflags |= GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT; + + if (clearflags != 0) + glClear(clearflags); + + for (int i = 0; i < info.colorAttachmentCount; i++) + info.colorAttachments[i].canvas->retain(); + + currentPass.info = info; + renderPassCount++; + if (gl.bugs.clearRequiresDriverTextureStateUpdate && Shader::current) { // This seems to be enough to fix the bug for me. Other methods I've @@ -533,21 +611,146 @@ void Graphics::clear(const std::vector &colors) } } -void Graphics::discard(const std::vector &colorbuffers, bool stencil) +void Graphics::endPass() +{ + endPass(0, 0, 0, 0, nullptr, nullptr); +} + +void Graphics::endPass(int sX, int sY, int sW, int sH, const ScreenshotInfo *info, void *screenshotCallbackData) +{ + if (!currentPass.active) + return; // Should this error instead? + + StrongRef imagedata; + + auto &attachments = currentPass.info.colorAttachments; + int attachmentcount = currentPass.info.colorAttachmentCount; + + if (info != nullptr) + { + if (attachmentcount == 0) + throw love::Exception("Use captureScreenshot to capture the main screen's contents."); + + if (sX < 0 || sY < 0 || sW <= 0 || sH <= 0 || (sX + sW) > getPassWidth() || (sY + sH) > getPassHeight()) + throw love::Exception("Invalid rectangle dimensions."); + + auto imagemodule = Module::getInstance(M_IMAGE); + + if (imagemodule == nullptr) + throw love::Exception("The love.image module must be loaded to capture a Canvas' contents."); + + image::ImageData::Format format; + switch (Canvas::getSizedFormat(attachments[0].canvas->getTextureFormat())) + { + case Canvas::FORMAT_RGB10A2: // FIXME: Conversions aren't supported in GLES + format = image::ImageData::FORMAT_RGBA16; + break; + case Canvas::FORMAT_R16F: + case Canvas::FORMAT_RG16F: + case Canvas::FORMAT_RGBA16F: + case Canvas::FORMAT_RG11B10F: // FIXME: Conversions aren't supported in GLES + format = image::ImageData::FORMAT_RGBA16F; + break; + case Canvas::FORMAT_R32F: + case Canvas::FORMAT_RG32F: + case Canvas::FORMAT_RGBA32F: + format = image::ImageData::FORMAT_RGBA32F; + break; + default: + format = image::ImageData::FORMAT_RGBA8; + break; + } + + imagedata.set(imagemodule->newImageData(sW, sH, format), Acquire::NORETAIN); + } + + if (currentPass.info.stencil) + discard(OpenGL::FRAMEBUFFER_ALL, {}, true); + + if (attachmentcount > 0 && attachments[0].canvas->getMSAA() > 1) + { + int w = attachments[0].canvas->getWidth(); + int h = attachments[0].canvas->getHeight(); + + for (int i = 0; i < attachmentcount; i++) + { + glReadBuffer(GL_COLOR_ATTACHMENT0 + i); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, attachments[i].canvas->getFBO()); + + if (GLAD_APPLE_framebuffer_multisample) + glResolveMultisampleFramebufferAPPLE(); + else + glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + } + + if (info != nullptr) + { + if (attachments[0].canvas->getMSAA() > 1) + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_READ, attachments[0].canvas->getFBO()); + else if (attachmentcount > 1) + glReadBuffer(GL_COLOR_ATTACHMENT0); + + GLenum datatype; + switch (imagedata->getFormat()) + { + case image::ImageData::FORMAT_RGBA16: + datatype = GL_UNSIGNED_SHORT; + break; + case image::ImageData::FORMAT_RGBA16F: + datatype = GL_HALF_FLOAT; + break; + case image::ImageData::FORMAT_RGBA32F: + datatype = GL_FLOAT; + break; + default: + datatype = GL_UNSIGNED_BYTE; + break; + } + + glReadPixels(sX, sY, sW, sH, GL_RGBA, datatype, imagedata->getData()); + + info->callback(imagedata, info->ref, screenshotCallbackData); + } + + for (int i = 0; i < currentPass.info.colorAttachmentCount; i++) + currentPass.info.colorAttachments[i].canvas->release(); + + currentPass.active = false; +} + +const PassInfo &Graphics::getActivePass() const +{ + return currentPass.info; +} + +bool Graphics::isPassActive() const +{ + return currentPass.active; +} + +void Graphics::discard(OpenGL::FramebufferTarget target, const std::vector &colorbuffers, bool depthstencil) { if (!(GLAD_VERSION_4_3 || GLAD_ARB_invalidate_subdata || GLAD_ES_VERSION_3_0 || GLAD_EXT_discard_framebuffer)) return; + GLenum gltarget = GL_FRAMEBUFFER; + if (target == OpenGL::FRAMEBUFFER_READ) + gltarget = GL_READ_FRAMEBUFFER; + else if (target == OpenGL::FRAMEBUFFER_DRAW) + gltarget = GL_DRAW_FRAMEBUFFER; + std::vector attachments; attachments.reserve(colorbuffers.size()); // glDiscardFramebuffer uses different attachment enums for the default FBO. - if (!Canvas::current && gl.getDefaultFBO() == 0) + if (currentPass.info.colorAttachmentCount == 0 && gl.getDefaultFBO() == 0) { if (colorbuffers.size() > 0 && colorbuffers[0]) attachments.push_back(GL_COLOR); - if (stencil) + if (depthstencil) { attachments.push_back(GL_STENCIL); attachments.push_back(GL_DEPTH); @@ -555,9 +758,7 @@ void Graphics::discard(const std::vector &colorbuffers, bool stencil) } else { - int rendertargetcount = 1; - if (Canvas::current) - rendertargetcount = (int) states.back().canvases.size(); + int rendertargetcount = std::max(currentPass.info.colorAttachmentCount, 1); for (int i = 0; i < (int) colorbuffers.size(); i++) { @@ -565,7 +766,7 @@ void Graphics::discard(const std::vector &colorbuffers, bool stencil) attachments.push_back(GL_COLOR_ATTACHMENT0 + i); } - if (stencil) + if (depthstencil) { attachments.push_back(GL_STENCIL_ATTACHMENT); attachments.push_back(GL_DEPTH_ATTACHMENT); @@ -574,22 +775,264 @@ void Graphics::discard(const std::vector &colorbuffers, bool stencil) // Hint for the driver that it doesn't need to save these buffers. if (GLAD_VERSION_4_3 || GLAD_ARB_invalidate_subdata || GLAD_ES_VERSION_3_0) - glInvalidateFramebuffer(GL_FRAMEBUFFER, (GLint) attachments.size(), &attachments[0]); + glInvalidateFramebuffer(gltarget, (GLint) attachments.size(), &attachments[0]); else if (GLAD_EXT_discard_framebuffer) - glDiscardFramebufferEXT(GL_FRAMEBUFFER, (GLint) attachments.size(), &attachments[0]); + glDiscardFramebufferEXT(gltarget, (GLint) attachments.size(), &attachments[0]); } -void Graphics::present() +void Graphics::bindCachedFBOForPass(const PassInfo &pass) +{ + PassBufferInfo info; + memset(&info, 0, sizeof(PassBufferInfo)); + + info.stencil = pass.stencil; + + int ncanvases = pass.colorAttachmentCount; + + for (int i = 0; i < ncanvases; i++) + info.canvases[i] = pass.colorAttachments[i].canvas; + + uint32 hash = XXH32(&info, offsetof(PassBufferInfo, canvases) + ncanvases * sizeof(Canvas *), 0); + + GLuint fbo = framebufferObjects[hash]; + + if (fbo != 0) + { + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); + } + else + { + int w = info.canvases[0]->getWidth(); + int h = info.canvases[0]->getHeight(); + int msaa = std::max(info.canvases[0]->getMSAA(), 1); + + glGenFramebuffers(1, &fbo); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, fbo); + + std::vector drawbuffers; + drawbuffers.reserve(ncanvases); + + for (int i = 0; i < ncanvases; i++) + { + drawbuffers.push_back(GL_COLOR_ATTACHMENT0 + i); + + if (msaa > 1) + { + GLuint rbo = (GLuint) info.canvases[i]->getMSAAHandle(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, drawbuffers[i], GL_RENDERBUFFER, rbo); + } + else + { + GLuint tex = *(GLuint *) info.canvases[i]->getHandle(); + glFramebufferTexture2D(GL_FRAMEBUFFER, drawbuffers[i], GL_TEXTURE_2D, tex, 0); + } + } + + if (drawbuffers.size() > 1) + glDrawBuffers(1, &drawbuffers[0]); + + GLuint stencil = attachCachedStencilBuffer(w, h, msaa); + + if (stencil == 0) + { + gl.deleteFramebuffer(fbo); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); + throw love::Exception("Could not create stencil buffer!"); + } + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + gl.deleteFramebuffer(fbo); + const char *sstr = OpenGL::framebufferStatusString(status); + throw love::Exception("Could not create Framebuffer Object! %s", sstr); + } + + framebufferObjects[hash] = fbo; + } +} + +GLuint Graphics::attachCachedStencilBuffer(int w, int h, int samples) +{ + samples = samples == 1 ? 0 : samples; + + for (const CachedRenderbuffer &rb : stencilBuffers) + { + if (rb.w == w && rb.h == h && rb.samples == samples) + { + // Attach the buffer to the framebuffer object. + for (GLenum attachment : rb.attachments) + { + if (attachment != GL_NONE) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rb.renderbuffer); + } + + return rb.renderbuffer; + } + } + + OpenGL::TempDebugGroup debuggroup("Created cached stencil buffer"); + + CachedRenderbuffer rb; + rb.w = w; + rb.h = h; + rb.samples = samples; + + rb.attachments[0] = GL_STENCIL_ATTACHMENT; + rb.attachments[1] = GL_NONE; + + GLenum format = GL_STENCIL_INDEX8; + + // Prefer a combined depth/stencil buffer. + if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object) + { + format = GL_DEPTH24_STENCIL8; + rb.attachments[0] = GL_DEPTH_STENCIL_ATTACHMENT; + } + else if (GLAD_EXT_packed_depth_stencil || GLAD_OES_packed_depth_stencil) + { + format = GL_DEPTH24_STENCIL8; + rb.attachments[0] = GL_DEPTH_ATTACHMENT; + rb.attachments[1] = GL_STENCIL_ATTACHMENT; + } + + glGenRenderbuffers(1, &rb.renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, rb.renderbuffer); + + if (rb.samples > 1) + glRenderbufferStorageMultisample(GL_RENDERBUFFER, rb.samples, format, rb.w, rb.h); + else + glRenderbufferStorage(GL_RENDERBUFFER, format, rb.w, rb.h); + + // Attach the buffer to the framebuffer object. + for (GLenum attachment : rb.attachments) + { + if (attachment != GL_NONE) + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rb.renderbuffer); + } + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + glDeleteRenderbuffers(1, &rb.renderbuffer); + rb.renderbuffer = 0; + } + + if (rb.renderbuffer != 0) + stencilBuffers.push_back(rb); + + return rb.renderbuffer; +} + +void Graphics::captureScreenshot(const ScreenshotInfo &info) +{ + if (!canCaptureScreenshot) + throw love::Exception("captureScreenshot cannot be called once rendering to the main screen has begun."); + + pendingScreenshotCallbacks.push_back(info); +} + +void Graphics::present(void *screenshotCallbackData) { if (!isActive()) return; - // Make sure we don't have a canvas active. - std::vector> canvases = states.back().canvases; - setCanvas(); + if (currentPass.active) + throw love::Exception("present cannot be called while a render pass is active."); - // Discard the stencil buffer before swapping. - discard({}, true); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO()); + + if (!pendingScreenshotCallbacks.empty()) + { + int w = getWidth(); + int h = getHeight(); + + size_t row = 4 * w; + size_t size = row * h; + + GLubyte *pixels = nullptr; + GLubyte *screenshot = nullptr; + + try + { + pixels = new GLubyte[size]; + screenshot = new GLubyte[size]; + } + catch (std::exception &) + { + delete[] pixels; + delete[] screenshot; + throw love::Exception("Out of memory."); + } + +#ifdef LOVE_IOS + SDL_SysWMinfo info = {}; + SDL_VERSION(&info.version); + SDL_GetWindowWMInfo(SDL_GL_GetCurrentWindow(), &info); + + if (info.info.uikit.resolveFramebuffer != 0) + { + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_DRAW, info.info.uikit.resolveFramebuffer); + + // We need to do an explicit MSAA resolve on iOS, because it uses GLES + // FBOs rather than a system framebuffer. + if (GLAD_ES_VERSION_3_0) + glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + else if (GLAD_APPLE_framebuffer_multisample) + glResolveMultisampleFramebufferAPPLE(); + + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_READ, info.info.uikit.resolveFramebuffer); + } +#endif + + glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Replace alpha values with full opacity. + for (size_t i = 3; i < size; i += 4) + pixels[i] = 255; + + // OpenGL sucks and reads pixels from the lower-left. Let's fix that. + GLubyte *src = pixels - row; + GLubyte *dst = screenshot + size; + + for (int i = 0; i < h; ++i) + memcpy(dst-=row, src+=row, row); + + delete[] pixels; + + auto imagemodule = Module::getInstance(M_IMAGE); + + for (int i = 0; i < (int) pendingScreenshotCallbacks.size(); i++) + { + const auto &info = pendingScreenshotCallbacks[i]; + image::ImageData *img = nullptr; + + try + { + img = imagemodule->newImageData(w, h, image::ImageData::FORMAT_RGBA8, screenshot); + } + catch (love::Exception &) + { + delete[] screenshot; + info.callback(nullptr, info.ref, nullptr); + for (int j = i + 1; j < (int) pendingScreenshotCallbacks.size(); j++) + { + const auto &ninfo = pendingScreenshotCallbacks[j]; + ninfo.callback(nullptr, ninfo.ref, nullptr); + } + pendingScreenshotCallbacks.clear(); + throw; + } + + info.callback(img, info.ref, screenshotCallbackData); + img->release(); + } + + delete[] screenshot; + pendingScreenshotCallbacks.clear(); + } #ifdef LOVE_IOS // Hack: SDL's color renderbuffer must be bound when swapBuffers is called. @@ -599,16 +1042,16 @@ void Graphics::present() glBindRenderbuffer(GL_RENDERBUFFER, info.info.uikit.colorbuffer); #endif - if (currentWindow.get()) - currentWindow->swapBuffers(); - - // Restore the currently active canvas, if there is one. - setCanvas(canvases); + auto window = getInstance(M_WINDOW); + if (window != nullptr) + window->swapBuffers(); // Reset the per-frame stat counts. gl.stats.drawCalls = 0; - gl.stats.framebufferBinds = 0; gl.stats.shaderSwitches = 0; + renderPassCount = 0; + + canCaptureScreenshot = true; } int Graphics::getWidth() const @@ -621,6 +1064,22 @@ int Graphics::getHeight() const return height; } +int Graphics::getPassWidth() const +{ + if (currentPass.active && currentPass.info.colorAttachmentCount > 0) + return currentPass.info.colorAttachments[0].canvas->getWidth(); + else + return width; +} + +int Graphics::getPassHeight() const +{ + if (currentPass.active && currentPass.info.colorAttachmentCount > 0) + return currentPass.info.colorAttachments[0].canvas->getHeight(); + else + return height; +} + bool Graphics::isCreated() const { return created; @@ -632,7 +1091,7 @@ void Graphics::setScissor(int x, int y, int width, int height) glEnable(GL_SCISSOR_TEST); // OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor. - gl.setScissor({rect.x, rect.y, rect.w, rect.h}); + gl.setScissor({rect.x, rect.y, rect.w, rect.h}, currentPass.info.colorAttachmentCount > 0); states.back().scissor = true; states.back().scissorRect = rect; @@ -679,11 +1138,10 @@ bool Graphics::getScissor(int &x, int &y, int &width, int &height) const void Graphics::drawToStencilBuffer(StencilAction action, int value) { - writingToStencil = true; + if (!currentPass.active || !currentPass.info.stencil) + throw love::Exception("Stenciling must be enabled in the active render pass."); - // Make sure the active canvas has a stencil buffer. - if (Canvas::current) - Canvas::current->checkCreateStencil(); + writingToStencil = true; // Disable color writes but don't save the state for it. glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); @@ -737,6 +1195,9 @@ void Graphics::stopDrawToStencilBuffer() void Graphics::setStencilTest(CompareMode compare, int value) { + if (!currentPass.info.stencil && compare != COMPARE_ALWAYS) + throw love::Exception("Stenciling must be enabled in the active render pass."); + DisplayState &state = states.back(); state.stencilCompare = compare; state.stencilTestValue = value; @@ -750,10 +1211,6 @@ void Graphics::setStencilTest(CompareMode compare, int value) return; } - // Make sure the active canvas has a stencil buffer. - if (Canvas::current) - Canvas::current->checkCreateStencil(); - GLenum glcompare = GL_EQUAL; /** @@ -863,9 +1320,6 @@ Canvas *Graphics::newCanvas(int width, int height, Canvas::Format format, int ms else if (height > gl.getMaxTextureSize()) throw Exception("Cannot create canvas: height of %d pixels is too large for this system.", height); - while (GL_NO_ERROR != glGetError()) - /* clear opengl error flag */; - Canvas *canvas = new Canvas(width, height, format, msaa); GLenum err = canvas->getStatus(); @@ -873,40 +1327,8 @@ Canvas *Graphics::newCanvas(int width, int height, Canvas::Format format, int ms if (err == GL_FRAMEBUFFER_COMPLETE) return canvas; - // create error message - std::stringstream error_string; - error_string << "Cannot create canvas: "; - switch (err) - { - case GL_FRAMEBUFFER_UNSUPPORTED: - error_string << "Not supported by your OpenGL drivers."; - break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - error_string << "Texture format cannot be rendered to on this system."; - break; - // remaining error codes are highly unlikely: - case GL_FRAMEBUFFER_UNDEFINED: - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: - case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: - case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: - error_string << "Error in graphics driver."; - break; - default: - // my intel hda card wrongly returns 0 to glCheckFramebufferStatus() but sets - // no error flag. I think it meant to return GL_FRAMEBUFFER_UNSUPPORTED, but who - // knows. - if (glGetError() == GL_NO_ERROR) - error_string << "May not be supported by your OpenGL drivers."; - // the remaining error is an indication of a serious fuckup since it should - // only be returned if glCheckFramebufferStatus() was called with the wrong - // arguments. - else - error_string << "Cannot create canvas: Aliens did it (OpenGL error code: " << glGetError() << ")"; - } - canvas->release(); - throw Exception("%s", error_string.str().c_str()); + throw love::Exception("Cannot create Canvas: %s", OpenGL::framebufferStatusString(err)); return nullptr; // never reached } @@ -1016,74 +1438,6 @@ Shader *Graphics::getShader() const return states.back().shader.get(); } -void Graphics::setCanvas(Canvas *canvas) -{ - if (canvas == nullptr) - return setCanvas(); - - DisplayState &state = states.back(); - - canvas->startGrab(); - - std::vector> canvasref; - canvasref.push_back(canvas); - - std::swap(state.canvases, canvasref); -} - -void Graphics::setCanvas(const std::vector &canvases) -{ - if (canvases.size() == 0) - return setCanvas(); - else if (canvases.size() == 1) - return setCanvas(canvases[0]); - - DisplayState &state = states.back(); - - auto attachments = std::vector(canvases.begin() + 1, canvases.end()); - canvases[0]->startGrab(attachments); - - std::vector> canvasrefs; - canvasrefs.reserve(canvases.size()); - - for (Canvas *c : canvases) - canvasrefs.push_back(c); - - std::swap(state.canvases, canvasrefs); -} - -void Graphics::setCanvas(const std::vector> &canvases) -{ - std::vector canvaslist; - canvaslist.reserve(canvases.size()); - - for (const StrongRef &c : canvases) - canvaslist.push_back(c.get()); - - return setCanvas(canvaslist); -} - -void Graphics::setCanvas() -{ - DisplayState &state = states.back(); - - if (Canvas::current != nullptr) - Canvas::current->stopGrab(); - - state.canvases.clear(); -} - -std::vector Graphics::getCanvas() const -{ - std::vector canvases; - canvases.reserve(states.back().canvases.size()); - - for (const StrongRef &c : states.back().canvases) - canvases.push_back(c.get()); - - return canvases; -} - void Graphics::setColorMask(ColorMask mask) { glColorMask(mask.r, mask.g, mask.b, mask.a); @@ -1258,8 +1612,27 @@ bool Graphics::isWireframe() const return states.back().wireframe; } +void Graphics::draw(Drawable *drawable, const Matrix4 &m) +{ + if (!currentPass.active) + throw RenderOutsidePassException(); + + drawable->draw(m); +} + +void Graphics::drawq(Texture *texture, Quad *quad, const Matrix4 &m) +{ + if (!currentPass.active) + throw RenderOutsidePassException(); + + texture->drawq(quad, m); +} + void Graphics::print(const std::vector &str, const Matrix4 &m) { + if (!currentPass.active) + throw RenderOutsidePassException(); + checkSetDefaultFont(); DisplayState &state = states.back(); @@ -1270,6 +1643,9 @@ void Graphics::print(const std::vector &str, const Matrix4 void Graphics::printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m) { + if (!currentPass.active) + throw RenderOutsidePassException(); + checkSetDefaultFont(); DisplayState &state = states.back(); @@ -1284,6 +1660,9 @@ void Graphics::printf(const std::vector &str, float wrap, F void Graphics::points(const float *coords, const uint8 *colors, size_t numpoints) { + if (!currentPass.active) + throw RenderOutsidePassException(); + OpenGL::TempDebugGroup debuggroup("Graphics points draw"); gl.prepareDraw(); @@ -1305,24 +1684,28 @@ void Graphics::points(const float *coords, const uint8 *colors, size_t numpoints void Graphics::polyline(const float *coords, size_t count) { + if (!currentPass.active) + throw RenderOutsidePassException(); + const DisplayState &state = states.back(); + float pixelsize = 1.0f / std::max((float) pixelScaleStack.back(), 0.000001f); if (state.lineJoin == LINE_JOIN_NONE) { NoneJoinPolyline line; - line.render(coords, count, state.lineWidth * .5f, float(pixelSizeStack.back()), state.lineStyle == LINE_SMOOTH); + line.render(coords, count, state.lineWidth * .5f, pixelsize, state.lineStyle == LINE_SMOOTH); line.draw(); } else if (state.lineJoin == LINE_JOIN_BEVEL) { BevelJoinPolyline line; - line.render(coords, count, state.lineWidth * .5f, float(pixelSizeStack.back()), state.lineStyle == LINE_SMOOTH); + line.render(coords, count, state.lineWidth * .5f, pixelsize, state.lineStyle == LINE_SMOOTH); line.draw(); } else // LINE_JOIN_MITER { MiterJoinPolyline line; - line.render(coords, count, state.lineWidth * .5f, float(pixelSizeStack.back()), state.lineStyle == LINE_SMOOTH); + line.render(coords, count, state.lineWidth * .5f, pixelsize, state.lineStyle == LINE_SMOOTH); line.draw(); } } @@ -1335,6 +1718,9 @@ void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h) void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h, float rx, float ry, int points) { + if (!currentPass.active) + throw RenderOutsidePassException(); + if (rx == 0 || ry == 0) { rectangle(mode, x, y, w, h); @@ -1397,8 +1783,7 @@ void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h, floa int Graphics::calculateEllipsePoints(float rx, float ry) const { - float pixelScale = 1.0f / std::max((float) pixelSizeStack.back(), 0.00001f); - int points = (int) sqrtf(((rx + ry) / 2.0f) * 20.0f * pixelScale); + int points = (int) sqrtf(((rx + ry) / 2.0f) * 20.0f * (float) pixelScaleStack.back()); return std::max(points, 8); } @@ -1419,6 +1804,9 @@ void Graphics::circle(DrawMode mode, float x, float y, float radius) void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b, int points) { + if (!currentPass.active) + throw RenderOutsidePassException(); + float two_pi = static_cast(LOVE_M_PI * 2); if (points <= 0) points = 1; float angle_shift = (two_pi / points); @@ -1446,6 +1834,9 @@ void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b) void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float radius, float angle1, float angle2, int points) { + if (!currentPass.active) + throw RenderOutsidePassException(); + // Nothing to display with no points or equal angles. (Or is there with line mode?) if (points <= 0 || angle1 == angle2) return; @@ -1539,6 +1930,9 @@ void Graphics::arc(DrawMode drawmode, ArcMode arcmode, float x, float y, float r /// @param count the number of coordinates/size of the array void Graphics::polygon(DrawMode mode, const float *coords, size_t count) { + if (!currentPass.active) + throw RenderOutsidePassException(); + // coords is an array of a closed loop of vertices, i.e. // coords[count-2] = coords[0], coords[count-1] = coords[1] if (mode == DRAW_LINE) @@ -1558,99 +1952,6 @@ void Graphics::polygon(DrawMode mode, const float *coords, size_t count) } } -love::image::ImageData *Graphics::newScreenshot(love::image::Image *image, bool copyAlpha) -{ - // Temporarily unbind the currently active canvas (glReadPixels reads the - // active framebuffer, not the main one.) - std::vector> canvases = states.back().canvases; - setCanvas(); - - int w = getWidth(); - int h = getHeight(); - - int row = 4 * w; - int size = row * h; - - GLubyte *pixels = nullptr; - GLubyte *screenshot = nullptr; - - try - { - pixels = new GLubyte[size]; - screenshot = new GLubyte[size]; - } - catch (std::exception &) - { - delete[] pixels; - delete[] screenshot; - setCanvas(canvases); - throw love::Exception("Out of memory."); - } - -#ifdef LOVE_IOS - SDL_SysWMinfo info = {}; - SDL_VERSION(&info.version); - SDL_GetWindowWMInfo(SDL_GL_GetCurrentWindow(), &info); - - if (info.info.uikit.resolveFramebuffer != 0) - { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, info.info.uikit.resolveFramebuffer); - - // We need to do an explicit MSAA resolve on iOS, because it uses GLES - // FBOs rather than a system framebuffer. - if (GLAD_ES_VERSION_3_0) - glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); - else if (GLAD_APPLE_framebuffer_multisample) - glResolveMultisampleFramebufferAPPLE(); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, info.info.uikit.resolveFramebuffer); - } -#endif - - glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - -#ifdef LOVE_IOS - // Restore the previous binding for the main framebuffer. - if (info.info.uikit.resolveFramebuffer != 0) - glBindFramebuffer(GL_FRAMEBUFFER, gl.getDefaultFBO()); -#endif - - if (!copyAlpha) - { - // Replace alpha values with full opacity. - for (int i = 3; i < size; i += 4) - pixels[i] = 255; - } - - // OpenGL sucks and reads pixels from the lower-left. Let's fix that. - GLubyte *src = pixels - row; - GLubyte *dst = screenshot + size; - - for (int i = 0; i < h; ++i) - memcpy(dst-=row, src+=row, row); - - delete[] pixels; - - love::image::ImageData *img = nullptr; - try - { - // Tell the new ImageData that it owns the screenshot data, so we don't - // need to delete it here. - img = image->newImageData(w, h, image::ImageData::FORMAT_RGBA8, screenshot, true); - } - catch (love::Exception &) - { - delete[] screenshot; - setCanvas(canvases); - throw; - } - - // Re-bind the active canvas, if necessary. - setCanvas(canvases); - - return img; -} - Graphics::RendererInfo Graphics::getRendererInfo() const { RendererInfo info; @@ -1686,7 +1987,7 @@ Graphics::Stats Graphics::getStats() const Stats stats; stats.drawCalls = gl.stats.drawCalls; - stats.canvasSwitches = gl.stats.framebufferBinds; + stats.renderPasses = renderPassCount; stats.shaderSwitches = gl.stats.shaderSwitches; stats.canvases = Canvas::canvasCount; stats.images = Image::imageCount; @@ -1708,6 +2009,8 @@ double Graphics::getSystemLimit(SystemLimit limittype) const return (double) gl.getMaxRenderTargets(); case Graphics::LIMIT_CANVAS_MSAA: return (double) gl.getMaxRenderbufferSamples(); + case Graphics::LIMIT_ANISOTROPY: + return (double) gl.getMaxAnisotropy(); default: return 0.0; } @@ -1723,6 +2026,10 @@ bool Graphics::isSupported(Feature feature) const return gl.isClampZeroTextureWrapSupported(); case FEATURE_LIGHTEN: return GLAD_VERSION_1_4 || GLAD_ES_VERSION_3_0 || GLAD_EXT_blend_minmax; + case FEATURE_FULL_NPOT: + return GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot; + case FEATURE_PIXEL_SHADER_HIGHP: + return gl.isPixelShaderHighpSupported(); default: return false; } @@ -1735,7 +2042,7 @@ void Graphics::push(StackType type) gl.pushTransform(); - pixelSizeStack.push_back(pixelSizeStack.back()); + pixelScaleStack.push_back(pixelScaleStack.back()); if (type == STACK_ALL) states.push_back(states.back()); @@ -1749,7 +2056,7 @@ void Graphics::pop() throw Exception("Minimum stack depth reached (more pops than pushes?)"); gl.popTransform(); - pixelSizeStack.pop_back(); + pixelScaleStack.pop_back(); if (stackTypes.back() == STACK_ALL) { @@ -1772,7 +2079,7 @@ void Graphics::rotate(float r) void Graphics::scale(float x, float y) { gl.getTransform().scale(x, y); - pixelSizeStack.back() *= 2. / (fabs(x) + fabs(y)); + pixelScaleStack.back() *= (fabs(x) + fabs(y)) / 2.0; } void Graphics::translate(float x, float y) @@ -1788,7 +2095,27 @@ void Graphics::shear(float kx, float ky) void Graphics::origin() { gl.getTransform().setIdentity(); - pixelSizeStack.back() = 1; + pixelScaleStack.back() = 1; +} + +void Graphics::applyTransform(love::math::Transform *transform) +{ + Matrix4 &m = gl.getTransform(); + m *= transform->getMatrix(); + + float sx, sy; + m.getApproximateScale(sx, sy); + pixelScaleStack.back() = (sx + sy) / 2.0; +} + +void Graphics::replaceTransform(love::math::Transform *transform) +{ + const Matrix4 &m = transform->getMatrix(); + gl.getTransform() = m; + + float sx, sy; + m.getApproximateScale(sx, sy); + pixelScaleStack.back() = (sx + sy) / 2.0; } Vector Graphics::transformPoint(Vector point) diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 138858103..265b4db7d 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -24,6 +24,7 @@ // STD #include #include +#include // 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 &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 &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 &canvases); - void setCanvas(const std::vector> &canvases); - void setCanvas(); - - std::vector 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; StrongRef shader; - std::vector> 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 &colorbuffers, bool depthstencil); + GLuint attachCachedStencilBuffer(int w, int h, int samples); + void checkSetDefaultFont(); int calculateEllipsePoints(float rx, float ry) const; - StrongRef currentWindow; - StrongRef defaultFont; - std::vector pixelSizeStack; // stores current size of a pixel (needed for line drawing) + std::vector pixelScaleStack; + + std::vector pendingScreenshotCallbacks; + + std::unordered_map framebufferObjects; + std::vector stencilBuffers; QuadIndices *quadIndices; @@ -520,11 +581,17 @@ private: bool created; bool active; + bool canCaptureScreenshot; + + CurrentPass currentPass; + bool writingToStencil; std::vector states; std::vector stackTypes; // Keeps track of the pushed stack types. + int renderPassCount; + static const size_t MAX_USER_STACK_DEPTH = 64; }; // Graphics diff --git a/src/modules/graphics/opengl/Image.cpp b/src/modules/graphics/opengl/Image.cpp index 4005bba6e..a105447fc 100644 --- a/src/modules/graphics/opengl/Image.cpp +++ b/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)) diff --git a/src/modules/graphics/opengl/Mesh.cpp b/src/modules/graphics/opengl/Mesh.cpp index 13b25b87a..7815f80f3 100644 --- a/src/modules/graphics/opengl/Mesh.cpp +++ b/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(); diff --git a/src/modules/graphics/opengl/Mesh.h b/src/modules/graphics/opengl/Mesh.h index df7e383a7..51ad66c14 100644 --- a/src/modules/graphics/opengl/Mesh.h +++ b/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); diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 50400c8ba..f5a44775c 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/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::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 == GL_FRAMEBUFFER) - ++stats.framebufferBinds; + 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 (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) diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index 64b4ff6c8..099ed07f9 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/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 transform; - std::vector 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; diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 0f0440924..3f7822ca8 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/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 #include +#include 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 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(); - - boundRetainables.clear(); - unloadVolatile(); + + for (const auto &p : uniforms) + { + // Allocated with malloc(). + if (p.second.data != nullptr) + free(p.second.data); + + 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(); + } + + 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 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(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 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; + } + } + else if (type == UNIFORM_INT || type == UNIFORM_BOOL || type == UNIFORM_SAMPLER) + { + 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::sendFloats(const UniformInfo *info, const float *vec, int count) +int Shader::getFreeTextureUnits(int count) { - if (info->baseType != UNIFORM_FLOAT && info->baseType != UNIFORM_BOOL) - return; + int startunit = -1; - TemporaryAttacher attacher(this); - - int location = info->location; - - switch (info->components) + // Ignore the first texture unit for Shader-local texture bindings. + for (int i = 1; i < (int) textureUnits.size(); i++) { - 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; + if (!textureUnits[i].active && i + count <= (int) textureUnits.size()) + { + startunit = i; + break; + } } + + if (startunit == -1) + throw love::Exception("No more texture units available for shader."); + + return startunit; } -void Shader::sendMatrices(const UniformInfo *info, const float *m, int count) -{ - if (info->baseType != UNIFORM_MATRIX) - return; - - TemporaryAttacher attacher(this); - - int location = info->location; - - 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); -} - -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(); + count = std::min(count, info->count); + bool updateuniform = false; - 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); - - 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(); - - auto it = boundRetainables.find(name); - if (it != boundRetainables.end()) - it->second->release(); - - boundRetainables[name] = object; -} - -int Shader::getTextureUnit(const std::string &name) -{ - auto it = texUnitPool.find(name); - - if (it != texUnitPool.end()) - return it->second; - - int texunit = 1; - - // prefer texture units which are unused by all other shaders - auto freeunit_it = std::find(textureCounters.begin(), textureCounters.end(), 0); - - if (freeunit_it != textureCounters.end()) + // Make sure the shader's samplers are associated with texture units. + for (int i = 0; i < count; i++) { - // 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 (info->ints[i] == 0 && textures[i] != nullptr) + { + int texunit = getFreeTextureUnits(1); + textureUnits[texunit].active = true; - if (nextunit_it == activeTexUnits.end()) - throw love::Exception("No more texture units available for shader."); - - // we don't want to use unit 0 - texunit = (int) std::distance(activeTexUnits.begin(), nextunit_it) + 1; + info->ints[i] = texunit; + updateuniform = true; + } } - texUnitPool[name] = texunit; - return texunit; + if (updateuniform) + updateUniform(info, count, internalUpdate); + + // Bind the textures to the texture units. + for (int i = 0; i < count; i++) + { + if (textures[i] != nullptr) + textures[i]->retain(); + + if (info->textures[i] != nullptr) + info->textures[i]->release(); + + info->textures[i] = textures[i]; + + int texunit = info->ints[i]; + + if (textures[i] != nullptr) + { + GLuint gltex = *(GLuint *) textures[i]->getHandle(); + + gl.bindTextureToUnit(gltex, texunit, false); + + // 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; + } + } } 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]); + const UniformInfo *info = getUniformInfo(names[i]); + if (info != nullptr) + { + videoTextureUnits[i] = getFreeTextureUnits(1); + textureUnits[videoTextureUnits[i]].active = true; - // 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]); + 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(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 &Shader::getBoundRetainables() const -{ - return boundRetainables; -} - std::string Shader::getGLSLVersion() { const char *tmp = (const char *) glGetString(GL_SHADING_LANGUAGE_VERSION); diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index c54104e89..b3e778053 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/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 &getBoundRetainables() const; - GLuint getProgram() const { return program; } - template - 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 uniforms; // Texture unit pool for setting images - std::map texUnitPool; // texUnitPool[name] = textureunit - std::vector activeTexUnits; // activeTexUnits[textureunit-1] = textureid + std::vector textureUnits; - // Uniform name to retainable objects - std::map boundRetainables; - - // 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 scratchBuffer; - - // Counts total number of textures bound to each texture unit in all shaders - static std::vector textureCounters; - static StringMap::Entry stageNameEntries[]; static StringMap stageNames; diff --git a/src/modules/graphics/opengl/wrap_Canvas.cpp b/src/modules/graphics/opengl/wrap_Canvas.cpp index d744864cd..6a348319b 100644 --- a/src/modules/graphics/opengl/wrap_Canvas.cpp +++ b/src/modules/graphics/opengl/wrap_Canvas.cpp @@ -33,55 +33,6 @@ Canvas *luax_checkcanvas(lua_State *L, int idx) return luax_checktype(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(Module::M_GRAPHICS); - - if (graphics) - { - // Save the current Canvas so we can restore it when we're done. - std::vector 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(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 } diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index 13b2d41d5..24753a1a4 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/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 #include @@ -60,79 +62,9 @@ int w_reset(lua_State *) return 0; } -int w_clear(lua_State *L) +int w_present(lua_State *L) { - Colorf color; - - if (lua_isnoneornil(L, 1)) - color.set(0.0, 0.0, 0.0, 0.0); - else if (lua_istable(L, 1)) - { - std::vector colors((size_t) lua_gettop(L)); - - for (int i = 0; i < lua_gettop(L); i++) - { - if (lua_isnoneornil(L, i + 1) || luax_objlen(L, i + 1) == 0) - { - colors[i].enabled = false; - continue; - } - - 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); - - lua_pop(L, 4); - } - - luax_catchexcept(L, [&]() { instance()->clear(colors); }); - return 0; - } - 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); - } - - luax_catchexcept(L, [&]() { instance()->clear(color); }); - return 0; -} - -int w_discard(lua_State *L) -{ - std::vector colorbuffers; - - if (lua_istable(L, 1)) - { - 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); - } - } - else - { - bool discardcolor = luax_optboolean(L, 1, true); - size_t numbuffers = std::max((size_t) 1, instance()->getCanvas().size()); - colorbuffers = std::vector(numbuffers, discardcolor); - } - - bool stencil = luax_optboolean(L, 2, true); - instance()->discard(colorbuffers, stencil); - return 0; -} - -int w_present(lua_State *) -{ - instance()->present(); + luax_catchexcept(L, [&]() { instance()->present(L); }); return 0; } @@ -173,6 +105,274 @@ int w_getDimensions(lua_State *L) 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)) + { + 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); + + if (lua_isnumber(L, 4)) + { + c.a = (float) lua_tonumber(L, 4); + nextstartidx = 5; + } + else + { + 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)) + { + attachment.clearColor.a = (float) lua_tonumber(L, 5); + nextstartidx = 6; + } + else + { + attachment.clearColor.a = 1.0f; + nextstartidx = 5; + } + } + else + nextstartidx = 2; + + PassInfo info; + info.addColorAttachment(attachment); + + if (lua_isboolean(L, nextstartidx)) + { + info.stencil = luax_toboolean(L, nextstartidx); + nextstartidx++; + } + else + info.stencil = false; + + luax_catchexcept(L, [&]() { instance()->beginPass(info); }); + } + else + { + 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; + } + + return nextstartidx; +} + +int w_beginPass(lua_State *L) +{ + w__beginPass(L); + return 0; +} + +static void screenshotCallback(love::image::ImageData *i, Reference *ref, void *gd) +{ + 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; +} + +static int w__endPass(lua_State *L, int startidx) +{ + if (lua_isnoneornil(L, startidx)) + { + luax_catchexcept(L, []() { instance()->endPass(); }); + } + else + { + 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; } + ); + } + + return 0; +} + +int w_endPass(lua_State *L) +{ + return w__endPass(L, 1); +} + +int w_renderPass(lua_State *L) +{ + int startidx = w__beginPass(L); + + if (lua_type(L, startidx) != LUA_TFUNCTION) + { + w__endPass(L, startidx + 1); + luaL_checktype(L, startidx, LUA_TFUNCTION); + return 0; + } + + 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_isPassActive(lua_State *L) +{ + luax_pushboolean(L, instance()->isPassActive()); + return 1; +} + +int w_getPassCanvases(lua_State *L) +{ + 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_captureScreenshot(lua_State *L) +{ + 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) { int nargs = lua_gettop(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(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 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 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(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 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(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 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(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(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 }, diff --git a/src/modules/graphics/opengl/wrap_Mesh.cpp b/src/modules/graphics/opengl/wrap_Mesh.cpp index c24de5ecb..a7785ef0a 100644 --- a/src/modules/graphics/opengl/wrap_Mesh.cpp +++ b/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 }, diff --git a/src/modules/graphics/opengl/wrap_Shader.cpp b/src/modules/graphics/opengl/wrap_Shader.cpp index bb2fc0486..f15db4b35 100644 --- a/src/modules/graphics/opengl/wrap_Shader.cpp +++ b/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 #include @@ -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 -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(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(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(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(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(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(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 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); } diff --git a/src/modules/graphics/opengl/wrap_SpriteBatch.cpp b/src/modules/graphics/opengl/wrap_SpriteBatch.cpp index 5d98ad9fd..ab614c331 100644 --- a/src/modules/graphics/opengl/wrap_SpriteBatch.cpp +++ b/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 @@ -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); + if (luax_istype(L, startidx, math::Transform::type)) + { + math::Transform *tf = luax_totype(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); + 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); - }); + luax_catchexcept(L, [&]() { + if (quad) + index = t->addq(quad, m, index); + else + index = t->add(m, index); + }); + } return index; } diff --git a/src/modules/graphics/opengl/wrap_Text.cpp b/src/modules/graphics/opengl/wrap_Text.cpp index 029732bd4..c67dd3125 100644 --- a/src/modules/graphics/opengl/wrap_Text.cpp +++ b/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 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); + if (luax_istype(L, 3, math::Transform::type)) + { + math::Transform *tf = luax_totype(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); }); + } - 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); - return 1; } @@ -151,6 +161,8 @@ int w_Text_addf(lua_State *L) { Text *t = luax_checktext(L, 1); + int index = 0; + std::vector 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); + if (luax_istype(L, 5, math::Transform::type)) + { + math::Transform *tf = luax_totype(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); }); + } - 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); - return 1; } diff --git a/src/modules/math/MathModule.cpp b/src/modules/math/MathModule.cpp index 02250e569..34dc04b8d 100644 --- a/src/modules/math/MathModule.cpp +++ b/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 @@ -374,6 +375,16 @@ BezierCurve *Math::newBezierCurve(const std::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()); diff --git a/src/modules/math/MathModule.h b/src/modules/math/MathModule.h index 8937308b4..027848010 100644 --- a/src/modules/math/MathModule.h +++ b/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 &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 { diff --git a/src/modules/math/Transform.cpp b/src/modules/math/Transform.cpp new file mode 100644 index 000000000..f5f0d1572 --- /dev/null +++ b/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 diff --git a/src/modules/math/Transform.h b/src/modules/math/Transform.h new file mode 100644 index 000000000..3e04aeba3 --- /dev/null +++ b/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 diff --git a/src/modules/math/wrap_Math.cpp b/src/modules/math/wrap_Math.cpp index 5736ff93b..fc7e952ac 100644 --- a/src/modules/math/wrap_Math.cpp +++ b/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 @@ -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 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 }; diff --git a/src/modules/math/wrap_Transform.cpp b/src/modules/math/wrap_Transform.cpp new file mode 100644 index 000000000..517bd5239 --- /dev/null +++ b/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(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 diff --git a/src/modules/math/wrap_Transform.h b/src/modules/math/wrap_Transform.h new file mode 100644 index 000000000..f3791dbc5 --- /dev/null +++ b/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 diff --git a/src/modules/window/Window.h b/src/modules/window/Window.h index c15d734c7..3549d384d 100644 --- a/src/modules/window/Window.h +++ b/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; diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index 81423f9c4..12c3ebe19 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/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(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(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(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(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(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; diff --git a/src/modules/window/sdl/Window.h b/src/modules/window/sdl/Window.h index ade2d6f59..91e68e04f 100644 --- a/src/modules/window/sdl/Window.h +++ b/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; + }; // Window } // sdl diff --git a/src/modules/window/wrap_Window.cpp b/src/modules/window/wrap_Window.cpp index 968d84d7c..bc08dd58c 100644 --- a/src/modules/window/wrap_Window.cpp +++ b/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; } diff --git a/src/scripts/boot.lua b/src/scripts/boot.lua index 98c2c9129..cd9d19d39 100644 --- a/src/scripts/boot.lua +++ b/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) diff --git a/src/scripts/boot.lua.h b/src/scripts/boot.lua.h index 8f499c12a..32c2df831 100644 --- a/src/scripts/boot.lua.h +++ b/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,