diff --git a/CMakeLists.txt b/CMakeLists.txt index c8af83674..f5b00344e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -341,6 +341,7 @@ set(LOVE_SRC_MODULE_IMAGE_MAGPIE src/modules/image/magpie/ddsHandler.h src/modules/image/magpie/DevilHandler.cpp src/modules/image/magpie/DevilHandler.h + src/modules/image/magpie/FormatHandler.cpp src/modules/image/magpie/FormatHandler.h src/modules/image/magpie/Image.cpp src/modules/image/magpie/Image.h diff --git a/changes.txt b/changes.txt index 569cb7ef4..1401af364 100644 --- a/changes.txt +++ b/changes.txt @@ -5,11 +5,27 @@ LOVE 0.9.1 [Baby Inspector] * Added Source:clone. * Added ParticleSystem:clone. - * Added Mesh:setWireframe and Mesh:isWireframe for debugging. + * Added ParticleSystem:moveTo, has smoother emitter movement compared to setPosition. + * Added ParticleSystem:setRelativeRotation. + * Added love.graphics.setWireframe for debugging. + * Added Mesh:setDrawRange and Mesh:getDrawRange. + * Added instancing support to Meshes with Mesh:setInstanceCount. * Added CircleShape:getPoint and CircleShape:setPoint. * Added Mesh/SpriteBatch/ParticleSystem:setTexture, accepts Canvases and Images. + * Added high-dpi window support for Retina displays in OS X, via the 'highdpi' window flag. + * Added love.window.getPixelScale. + * Added love.graphics.getSystemLimit. + * Added antialiasing support to Canvases. + * Added Canvas:getFSAA. + * Added 'love_ScreenSize' built-in variable in shaders. + * Added love.getVersion. + * Added support for gamma-correct rendering. + * Added love.graphics.isSupported("srgb"). + * Added love.math.gammaToLinear and love.math.linearToGamma. + * Added RandomGenerator:getState and RandomGenerator:setState. * Deprecated Mesh/SpriteBatch/ParticleSystem:setImage. + * Deprecated love.graphics.getMaxImageSize and love.graphics.getMaxPointSize. * Fixed love.graphics.scale with negative values causing incorrect line widths. * Fixed Joystick:isDown using 0-based button index arguments. @@ -17,11 +33,20 @@ LOVE 0.9.1 [Baby Inspector] * Fixed love.graphics.setCanvas() to restore the proper viewport and scissor rectangles. * Fixed TrueType font glyphs which request a monochrome bitmap pixel mode. * Fixed love.graphics.reset causing crashes when called in between love.graphics.push/pop. + * Fixed tab characters ("\t") to display properly with love.graphics.print. + * Fixed World:getBodyList and World:getJointList causing hard crashes. + + * Renamed love.graphics.getMaxImageSize to love.graphics.getMaxTextureSize (old function still exists.) * Updated the error text for love.filesystem’s module searchers when require fails. * Updated the love.filesystem module searchers to be tried after package.preload instead of before. * Updated love.graphics.newParticleSystem, newSpriteBatch, and newMesh to accept Canvases. * Updated Canvas drawing code, texture coordinates are no longer flipped vertically. + * Updated Canvas:renderTo to work properly if a Canvas is currently active. + * Updated ParticleSystem:setEmissionRate to accept non-integer numbers. + * Updated Source:play to return a boolean indicating success. + * Updated t.console in conf.lua to create the console before modules are loaded in Windows. + * Updated Mesh vertex maps (index buffers) to use less space in VRAM. LOVE 0.9.0 [Baby Inspector] --------------------------- diff --git a/platform/macosx/Info-Framework.plist b/platform/macosx/Info-Framework.plist index 2bdfccf99..9ac7b9f46 100644 --- a/platform/macosx/Info-Framework.plist +++ b/platform/macosx/Info-Framework.plist @@ -17,11 +17,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.0 + 0.9.1 CFBundleSignature LoVe CFBundleVersion - 0.9.0 + 0.9.1 NSPrincipalClass diff --git a/platform/macosx/love-Info.plist b/platform/macosx/love-Info.plist index b14f6022a..c7585e930 100644 --- a/platform/macosx/love-Info.plist +++ b/platform/macosx/love-Info.plist @@ -46,7 +46,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.9.0 + 0.9.1 CFBundleSignature LoVe LSApplicationCategoryType diff --git a/platform/macosx/love-framework.xcodeproj/project.pbxproj b/platform/macosx/love-framework.xcodeproj/project.pbxproj index dedef2bf8..84e5457de 100644 --- a/platform/macosx/love-framework.xcodeproj/project.pbxproj +++ b/platform/macosx/love-framework.xcodeproj/project.pbxproj @@ -289,6 +289,7 @@ FAC86E641724552C00EED715 /* wrap_Quad.h in Headers */ = {isa = PBXBuildFile; fileRef = FAC86E621724552C00EED715 /* wrap_Quad.h */; }; FAC86E6B1724555D00EED715 /* Quad.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAC86E671724555D00EED715 /* Quad.cpp */; }; FAC86E6C1724555D00EED715 /* Quad.h in Headers */ = {isa = PBXBuildFile; fileRef = FAC86E681724555D00EED715 /* Quad.h */; }; + FADD58DD18C30367005FC3BF /* FormatHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FADD58DC18C30367005FC3BF /* FormatHandler.cpp */; }; FAE010DB170DDE99006F29D0 /* ddsinfo.h in Headers */ = {isa = PBXBuildFile; fileRef = FAE010D8170DDE99006F29D0 /* ddsinfo.h */; }; FAE010DC170DDE99006F29D0 /* ddsparse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAE010D9170DDE99006F29D0 /* ddsparse.cpp */; }; FAE010DD170DDE99006F29D0 /* ddsparse.h in Headers */ = {isa = PBXBuildFile; fileRef = FAE010DA170DDE99006F29D0 /* ddsparse.h */; }; @@ -847,6 +848,7 @@ FAC86E621724552C00EED715 /* wrap_Quad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Quad.h; sourceTree = ""; }; FAC86E671724555D00EED715 /* Quad.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Quad.cpp; sourceTree = ""; }; FAC86E681724555D00EED715 /* Quad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Quad.h; sourceTree = ""; }; + FADD58DC18C30367005FC3BF /* FormatHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FormatHandler.cpp; sourceTree = ""; }; FAE010D8170DDE99006F29D0 /* ddsinfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ddsinfo.h; sourceTree = ""; }; FAE010D9170DDE99006F29D0 /* ddsparse.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ddsparse.cpp; sourceTree = ""; }; FAE010DA170DDE99006F29D0 /* ddsparse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ddsparse.h; sourceTree = ""; }; @@ -1088,6 +1090,7 @@ FAE010DF170DE25E006F29D0 /* ddsHandler.h */, 1AA7781A230065F346E2313A /* DevilHandler.cpp */, 283342E174613897621A43F1 /* DevilHandler.h */, + FADD58DC18C30367005FC3BF /* FormatHandler.cpp */, FA0CDE3B1710F9A50056E8D7 /* FormatHandler.h */, 505F23A73BFE250833D650E4 /* Image.cpp */, 68616BD516DB124312B47EB3 /* Image.h */, @@ -2045,7 +2048,7 @@ 08FB7793FE84155DC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0500; + LastUpgradeCheck = 0510; }; buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "love-framework" */; compatibilityVersion = "Xcode 3.2"; @@ -2267,6 +2270,7 @@ FA08F66816C7548200F007B5 /* wrap_WheelJoint.cpp in Sources */, FA08F66916C7548200F007B5 /* wrap_World.cpp in Sources */, FA08F66A16C7549200F007B5 /* Sound.cpp in Sources */, + FADD58DD18C30367005FC3BF /* FormatHandler.cpp in Sources */, FA08F66B16C7549200F007B5 /* SoundData.cpp in Sources */, FA08F66C16C7549200F007B5 /* wrap_Decoder.cpp in Sources */, FA08F66D16C7549200F007B5 /* wrap_Sound.cpp in Sources */, @@ -2407,6 +2411,66 @@ }; name = Debug; }; + FA5326C4189719C700F7BBF4 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_ENABLE_MODULES = YES; + DEAD_CODE_STRIPPING = YES; + FRAMEWORK_SEARCH_PATHS = /Library/Frameworks; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = LOVE_MACOSX_USE_FRAMEWORKS; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_PARAMETER = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "\"$(SRCROOT)/../../src\"", + "\"$(SRCROOT)/../../src/libraries\"", + "\"$(SRCROOT)/../../src/modules\"", + "\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"", + /Library/Frameworks/FreeType.framework/Headers, + /Library/Frameworks/Lua.framework/Headers, + /Library/Frameworks/SDL2.framework/Headers, + ); + LD_RUNPATH_SEARCH_PATHS = "@rpath"; + LIBRARY_SEARCH_PATHS = ""; + LLVM_LTO = YES; + MACOSX_DEPLOYMENT_TARGET = 10.6; + ONLY_ACTIVE_ARCH = NO; + USE_HEADERMAP = NO; + WARNING_CFLAGS = "-Wall"; + }; + name = Distribution; + }; + FA5326C5189719C700F7BBF4 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_MODULES = NO; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DYLIB_COMPATIBILITY_VERSION = 9.0; + DYLIB_CURRENT_VERSION = 9.0; + FRAMEWORK_VERSION = A; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + INFOPLIST_FILE = "Info-Framework.plist"; + LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; + OTHER_LDFLAGS = ( + "-undefined", + dynamic_lookup, + ); + PRODUCT_NAME = love; + SKIP_INSTALL = YES; + WRAPPER_EXTENSION = framework; + }; + name = Distribution; + }; FA577AC016C7507900860150 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2464,6 +2528,7 @@ buildConfigurations = ( 64274E785071353E1A1D0D4B /* Debug */, 10D5479E63C26BB35EB5482E /* Release */, + FA5326C4189719C700F7BBF4 /* Distribution */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; @@ -2473,6 +2538,7 @@ buildConfigurations = ( FA577AC016C7507900860150 /* Debug */, FA577AC116C7507900860150 /* Release */, + FA5326C5189719C700F7BBF4 /* Distribution */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; diff --git a/platform/macosx/love.xcodeproj/project.pbxproj b/platform/macosx/love.xcodeproj/project.pbxproj index 9c4739788..ab34a46bf 100644 --- a/platform/macosx/love.xcodeproj/project.pbxproj +++ b/platform/macosx/love.xcodeproj/project.pbxproj @@ -198,7 +198,7 @@ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0500; + LastUpgradeCheck = 0510; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "love" */; compatibilityVersion = "Xcode 3.2"; @@ -402,6 +402,79 @@ }; name = Release; }; + FA5326C618971A0900F7BBF4 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_ENABLE_MODULES = YES; + DEPLOYMENT_POSTPROCESSING = NO; + FRAMEWORK_SEARCH_PATHS = /Library/Frameworks; + GCC_INPUT_FILETYPE = automatic; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = LOVE_MACOSX_USE_FRAMEWORKS; + GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = NO; + GCC_WARN_ABOUT_MISSING_NEWLINE = NO; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO; + GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES; + GCC_WARN_ABOUT_RETURN_TYPE = NO; + GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; + GCC_WARN_FOUR_CHARACTER_CONSTANTS = NO; + GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = NO; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = NO; + GCC_WARN_MISSING_PARENTHESES = NO; + GCC_WARN_NON_VIRTUAL_DESTRUCTOR = NO; + GCC_WARN_PEDANTIC = NO; + GCC_WARN_SHADOW = NO; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = NO; + GCC_WARN_UNUSED_PARAMETER = NO; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "\"$(SRCROOT)/../../src\"", + "\"$(SRCROOT)/../../src/libraries\"", + "\"$(SRCROOT)/../../src/modules\"", + /Library/Frameworks/Lua.framework/Headers, + /Library/Frameworks/SDL2.framework/Headers, + ); + INFOPLIST_FILE = "love-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; + LLVM_LTO = YES; + MACOSX_DEPLOYMENT_TARGET = 10.6; + ONLY_ACTIVE_ARCH = NO; + OTHER_LDFLAGS = ""; + "OTHER_LDFLAGS[arch=x86_64]" = ( + "-pagezero_size", + 10000, + "-image_base", + 100000000, + ); + PRODUCT_NAME = love; + SCAN_ALL_SOURCE_FILES_FOR_INCLUDES = YES; + WARNING_CFLAGS = ( + "-Wall", + "-W", + ); + }; + name = Distribution; + }; + FA5326C718971A0900F7BBF4 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/build/Release\"", + "\"$(SRCROOT)/build/Debug\"", + ); + INSTALL_PATH = /Applications; + PRODUCT_NAME = love; + }; + name = Distribution; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -410,6 +483,7 @@ buildConfigurations = ( C01FCF4B08A954540054247B /* Debug */, C01FCF4C08A954540054247B /* Release */, + FA5326C718971A0900F7BBF4 /* Distribution */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -419,6 +493,7 @@ buildConfigurations = ( C01FCF4F08A954540054247B /* Debug */, C01FCF5008A954540054247B /* Release */, + FA5326C618971A0900F7BBF4 /* Distribution */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/platform/unix/debian/love.install b/platform/unix/debian/love.install index 487155e20..ac4c7a440 100644 --- a/platform/unix/debian/love.install +++ b/platform/unix/debian/love.install @@ -1 +1,6 @@ usr/bin/love +usr/share/man/man1/love.1 +usr/share/pixmaps/love.svg +usr/share/mime/packages/love.xml +usr/share/icons/hicolor/scalable/mimetypes/application-x-love-game.svg +usr/share/applications/love.desktop diff --git a/platform/unix/love.1 b/platform/unix/love.1 index 0abaa5577..539cf9a9c 100644 --- a/platform/unix/love.1 +++ b/platform/unix/love.1 @@ -1,4 +1,5 @@ .\" (c) 2008-2011 Miriam Ruiz +.\" (c) 2013 Bart van Strien .\" .\" This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damagesarising from the use of this software. .\" @@ -11,16 +12,25 @@ .\" 3. This notice may not be removed or altered from any source distribution. .\" .\" Modifications: -.\" - Update version to 0.9 and remove reference to doc dir - Bart van Strien, 2013 +.\" - Update version to 0.9 and remove reference to doc dir +.\" - Add fused and version flags + .TH "LÖVE" "1" "0.9" "" "" .SH "NAME" love \- 2D game development framework + .SH "SYNOPSIS" .B love -<\fIgame.love\fR> +[--fused] <\fIgame.love\fR> +.PP +.B love +--version +.PP + .SH "DESCRIPTION" LÖVE was created to be a user\-friendly engine in which simple (or complicated) games could be made without having extensive knowledge of system or graphics functions and without having to dedicate time towards developing the same engine features time and time again. .P Developed with cross\-platform implementation in mind, it utilizes the latest open source libraries to deliver a similar game experience, independent of operating system. By relying on the Lua scripting language for game\-specific programming, it allows even the novice game creator to quickly and efficiently develop an idea into a fully working game. + .SH "SEE ALSO" You can find more information at \fIhttp://love2d.org/\fR diff --git a/src/common/Data.h b/src/common/Data.h index ea31e69e7..ffea71cf9 100644 --- a/src/common/Data.h +++ b/src/common/Data.h @@ -38,7 +38,7 @@ public: /** * Destructor. **/ - virtual ~Data() {}; + virtual ~Data() {} /** * Gets a pointer to the data. This pointer will obviously not diff --git a/src/common/Memoizer.cpp b/src/common/Memoizer.cpp index dcc8e5df2..8d6ec7d4f 100644 --- a/src/common/Memoizer.cpp +++ b/src/common/Memoizer.cpp @@ -38,8 +38,12 @@ void Memoizer::remove(void *key) void *Memoizer::find(void *key) { - if (objectMap.count(key)) return objectMap[key]; - return NULL; + auto it = objectMap.find(key); + + if (it != objectMap.end()) + return it->second; + else + return nullptr; } } // love diff --git a/src/common/Reference.cpp b/src/common/Reference.cpp index 38e28e099..d2ea05774 100644 --- a/src/common/Reference.cpp +++ b/src/common/Reference.cpp @@ -26,7 +26,7 @@ namespace love const char REFERENCE_TABLE_NAME[] = "love-references"; Reference::Reference() - : L(0) + : L(nullptr) , idx(LUA_REFNIL) { } @@ -64,21 +64,31 @@ void Reference::unref() } } -void Reference::push() +void Reference::push(lua_State *newL) { if (idx != LUA_REFNIL) { - luax_insist(L, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME); - lua_rawgeti(L, -1, idx); - lua_remove(L, -2); + luax_insist(newL, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME); + lua_rawgeti(newL, -1, idx); + lua_remove(newL, -2); } else - lua_pushnil(L); + lua_pushnil(newL); } -lua_State *Reference::getL() +void Reference::push() +{ + push(L); +} + +lua_State *Reference::getL() const { return L; } +void Reference::setL(lua_State *newL) +{ + L = newL; +} + } // love diff --git a/src/common/Reference.h b/src/common/Reference.h index 5d7341b20..eb8e8b288 100644 --- a/src/common/Reference.h +++ b/src/common/Reference.h @@ -63,6 +63,14 @@ public: **/ void unref(); + /** + * Pushes the referred value onto the stack of a different coroutine + * in the same main Lua state. + * THIS SHOULD NOT BE USED FOR DIFFERENT LUA STATES (created with + * luaL_newstate)! Only with different coroutines! + **/ + void push(lua_State *newL); + /** * Pushes the referred value onto the stack. **/ @@ -72,7 +80,15 @@ public: * Gets the Lua state associated with this * reference. **/ - lua_State *getL(); + lua_State *getL() const; + + /** + * Associates a new Lua state with this reference. + * THIS IS DANGEROUS! It is only designed to be + * used with different coroutines from the same + * main Lua state! + **/ + void setL(lua_State *newL); private: diff --git a/src/common/Vector.cpp b/src/common/Vector.cpp index 8e0ae515b..c2d37869d 100644 --- a/src/common/Vector.cpp +++ b/src/common/Vector.cpp @@ -23,4 +23,4 @@ namespace love { // Implementation in header. -} \ No newline at end of file +} diff --git a/src/common/b64.h b/src/common/b64.h index 99685cdac..55fe47dea 100644 --- a/src/common/b64.h +++ b/src/common/b64.h @@ -38,4 +38,4 @@ char *b64_decode(const char *src, int slen, int &size); } // love -#endif // LOVE_B64_H \ No newline at end of file +#endif // LOVE_B64_H diff --git a/src/common/runtime.cpp b/src/common/runtime.cpp index 55c216adb..e4da8991a 100644 --- a/src/common/runtime.cpp +++ b/src/common/runtime.cpp @@ -464,7 +464,7 @@ void luax_pushtype(lua_State *L, const char *name, bits flags, love::Object *dat bool luax_istype(lua_State *L, int idx, love::bits type) { - if (lua_isuserdata(L, idx) == 0) + if (lua_type(L, idx) != LUA_TUSERDATA) return false; return ((((Proxy *)lua_touserdata(L, idx))->flags & type) == type); diff --git a/src/common/runtime.h b/src/common/runtime.h index 8f99f259f..de9c6fbde 100644 --- a/src/common/runtime.h +++ b/src/common/runtime.h @@ -401,7 +401,7 @@ extern "C" { // Also called from luasocket template T *luax_checktype(lua_State *L, int idx, const char *name, love::bits type) { - if (lua_isuserdata(L, idx) == 0) + if (lua_type(L, idx) != LUA_TUSERDATA) luax_typerror(L, idx, name); Proxy *u = (Proxy *)lua_touserdata(L, idx); diff --git a/src/common/utf8.h b/src/common/utf8.h index 6f0d78ef1..9a2c51e9b 100644 --- a/src/common/utf8.h +++ b/src/common/utf8.h @@ -45,4 +45,4 @@ void replace_char(std::string &str, char find, char replace); } // love -#endif // LOVE_WINDOWS \ No newline at end of file +#endif // LOVE_WINDOWS diff --git a/src/common/version.h b/src/common/version.h index d8798f9dc..5e3e15f72 100644 --- a/src/common/version.h +++ b/src/common/version.h @@ -27,9 +27,9 @@ namespace love // Version stuff. const int VERSION_MAJOR = 0; const int VERSION_MINOR = 9; -const int VERSION_REV = 0; -const char *VERSION = "0.9.0"; -const char *VERSION_COMPATIBILITY[] = { VERSION, 0 }; +const int VERSION_REV = 1; +const char *VERSION = "0.9.1"; +const char *VERSION_COMPATIBILITY[] = { VERSION, "0.9.0", 0 }; const char *VERSION_CODENAME = "Baby Inspector"; } // love diff --git a/src/libraries/ddsparse/ddsparse.cpp b/src/libraries/ddsparse/ddsparse.cpp index 590d665ce..f6fe62bcc 100644 --- a/src/libraries/ddsparse/ddsparse.cpp +++ b/src/libraries/ddsparse/ddsparse.cpp @@ -237,36 +237,37 @@ size_t Parser::getMipmapCount() const size_t Parser::parseImageSize(Format fmt, int width, int height) const { - size_t size = 0; + size_t numBlocksWide = 0; + size_t numBlocksHigh = 0; + size_t numBytesPerBlock = 0; switch (fmt) { case FORMAT_DXT1: + case FORMAT_BC4: + case FORMAT_BC4s: + numBytesPerBlock = 8; + break; case FORMAT_DXT3: case FORMAT_DXT5: case FORMAT_BC5s: case FORMAT_BC5: + case FORMAT_BC6H: case FORMAT_BC7: case FORMAT_BC7srgb: - { - int numBlocksWide = 0; - if (width > 0) - numBlocksWide = std::max(1, (width + 3) / 4); - - int numBlocksHigh = 0; - if (height > 0) - numBlocksHigh = std::max(1, (height + 3) / 4); - - int numBytesPerBlock = (fmt == FORMAT_DXT1 ? 8 : 16); - - size = numBlocksWide * numBytesPerBlock * numBlocksHigh; - } + numBytesPerBlock = 16; break; default: break; } - return size; + if (width > 0) + numBlocksWide = std::max(1, (width + 3) / 4); + + if (height > 0) + numBlocksHigh = std::max(1, (height + 3) / 4); + + return numBlocksWide * numBytesPerBlock * numBlocksHigh; } bool Parser::parseTexData(const uint8_t *data, size_t dataSize, Format fmt, int w, int h, int mips) diff --git a/src/libraries/ddsparse/ddsparse.h b/src/libraries/ddsparse/ddsparse.h index 82c2c41cc..6ab659347 100644 --- a/src/libraries/ddsparse/ddsparse.h +++ b/src/libraries/ddsparse/ddsparse.h @@ -60,7 +60,7 @@ struct Image const uint8_t *data; Image() : width(0), height(0), dataSize(0), data(0) - {}; + {} }; /** diff --git a/src/libraries/enet/enet.cpp b/src/libraries/enet/enet.cpp index 836c166c6..34e215c78 100644 --- a/src/libraries/enet/enet.cpp +++ b/src/libraries/enet/enet.cpp @@ -716,10 +716,6 @@ static const struct luaL_Reg enet_peer_funcs [] = { {NULL, NULL} }; -static const struct luaL_Reg enet_event_funcs [] = { - {NULL, NULL} -}; - int luaopen_enet(lua_State *l) { enet_initialize(); atexit(enet_deinitialize); diff --git a/src/libraries/luasocket/libluasocket/lua.h b/src/libraries/luasocket/libluasocket/lua.h index e97bc2ac2..a280df842 100644 --- a/src/libraries/luasocket/libluasocket/lua.h +++ b/src/libraries/luasocket/libluasocket/lua.h @@ -1,3 +1,6 @@ +#ifndef LUA_WRAP_H +#define LUA_WRAP_H + #define LUA_COMPAT_ALL #include #include @@ -10,3 +13,5 @@ extern int luax_typerror(lua_State *L, int narg, const char *type); #endif + +#endif // LUA_WRAP_H diff --git a/src/modules/audio/Audio.h b/src/modules/audio/Audio.h index b7c192577..9a8ca863c 100644 --- a/src/modules/audio/Audio.h +++ b/src/modules/audio/Audio.h @@ -67,7 +67,7 @@ public: /** * Destructor. **/ - virtual ~Audio() {}; + virtual ~Audio() {} virtual Source *newSource(love::sound::Decoder *decoder) = 0; virtual Source *newSource(love::sound::SoundData *soundData) = 0; @@ -88,7 +88,7 @@ public: * Play the specified Source. * @param source The Source to play. **/ - virtual void play(Source *source) = 0; + virtual bool play(Source *source) = 0; /** * Stops playback on the specified source. diff --git a/src/modules/audio/Source.h b/src/modules/audio/Source.h index d074e8706..d5cf86d7d 100644 --- a/src/modules/audio/Source.h +++ b/src/modules/audio/Source.h @@ -53,7 +53,7 @@ public: virtual Source *clone() = 0; - virtual void play() = 0; + virtual bool play() = 0; virtual void stop() = 0; virtual void pause() = 0; virtual void resume() = 0; diff --git a/src/modules/audio/null/Audio.cpp b/src/modules/audio/null/Audio.cpp index 2e5031ae1..6b6fd3f93 100644 --- a/src/modules/audio/null/Audio.cpp +++ b/src/modules/audio/null/Audio.cpp @@ -61,12 +61,9 @@ int Audio::getMaxSources() const return 0; } -void Audio::play(love::audio::Source *) -{ -} - -void Audio::play() +bool Audio::play(love::audio::Source *) { + return false; } void Audio::stop(love::audio::Source *) diff --git a/src/modules/audio/null/Audio.h b/src/modules/audio/null/Audio.h index 3f571da67..372f756c5 100644 --- a/src/modules/audio/null/Audio.h +++ b/src/modules/audio/null/Audio.h @@ -48,8 +48,7 @@ public: love::audio::Source *newSource(love::sound::SoundData *soundData); int getSourceCount() const; int getMaxSources() const; - void play(love::audio::Source *source); - void play(); + bool play(love::audio::Source *source); void stop(love::audio::Source *source); void stop(); void pause(love::audio::Source *source); diff --git a/src/modules/audio/null/Source.cpp b/src/modules/audio/null/Source.cpp index ce1ec7f5f..caa212374 100644 --- a/src/modules/audio/null/Source.cpp +++ b/src/modules/audio/null/Source.cpp @@ -42,8 +42,9 @@ love::audio::Source *Source::clone() return this; } -void Source::play() +bool Source::play() { + return false; } void Source::stop() diff --git a/src/modules/audio/null/Source.h b/src/modules/audio/null/Source.h index 303eb879c..0a5c5253d 100644 --- a/src/modules/audio/null/Source.h +++ b/src/modules/audio/null/Source.h @@ -39,7 +39,7 @@ public: virtual ~Source(); virtual love::audio::Source *clone(); - virtual void play(); + virtual bool play(); virtual void stop(); virtual void pause(); virtual void resume(); diff --git a/src/modules/audio/openal/Audio.cpp b/src/modules/audio/openal/Audio.cpp index 07ce2959d..c6af9be19 100644 --- a/src/modules/audio/openal/Audio.cpp +++ b/src/modules/audio/openal/Audio.cpp @@ -69,22 +69,25 @@ void Audio::PoolThread::setFinish() } Audio::Audio() - : distanceModel(DISTANCE_INVERSE_CLAMPED) + : device(nullptr) + , capture(nullptr) + , context(nullptr) + , pool(nullptr) + , poolThread(nullptr) + , distanceModel(DISTANCE_INVERSE_CLAMPED) { - // Passing zero for default device. - device = alcOpenDevice(0); + // Passing null for default device. + device = alcOpenDevice(nullptr); - if (device == 0) + if (device == nullptr) throw love::Exception("Could not open device."); - context = alcCreateContext(device, 0); + context = alcCreateContext(device, nullptr); - if (context == 0) + if (context == nullptr) throw love::Exception("Could not create context."); - alcMakeContextCurrent(context); - - if (alcGetError(device) != ALC_NO_ERROR) + 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)); @@ -108,7 +111,18 @@ Audio::Audio() }*/ // pool must be allocated after AL context. - pool = new Pool(); + try + { + pool = new Pool(); + } + catch (love::Exception &) + { + alcMakeContextCurrent(nullptr); + alcDestroyContext(context); + //if (capture) alcCaptureCloseDevice(capture); + alcCloseDevice(device); + throw; + } poolThread = new PoolThread(pool); poolThread->start(); @@ -122,7 +136,7 @@ Audio::~Audio() delete poolThread; delete pool; - alcMakeContextCurrent(0); + alcMakeContextCurrent(nullptr); alcDestroyContext(context); //if (capture) alcCaptureCloseDevice(capture); alcCloseDevice(device); @@ -154,9 +168,9 @@ int Audio::getMaxSources() const return pool->getMaxSources(); } -void Audio::play(love::audio::Source *source) +bool Audio::play(love::audio::Source *source) { - source->play(); + return source->play(); } void Audio::stop(love::audio::Source *source) @@ -281,7 +295,7 @@ bool Audio::canRecord() Audio::DistanceModel Audio::getDistanceModel() const { - return this->distanceModel; + return distanceModel; } void Audio::setDistanceModel(DistanceModel distanceModel) diff --git a/src/modules/audio/openal/Audio.h b/src/modules/audio/openal/Audio.h index a40494b2a..6e5f50696 100644 --- a/src/modules/audio/openal/Audio.h +++ b/src/modules/audio/openal/Audio.h @@ -67,8 +67,7 @@ public: love::audio::Source *newSource(love::sound::SoundData *soundData); int getSourceCount() const; int getMaxSources() const; - void play(love::audio::Source *source); - void play(); + bool play(love::audio::Source *source); void stop(love::audio::Source *source); void stop(); void pause(love::audio::Source *source); diff --git a/src/modules/audio/openal/Pool.cpp b/src/modules/audio/openal/Pool.cpp index f3d74a91a..8d239f4d7 100644 --- a/src/modules/audio/openal/Pool.cpp +++ b/src/modules/audio/openal/Pool.cpp @@ -30,23 +30,45 @@ namespace openal { Pool::Pool() + : sources() + , totalSources(0) + , mutex(nullptr) { + // Clear errors. + alGetError(); + // Generate sources. - alGenSources(NUM_SOURCES, sources); + for (int i = 0; i < MAX_SOURCES; i++) + { + alGenSources(1, &sources[i]); + + // We might hit an implementation-dependent limit on the total number + // of sources before reaching MAX_SOURCES. + if (alGetError() != AL_NO_ERROR) + break; + + totalSources++; + } + + if (totalSources < 4) + throw love::Exception("Could not generate sources."); // Create the mutex. mutex = thread::newMutex(); - if (alGetError() != AL_NO_ERROR) - throw love::Exception("Could not generate sources."); + ALboolean hasext = alIsExtensionPresent("AL_SOFT_direct_channels"); // Make all sources available initially. - for (int i = 0; i < NUM_SOURCES; i++) + for (int i = 0; i < totalSources; i++) { -#ifdef AL_DIRECT_CHANNELS_SOFT - // Bypassing virtualization of speakers for multi-channel sources in OpenAL Soft. - alSourcei(sources[i], AL_DIRECT_CHANNELS_SOFT, AL_TRUE); +#ifdef AL_SOFT_direct_channels + if (hasext) + { + // Bypass virtualization of speakers for multi-channel sources in OpenAL Soft. + alSourcei(sources[i], AL_DIRECT_CHANNELS_SOFT, AL_TRUE); + } #endif + available.push(sources[i]); } } @@ -58,7 +80,7 @@ Pool::~Pool() delete mutex; // Free all sources. - alDeleteSources(NUM_SOURCES, sources); + alDeleteSources(totalSources, sources); } bool Pool::isAvailable() const @@ -113,7 +135,7 @@ int Pool::getSourceCount() const int Pool::getMaxSources() const { - return NUM_SOURCES; + return totalSources; } bool Pool::play(Source *source, ALuint &out) @@ -141,9 +163,7 @@ bool Pool::play(Source *source, ALuint &out) source->retain(); - source->playAtomic(); - - ok = true; + ok = source->playAtomic(); } else { diff --git a/src/modules/audio/openal/Pool.h b/src/modules/audio/openal/Pool.h index 46353e759..f0896ea97 100644 --- a/src/modules/audio/openal/Pool.h +++ b/src/modules/audio/openal/Pool.h @@ -36,6 +36,7 @@ #ifdef LOVE_MACOSX_USE_FRAMEWORKS #include #include +#include #else #include #include @@ -99,11 +100,15 @@ private: bool findSource(Source *source, ALuint &out); bool removeSource(Source *source); - // Number of OpenAL sources. - static const int NUM_SOURCES = 64; + + // Maximum possible number of OpenAL sources the pool attempts to generate. + static const int MAX_SOURCES = 64; // OpenAL sources - ALuint sources[NUM_SOURCES]; + ALuint sources[MAX_SOURCES]; + + // Total number of created sources in the pool. + int totalSources; // A queue of available sources. std::queue available; diff --git a/src/modules/audio/openal/Source.cpp b/src/modules/audio/openal/Source.cpp index a9a38b139..877a35aeb 100644 --- a/src/modules/audio/openal/Source.cpp +++ b/src/modules/audio/openal/Source.cpp @@ -165,15 +165,16 @@ love::audio::Source *Source::clone() return new Source(*this); } -void Source::play() +bool Source::play() { if (valid && paused) { pool->resume(this); - return; + return true; } valid = pool->play(this, source); + return valid; } void Source::stop() @@ -512,7 +513,7 @@ bool Source::isLooping() const return looping; } -void Source::playAtomic() +bool Source::playAtomic() { if (type == TYPE_STATIC) { @@ -539,10 +540,19 @@ void Source::playAtomic() // of the new one. reset(); + // Clear errors. + alGetError(); + 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 + + return success; } void Source::stopAtomic() diff --git a/src/modules/audio/openal/Source.h b/src/modules/audio/openal/Source.h index bb8bc18e9..a2cc9520c 100644 --- a/src/modules/audio/openal/Source.h +++ b/src/modules/audio/openal/Source.h @@ -76,7 +76,7 @@ public: virtual ~Source(); virtual love::audio::Source *clone(); - virtual void play(); + virtual bool play(); virtual void stop(); virtual void pause(); virtual void resume(); @@ -117,7 +117,7 @@ public: virtual float getMaxDistance() const; virtual int getChannels() const; - void playAtomic(); + bool playAtomic(); void stopAtomic(); void pauseAtomic(); void resumeAtomic(); diff --git a/src/modules/audio/wrap_Audio.cpp b/src/modules/audio/wrap_Audio.cpp index 4b8e39969..217c4f98e 100644 --- a/src/modules/audio/wrap_Audio.cpp +++ b/src/modules/audio/wrap_Audio.cpp @@ -75,8 +75,8 @@ int w_newSource(lua_State *L) int w_play(lua_State *L) { Source *s = luax_checksource(L, 1); - instance->play(s); - return 0; + luax_pushboolean(L, instance->play(s)); + return 1; } int w_stop(lua_State *L) diff --git a/src/modules/audio/wrap_Source.cpp b/src/modules/audio/wrap_Source.cpp index 44b32a07b..c94ded3b4 100644 --- a/src/modules/audio/wrap_Source.cpp +++ b/src/modules/audio/wrap_Source.cpp @@ -44,8 +44,8 @@ int w_Source_clone(lua_State *L) int w_Source_play(lua_State *L) { Source *t = luax_checksource(L, 1); - t->play(); - return 0; + luax_pushboolean(L, t->play()); + return 1; } int w_Source_stop(lua_State *L) diff --git a/src/modules/event/sdl/Event.cpp b/src/modules/event/sdl/Event.cpp index ece1f7bfd..425da01fa 100644 --- a/src/modules/event/sdl/Event.cpp +++ b/src/modules/event/sdl/Event.cpp @@ -37,6 +37,24 @@ namespace event namespace sdl { +// SDL reports mouse coordinates in the window coordinate system in OS X, but +// we want them in pixel coordinates (may be different with high-DPI enabled.) +static void windowToPixelCoords(int *x, int *y) +{ + double scale = 1.0; + + window::Window *window = (window::Window *) Module::findInstance("love.window."); + if (window != nullptr) + scale = window->getPixelScale(); + + if (x != nullptr) + *x = int(double(*x) * scale); + + if (y != nullptr) + *y = int(double(*y) * scale); +} + + const char *Event::getName() const { return "love.event.sdl"; @@ -162,8 +180,11 @@ Message *Event::convert(const SDL_Event &e) const case SDL_MOUSEBUTTONUP: if (buttons.find(e.button.button, button) && mouse::Mouse::getConstant(button, txt)) { - arg1 = new Variant((double) e.button.x); - arg2 = new Variant((double) e.button.y); + int x = e.button.x; + int y = e.button.y; + windowToPixelCoords(&x, &y); + arg1 = new Variant((double) x); + arg2 = new Variant((double) y); arg3 = new Variant(txt, strlen(txt)); msg = new Message((e.type == SDL_MOUSEBUTTONDOWN) ? "mousepressed" : "mousereleased", @@ -182,6 +203,7 @@ Message *Event::convert(const SDL_Event &e) const int mx, my; SDL_GetMouseState(&mx, &my); + windowToPixelCoords(&mx, &my); arg1 = new Variant((double) mx); arg2 = new Variant((double) my); @@ -366,7 +388,7 @@ Message *Event::convertJoystickEvent(const SDL_Event &e) const Message *Event::convertWindowEvent(const SDL_Event &e) const { Message *msg = 0; - Variant *arg1, *arg2; + Variant *arg1, *arg2, *arg3, *arg4; window::Window *win = 0; if (e.type != SDL_WINDOWEVENT) @@ -402,17 +424,31 @@ Message *Event::convertWindowEvent(const SDL_Event &e) const win = (window::Window *) Module::findInstance("love.window."); if (win) { + int px_w = e.window.data1; + int px_h = e.window.data2; + +#if SDL_VERSION_ATLEAST(2,0,1) + SDL_Window *sdlwin = SDL_GetWindowFromID(e.window.windowID); + if (sdlwin) + SDL_GL_GetDrawableSize(sdlwin, &px_w, &px_h); +#endif + win->onWindowResize(e.window.data1, e.window.data2); graphics::Graphics *gfx = (graphics::Graphics *) Module::findInstance("love.graphics."); if (gfx) - gfx->setViewportSize(e.window.data1, e.window.data2); + gfx->setViewportSize(px_w, px_h); + + arg1 = new Variant((double) px_w); + arg2 = new Variant((double) px_h); + arg3 = new Variant((double) e.window.data1); + arg4 = new Variant((double) e.window.data2); + msg = new Message("resize", arg1, arg2, arg3, arg4); + arg1->release(); + arg2->release(); + arg3->release(); + arg4->release(); } - arg1 = new Variant((double) e.window.data1); - arg2 = new Variant((double) e.window.data2); - msg = new Message("resize", arg1, arg2); - arg1->release(); - arg2->release(); break; } @@ -608,6 +644,17 @@ std::map Event::createKeyMap() k[SDLK_AUDIOPLAY] = Keyboard::KEY_AUDIOPLAY; k[SDLK_AUDIOMUTE] = Keyboard::KEY_AUDIOMUTE; k[SDLK_MEDIASELECT] = Keyboard::KEY_MEDIASELECT; + k[SDLK_WWW] = Keyboard::KEY_WWW; + k[SDLK_MAIL] = Keyboard::KEY_MAIL; + k[SDLK_CALCULATOR] = Keyboard::KEY_CALCULATOR; + k[SDLK_COMPUTER] = Keyboard::KEY_COMPUTER; + k[SDLK_AC_SEARCH] = Keyboard::KEY_APP_SEARCH; + k[SDLK_AC_HOME] = Keyboard::KEY_APP_HOME; + k[SDLK_AC_BACK] = Keyboard::KEY_APP_BACK; + k[SDLK_AC_FORWARD] = Keyboard::KEY_APP_FORWARD; + k[SDLK_AC_STOP] = Keyboard::KEY_APP_STOP; + k[SDLK_AC_REFRESH] = Keyboard::KEY_APP_REFRESH; + k[SDLK_AC_BOOKMARKS] = Keyboard::KEY_APP_BOOKMARKS; k[SDLK_BRIGHTNESSDOWN] = Keyboard::KEY_BRIGHTNESSDOWN; k[SDLK_BRIGHTNESSUP] = Keyboard::KEY_BRIGHTNESSUP; diff --git a/src/modules/font/Font.h b/src/modules/font/Font.h index dacde0348..a92b88efc 100644 --- a/src/modules/font/Font.h +++ b/src/modules/font/Font.h @@ -54,4 +54,4 @@ public: } // font } // love -#endif // LOVE_FONT_FONT_H \ No newline at end of file +#endif // LOVE_FONT_FONT_H diff --git a/src/modules/font/GlyphData.h b/src/modules/font/GlyphData.h index 59aa1200b..6144dcb9b 100644 --- a/src/modules/font/GlyphData.h +++ b/src/modules/font/GlyphData.h @@ -46,7 +46,6 @@ struct GlyphMetrics int advance; int bearingX; int bearingY; - int spacing; }; /** diff --git a/src/modules/font/ImageRasterizer.h b/src/modules/font/ImageRasterizer.h index 5c7e9e8f6..482d442c6 100644 --- a/src/modules/font/ImageRasterizer.h +++ b/src/modules/font/ImageRasterizer.h @@ -79,4 +79,4 @@ private: } // font } // love -#endif // LOVE_FONT_IMAGE_RASTERIZER_H \ No newline at end of file +#endif // LOVE_FONT_IMAGE_RASTERIZER_H diff --git a/src/modules/font/Rasterizer.cpp b/src/modules/font/Rasterizer.cpp index 1454f2557..cec9f0bef 100644 --- a/src/modules/font/Rasterizer.cpp +++ b/src/modules/font/Rasterizer.cpp @@ -96,4 +96,4 @@ bool Rasterizer::hasGlyphs(const std::string &text) const } } // font -} // love \ No newline at end of file +} // love diff --git a/src/modules/font/Rasterizer.h b/src/modules/font/Rasterizer.h index a4cd6e5c9..ff1cd22e5 100644 --- a/src/modules/font/Rasterizer.h +++ b/src/modules/font/Rasterizer.h @@ -114,4 +114,4 @@ protected: } // font } // love -#endif // LOVE_FONT_RASTERIZER_H \ No newline at end of file +#endif // LOVE_FONT_RASTERIZER_H diff --git a/src/modules/font/freetype/TrueTypeRasterizer.cpp b/src/modules/font/freetype/TrueTypeRasterizer.cpp index 847793687..e09328f70 100644 --- a/src/modules/font/freetype/TrueTypeRasterizer.cpp +++ b/src/modules/font/freetype/TrueTypeRasterizer.cpp @@ -146,4 +146,4 @@ bool TrueTypeRasterizer::hasGlyph(uint32 glyph) const } // freetype } // font -} // love \ No newline at end of file +} // love diff --git a/src/modules/graphics/Drawable.h b/src/modules/graphics/Drawable.h index aea03b340..a4cd7e2ff 100644 --- a/src/modules/graphics/Drawable.h +++ b/src/modules/graphics/Drawable.h @@ -55,7 +55,7 @@ public: * @param kx Shear along the x-axis. * @param ky Shear along the y-axis. **/ - virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const = 0; + virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) = 0; }; } // graphics diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 9976ec4fe..9e997c595 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -99,6 +99,16 @@ bool Graphics::getConstant(RendererInfo in, const char *&out) return rendererInfo.find(in, out); } +bool Graphics::getConstant(const char *in, SystemLimit &out) +{ + return systemLimits.find(in, out); +} + +bool Graphics::getConstant(SystemLimit in, const char *&out) +{ + return systemLimits.find(in, out); +} + StringMap::Entry Graphics::drawModeEntries[] = { { "line", Graphics::DRAW_LINE }, @@ -157,6 +167,8 @@ StringMap::Entry Graphics::suppor { "mipmap", Graphics::SUPPORT_MIPMAP }, { "dxt", Graphics::SUPPORT_DXT }, { "bc5", Graphics::SUPPORT_BC5 }, + { "instancing", Graphics::SUPPORT_INSTANCING }, + { "srgb", Graphics::SUPPORT_SRGB }, }; StringMap Graphics::support(Graphics::supportEntries, sizeof(Graphics::supportEntries)); @@ -171,5 +183,15 @@ StringMap::Entry Graph StringMap Graphics::rendererInfo(Graphics::rendererInfoEntries, sizeof(Graphics::rendererInfoEntries)); +StringMap::Entry Graphics::systemLimitEntries[] = +{ + {"pointsize", Graphics::LIMIT_POINT_SIZE}, + {"texturesize", Graphics::LIMIT_TEXTURE_SIZE}, + {"multicanvas", Graphics::LIMIT_MULTI_CANVAS}, + {"canvasfsaa", Graphics::LIMIT_CANVAS_FSAA}, +}; + +StringMap Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries)); + } // graphics } // love diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 3c35de337..b8a0532bf 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -87,6 +87,8 @@ public: SUPPORT_MIPMAP, SUPPORT_DXT, SUPPORT_BC5, + SUPPORT_INSTANCING, + SUPPORT_SRGB, SUPPORT_MAX_ENUM }; @@ -99,6 +101,15 @@ public: RENDERER_INFO_MAX_ENUM }; + enum SystemLimit + { + LIMIT_POINT_SIZE, + LIMIT_TEXTURE_SIZE, + LIMIT_MULTI_CANVAS, + LIMIT_CANVAS_FSAA, + LIMIT_MAX_ENUM + }; + virtual ~Graphics(); /** @@ -111,7 +122,7 @@ public: * @param width The viewport width. * @param height The viewport height. **/ - virtual bool setMode(int width, int height) = 0; + virtual bool setMode(int width, int height, bool &sRGB) = 0; /** * Un-sets the current graphics display mode (uninitializing objects if @@ -140,6 +151,9 @@ public: static bool getConstant(const char *in, RendererInfo &out); static bool getConstant(RendererInfo in, const char *&out); + static bool getConstant(const char *in, SystemLimit &out); + static bool getConstant(SystemLimit in, const char *&out); + private: static StringMap::Entry drawModeEntries[]; @@ -163,6 +177,9 @@ private: static StringMap::Entry rendererInfoEntries[]; static StringMap rendererInfo; + static StringMap::Entry systemLimitEntries[]; + static StringMap systemLimits; + }; // Graphics } // graphics diff --git a/src/modules/graphics/Texture.cpp b/src/modules/graphics/Texture.cpp index 25034a3d0..0a8d85541 100644 --- a/src/modules/graphics/Texture.cpp +++ b/src/modules/graphics/Texture.cpp @@ -109,6 +109,16 @@ bool Texture::getConstant(WrapMode in, const char *&out) return wrapModes.find(in, out); } +bool Texture::getConstant(const char *in, Format &out) +{ + return formats.find(in, out); +} + +bool Texture::getConstant(Format in, const char *&out) +{ + return formats.find(in, out); +} + StringMap::Entry Texture::filterModeEntries[] = { { "linear", Texture::FILTER_LINEAR }, @@ -125,6 +135,15 @@ StringMap::Entry Texture::wrapModeEnt StringMap Texture::wrapModes(Texture::wrapModeEntries, sizeof(Texture::wrapModeEntries)); +StringMap::Entry Texture::formatEntries[] = +{ + {"normal", Texture::FORMAT_NORMAL}, + {"hdr", Texture::FORMAT_HDR}, + {"srgb", Texture::FORMAT_SRGB}, +}; + +StringMap Texture::formats(Texture::formatEntries, sizeof(Texture::formatEntries)); + } // graphics } // love diff --git a/src/modules/graphics/Texture.h b/src/modules/graphics/Texture.h index e87c432f4..d9c83171e 100644 --- a/src/modules/graphics/Texture.h +++ b/src/modules/graphics/Texture.h @@ -55,6 +55,14 @@ public: FILTER_MAX_ENUM }; + enum Format + { + FORMAT_NORMAL, + FORMAT_HDR, + FORMAT_SRGB, + FORMAT_MAX_ENUM + }; + struct Filter { Filter(); @@ -88,7 +96,7 @@ public: * @param kx Shear along the x-axis. * @param ky Shear along the y-axis. **/ - virtual void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const = 0; + virtual void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) = 0; virtual int getWidth() const; virtual int getHeight() const; @@ -111,6 +119,9 @@ public: static bool getConstant(const char *in, WrapMode &out); static bool getConstant(WrapMode in, const char *&out); + static bool getConstant(const char *in, Format &out); + static bool getConstant(Format in, const char *&out); + protected: int width; @@ -132,6 +143,9 @@ private: static StringMap::Entry wrapModeEntries[]; static StringMap wrapModes; + static StringMap::Entry formatEntries[]; + static StringMap formats; + }; // Texture } // graphics diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index 855830dd1..58ee616c4 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/src/modules/graphics/opengl/Canvas.cpp @@ -55,10 +55,25 @@ struct FramebufferStrategy /** * @param[in] width Width of the stencil buffer * @param[in] height Height of the stencil buffer + * @param[in] samples Number of samples to use * @param[out] stencil Name for stencil buffer * @return Whether the stencil buffer was successfully created **/ - virtual bool createStencil(int, int, GLuint &) + virtual bool createStencil(int, int, int, GLuint &) + { + return false; + } + + /// Create a MSAA renderbuffer and attach it to the active FBO. + /** + * @param[in] width Width of the MSAA buffer + * @param[in] height Height of the MSAA buffer + * @param[inout] samples Number of samples to use + * @param[in] internalformat The internal format to use for the buffer + * @param[out] buffer Name for the MSAA buffer + * @return Whether the MSAA buffer was successfully created and attached + **/ + virtual bool createMSAABuffer(int, int, int &, GLenum, GLuint &) { return false; } @@ -68,7 +83,7 @@ struct FramebufferStrategy * @param[in] framebuffer Framebuffer name * @param[in] depth_stencil Name for packed depth and stencil buffer */ - virtual void deleteFBO(GLuint, GLuint) {} + virtual void deleteFBO(GLuint, GLuint, GLuint) {} virtual void bindFBO(GLuint) {} /// attach additional canvases to the active framebuffer for rendering @@ -93,8 +108,11 @@ struct FramebufferStrategyGL3 : public FramebufferStrategy glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, texture, 0); + if (texture != 0) + { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, texture, 0); + } // check status GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); @@ -104,27 +122,62 @@ struct FramebufferStrategyGL3 : public FramebufferStrategy return status; } - virtual bool createStencil(int width, int height, GLuint &stencil) + virtual bool createStencil(int width, int height, int samples, GLuint &stencil) { // create combined depth/stencil buffer glDeleteRenderbuffers(1, &stencil); glGenRenderbuffers(1, &stencil); glBindRenderbuffer(GL_RENDERBUFFER, stencil); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, width, height); + + if (samples > 1) + glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH_STENCIL, width, height); + else + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_RENDERBUFFER, stencil); + GL_RENDERBUFFER, stencil); glBindRenderbuffer(GL_RENDERBUFFER, 0); // check status - return glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE; + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + glDeleteRenderbuffers(1, &stencil); + stencil = 0; + return false; + } + + return true; } - virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil) + virtual bool createMSAABuffer(int width, int height, int &samples, GLenum internalformat, GLuint &buffer) + { + glGenRenderbuffers(1, &buffer); + glBindRenderbuffer(GL_RENDERBUFFER, buffer); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internalformat, + width, height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, buffer); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + glDeleteRenderbuffers(1, &buffer); + buffer = 0; + return false; + } + + return true; + } + + virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil, GLuint msaa_buffer) { if (depth_stencil != 0) glDeleteRenderbuffers(1, &depth_stencil); + if (msaa_buffer != 0) + glDeleteRenderbuffers(1, &msaa_buffer); if (framebuffer != 0) glDeleteFramebuffers(1, &framebuffer); } @@ -189,27 +242,71 @@ struct FramebufferStrategyPackedEXT : public FramebufferStrategy return status; } - virtual bool createStencil(int width, int height, GLuint &stencil) + virtual bool createStencil(int width, int height, int samples, GLuint &stencil) { // create combined depth/stencil buffer - glDeleteRenderbuffers(1, &stencil); + glDeleteRenderbuffersEXT(1, &stencil); glGenRenderbuffersEXT(1, &stencil); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, stencil); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_STENCIL_EXT, - width, height); + + if (samples > 1) + { + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples, + GL_DEPTH_STENCIL, width, height); + } + else + { + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_STENCIL_EXT, + width, height); + } + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, stencil); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); // check status - return glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT; + if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + glDeleteRenderbuffersEXT(1, &stencil); + stencil = 0; + return false; + } + + return true; } - virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil) + virtual bool createMSAABuffer(int width, int height, int &samples, GLenum internalformat, GLuint &buffer) + { + if (!GLEE_EXT_framebuffer_multisample) + return false; + + glGenRenderbuffersEXT(1, &buffer); + glBindRenderbufferEXT(GL_RENDERBUFFER, buffer); + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples, + internalformat, width, height); + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, buffer); + glGetRenderbufferParameterivEXT(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples); + + glBindRenderbufferEXT(GL_RENDERBUFFER, 0); + + if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + glDeleteRenderbuffersEXT(1, &buffer); + buffer = 0; + return false; + } + + return true; + } + + virtual void deleteFBO(GLuint framebuffer, GLuint depth_stencil, GLuint msaa_buffer) { if (depth_stencil != 0) glDeleteRenderbuffersEXT(1, &depth_stencil); + if (msaa_buffer != 0) + glDeleteRenderbuffersEXT(1, &msaa_buffer); if (framebuffer != 0) glDeleteFramebuffersEXT(1, &framebuffer); } @@ -254,33 +351,50 @@ struct FramebufferStrategyPackedEXT : public FramebufferStrategy struct FramebufferStrategyEXT : public FramebufferStrategyPackedEXT { - virtual bool createStencil(int width, int height, GLuint &stencil) + virtual bool createStencil(int width, int height, int samples, GLuint &stencil) { // create stencil buffer - glDeleteRenderbuffers(1, &stencil); + glDeleteRenderbuffersEXT(1, &stencil); glGenRenderbuffersEXT(1, &stencil); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, stencil); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_STENCIL_INDEX, - width, height); + + if (samples > 1) + { + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples, + GL_STENCIL_INDEX, width, height); + } + else + { + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_STENCIL_INDEX, + width, height); + } + glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, stencil); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); // check status - return glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT; + if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + glDeleteRenderbuffersEXT(1, &stencil); + stencil = 0; + return false; + } + + return true; } bool isSupported() { GLuint fb = 0, stencil = 0; GLenum status = createFBO(fb, 0); - deleteFBO(fb, stencil); + deleteFBO(fb, stencil, 0); return status == GL_FRAMEBUFFER_COMPLETE; } }; -FramebufferStrategy *strategy = NULL; +FramebufferStrategy *strategy = nullptr; FramebufferStrategy strategyNone; @@ -290,8 +404,9 @@ FramebufferStrategyPackedEXT strategyPackedEXT; FramebufferStrategyEXT strategyEXT; -Canvas *Canvas::current = NULL; +Canvas *Canvas::current = nullptr; OpenGL::Viewport Canvas::systemViewport = OpenGL::Viewport(); +bool Canvas::screenHasSRGB = false; static void getStrategy() { @@ -308,14 +423,15 @@ static void getStrategy() } } -static int maxFBOColorAttachments = 0; -static int maxDrawBuffers = 0; - -Canvas::Canvas(int width, int height, TextureType texture_type) +Canvas::Canvas(int width, int height, Texture::Format format, int fsaa) : fbo(0) + , resolve_fbo(0) , texture(0) + , fsaa_buffer(0) , depth_stencil(0) - , texture_type(texture_type) + , format(format) + , fsaa_samples(fsaa) + , fsaa_dirty(false) { this->width = width; this->height = height; @@ -357,9 +473,50 @@ Canvas::~Canvas() unloadVolatile(); } +bool Canvas::createFSAAFBO(GLenum internalformat) +{ + // Create our FBO without a texture. + status = strategy->createFBO(fbo, 0); + + GLuint previous = 0; + if (current != this) + { + if (current != nullptr) + previous = current->fbo; + + strategy->bindFBO(fbo); + } + + // Create and attach the MSAA buffer for our FBO. + if (strategy->createMSAABuffer(width, height, fsaa_samples, internalformat, fsaa_buffer)) + status = GL_FRAMEBUFFER_COMPLETE; + else + status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; + + // Create the FBO used for the MSAA resolve, and attach the texture. + if (status == GL_FRAMEBUFFER_COMPLETE) + status = strategy->createFBO(resolve_fbo, texture); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + // Clean up. + strategy->deleteFBO(fbo, 0, fsaa_buffer); + strategy->deleteFBO(resolve_fbo, 0, 0); + fbo = fsaa_buffer = resolve_fbo = 0; + fsaa_samples = 0; + } + + if (current != this) + strategy->bindFBO(previous); + + return status == GL_FRAMEBUFFER_COMPLETE; +} + bool Canvas::loadVolatile() { fbo = depth_stencil = texture = 0; + resolve_fbo = fsaa_buffer = 0; + status = GL_FRAMEBUFFER_COMPLETE; // glTexImage2D is guaranteed to error in this case. if (width > gl.getMaxTextureSize() || height > gl.getMaxTextureSize()) @@ -376,13 +533,17 @@ bool Canvas::loadVolatile() GLint internalformat; GLenum textype; - switch (texture_type) + switch (format) { - case TYPE_HDR: + case Texture::FORMAT_HDR: internalformat = GL_RGBA16F; textype = GL_FLOAT; break; - case TYPE_NORMAL: + case Texture::FORMAT_SRGB: + internalformat = GL_SRGB8_ALPHA8; + textype = GL_UNSIGNED_BYTE; + break; + case Texture::FORMAT_NORMAL: default: internalformat = GL_RGBA8; textype = GL_UNSIGNED_BYTE; @@ -406,21 +567,44 @@ bool Canvas::loadVolatile() return false; } - status = strategy->createFBO(fbo, texture); + int max_samples = 0; + if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object + || GLEE_EXT_framebuffer_multisample) + { + glGetIntegerv(GL_MAX_SAMPLES, &max_samples); + } + + if (fsaa_samples > max_samples) + fsaa_samples = max_samples; + + // Try to create a FSAA FBO if requested. + bool fsaasuccess = false; + if (fsaa_samples > 1) + fsaasuccess = createFSAAFBO(internalformat); + + // On failure (or no requested FSAA), fall back to a regular FBO. + if (!fsaasuccess) + status = strategy->createFBO(fbo, texture); + if (status != GL_FRAMEBUFFER_COMPLETE) return false; clear(Color(0, 0, 0, 0)); + + fsaa_dirty = (fsaa_buffer != 0); + return true; } void Canvas::unloadVolatile() { - strategy->deleteFBO(fbo, depth_stencil); + strategy->deleteFBO(fbo, depth_stencil, fsaa_buffer); + strategy->deleteFBO(resolve_fbo, 0, 0); gl.deleteTexture(texture); fbo = depth_stencil = texture = 0; + resolve_fbo = fsaa_buffer = 0; for (size_t i = 0; i < attachedCanvases.size(); i++) attachedCanvases[i]->release(); @@ -428,13 +612,12 @@ void Canvas::unloadVolatile() attachedCanvases.clear(); } -void Canvas::drawv(const Matrix &t, const Vertex *v) const +void Canvas::drawv(const Matrix &t, const Vertex *v) { glPushMatrix(); - glMultMatrixf((const GLfloat *)t.getElements()); - gl.bindTexture(texture); + predraw(); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); @@ -447,11 +630,13 @@ void Canvas::drawv(const Matrix &t, const Vertex *v) const glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); + + postdraw(); glPopMatrix(); } -void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const +void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) { static Matrix t; t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky); @@ -459,7 +644,7 @@ void Canvas::draw(float x, float y, float angle, float sx, float sy, float ox, f drawv(t, vertices); } -void Canvas::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const +void Canvas::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) { static Matrix t; t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky); @@ -487,8 +672,12 @@ GLuint Canvas::getGLTexture() const return texture; } -void Canvas::predraw() const +void Canvas::predraw() { + // We need to make sure the texture is up-to-date by resolving the MSAA + // buffer (which we render to when the canvas is active) to it. + resolveMSAA(); + gl.bindTexture(texture); } @@ -498,12 +687,19 @@ void Canvas::setupGrab() if (current == this) return; - // cleanup after previous fbo - if (current != NULL) - current->stopGrab(); + // 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. - systemViewport = gl.getViewport(); strategy->bindFBO(fbo); gl.setViewport(OpenGL::Viewport(0, 0, width, height)); @@ -518,8 +714,14 @@ void Canvas::setupGrab() // Switch back to modelview matrix glMatrixMode(GL_MODELVIEW); - // indicate we are using this fbo - current = this; + // Make sure the correct sRGB setting is used when drawing to the canvas. + if (format == FORMAT_SRGB) + glEnable(GL_FRAMEBUFFER_SRGB); + else if (screenHasSRGB) + glDisable(GL_FRAMEBUFFER_SRGB); + + if (fsaa_buffer != 0) + fsaa_dirty = true; } void Canvas::startGrab(const std::vector &canvases) @@ -533,8 +735,11 @@ void Canvas::startGrab(const std::vector &canvases) if (!isMultiCanvasSupported()) throw love::Exception("Multi-canvas rendering is not supported on this system."); - if (canvases.size()+1 > size_t(maxDrawBuffers) || canvases.size()+1 > size_t(maxFBOColorAttachments)) + if ((int) canvases.size() + 1 > gl.getMaxRenderTargets()) throw love::Exception("This system can't simultaniously render to %d canvases.", canvases.size()+1); + + if (fsaa_samples != 0) + throw love::Exception("Multi-canvas rendering is not supported with FSAA."); } for (size_t i = 0; i < canvases.size(); i++) @@ -542,8 +747,11 @@ void Canvas::startGrab(const std::vector &canvases) if (canvases[i]->getWidth() != width || canvases[i]->getHeight() != height) throw love::Exception("All canvas arguments must have the same dimensions."); - if (canvases[i]->getTextureType() != texture_type) - throw love::Exception("All canvas arguments must have the same texture type."); + if (canvases[i]->getTextureFormat() != format) + throw love::Exception("All canvas arguments must have the same texture format."); + + if (canvases[i]->getFSAA() != 0) + throw love::Exception("Multi-canvas rendering is not supported with FSAA."); if (!canvaseschanged && canvases[i] != attachedCanvases[i]) canvaseschanged = true; @@ -584,21 +792,33 @@ void Canvas::startGrab() attachedCanvases.clear(); } -void Canvas::stopGrab() +void Canvas::stopGrab(bool switchingToOtherCanvas) { // i am not grabbing. leave me alone if (current != this) return; - // bind default - strategy->bindFBO(0); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); - current = nullptr; + if (switchingToOtherCanvas) + { + if (format == FORMAT_SRGB) + glDisable(GL_FRAMEBUFFER_SRGB); + } + else + { + // bind system framebuffer. + strategy->bindFBO(0); + current = nullptr; + gl.setViewport(systemViewport); - gl.setViewport(systemViewport); + if (format == FORMAT_SRGB && !screenHasSRGB) + glDisable(GL_FRAMEBUFFER_SRGB); + else if (format != FORMAT_SRGB && screenHasSRGB) + glEnable(GL_FRAMEBUFFER_SRGB); + } } void Canvas::clear(Color c) @@ -651,6 +871,9 @@ void Canvas::clear(Color c) if (current != this) strategy->bindFBO(previous); + + if (fsaa_buffer != 0) + fsaa_dirty = true; } bool Canvas::checkCreateStencil() @@ -662,7 +885,7 @@ bool Canvas::checkCreateStencil() if (current != this) strategy->bindFBO(fbo); - bool success = strategy->createStencil(width, height, depth_stencil); + bool success = strategy->createStencil(width, height, fsaa_samples, depth_stencil); if (current && current != this) strategy->bindFBO(current->fbo); @@ -674,12 +897,22 @@ bool Canvas::checkCreateStencil() love::image::ImageData *Canvas::getImageData(love::image::Image *image) { + resolveMSAA(); + int row = 4 * width; int size = row * height; GLubyte *pixels = new GLubyte[size]; - strategy->bindFBO(fbo); + // Our texture is attached to 'resolve_fbo' when we use MSAA. + if (fsaa_samples > 1 && (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object)) + glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo); + else if (fsaa_samples > 1 && GLEE_EXT_framebuffer_multisample) + glBindFramebufferEXT(GL_READ_FRAMEBUFFER, resolve_fbo); + else + strategy->bindFBO(fbo); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + if (current) strategy->bindFBO(current->fbo); else @@ -692,10 +925,17 @@ love::image::ImageData *Canvas::getImageData(love::image::Image *image) void Canvas::getPixel(unsigned char* pixel_rgba, int x, int y) { - if (current != this) + resolveMSAA(); + + // Our texture is attached to 'resolve_fbo' when we use MSAA. + if (fsaa_samples > 1 && (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object)) + glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo); + else if (fsaa_samples > 1 && GLEE_EXT_framebuffer_multisample) + glBindFramebufferEXT(GL_READ_FRAMEBUFFER, resolve_fbo); + else if (current != this) strategy->bindFBO(fbo); - glReadPixels(x, height - y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_rgba); + glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel_rgba); if (current && current != this) strategy->bindFBO(current->fbo); @@ -703,6 +943,44 @@ void Canvas::getPixel(unsigned char* pixel_rgba, int x, int y) strategy->bindFBO(0); } +bool Canvas::resolveMSAA() +{ + if (resolve_fbo == 0 || fsaa_buffer == 0) + return false; + + if (!fsaa_dirty) + return true; + + GLuint previous = 0; + if (current != nullptr) + previous = current->fbo; + + // Do the MSAA resolve by blitting the MSAA renderbuffer to the texture. + if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object) + { + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo); + glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + else if (GLEE_EXT_framebuffer_multisample && GLEE_EXT_framebuffer_blit) + { + glBindFramebufferEXT(GL_READ_FRAMEBUFFER, fbo); + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, resolve_fbo); + glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + else + return false; + + strategy->bindFBO(previous); + + if (current != this) + fsaa_dirty = false; + + return true; +} + bool Canvas::isSupported() { getStrategy(); @@ -714,19 +992,22 @@ bool Canvas::isHDRSupported() return GLEE_VERSION_3_0 || (isSupported() && GLEE_ARB_texture_float); } -bool Canvas::isMultiCanvasSupported() +bool Canvas::isSRGBSupported() { - if (!(isSupported() && (GLEE_VERSION_2_0 || GLEE_ARB_draw_buffers))) + if (GLEE_VERSION_3_0) + return true; + + if (!isSupported()) return false; - if (maxFBOColorAttachments == 0 || maxDrawBuffers == 0) - { - glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxFBOColorAttachments); - glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); - } + return (GLEE_ARB_framebuffer_sRGB || GLEE_EXT_framebuffer_sRGB) + && GLEE_EXT_texture_sRGB; +} - // system must support at least 4 simultanious active canvases - return maxFBOColorAttachments >= 4 && maxDrawBuffers >= 4; +bool Canvas::isMultiCanvasSupported() +{ + // system must support at least 4 simultanious active canvases. + return gl.getMaxRenderTargets() >= 4; } void Canvas::bindDefaultCanvas() @@ -735,23 +1016,6 @@ void Canvas::bindDefaultCanvas() current->stopGrab(); } -bool Canvas::getConstant(const char *in, Canvas::TextureType &out) -{ - return textureTypes.find(in, out); -} - -bool Canvas::getConstant(Canvas::TextureType in, const char *&out) -{ - return textureTypes.find(in, out); -} - -StringMap::Entry Canvas::textureTypeEntries[] = -{ - {"normal", Canvas::TYPE_NORMAL}, - {"hdr", Canvas::TYPE_HDR}, -}; -StringMap Canvas::textureTypes(Canvas::textureTypeEntries, sizeof(Canvas::textureTypeEntries)); - } // opengl } // graphics } // love diff --git a/src/modules/graphics/opengl/Canvas.h b/src/modules/graphics/opengl/Canvas.h index e9e9f7ca0..2d87b0bbc 100644 --- a/src/modules/graphics/opengl/Canvas.h +++ b/src/modules/graphics/opengl/Canvas.h @@ -39,14 +39,7 @@ class Canvas : public Texture { public: - enum TextureType - { - TYPE_NORMAL, - TYPE_HDR, - TYPE_MAX_ENUM - }; - - Canvas(int width, int height, TextureType texture_type = TYPE_NORMAL); + Canvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0); virtual ~Canvas(); // Implements Volatile. @@ -54,14 +47,14 @@ public: virtual void unloadVolatile(); // Implements Drawable. - virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const; + virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky); // Implements Texture. - virtual void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const; + virtual void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky); virtual void setFilter(const Texture::Filter &f); virtual void setWrap(const Texture::Wrap &w); virtual GLuint getGLTexture() const; - virtual void predraw() const; + virtual void predraw(); /** * @param canvases A list of other canvases to temporarily attach to this one, @@ -69,7 +62,7 @@ public: **/ void startGrab(const std::vector &canvases); void startGrab(); - void stopGrab(); + void stopGrab(bool switchingToOtherCanvas = false); void clear(Color c); @@ -92,42 +85,56 @@ public: return status; } - inline TextureType getTextureType() const + inline Texture::Format getTextureFormat() const { - return texture_type; + return format; } + inline int getFSAA() const + { + return fsaa_samples; + } + + bool resolveMSAA(); + static bool isSupported(); static bool isHDRSupported(); + static bool isSRGBSupported(); static bool isMultiCanvasSupported(); - static bool getConstant(const char *in, TextureType &out); - static bool getConstant(TextureType in, const char *&out); - static Canvas *current; static void bindDefaultCanvas(); // 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; + private: + bool createFSAAFBO(GLenum internalformat); + GLuint fbo; + GLuint resolve_fbo; + GLuint texture; + GLuint fsaa_buffer; GLuint depth_stencil; - TextureType texture_type; + Format format; GLenum status; std::vector attachedCanvases; - void setupGrab(); - void drawv(const Matrix &t, const Vertex *v) const; + int fsaa_samples; + bool fsaa_dirty; - static StringMap::Entry textureTypeEntries[]; - static StringMap textureTypes; -}; + void setupGrab(); + void drawv(const Matrix &t, const Vertex *v); + +}; // Canvas } // opengl } // graphics diff --git a/src/modules/graphics/opengl/Font.cpp b/src/modules/graphics/opengl/Font.cpp index 2b4bf8750..5cb062cb3 100644 --- a/src/modules/graphics/opengl/Font.cpp +++ b/src/modules/graphics/opengl/Font.cpp @@ -46,6 +46,7 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter) , lineHeight(1) , mSpacing(1) , filter(filter) + , useSpacesAsTab(false) { this->filter.mipmap = Texture::FILTER_NONE; @@ -67,13 +68,16 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter) textureWidth = TEXTURE_WIDTHS[textureSizeIndex]; textureHeight = TEXTURE_HEIGHTS[textureSizeIndex]; - love::font::GlyphData *gd = 0; + love::font::GlyphData *gd = nullptr; try { - gd = r->getGlyphData(32); + gd = r->getGlyphData(32); // Space character. type = (gd->getFormat() == love::font::GlyphData::FORMAT_LUMINANCE_ALPHA) ? FONT_TRUETYPE : FONT_IMAGE; + if (!r->hasGlyph(9)) // No tab character in the Rasterizer. + useSpacesAsTab = true; + loadVolatile(); } catch (love::Exception &) @@ -172,7 +176,26 @@ void Font::createTexture() Font::Glyph *Font::addGlyph(uint32 glyph) { - love::font::GlyphData *gd = rasterizer->getGlyphData(glyph); + love::font::GlyphData *gd = nullptr; + + // Use spaces for the tab 'glyph'. + if (glyph == 9 && useSpacesAsTab) + { + love::font::GlyphData *spacegd = rasterizer->getGlyphData(32); + + love::font::GlyphMetrics gm = {}; + gm.advance = spacegd->getAdvance() * SPACES_PER_TAB; + gm.bearingX = spacegd->getBearingX(); + gm.bearingY = spacegd->getBearingY(); + love::font::GlyphData::Format f = spacegd->getFormat(); + + spacegd->release(); + + gd = new love::font::GlyphData(glyph, gm, f); + } + else + gd = rasterizer->getGlyphData(glyph); + int w = gd->getWidth(); int h = gd->getHeight(); @@ -186,7 +209,15 @@ Font::Glyph *Font::addGlyph(uint32 glyph) if (textureY + h + TEXTURE_PADDING > textureHeight) { // totally out of space - new texture! - createTexture(); + try + { + createTexture(); + } + catch (love::Exception &) + { + gd->release(); + throw; + } } Glyph *g = new Glyph; diff --git a/src/modules/graphics/opengl/Font.h b/src/modules/graphics/opengl/Font.h index 1d92bac63..fee277452 100644 --- a/src/modules/graphics/opengl/Font.h +++ b/src/modules/graphics/opengl/Font.h @@ -175,7 +175,7 @@ private: return texture < other.texture; else return startvertex < other.startvertex; - }; + } }; bool initializeTexture(GLenum format); @@ -202,14 +202,19 @@ private: FontType type; Texture::Filter filter; + int textureX, textureY; + int rowHeight; + + bool useSpacesAsTab; + static const int NUM_TEXTURE_SIZES = 7; static const int TEXTURE_WIDTHS[NUM_TEXTURE_SIZES]; static const int TEXTURE_HEIGHTS[NUM_TEXTURE_SIZES]; static const int TEXTURE_PADDING = 1; - int textureX, textureY; - int rowHeight; + // This will be used if the Rasterizer doesn't have a tab character itself. + static const int SPACES_PER_TAB = 4; }; // Font diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index ba093a6ec..0a25792f9 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -53,12 +53,18 @@ Graphics::Graphics() , width(0) , height(0) , created(false) + , activeStencil(false) , savedState() { currentWindow = love::window::sdl::Window::createSingleton(); + int w, h; + love::window::WindowSettings wsettings; + + currentWindow->getWindow(w, h, wsettings); + if (currentWindow->isCreated()) - setMode(currentWindow->getWidth(), currentWindow->getHeight()); + setMode(w, h, wsettings.sRGB); } Graphics::~Graphics() @@ -95,6 +101,8 @@ DisplayState Graphics::saveState() for (int i = 0; i < 4; i++) s.colorMask[i] = colorMask[i]; + wireframe = isWireframe(); + return s; } @@ -111,6 +119,7 @@ void Graphics::restoreState(const DisplayState &s) else setScissor(); setColorMask(s.colorMask[0], s.colorMask[1], s.colorMask[2], s.colorMask[3]); + setWireframe(s.wireframe); } void Graphics::setViewportSize(int width, int height) @@ -147,7 +156,7 @@ void Graphics::setViewportSize(int width, int height) c->startGrab(c->getAttachedCanvases()); } -bool Graphics::setMode(int width, int height) +bool Graphics::setMode(int width, int height, bool &sRGB) { this->width = width; this->height = height; @@ -166,9 +175,6 @@ bool Graphics::setMode(int width, int height) // Enable blending glEnable(GL_BLEND); - // "Normal" blending - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - // Enable all color component writes. setColorMask(true, true, true, true); @@ -207,6 +213,31 @@ bool Graphics::setMode(int width, int height) glGetIntegerv(GL_MAX_MODELVIEW_STACK_DEPTH, &matrixLimit); matrixLimit -= 5; + // Set whether drawing converts input from linear -> sRGB colorspace. + if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_sRGB || GLEE_EXT_framebuffer_sRGB) + { + if (sRGB) + glEnable(GL_FRAMEBUFFER_SRGB); + else + glDisable(GL_FRAMEBUFFER_SRGB); + } + else + sRGB = false; + + Canvas::screenHasSRGB = sRGB; + + bool enabledebug = false; + + if (GLEE_VERSION_3_0) + { + // Enable OpenGL's debug output if a debug context has been created. + GLint flags = 0; + glGetIntegerv(GL_CONTEXT_FLAGS, &flags); + enabledebug = (flags & GL_CONTEXT_FLAG_DEBUG_BIT) != 0; + } + + setDebug(enabledebug); + return true; } @@ -223,6 +254,61 @@ void Graphics::unSetMode() gl.deInitContext(); } +static void APIENTRY debugCB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei /*len*/, const GLchar *msg, GLvoid* /*usr*/) +{ + // Human-readable strings for the debug info. + const char *sourceStr = OpenGL::debugSourceString(source); + const char *typeStr = OpenGL::debugTypeString(type); + const char *severityStr = OpenGL::debugSeverityString(severity); + + const char *fmt = "OpenGL: %s [source=%s, type=%s, severity=%s, id=%d]\n"; + printf(fmt, msg, sourceStr, typeStr, severityStr, id); +} + +void Graphics::setDebug(bool enable) +{ + // Make sure debug output is supported. The AMD ext. is a bit different + // so we don't make use of it, since AMD drivers now support KHR_debug. + if (!(GLEE_VERSION_4_3 || GLEE_KHR_debug || GLEE_ARB_debug_output)) + return; + + // Ugly hack to reduce code duplication. + if (GLEE_ARB_debug_output && !(GLEE_VERSION_4_3 || GLEE_KHR_debug)) + { + glDebugMessageCallback = (GLEEPFNGLDEBUGMESSAGECALLBACKPROC) glDebugMessageCallbackARB; + glDebugMessageControl = (GLEEPFNGLDEBUGMESSAGECONTROLPROC) glDebugMessageControlARB; + } + + if (!enable) + { + // Disable the debug callback function. + glDebugMessageCallback(nullptr, nullptr); + + // We can disable debug output entirely with KHR_debug. + if (GLEE_VERSION_4_3 || GLEE_KHR_debug) + glDisable(GL_DEBUG_OUTPUT); + + return; + } + + // We don't want asynchronous debug output. + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + + glDebugMessageCallback(debugCB, nullptr); + + // Initially, enable everything. + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, 0, GL_TRUE); + + // Disable messages about deprecated OpenGL functionality. + glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR, GL_DONT_CARE, 0, 0, GL_FALSE); + glDebugMessageControl(GL_DEBUG_SOURCE_SHADER_COMPILER, GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR, GL_DONT_CARE, 0, 0, GL_FALSE); + + if (GLEE_VERSION_4_3 || GLEE_KHR_debug) + glEnable(GL_DEBUG_OUTPUT); + + ::printf("OpenGL debug output enabled (LOVE_GRAPHICS_DEBUG=1)\n"); +} + void Graphics::reset() { DisplayState s; @@ -295,6 +381,8 @@ void Graphics::defineStencil() glEnable(GL_STENCIL_TEST); glStencilFunc(GL_ALWAYS, 1, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + + activeStencil = true; } void Graphics::useStencil(bool invert) @@ -306,19 +394,18 @@ void Graphics::useStencil(bool invert) void Graphics::discardStencil() { + if (!activeStencil) + return; + setColorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]); glDisable(GL_STENCIL_TEST); + activeStencil = false; } -int Graphics::getMaxTextureSize() const -{ - return gl.getMaxTextureSize(); -} - -Image *Graphics::newImage(love::image::ImageData *data) +Image *Graphics::newImage(love::image::ImageData *data, Texture::Format format) { // Create the image. - Image *image = new Image(data); + Image *image = new Image(data, format); if (!isCreated()) return image; @@ -342,10 +429,10 @@ Image *Graphics::newImage(love::image::ImageData *data) return image; } -Image *Graphics::newImage(love::image::CompressedData *cdata) +Image *Graphics::newImage(love::image::CompressedData *cdata, Texture::Format format) { // Create the image. - Image *image = new Image(cdata); + Image *image = new Image(cdata, format); if (!isCreated()) return image; @@ -389,11 +476,14 @@ ParticleSystem *Graphics::newParticleSystem(Texture *texture, int size) return new ParticleSystem(texture, size); } -Canvas *Graphics::newCanvas(int width, int height, Canvas::TextureType texture_type) +Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int fsaa) { - if (texture_type == Canvas::TYPE_HDR && !Canvas::isHDRSupported()) + if (format == Texture::FORMAT_HDR && !Canvas::isHDRSupported()) throw Exception("HDR Canvases are not supported by your OpenGL implementation"); + if (format == Texture::FORMAT_SRGB && !Canvas::isSRGBSupported()) + throw Exception("sRGB Canvases are not supported by your OpenGL implementation"); + if (width > gl.getMaxTextureSize()) throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width); else if (height > gl.getMaxTextureSize()) @@ -402,7 +492,7 @@ Canvas *Graphics::newCanvas(int width, int height, Canvas::TextureType texture_t while (GL_NO_ERROR != glGetError()) /* clear opengl error flag */; - Canvas *canvas = new Canvas(width, height, texture_type); + Canvas *canvas = new Canvas(width, height, format, fsaa); GLenum err = canvas->getStatus(); // everything ok, return canvas (early out) @@ -457,6 +547,11 @@ Mesh *Graphics::newMesh(const std::vector &vertices, Mesh::DrawMode mode return new Mesh(vertices, mode); } +Mesh *Graphics::newMesh(int vertexcount, Mesh::DrawMode mode) +{ + return new Mesh(vertexcount, mode); +} + void Graphics::setColor(const Color &c) { gl.setColor(c); @@ -509,22 +604,16 @@ const bool *Graphics::getColorMask() const void Graphics::setBlendMode(Graphics::BlendMode mode) { - const int gl_1_4 = GLEE_VERSION_1_4; - - GLenum func = GL_FUNC_ADD; - GLenum src_rgb = GL_ONE; - GLenum src_a = GL_ONE; - GLenum dst_rgb = GL_ZERO; - GLenum dst_a = GL_ZERO; + OpenGL::BlendState state = {GL_ONE, GL_ONE, GL_ZERO, GL_ZERO, GL_FUNC_ADD}; switch (mode) { case BLEND_ALPHA: - if (gl_1_4 || GLEE_EXT_blend_func_separate) + if (GLEE_VERSION_1_4 || GLEE_EXT_blend_func_separate) { - src_rgb = GL_SRC_ALPHA; - src_a = GL_ONE; - dst_rgb = dst_a = GL_ONE_MINUS_SRC_ALPHA; + state.srcRGB = GL_SRC_ALPHA; + state.srcA = GL_ONE; + state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA; } else { @@ -532,98 +621,56 @@ void Graphics::setBlendMode(Graphics::BlendMode mode) // This will most likely only be used for the Microsoft software renderer and // since it's still stuck with OpenGL 1.1, the only expected difference is a // different alpha value when reading back the default framebuffer (newScreenshot). - src_rgb = src_a = GL_SRC_ALPHA; - dst_rgb = dst_a = GL_ONE_MINUS_SRC_ALPHA; + state.srcRGB = state.srcA = GL_SRC_ALPHA; + state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA; } break; case BLEND_MULTIPLICATIVE: - src_rgb = src_a = GL_DST_COLOR; - dst_rgb = dst_a = GL_ZERO; + state.srcRGB = state.srcA = GL_DST_COLOR; + state.dstRGB = state.dstA = GL_ZERO; break; case BLEND_PREMULTIPLIED: - src_rgb = src_a = GL_ONE; - dst_rgb = dst_a = GL_ONE_MINUS_SRC_ALPHA; + state.srcRGB = state.srcA = GL_ONE; + state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA; break; case BLEND_SUBTRACTIVE: - func = GL_FUNC_REVERSE_SUBTRACT; + state.func = GL_FUNC_REVERSE_SUBTRACT; case BLEND_ADDITIVE: - src_rgb = src_a = GL_SRC_ALPHA; - dst_rgb = dst_a = GL_ONE; + state.srcRGB = state.srcA = GL_SRC_ALPHA; + state.dstRGB = state.dstA = GL_ONE; break; case BLEND_REPLACE: default: - src_rgb = src_a = GL_ONE; - dst_rgb = dst_a = GL_ZERO; + state.srcRGB = state.srcA = GL_ONE; + state.dstRGB = state.dstA = GL_ZERO; break; } - if (gl_1_4 || GLEE_ARB_imaging) - glBlendEquation(func); - else if (GLEE_EXT_blend_minmax && GLEE_EXT_blend_subtract) - glBlendEquationEXT(func); - else - { - if (func == GL_FUNC_REVERSE_SUBTRACT) - throw Exception("This graphics card does not support the subtractive blend mode!"); - // GL_FUNC_ADD is the default even without access to glBlendEquation, so that'll still work. - } - - if (src_rgb == src_a && dst_rgb == dst_a) - glBlendFunc(src_rgb, dst_rgb); - else - { - if (gl_1_4) - glBlendFuncSeparate(src_rgb, dst_rgb, src_a, dst_a); - else if (GLEE_EXT_blend_func_separate) - glBlendFuncSeparateEXT(src_rgb, dst_rgb, src_a, dst_a); - else - throw Exception("This graphics card does not support separated rgb and alpha blend functions!"); - } + gl.setBlendState(state); } Graphics::BlendMode Graphics::getBlendMode() const { - const int gl_1_4 = GLEE_VERSION_1_4; + OpenGL::BlendState state = gl.getBlendState(); - GLint src_rgb, src_a, dst_rgb, dst_a; - GLint equation = GL_FUNC_ADD; - - if (gl_1_4 || GLEE_EXT_blend_func_separate) - { - glGetIntegerv(GL_BLEND_SRC_RGB, &src_rgb); - glGetIntegerv(GL_BLEND_SRC_ALPHA, &src_a); - glGetIntegerv(GL_BLEND_DST_RGB, &dst_rgb); - glGetIntegerv(GL_BLEND_DST_ALPHA, &dst_a); - } - else - { - glGetIntegerv(GL_BLEND_SRC, &src_rgb); - glGetIntegerv(GL_BLEND_DST, &dst_rgb); - src_a = src_rgb; - dst_a = dst_rgb; - } - - if (gl_1_4 || GLEE_ARB_imaging || (GLEE_EXT_blend_minmax && GLEE_EXT_blend_subtract)) - glGetIntegerv(GL_BLEND_EQUATION, &equation); - - if (equation == GL_FUNC_REVERSE_SUBTRACT) // && src == GL_SRC_ALPHA && dst == GL_ONE + if (state.func == GL_FUNC_REVERSE_SUBTRACT) // && src == GL_SRC_ALPHA && dst == GL_ONE return BLEND_SUBTRACTIVE; // Everything else has equation == GL_FUNC_ADD. - else if (src_rgb == src_a && dst_rgb == dst_a) + else if (state.srcRGB == state.srcA && state.dstRGB == state.dstA) { - if (src_rgb == GL_SRC_ALPHA && dst_rgb == GL_ONE) + if (state.srcRGB == GL_SRC_ALPHA && state.dstRGB == GL_ONE) return BLEND_ADDITIVE; - else if (src_rgb == GL_SRC_ALPHA && dst_rgb == GL_ONE_MINUS_SRC_ALPHA) + else if (state.srcRGB == GL_SRC_ALPHA && state.dstRGB == GL_ONE_MINUS_SRC_ALPHA) return BLEND_ALPHA; // alpha blend mode fallback for very old OpenGL versions. - else if (src_rgb == GL_DST_COLOR && dst_rgb == GL_ZERO) + else if (state.srcRGB == GL_DST_COLOR && state.dstRGB == GL_ZERO) return BLEND_MULTIPLICATIVE; - else if (src_rgb == GL_ONE && dst_rgb == GL_ONE_MINUS_SRC_ALPHA) + else if (state.srcRGB == GL_ONE && state.dstRGB == GL_ONE_MINUS_SRC_ALPHA) return BLEND_PREMULTIPLIED; - else if (src_rgb == GL_ONE && dst_rgb == GL_ZERO) + else if (state.srcRGB == GL_ONE && state.dstRGB == GL_ZERO) return BLEND_REPLACE; } - else if (src_rgb == GL_SRC_ALPHA && src_a == GL_ONE && - dst_rgb == GL_ONE_MINUS_SRC_ALPHA && dst_a == GL_ONE_MINUS_SRC_ALPHA) + else if (state.srcRGB == GL_SRC_ALPHA && state.srcA == GL_ONE && + state.dstRGB == GL_ONE_MINUS_SRC_ALPHA && state.dstA == GL_ONE_MINUS_SRC_ALPHA) return BLEND_ALPHA; throw Exception("Unknown blend mode"); @@ -693,11 +740,15 @@ float Graphics::getPointSize() const return (float)size; } -int Graphics::getMaxPointSize() const +void Graphics::setWireframe(bool enable) { - GLint max; - glGetIntegerv(GL_POINT_SIZE_MAX, &max); - return (int)max; + wireframe = enable; + glPolygonMode(GL_FRONT_AND_BACK, enable ? GL_LINE : GL_FILL); +} + +bool Graphics::isWireframe() const +{ + return wireframe; } void Graphics::print(const std::string &str, float x, float y , float angle, float sx, float sy, float ox, float oy, float kx, float ky) @@ -1007,6 +1058,41 @@ std::string Graphics::getRendererInfo(Graphics::RendererInfo infotype) const return std::string(infostr); } +double Graphics::getSystemLimit(SystemLimit limittype) const +{ + double limit = 0.0; + + switch (limittype) + { + case Graphics::LIMIT_POINT_SIZE: + { + GLfloat limits[2]; + glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, limits); + limit = limits[1]; + } + break; + case Graphics::LIMIT_TEXTURE_SIZE: + limit = (double) gl.getMaxTextureSize(); + break; + case Graphics::LIMIT_MULTI_CANVAS: + limit = (double) gl.getMaxRenderTargets(); + break; + case Graphics::LIMIT_CANVAS_FSAA: + if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object + || GLEE_EXT_framebuffer_multisample) + { + GLint intlimit = 0; + glGetIntegerv(GL_MAX_SAMPLES, &intlimit); + limit = (double) intlimit; + } + break; + default: + break; + } + + return limit; +} + void Graphics::push() { if (userMatrices == matrixLimit) diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index e01ffe026..c2595174a 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -80,6 +80,8 @@ struct DisplayState // Color mask. bool colorMask[4]; + bool wireframe; + // Default values. DisplayState() { @@ -91,6 +93,7 @@ struct DisplayState pointSize = 1.0f; scissor = false; colorMask[0] = colorMask[1] = colorMask[2] = colorMask[3] = true; + wireframe = false; } }; @@ -110,9 +113,11 @@ public: void restoreState(const DisplayState &s); virtual void setViewportSize(int width, int height); - virtual bool setMode(int width, int height); + virtual bool setMode(int width, int height, bool &sRGB); virtual void unSetMode(); + void setDebug(bool enable); + /** * Resets the current color, background color, * line style, and so forth. (This will be called @@ -184,16 +189,11 @@ public: */ void discardStencil(); - /** - * Gets the maximum supported width or height of Textures on this system. - **/ - int getMaxTextureSize() const; - /** * Creates an Image object with padding and/or optimization. **/ - Image *newImage(love::image::ImageData *data); - Image *newImage(love::image::CompressedData *cdata); + Image *newImage(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL); + Image *newImage(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL); Quad *newQuad(Quad::Viewport v, float sw, float sh); @@ -206,11 +206,12 @@ public: ParticleSystem *newParticleSystem(Texture *texture, int size); - Canvas *newCanvas(int width, int height, Canvas::TextureType texture_type = Canvas::TYPE_NORMAL); + Canvas *newCanvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0); Shader *newShader(const Shader::ShaderSources &sources); Mesh *newMesh(const std::vector &vertices, Mesh::DrawMode mode = Mesh::DRAW_MODE_FAN); + Mesh *newMesh(int vertexcount, Mesh::DrawMode mode = Mesh::DRAW_MODE_FAN); /** * Sets the foreground color. @@ -324,10 +325,17 @@ public: float getPointSize() const; /** - * Gets the maximum point size supported. - * This may vary from computer to computer. + * Sets whether graphics will be drawn as wireframe lines instead of filled + * triangles (has no effect for drawn points.) + * This should only be used as a debugging tool. The wireframe lines do not + * behave the same as regular love.graphics lines. **/ - int getMaxPointSize() const; + void setWireframe(bool enable); + + /** + * Gets whether wireframe drawing mode is enabled. + **/ + bool isWireframe() const; /** * Draws text at the specified coordinates, with rotation and @@ -430,6 +438,11 @@ public: **/ std::string getRendererInfo(Graphics::RendererInfo infotype) const; + /** + * Gets the system-dependent numeric limit for the specified parameter. + **/ + double getSystemLimit(SystemLimit limittype) const; + void push(); void pop(); void rotate(float r); @@ -450,11 +463,14 @@ private: GLint matrixLimit; GLint userMatrices; bool colorMask[4]; + bool wireframe; int width; int height; bool created; + bool activeStencil; + DisplayState savedState; }; // Graphics diff --git a/src/modules/graphics/opengl/Image.cpp b/src/modules/graphics/opengl/Image.cpp index 2d7719656..2166fd4a5 100644 --- a/src/modules/graphics/opengl/Image.cpp +++ b/src/modules/graphics/opengl/Image.cpp @@ -36,7 +36,7 @@ float Image::maxMipmapSharpness = 0.0f; Texture::FilterMode Image::defaultMipmapFilter = Texture::FILTER_NONE; float Image::defaultMipmapSharpness = 0.0f; -Image::Image(love::image::ImageData *data) +Image::Image(love::image::ImageData *data, Texture::Format format) : data(data) , cdata(nullptr) , paddedWidth(width) @@ -45,6 +45,7 @@ Image::Image(love::image::ImageData *data) , mipmapSharpness(defaultMipmapSharpness) , mipmapsCreated(false) , compressed(false) + , format(format) , usingDefaultTexture(false) { width = data->getWidth(); @@ -54,7 +55,7 @@ Image::Image(love::image::ImageData *data) preload(); } -Image::Image(love::image::CompressedData *cdata) +Image::Image(love::image::CompressedData *cdata, Texture::Format format) : data(nullptr) , cdata(cdata) , paddedWidth(width) @@ -63,6 +64,7 @@ Image::Image(love::image::CompressedData *cdata) , mipmapSharpness(defaultMipmapSharpness) , mipmapsCreated(false) , compressed(true) + , format(format) , usingDefaultTexture(false) { width = cdata->getWidth(0); @@ -91,7 +93,7 @@ love::image::CompressedData *Image::getCompressedData() const return cdata; } -void Image::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const +void Image::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) { Matrix t; t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky); @@ -99,7 +101,7 @@ void Image::draw(float x, float y, float angle, float sx, float sy, float ox, fl drawv(t, vertices); } -void Image::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const +void Image::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) { Matrix t; t.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky); @@ -107,7 +109,7 @@ void Image::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, drawv(t, quad->getVertices()); } -void Image::predraw() const +void Image::predraw() { bind(); @@ -121,7 +123,7 @@ void Image::predraw() const } } -void Image::postdraw() const +void Image::postdraw() { if (width != paddedWidth || height != paddedHeight) { @@ -328,6 +330,9 @@ void Image::unload() bool Image::loadVolatile() { + if (format == FORMAT_SRGB && !hasSRGBSupport()) + throw love::Exception("sRGB images are not supported on this system."); + if (isCompressed() && cdata && !hasCompressedTextureSupport(cdata->getFormat())) { const char *str; @@ -400,9 +405,10 @@ void Image::uploadTexturePadded() } else if (data) { + GLenum iformat = (format == FORMAT_SRGB) ? GL_SRGB8_ALPHA8 : GL_RGBA8; glTexImage2D(GL_TEXTURE_2D, 0, - GL_RGBA8, + iformat, (GLsizei)paddedWidth, (GLsizei)paddedHeight, 0, @@ -437,9 +443,10 @@ void Image::uploadTexture() } else if (data) { + GLenum iformat = (format == FORMAT_SRGB) ? GL_SRGB8_ALPHA8 : GL_RGBA8; glTexImage2D(GL_TEXTURE_2D, 0, - GL_RGBA8, + iformat, (GLsizei)width, (GLsizei)height, 0, @@ -497,6 +504,11 @@ bool Image::refresh() return true; } +Texture::Format Image::getFormat() const +{ + return format; +} + void Image::uploadDefaultTexture() { usingDefaultTexture = true; @@ -511,7 +523,7 @@ void Image::uploadDefaultTexture() glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, px); } -void Image::drawv(const Matrix &t, const Vertex *v) const +void Image::drawv(const Matrix &t, const Vertex *v) { predraw(); @@ -561,16 +573,27 @@ bool Image::isCompressed() const return compressed; } -GLenum Image::getCompressedFormat(image::CompressedData::Format format) const +GLenum Image::getCompressedFormat(image::CompressedData::Format cformat) const { - switch (format) + bool srgb = format == FORMAT_SRGB; + + switch (cformat) { case image::CompressedData::FORMAT_DXT1: - return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + if (srgb) + return GL_COMPRESSED_SRGB_S3TC_DXT1_EXT; + else + return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; case image::CompressedData::FORMAT_DXT3: - return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + if (srgb) + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; + else + return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; case image::CompressedData::FORMAT_DXT5: - return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + if (srgb) + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + else + return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; case image::CompressedData::FORMAT_BC4: return GL_COMPRESSED_RED_RGTC1; case image::CompressedData::FORMAT_BC4s: @@ -580,7 +603,10 @@ GLenum Image::getCompressedFormat(image::CompressedData::Format format) const case image::CompressedData::FORMAT_BC5s: return GL_COMPRESSED_SIGNED_RG_RGTC2; default: - return GL_RGBA8; + if (srgb) + return GL_SRGB8_ALPHA8; + else + return GL_RGBA8; } } @@ -632,6 +658,11 @@ bool Image::hasCompressedTextureSupport(image::CompressedData::Format format) return false; } +bool Image::hasSRGBSupport() +{ + return GLEE_VERSION_2_1 || GLEE_EXT_texture_sRGB; +} + } // opengl } // graphics } // love diff --git a/src/modules/graphics/opengl/Image.h b/src/modules/graphics/opengl/Image.h index 0622fb8dc..d23f385ec 100644 --- a/src/modules/graphics/opengl/Image.h +++ b/src/modules/graphics/opengl/Image.h @@ -56,14 +56,14 @@ public: * * @param data The data from which to load the image. **/ - Image(love::image::ImageData *data); + Image(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL); /** * Creates a new Image with compressed image data. * * @param cdata The compressed data from which to load the image. **/ - Image(love::image::CompressedData *cdata); + Image(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL); /** * Destructor. Deletes the hardware texture and other resources. @@ -76,20 +76,20 @@ public: /** * @copydoc Drawable::draw() **/ - void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const; + void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky); /** * @copydoc Texture::drawq() **/ - void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const; + void drawq(Quad *quad, float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky); /** * Call before using this Image's texture to draw. Binds the texture, * globally scales texture coordinates if the Image has NPOT dimensions and * NPOT isn't supported, etc. **/ - virtual void predraw() const; - virtual void postdraw() const; + virtual void predraw(); + virtual void postdraw(); virtual GLuint getGLTexture() const; @@ -120,6 +120,8 @@ public: **/ bool refresh(); + Texture::Format getFormat() const; + static void setDefaultMipmapSharpness(float sharpness); static float getDefaultMipmapSharpness(); static void setDefaultMipmapFilter(FilterMode f); @@ -133,11 +135,13 @@ public: static bool hasCompressedTextureSupport(); static bool hasCompressedTextureSupport(image::CompressedData::Format format); + static bool hasSRGBSupport(); + private: void uploadDefaultTexture(); - void drawv(const Matrix &t, const Vertex *v) const; + void drawv(const Matrix &t, const Vertex *v); // The ImageData from which the texture is created. May be null if // Compressed image data was used to create the texture. @@ -162,6 +166,9 @@ private: // Whether this Image is using a compressed texture. bool compressed; + // The format to interpret the texture's data as. + Texture::Format format; + // True if the image wasn't able to be properly created and it had to fall // back to a default texture. bool usingDefaultTexture; @@ -180,7 +187,7 @@ private: static FilterMode defaultMipmapFilter; static float defaultMipmapSharpness; - GLenum getCompressedFormat(image::CompressedData::Format format) const; + GLenum getCompressedFormat(image::CompressedData::Format cformat) const; }; // Image diff --git a/src/modules/graphics/opengl/Mesh.cpp b/src/modules/graphics/opengl/Mesh.cpp index f73ff7dca..10597568f 100644 --- a/src/modules/graphics/opengl/Mesh.cpp +++ b/src/modules/graphics/opengl/Mesh.cpp @@ -23,6 +23,9 @@ #include "common/Matrix.h" #include "common/Exception.h" +// C++ +#include + namespace love { namespace graphics @@ -35,14 +38,46 @@ Mesh::Mesh(const std::vector &verts, Mesh::DrawMode mode) , vertex_count(0) , ibo(nullptr) , element_count(0) + , element_data_type(getGLDataTypeFromMax(verts.size())) + , instance_count(1) , draw_mode(mode) + , range_min(-1) + , range_max(-1) , texture(nullptr) , colors_enabled(false) - , wireframe(false) { setVertices(verts); } +Mesh::Mesh(int vertexcount, Mesh::DrawMode mode) + : vbo(nullptr) + , vertex_count(0) + , ibo(nullptr) + , element_count(0) + , element_data_type(getGLDataTypeFromMax(vertexcount)) + , draw_mode(mode) + , range_min(-1) + , range_max(-1) + , texture(nullptr) + , colors_enabled(false) +{ + if (vertexcount < 1) + throw love::Exception("Invalid number of vertices."); + + std::vector verts(vertexcount); + + // Default-initialized vertices should have a white opaque color. + for (size_t i = 0; i < verts.size(); i++) + { + verts[i].r = 255; + verts[i].g = 255; + verts[i].b = 255; + verts[i].a = 255; + } + + setVertices(verts); +} + Mesh::~Mesh() { delete vbo; @@ -51,8 +86,8 @@ Mesh::~Mesh() void Mesh::setVertices(const std::vector &verts) { - if (verts.size() < 3) - throw love::Exception("At least 3 vertices are required."); + if (verts.size() == 0) + throw love::Exception("At least one vertex is required."); size_t size = sizeof(Vertex) * verts.size(); @@ -118,15 +153,42 @@ size_t Mesh::getVertexCount() const return vertex_count; } +/** + * Copies index data from a vector to a mapped index buffer. + **/ +template +static void copyToIndexBuffer(const std::vector &indices, VertexBuffer::Mapper &buffermap, size_t maxval) +{ + T *elems = (T *) buffermap.get(); + + for (size_t i = 0; i < indices.size(); i++) + { + if (indices[i] >= maxval) + throw love::Exception("Invalid vertex map value: %d", indices[i] + 1); + + elems[i] = (T) indices[i]; + } +} + void Mesh::setVertexMap(const std::vector &map) { - for (size_t i = 0; i < map.size(); i++) - { - if (map[i] >= vertex_count) - throw love::Exception("Invalid vertex map value: %d", map[i] + 1); - } + GLenum datatype = getGLDataTypeFromMax(vertex_count); - size_t size = sizeof(uint32) * map.size(); + // Calculate the size in bytes of the index buffer data. + size_t size = map.size(); + switch (datatype) + { + case GL_UNSIGNED_BYTE: + size *= sizeof(uint8); + break; + case GL_UNSIGNED_SHORT: + size *= sizeof(uint16); + break; + case GL_UNSIGNED_INT: + default: + size *= sizeof(uint32); + break; + } if (ibo && size > ibo->getSize()) { @@ -142,27 +204,68 @@ void Mesh::setVertexMap(const std::vector &map) element_count = map.size(); - if (ibo && element_count > 0) - { - VertexBuffer::Bind ibo_bind(*ibo); - VertexBuffer::Mapper ibo_map(*ibo); + if (!ibo || element_count == 0) + return; - // Fill the buffer. - memcpy(ibo_map.get(), &map[0], size); + VertexBuffer::Bind ibo_bind(*ibo); + VertexBuffer::Mapper ibo_map(*ibo); + + // Fill the buffer with the index values from the vector. + switch (datatype) + { + case GL_UNSIGNED_BYTE: + copyToIndexBuffer(map, ibo_map, vertex_count); + break; + case GL_UNSIGNED_SHORT: + copyToIndexBuffer(map, ibo_map, vertex_count); + break; + case GL_UNSIGNED_INT: + default: + copyToIndexBuffer(map, ibo_map, vertex_count); + break; } + + element_data_type = datatype; } -const uint32 *Mesh::getVertexMap() const +/** + * Copies index data from a mapped buffer to a vector. + **/ +template +static void copyFromIndexBuffer(void *buffer, std::vector &indices, size_t maxval) { - if (ibo && element_count > 0) + T *elems = (T *) buffer; + for (size_t i = 0; i < maxval; i++) + indices.push_back((uint32) elems[i]); +} + +void Mesh::getVertexMap(std::vector &map) const +{ + if (!ibo || element_count == 0) + return; + + map.clear(); + map.reserve(element_count); + + VertexBuffer::Bind ibo_bind(*ibo); + + // We unmap the buffer in Mesh::draw and Mesh::setVertexMap. + void *buffer = ibo->map(); + + // Fill the vector from the buffer. + switch (element_data_type) { - VertexBuffer::Bind ibo_bind(*ibo); - - // We unmap the buffer in Mesh::draw and Mesh::setVertexMap. - return (uint32 *) ibo->map(); + case GL_UNSIGNED_BYTE: + copyFromIndexBuffer(buffer, map, vertex_count); + break; + case GL_UNSIGNED_SHORT: + copyFromIndexBuffer(buffer, map, vertex_count); + break; + case GL_UNSIGNED_INT: + default: + copyFromIndexBuffer(buffer, map, vertex_count); + break; } - - return 0; } size_t Mesh::getVertexMapCount() const @@ -170,6 +273,16 @@ size_t Mesh::getVertexMapCount() const return element_count; } +void Mesh::setInstanceCount(int count) +{ + instance_count = std::max(count, 1); +} + +int Mesh::getInstanceCount() const +{ + return instance_count; +} + void Mesh::setTexture(Texture *tex) { tex->retain(); @@ -203,6 +316,26 @@ Mesh::DrawMode Mesh::getDrawMode() const return draw_mode; } +void Mesh::setDrawRange(int min, int max) +{ + if (min < 0 || max < 0 || min > max) + throw love::Exception("Invalid draw range."); + + range_min = min; + range_max = max; +} + +void Mesh::setDrawRange() +{ + range_min = range_max = -1; +} + +void Mesh::getDrawRange(int &min, int &max) const +{ + min = range_min; + max = range_max; +} + void Mesh::setVertexColors(bool enable) { colors_enabled = enable; @@ -213,17 +346,7 @@ bool Mesh::hasVertexColors() const return colors_enabled; } -void Mesh::setWireframe(bool enable) -{ - wireframe = enable; -} - -bool Mesh::isWireframe() const -{ - return wireframe; -} - -void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const +void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) { const size_t pos_offset = offsetof(Vertex, x); const size_t tex_offset = offsetof(Vertex, s); @@ -261,31 +384,50 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), vbo->getPointer(color_offset)); } - if (wireframe) - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - GLenum mode = getGLDrawMode(draw_mode); gl.prepareDraw(); if (ibo && element_count > 0) { + // Use the custom vertex map (index buffer) to draw the vertices. VertexBuffer::Bind ibo_bind(*ibo); // Make sure the index buffer isn't mapped (sends data to GPU if needed.) ibo->unmap(); - // Use the custom vertex map to draw the vertices. - glDrawElements(mode, element_count, GL_UNSIGNED_INT, ibo->getPointer(0)); + int max = element_count - 1; + if (range_max >= 0) + max = std::min(std::max(range_max, 0), (int) element_count - 1); + + int min = 0; + if (range_min >= 0) + min = std::min(std::max(range_min, 0), max); + + const void *indices = ibo->getPointer(min * sizeof(uint32)); + GLenum type = element_data_type; + + if (instance_count > 1) + gl.drawElementsInstanced(mode, max - min + 1, type, indices, instance_count); + else + glDrawElements(mode, max - min + 1, type, indices); } else { - // Normal non-indexed drawing (no custom vertex map.) - glDrawArrays(mode, 0, vertex_count); - } + int max = vertex_count - 1; + if (range_max >= 0) + max = std::min(std::max(range_max, 0), (int) vertex_count - 1); - if (wireframe) - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + int min = 0; + if (range_min >= 0) + min = std::min(std::max(range_min, 0), max); + + // Normal non-indexed drawing (no custom vertex map.) + if (instance_count > 1) + gl.drawArraysInstanced(mode, min, max - min + 1, instance_count); + else + glDrawArrays(mode, min, max - min + 1); + } glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); @@ -303,7 +445,7 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo texture->postdraw(); } -GLenum Mesh::getGLDrawMode(Mesh::DrawMode mode) const +GLenum Mesh::getGLDrawMode(DrawMode mode) const { switch (mode) { @@ -322,6 +464,16 @@ GLenum Mesh::getGLDrawMode(Mesh::DrawMode mode) const return GL_TRIANGLES; } +GLenum Mesh::getGLDataTypeFromMax(size_t maxvalue) const +{ + if (maxvalue > LOVE_UINT16_MAX) + return GL_UNSIGNED_INT; + else if (maxvalue > LOVE_UINT8_MAX) + return GL_UNSIGNED_SHORT; + else + return GL_UNSIGNED_BYTE; +} + bool Mesh::getConstant(const char *in, Mesh::DrawMode &out) { return drawModes.find(in, out); diff --git a/src/modules/graphics/opengl/Mesh.h b/src/modules/graphics/opengl/Mesh.h index 498e501ed..da73af438 100644 --- a/src/modules/graphics/opengl/Mesh.h +++ b/src/modules/graphics/opengl/Mesh.h @@ -22,6 +22,7 @@ #define LOVE_GRAPHICS_OPENGL_MESH_H // LOVE +#include "common/config.h" #include "common/int.h" #include "common/math.h" #include "common/StringMap.h" @@ -64,6 +65,16 @@ public: * @param mode The draw mode to use when drawing the Mesh. **/ Mesh(const std::vector &verts, DrawMode mode = DRAW_MODE_FAN); + + /** + * Constructor. + * Creates a Mesh with a certain number of default-initialized (hidden) + * vertices. + * @param vertexcount The number of vertices to use in the Mesh. + * @param mode The draw mode to use when drawing the Mesh. + **/ + Mesh(int vertexcount, DrawMode mode = DRAW_MODE_FAN); + virtual ~Mesh(); /** @@ -98,17 +109,25 @@ public: void setVertexMap(const std::vector &map); /** - * Gets a pointer to the vertex map array. The pointer is only valid until - * the next function call in the graphics module. - * May return null if the vertex map is empty. + * Fills the uint32 vector passed into the method with the previously set + * vertex map (index buffer) values. **/ - const uint32 *getVertexMap() const; + void getVertexMap(std::vector &map) const; /** * Gets the total number of elements in the vertex map array. **/ size_t getVertexMapCount() const; + /** + * Sets the number of instances of this Mesh to draw (uses hardware + * instancing when possible.) + * A custom vertex shader is necessary in order to introduce differences + * in each instance. + **/ + void setInstanceCount(int count); + int getInstanceCount() const; + /** * Sets the texture used when drawing the Mesh. **/ @@ -131,6 +150,10 @@ public: void setDrawMode(DrawMode mode); DrawMode getDrawMode() const; + void setDrawRange(int min, int max); + void setDrawRange(); + void getDrawRange(int &min, int &max) const; + /** * Sets whether per-vertex colors are enabled. If this is disabled, the * global color (love.graphics.setColor) will be used for the entire Mesh. @@ -138,17 +161,8 @@ public: void setVertexColors(bool enable); bool hasVertexColors() const; - /** - * Sets whether the Mesh will be drawn as wireframe lines instead of filled - * triangles (has no effect for DRAW_MODE_POINTS.) - * This should only be used as a debugging tool. The wireframe lines do not - * behave the same as regular love.graphics lines. - **/ - void setWireframe(bool enable); - bool isWireframe() const; - // Implements Drawable. - void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const; + void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky); static bool getConstant(const char *in, DrawMode &out); static bool getConstant(DrawMode in, const char *&out); @@ -156,6 +170,7 @@ public: private: GLenum getGLDrawMode(DrawMode mode) const; + GLenum getGLDataTypeFromMax(size_t maxvalue) const; // Vertex buffer. VertexBuffer *vbo; @@ -164,16 +179,20 @@ private: // Element (vertex index) buffer, for the vertex map. VertexBuffer *ibo; size_t element_count; + GLenum element_data_type; + + int instance_count; DrawMode draw_mode; + int range_min; + int range_max; + Texture *texture; // Whether the per-vertex colors are used when drawing. bool colors_enabled; - bool wireframe; - static StringMap::Entry drawModeEntries[]; static StringMap drawModes; diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 18301f223..fea9ca877 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -43,6 +43,7 @@ OpenGL::OpenGL() : contextInitialized(false) , maxAnisotropy(1.0f) , maxTextureSize(0) + , maxRenderTargets(0) , vendor(VENDOR_UNKNOWN) , state() { @@ -110,9 +111,14 @@ void OpenGL::initContext() glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint *) &state.textureUnits[0]); } + BlendState blend = {GL_ONE, GL_ONE, GL_ZERO, GL_ZERO, GL_FUNC_ADD}; + setBlendState(blend); + initMaxValues(); createDefaultTexture(); + state.lastPseudoInstanceID = -1; + contextInitialized = true; } @@ -188,6 +194,19 @@ void OpenGL::initMaxValues() maxAnisotropy = 1.0f; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + + if (Canvas::isSupported() && (GLEE_VERSION_2_0 || GLEE_ARB_draw_buffers)) + { + int maxattachments = 0; + glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxattachments); + + int maxdrawbuffers = 0; + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxdrawbuffers); + + maxRenderTargets = std::min(maxattachments, maxdrawbuffers); + } + else + maxRenderTargets = 0; } void OpenGL::createDefaultTexture() @@ -214,10 +233,81 @@ void OpenGL::createDefaultTexture() void OpenGL::prepareDraw() { - // Make sure the active shader has the correct values for the built-in - // screen params uniform. - if (Shader::current) - Shader::current->checkSetScreenParams(); + Shader *shader = Shader::current; + if (shader != nullptr) + { + // Make sure the active shader has the correct values for its + // love-provided uniforms. + shader->checkSetScreenParams(); + + // Make sure the Instance ID variable is up-to-date when + // pseudo-instancing is used. + if (state.lastPseudoInstanceID != 0 && shader->hasVertexAttrib(ATTRIB_PSEUDO_INSTANCE_ID)) + { + glVertexAttrib1f((GLuint) ATTRIB_PSEUDO_INSTANCE_ID, 0.0f); + state.lastPseudoInstanceID = 0; + } + + // We need to make sure antialiased Canvases are properly resolved + // before sampling from their textures in a shader. + // This is kind of a big hack. :( + const std::map &r = shader->getBoundRetainables(); + for (auto it = r.begin(); it != r.end(); ++it) + { + // Even bigger hack! D: + Canvas *canvas = dynamic_cast(it->second); + if (canvas != nullptr) + canvas->resolveMSAA(); + } + } +} + +void OpenGL::drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount) +{ + Shader *shader = Shader::current; + + if (GLEE_ARB_draw_instanced) + glDrawArraysInstancedARB(mode, first, count, primcount); + else + { + bool shaderHasID = shader && shader->hasVertexAttrib(ATTRIB_PSEUDO_INSTANCE_ID); + + // Pseudo-instancing fallback. + for (int i = 0; i < primcount; i++) + { + if (shaderHasID) + glVertexAttrib1f((GLuint) ATTRIB_PSEUDO_INSTANCE_ID, (GLfloat) i); + + glDrawArrays(mode, first, count); + } + + if (shaderHasID) + state.lastPseudoInstanceID = primcount - 1; + } +} + +void OpenGL::drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount) +{ + Shader *shader = Shader::current; + + if (GLEE_ARB_draw_instanced) + glDrawElementsInstancedARB(mode, count, type, indices, primcount); + else + { + bool shaderHasID = shader && shader->hasVertexAttrib(ATTRIB_PSEUDO_INSTANCE_ID); + + // Pseudo-instancing fallback. + for (int i = 0; i < primcount; i++) + { + if (shaderHasID) + glVertexAttrib1f((GLuint) ATTRIB_PSEUDO_INSTANCE_ID, (GLfloat) i); + + glDrawElements(mode, count, type, indices); + } + + if (shaderHasID) + state.lastPseudoInstanceID = primcount - 1; + } } void OpenGL::setColor(const Color &c) @@ -277,6 +367,39 @@ OpenGL::Viewport OpenGL::getScissor() const return state.scissor; } +void OpenGL::setBlendState(const BlendState &blend) +{ + if (GLEE_VERSION_1_4 || GLEE_ARB_imaging) + glBlendEquation(blend.func); + else if (GLEE_EXT_blend_minmax && GLEE_EXT_blend_subtract) + glBlendEquationEXT(blend.func); + else + { + if (blend.func == GL_FUNC_REVERSE_SUBTRACT) + throw love::Exception("This graphics card does not support the subtractive blend mode!"); + // GL_FUNC_ADD is the default even without access to glBlendEquation, so that'll still work. + } + + if (blend.srcRGB == blend.srcA && blend.dstRGB == blend.dstA) + glBlendFunc(blend.srcRGB, blend.dstRGB); + else + { + if (GLEE_VERSION_1_4) + glBlendFuncSeparate(blend.srcRGB, blend.dstRGB, blend.srcA, blend.dstA); + else if (GLEE_EXT_blend_func_separate) + glBlendFuncSeparateEXT(blend.srcRGB, blend.dstRGB, blend.srcA, blend.dstA); + else + throw love::Exception("This graphics card does not support separated rgb and alpha blend functions!"); + } + + state.blend = blend; +} + +OpenGL::BlendState OpenGL::getBlendState() const +{ + return state.blend; +} + void OpenGL::setTextureUnit(int textureunit) { if (textureunit < 0 || (size_t) textureunit >= state.textureUnits.size()) @@ -504,11 +627,77 @@ int OpenGL::getMaxTextureSize() const return maxTextureSize; } +int OpenGL::getMaxRenderTargets() const +{ + return maxRenderTargets; +} + OpenGL::Vendor OpenGL::getVendor() const { return vendor; } +const char *OpenGL::debugSeverityString(GLenum severity) +{ + switch (severity) + { + case GL_DEBUG_SEVERITY_HIGH: + return "high"; + case GL_DEBUG_SEVERITY_MEDIUM: + return "medium"; + case GL_DEBUG_SEVERITY_LOW: + return "low"; + default: + break; + } + return "unknown"; +} + +const char *OpenGL::debugSourceString(GLenum source) +{ + switch (source) + { + case GL_DEBUG_SOURCE_API: + return "API"; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: + return "window"; + case GL_DEBUG_SOURCE_SHADER_COMPILER: + return "shader"; + case GL_DEBUG_SOURCE_THIRD_PARTY: + return "external"; + case GL_DEBUG_SOURCE_APPLICATION: + return "LOVE"; + case GL_DEBUG_SOURCE_OTHER: + return "other"; + default: + break; + } + return "unknown"; +} + +const char *OpenGL::debugTypeString(GLenum type) +{ + switch (type) + { + case GL_DEBUG_TYPE_ERROR: + return "error"; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: + return "deprecated behavior"; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + return "undefined behavior"; + case GL_DEBUG_TYPE_PERFORMANCE: + return "performance"; + case GL_DEBUG_TYPE_PORTABILITY: + return "portability"; + case GL_DEBUG_TYPE_OTHER: + return "other"; + default: + break; + } + return "unknown"; +} + + // OpenGL class instance singleton. OpenGL gl; diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index 29bdcf835..b912d4aac 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/src/modules/graphics/opengl/OpenGL.h @@ -63,6 +63,17 @@ public: VENDOR_UNKNOWN }; + // Vertex attributes used in shaders by LOVE. The values map to OpenGL + // generic vertex attribute indices, when applicable. + // LOVE uses the old hard-coded attribute APIs for positions, colors, etc. + // (for now.) + enum VertexAttrib + { + // Instance ID when pseudo-instancing is used. + ATTRIB_PSEUDO_INSTANCE_ID = 1, + ATTRIB_MAX_ENUM + }; + // A rectangle representing an OpenGL viewport or a scissor box. struct Viewport { @@ -76,6 +87,18 @@ public: Viewport(int _x, int _y, int _w, int _h) : x(_x), y(_y), w(_w), h(_h) {} + + bool operator == (const Viewport &rhs) const + { + return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h; + } + }; + + struct BlendState + { + GLenum srcRGB, srcA; + GLenum dstRGB, dstA; + GLenum func; }; OpenGL(); @@ -99,6 +122,16 @@ public: **/ void prepareDraw(); + /** + * glDrawArraysInstanced with a pseudo-instancing fallback. + **/ + void drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount); + + /** + * glDrawElementsInstanced with a pseudo-instancing fallback. + **/ + void drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); + /** * Sets the current constant color. **/ @@ -141,6 +174,17 @@ public: **/ Viewport getScissor() const; + /** + * Sets blending functionality. + * Note: This does not globally enable or disable blending. + **/ + void setBlendState(const BlendState &blend); + + /** + * Gets the currently set blending functionality. + **/ + BlendState getBlendState() const; + /** * Helper for setting the active texture unit. * @@ -194,11 +238,21 @@ public: **/ int getMaxTextureSize() const; + /** + * Returns the maximum supported number of simultaneous render targets. + **/ + int getMaxRenderTargets() const; + /** * Get the GPU vendor of this OpenGL context. **/ Vendor getVendor() const; + // Get human-readable strings for debug info. + static const char *debugSeverityString(GLenum severity); + static const char *debugSourceString(GLenum source); + static const char *debugTypeString(GLenum type); + private: void initVendor(); @@ -210,6 +264,7 @@ private: float maxAnisotropy; int maxTextureSize; + int maxRenderTargets; Vendor vendor; @@ -230,6 +285,11 @@ private: Viewport viewport; Viewport scissor; + BlendState blend; + + // The last ID value used for pseudo-instancing. + int lastPseudoInstanceID; + } state; }; // OpenGL diff --git a/src/modules/graphics/opengl/ParticleSystem.cpp b/src/modules/graphics/opengl/ParticleSystem.cpp index dbc26eb2e..e47525005 100644 --- a/src/modules/graphics/opengl/ParticleSystem.cpp +++ b/src/modules/graphics/opengl/ParticleSystem.cpp @@ -94,6 +94,7 @@ ParticleSystem::ParticleSystem(Texture *texture, uint32 size) , spinVariation(0) , offsetX(float(texture->getWidth())*0.5f) , offsetY(float(texture->getHeight())*0.5f) + , relativeRotation(false) { if (size == 0 || size > MAX_PARTICLES) throw love::Exception("Invalid ParticleSystem size."); @@ -118,6 +119,7 @@ ParticleSystem::ParticleSystem(const ParticleSystem &p) , emissionRate(p.emissionRate) , emitCounter(0.0f) , position(p.position) + , prevPosition(p.prevPosition) , areaSpreadDistribution(p.areaSpreadDistribution) , areaSpread(p.areaSpread) , lifetime(p.lifetime) @@ -144,6 +146,7 @@ ParticleSystem::ParticleSystem(const ParticleSystem &p) , offsetX(p.offsetX) , offsetY(p.offsetY) , colors(p.colors) + , relativeRotation(p.relativeRotation) { setBufferSize(maxParticles); @@ -168,7 +171,7 @@ void ParticleSystem::createBuffers(size_t size) { try { - pFree = pMem = new particle[size]; + pFree = pMem = new Particle[size]; particleVerts = new love::Vertex[size * 4]; maxParticles = (uint32) size; } @@ -205,14 +208,14 @@ uint32 ParticleSystem::getBufferSize() const return maxParticles; } -void ParticleSystem::addParticle() +void ParticleSystem::addParticle(float t) { if (isFull()) return; // Gets a free particle and updates the allocation pointer. - particle *p = pFree++; - initParticle(p); + Particle *p = pFree++; + initParticle(p, t); switch (insertMode) { @@ -231,10 +234,13 @@ void ParticleSystem::addParticle() activeParticles++; } -void ParticleSystem::initParticle(particle *p) +void ParticleSystem::initParticle(Particle *p, float t) { float min,max; + // Linearly interpolate between the previous and current emitter position. + love::Vector pos = prevPosition + (position - prevPosition) * t; + min = particleLifeMin; max = particleLifeMax; if (min == max) @@ -243,8 +249,8 @@ void ParticleSystem::initParticle(particle *p) p->life = (float) rng.random(min, max); p->lifetime = p->life; - p->position[0] = position.getX(); - p->position[1] = position.getY(); + p->position[0] = pos.x; + p->position[1] = pos.y; switch (areaSpreadDistribution) { @@ -265,7 +271,7 @@ void ParticleSystem::initParticle(particle *p) max = direction + spread/2.0f; p->direction = (float) rng.random(min, max); - p->origin = position; + p->origin = pos; min = speedMin; max = speedMax; @@ -294,10 +300,14 @@ void ParticleSystem::initParticle(particle *p) p->spinEnd = calculate_variation(spinEnd, spinStart, spinVariation); p->rotation = (float) rng.random(min, max); + p->angle = p->rotation; + if (relativeRotation) + p->angle += atan2f(p->speed.y, p->speed.x); + p->color = colors[0]; } -void ParticleSystem::insertTop(particle *p) +void ParticleSystem::insertTop(Particle *p) { if (pHead == nullptr) { @@ -313,7 +323,7 @@ void ParticleSystem::insertTop(particle *p) pTail = p; } -void ParticleSystem::insertBottom(particle *p) +void ParticleSystem::insertBottom(Particle *p) { if (pTail == nullptr) { @@ -329,7 +339,7 @@ void ParticleSystem::insertBottom(particle *p) pHead = p; } -void ParticleSystem::insertRandom(particle *p) +void ParticleSystem::insertRandom(Particle *p) { // Nonuniform, but 64-bit is so large nobody will notice. Hopefully. uint64 pos = rng.rand() % ((int64) activeParticles + 1); @@ -337,7 +347,7 @@ void ParticleSystem::insertRandom(particle *p) // Special case where the particle gets inserted before the head. if (pos == activeParticles) { - particle *pA = pHead; + Particle *pA = pHead; if (pA) pA->prev = p; p->prev = nullptr; @@ -347,8 +357,8 @@ void ParticleSystem::insertRandom(particle *p) } // Inserts the particle after the randomly selected particle. - particle *pA = pMem + pos; - particle *pB = pA->next; + Particle *pA = pMem + pos; + Particle *pB = pA->next; pA->next = p; if (pB) pB->prev = p; @@ -358,12 +368,12 @@ void ParticleSystem::insertRandom(particle *p) p->next = pB; } -ParticleSystem::particle *ParticleSystem::removeParticle(particle *p) +ParticleSystem::Particle *ParticleSystem::removeParticle(Particle *p) { // The linked list is updated in this function and old pointers may be // invalidated. The returned pointer will inform the caller of the new // pointer to the next particle. - particle *pNext = nullptr; + Particle *pNext = nullptr; // Removes the particle from the linked list. if (p->prev) @@ -428,14 +438,14 @@ ParticleSystem::InsertMode ParticleSystem::getInsertMode() const return insertMode; } -void ParticleSystem::setEmissionRate(int rate) +void ParticleSystem::setEmissionRate(float rate) { - if (rate < 0) + if (rate < 0.0f) throw love::Exception("Invalid emission rate"); emissionRate = rate; } -int ParticleSystem::getEmissionRate() const +float ParticleSystem::getEmissionRate() const { return emissionRate; } @@ -470,6 +480,7 @@ void ParticleSystem::getParticleLifetime(float *min, float *max) const void ParticleSystem::setPosition(float x, float y) { position = love::Vector(x, y); + prevPosition = position; } const love::Vector &ParticleSystem::getPosition() const @@ -477,6 +488,11 @@ const love::Vector &ParticleSystem::getPosition() const return position; } +void ParticleSystem::moveTo(float x, float y) +{ + position = love::Vector(x, y); +} + void ParticleSystem::setAreaSpread(AreaSpreadDistribution distribution, float x, float y) { areaSpread = love::Vector(x, y); @@ -705,6 +721,16 @@ std::vector ParticleSystem::getColor() const return ncolors; } +void ParticleSystem::setRelativeRotation(bool enable) +{ + relativeRotation = enable; +} + +bool ParticleSystem::hasRelativeRotation() const +{ + return relativeRotation; +} + uint32 ParticleSystem::getCount() const { return activeParticles; @@ -748,7 +774,7 @@ void ParticleSystem::emit(uint32 num) num = std::min(num, maxParticles - activeParticles); while(num--) - addParticle(); + addParticle(1.0f); } bool ParticleSystem::isActive() const @@ -776,7 +802,7 @@ bool ParticleSystem::isFull() const return activeParticles == maxParticles; } -void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const +void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) { uint32 pCount = getCount(); if (pCount == 0 || texture == nullptr || pMem == nullptr || particleVerts == nullptr) @@ -792,13 +818,13 @@ void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, flo const Vertex *textureVerts = texture->getVertices(); Vertex *pVerts = particleVerts; - particle *p = pHead; + Particle *p = pHead; // set the vertex data for each particle (transformation, texcoords, color) while (p) { // particle vertices are image vertices transformed by particle information - t.setTransformation(p->position[0], p->position[1], p->rotation, p->size, p->size, offsetX, offsetY, 0.0f, 0.0f); + t.setTransformation(p->position[0], p->position[1], p->angle, p->size, p->size, offsetX, offsetY, 0.0f, 0.0f); t.transform(pVerts, textureVerts, 4); // set the texture coordinate and color data for particle vertices @@ -847,27 +873,8 @@ void ParticleSystem::update(float dt) if (pMem == nullptr || dt == 0.0f) return; - // Make some more particles. - if (active) - { - float rate = 1.0f / emissionRate; // the amount of time between each particle emit - emitCounter += dt; - while (emitCounter > rate) - { - addParticle(); - emitCounter -= rate; - } - /*int particles = (int)(emissionRate * dt); - for (int i = 0; i != particles; i++) - add();*/ - - life -= dt; - if (lifetime != -1 && life < 0) - stop(); - } - // Traverse all particles and update. - particle *p = pHead; + Particle *p = pHead; while (p) { @@ -912,7 +919,12 @@ void ParticleSystem::update(float dt) const float t = 1.0f - p->life / p->lifetime; // Rotate. - p->rotation += (p->spinStart * (1.0f - t) + p->spinEnd * t)*dt; + p->rotation += (p->spinStart * (1.0f - t) + p->spinEnd * t) * dt; + + p->angle = p->rotation; + + if (relativeRotation) + p->angle += atan2f(p->speed.y, p->speed.x); // Change size according to given intervals: // i = 0 1 2 3 n-1 @@ -940,6 +952,28 @@ void ParticleSystem::update(float dt) p = p->next; } } + + // Make some more particles. + if (active) + { + float rate = 1.0f / emissionRate; // the amount of time between each particle emit + emitCounter += dt; + float total = emitCounter - rate; + while (emitCounter > rate) + { + addParticle(1.0f - (emitCounter - rate) / total); + emitCounter -= rate; + } + /*int particles = (int)(emissionRate * dt); + for (int i = 0; i != particles; i++) + add();*/ + + life -= dt; + if (lifetime != -1 && life < 0) + stop(); + } + + prevPosition = position; } bool ParticleSystem::getConstant(const char *in, AreaSpreadDistribution &out) diff --git a/src/modules/graphics/opengl/ParticleSystem.h b/src/modules/graphics/opengl/ParticleSystem.h index 08f692cb5..272ab8303 100644 --- a/src/modules/graphics/opengl/ParticleSystem.h +++ b/src/modules/graphics/opengl/ParticleSystem.h @@ -65,7 +65,7 @@ public: INSERT_MODE_TOP, INSERT_MODE_BOTTOM, INSERT_MODE_RANDOM, - INSERT_MODE_MAX_ENUM, + INSERT_MODE_MAX_ENUM }; /** @@ -131,12 +131,12 @@ public: * Sets the emission rate. * @param rate The amount of particles per second. **/ - void setEmissionRate(int rate); + void setEmissionRate(float rate); /** * Returns the number of particles created per second. **/ - int getEmissionRate() const; + float getEmissionRate() const; /** * Sets the lifetime of the particle emitter (-1 means eternal) @@ -176,6 +176,15 @@ public: **/ const love::Vector &getPosition() const; + /** + * Moves the position of the center of the emitter. + * When update is called, newly spawned particles will appear in a line + * between the old emitter position and where the emitter was moved to, + * resulting in a smoother-feeling particle system if moveTo is called + * repeatedly. + **/ + void moveTo(float x, float y); + /** * Sets the emission area spread parameters and distribution type. The interpretation of * the parameters depends on the distribution type: @@ -412,6 +421,12 @@ public: **/ std::vector getColor() const; + /** + * sets whether particle angles & rotations are relative to their velocities. + **/ + void setRelativeRotation(bool enable); + bool hasRelativeRotation() const; + /** * Returns the amount of particles that are currently active in the system. **/ @@ -470,7 +485,7 @@ public: * @param x The x-coordinate. * @param y The y-coordinate. **/ - virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const; + virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky); /** * Updates the particle system. @@ -485,11 +500,12 @@ public: static bool getConstant(InsertMode in, const char *&out); protected: + // Represents a single particle. - struct particle + struct Particle { - particle *prev; - particle *next; + Particle *prev; + Particle *next; float lifetime; float life; @@ -509,7 +525,8 @@ protected: float sizeOffset; float sizeIntervalSize; - float rotation; + float rotation; // Amount of rotation applied to the final angle. + float angle; float spinStart; float spinEnd; @@ -517,16 +534,16 @@ protected: }; // Pointer to the beginning of the allocated memory. - particle *pMem; + Particle *pMem; // Pointer to a free particle. - particle *pFree; + Particle *pFree; // Pointer to the start of the linked list. - particle *pHead; + Particle *pHead; // Pointer to the end of the linked list. - particle *pTail; + Particle *pTail; // array of transformed vertex data for all particles, for drawing Vertex *particleVerts; @@ -547,13 +564,14 @@ protected: uint32 activeParticles; // The emission rate (particles/sec). - int emissionRate; + float emissionRate; // Used to determine when a particle should be emitted. float emitCounter; // The relative position of the particle emitter. love::Vector position; + love::Vector prevPosition; // Emission area spread. AreaSpreadDistribution areaSpreadDistribution; @@ -607,17 +625,19 @@ protected: // Color. std::vector colors; + bool relativeRotation; + void createBuffers(size_t size); void deleteBuffers(); - void addParticle(); - particle *removeParticle(particle *p); + void addParticle(float t); + Particle *removeParticle(Particle *p); // Called by addParticle. - void initParticle(particle *p); - void insertTop(particle *p); - void insertBottom(particle *p); - void insertRandom(particle *p); + void initParticle(Particle *p, float t); + void insertTop(Particle *p); + void insertBottom(Particle *p); + void insertRandom(Particle *p); static StringMap::Entry distributionsEntries[]; static StringMap distributions; diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 88ebbdac3..7b94e805c 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -70,7 +70,9 @@ Shader::Shader(const ShaderSources &sources) : shaderSources(sources) , program(0) , builtinUniforms() + , vertexAttributes() , lastCanvas((Canvas *) -1) + , lastViewport() { if (shaderSources.empty()) throw love::Exception("Cannot create shader: no source code!"); @@ -181,6 +183,14 @@ void Shader::createProgram(const std::vector &shaderids) for (it = shaderids.begin(); it != shaderids.end(); ++it) glAttachShader(program, *it); + // Bind generic vertex attribute indices to names in the shader. + for (int i = 0; i < int(OpenGL::ATTRIB_MAX_ENUM); i++) + { + const char *name = nullptr; + if (attribNames.find((OpenGL::VertexAttrib) i, name)) + glBindAttribLocation(program, i, (const GLchar *) name); + } + glLinkProgram(program); // flag shaders for auto-deletion when the program object is deleted. @@ -273,6 +283,15 @@ bool Shader::loadVolatile() // Retreive all active uniform variables in this shader from OpenGL. mapActiveUniforms(); + for (int i = 0; i < int(OpenGL::ATTRIB_MAX_ENUM); i++) + { + const char *name = nullptr; + if (attribNames.find(OpenGL::VertexAttrib(i), name)) + vertexAttributes[i] = glGetAttribLocation(program, name); + else + vertexAttributes[i] = -1; + } + if (current == this) { // make sure glUseProgram gets called. @@ -633,6 +652,11 @@ int Shader::getTextureUnit(const std::string &name) return texunit; } +bool Shader::hasVertexAttrib(OpenGL::VertexAttrib attrib) const +{ + return vertexAttributes[int(attrib)] != -1; +} + bool Shader::hasBuiltinExtern(BuiltinExtern builtin) const { return builtinUniforms[int(builtin)] != -1; @@ -670,30 +694,42 @@ bool Shader::sendBuiltinFloat(BuiltinExtern builtin, int size, const GLfloat *ve void Shader::checkSetScreenParams() { - if (lastCanvas == Canvas::current) + OpenGL::Viewport view = gl.getViewport(); + + if (view == lastViewport && lastCanvas == Canvas::current) return; - // In the shader, we do pixcoord.y = gl_FragCoord.y * params[0] + params[1]. + // In the shader, we do pixcoord.y = gl_FragCoord.y * params.z + params.w. // This lets us flip pixcoord.y when needed, to be consistent (Canvases // have flipped y-values for pixel coordinates.) - GLfloat params[] = {0.0f, 0.0f}; + GLfloat params[] = { + (GLfloat) view.w, (GLfloat) view.h, + 0.0f, 0.0f, + }; if (Canvas::current != nullptr) { // gl_FragCoord.y is flipped in Canvases, so we un-flip: // pixcoord.y = gl_FragCoord.y * -1.0 + height. - params[0] = -1.0f; - params[1] = (float) Canvas::current->getHeight(); + params[2] = -1.0f; + params[3] = (GLfloat) view.h; } else { // No flipping: pixcoord.y = gl_FragCoord.y * 1.0 + 0.0. - params[0] = 1.0f; - params[1] = 0.0f; + params[2] = 1.0f; + params[3] = 0.0f; } - sendBuiltinFloat(BUILTIN_SCREEN_PARAMS, 2, params, 1); + sendBuiltinFloat(BUILTIN_SCREEN_SIZE, 4, params, 1); + lastCanvas = Canvas::current; + lastViewport = view; +} + +const std::map &Shader::getBoundRetainables() const +{ + return boundRetainables; } std::string Shader::getGLSLVersion() @@ -730,9 +766,16 @@ StringMap::Entry Shader::typeNameEntr StringMap Shader::typeNames(Shader::typeNameEntries, sizeof(Shader::typeNameEntries)); +StringMap::Entry Shader::attribNameEntries[] = +{ + {"love_PseudoInstanceID", OpenGL::ATTRIB_PSEUDO_INSTANCE_ID}, +}; + +StringMap Shader::attribNames(Shader::attribNameEntries, sizeof(Shader::attribNameEntries)); + StringMap::Entry Shader::builtinNameEntries[] = { - {"love_ScreenParams", Shader::BUILTIN_SCREEN_PARAMS}, + {"love_ScreenSize", Shader::BUILTIN_SCREEN_SIZE}, }; StringMap Shader::builtinNames(Shader::builtinNameEntries, sizeof(Shader::builtinNameEntries)); diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index 97fcafc43..db116cc47 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/src/modules/graphics/opengl/Shader.h @@ -59,7 +59,7 @@ public: // Built-in extern (uniform) variables. enum BuiltinExtern { - BUILTIN_SCREEN_PARAMS, + BUILTIN_SCREEN_SIZE, BUILTIN_MAX_ENUM }; @@ -138,10 +138,13 @@ public: /** * Internal use only. **/ + bool hasVertexAttrib(OpenGL::VertexAttrib attrib) const; bool hasBuiltinExtern(BuiltinExtern builtin) const; bool sendBuiltinFloat(BuiltinExtern builtin, int size, const GLfloat *m, int count); void checkSetScreenParams(); + const std::map &getBoundRetainables() const; + static std::string getGLSLVersion(); static bool isSupported(); @@ -198,6 +201,9 @@ private: // Location values for any built-in uniform variables. GLint builtinUniforms[BUILTIN_MAX_ENUM]; + // Location values for any generic vertex attribute variables. + GLint vertexAttributes[OpenGL::ATTRIB_MAX_ENUM]; + // Uniform location buffer map std::map uniforms; @@ -210,6 +216,7 @@ private: // Pointer to the active Canvas when the screen params were last checked. Canvas *lastCanvas; + OpenGL::Viewport lastViewport; // Max GPU texture units available for sent images static GLint maxTexUnits; @@ -220,6 +227,10 @@ private: static StringMap::Entry typeNameEntries[]; static StringMap typeNames; + // Names for the generic vertex attributes used by love. + static StringMap::Entry attribNameEntries[]; + static StringMap attribNames; + // Names for the built-in uniform variables. static StringMap::Entry builtinNameEntries[]; static StringMap builtinNames; diff --git a/src/modules/graphics/opengl/SpriteBatch.cpp b/src/modules/graphics/opengl/SpriteBatch.cpp index 7b583c4b8..bd2e52f99 100644 --- a/src/modules/graphics/opengl/SpriteBatch.cpp +++ b/src/modules/graphics/opengl/SpriteBatch.cpp @@ -105,6 +105,8 @@ int SpriteBatch::add(float x, float y, float a, float sx, float sy, float ox, fl if ((index == -1 && next >= size) || index < -1 || index >= size) return -1; + Vertex sprite[4]; + // Needed for colors. memcpy(sprite, texture->getVertices(), sizeof(Vertex) * 4); @@ -131,6 +133,8 @@ int SpriteBatch::addq(Quad *quad, float x, float y, float a, float sx, float sy, if ((index == -1 && next >= size) || index < -1 || index >= next) return -1; + Vertex sprite[4]; + // Needed for colors. memcpy(sprite, quad->getVertices(), sizeof(Vertex) * 4); @@ -263,7 +267,7 @@ int SpriteBatch::getBufferSize() const return size; } -void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const +void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) { const size_t vertex_offset = offsetof(Vertex, x); const size_t texel_offset = offsetof(Vertex, s); diff --git a/src/modules/graphics/opengl/SpriteBatch.h b/src/modules/graphics/opengl/SpriteBatch.h index de14bf099..7339c7c77 100644 --- a/src/modules/graphics/opengl/SpriteBatch.h +++ b/src/modules/graphics/opengl/SpriteBatch.h @@ -109,7 +109,7 @@ public: int getBufferSize() const; // Implements Drawable. - void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) const; + void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky); static bool getConstant(const char *in, UsageHint &out); static bool getConstant(UsageHint in, const char *&out); @@ -135,8 +135,6 @@ private: // The next free element. int next; - Vertex sprite[4]; - // Current color. This color, if present, will be applied to the next // added sprite. Color *color; diff --git a/src/modules/graphics/opengl/Texture.h b/src/modules/graphics/opengl/Texture.h index 6dee55350..3a02decbd 100644 --- a/src/modules/graphics/opengl/Texture.h +++ b/src/modules/graphics/opengl/Texture.h @@ -48,12 +48,12 @@ public: * Any setup the texture might need to do before drawing, e.g. binding * the OpenGL texture for use. **/ - virtual void predraw() const {} + virtual void predraw() {} /** * Any cleanup the texture might need to do directly after drawing. **/ - virtual void postdraw() const {} + virtual void postdraw() {} }; // Texture diff --git a/src/modules/graphics/opengl/wrap_Canvas.cpp b/src/modules/graphics/opengl/wrap_Canvas.cpp index 4d5ab1282..a3c31b7f9 100644 --- a/src/modules/graphics/opengl/wrap_Canvas.cpp +++ b/src/modules/graphics/opengl/wrap_Canvas.cpp @@ -34,22 +34,21 @@ Canvas *luax_checkcanvas(lua_State *L, int idx) int w_Canvas_renderTo(lua_State *L) { - // As startGrab() clears the framebuffer, better not allow - // grabbing inside another grabbing - if (Canvas::current != NULL) - { - Canvas::bindDefaultCanvas(); - return luaL_error(L, "Current render target not the default canvas!"); - } - Canvas *canvas = luax_checkcanvas(L, 1); luaL_checktype(L, 2, LUA_TFUNCTION); + // Save the current Canvas so we can restore it when we're done. + Canvas *oldcanvas = Canvas::current; + EXCEPT_GUARD(canvas->startGrab();) lua_settop(L, 2); // make sure the function is on top of the stack lua_call(L, 0, 0); - canvas->stopGrab(); + + if (oldcanvas != nullptr) + oldcanvas->startGrab(oldcanvas->getAttachedCanvases()); + else + Canvas::bindDefaultCanvas(); return 0; } @@ -111,16 +110,25 @@ int w_Canvas_clear(lua_State *L) return 0; } -int w_Canvas_getType(lua_State *L) +int w_Canvas_getFormat(lua_State *L) { Canvas *canvas = luax_checkcanvas(L, 1); - Canvas::TextureType type = canvas->getTextureType(); + Texture::Format format = canvas->getTextureFormat(); const char *str; - Canvas::getConstant(type, str); + if (!Texture::getConstant(format, str)) + return luaL_error(L, "Unknown texture format."); + lua_pushstring(L, str); return 1; } +int w_Canvas_getFSAA(lua_State *L) +{ + Canvas *canvas = luax_checkcanvas(L, 1); + lua_pushinteger(L, canvas->getFSAA()); + return 1; +} + static const luaL_Reg functions[] = { // From wrap_Texture. @@ -136,7 +144,11 @@ static const luaL_Reg functions[] = { "getImageData", w_Canvas_getImageData }, { "getPixel", w_Canvas_getPixel }, { "clear", w_Canvas_clear }, - { "getType", w_Canvas_getType }, + { "getFormat", w_Canvas_getFormat }, + { "getFSAA", w_Canvas_getFSAA }, + + // Deprecated since 0.9.1. + { "getType", w_Canvas_getFormat }, { 0, 0 } }; diff --git a/src/modules/graphics/opengl/wrap_Canvas.h b/src/modules/graphics/opengl/wrap_Canvas.h index 4a6bea87b..4e48ffe03 100644 --- a/src/modules/graphics/opengl/wrap_Canvas.h +++ b/src/modules/graphics/opengl/wrap_Canvas.h @@ -39,7 +39,8 @@ int w_Canvas_renderTo(lua_State *L); int w_Canvas_getImageData(lua_State *L); int w_Canvas_getPixel(lua_State * L); int w_Canvas_clear(lua_State *L); -int w_Canvas_getType(lua_State *L); +int w_Canvas_getFormat(lua_State *L); +int w_Canvas_getFSAA(lua_State *L); extern "C" int luaopen_canvas(lua_State *L); } // opengl diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index 5022d5a36..816f75a55 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/src/modules/graphics/opengl/wrap_Graphics.cpp @@ -146,14 +146,23 @@ int w_setInvertedStencil(lua_State *L) int w_getMaxTextureSize(lua_State *L) { - lua_pushinteger(L, instance->getMaxTextureSize()); + lua_pushinteger(L, instance->getSystemLimit(Graphics::LIMIT_TEXTURE_SIZE)); return 1; } int w_newImage(lua_State *L) { - love::image::ImageData *data = 0; - love::image::CompressedData *cdata = 0; + love::image::ImageData *data = nullptr; + love::image::CompressedData *cdata = nullptr; + + Texture::Format format = Texture::FORMAT_NORMAL; + const char *fstr = lua_isnoneornil(L, 2) ? nullptr : luaL_checkstring(L, 2); + + if (fstr != nullptr && !Texture::getConstant(fstr, format)) + return luaL_error(L, "Invalid texture format: %s", fstr); + + if (format == Texture::FORMAT_HDR) // For now... + return luaL_error(L, "HDR images are not supported."); // Convert to FileData, if necessary. if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_T)) @@ -185,15 +194,15 @@ int w_newImage(lua_State *L) return luaL_error(L, "Error creating image."); // Create the image. - Image *image = 0; + Image *image = nullptr; EXCEPT_GUARD( if (cdata) - image = instance->newImage(cdata); + image = instance->newImage(cdata, format); else if (data) - image = instance->newImage(data); + image = instance->newImage(data, format); ) - if (image == 0) + if (image == nullptr) return luaL_error(L, "Could not load image."); // Push the type. @@ -323,15 +332,16 @@ int w_newCanvas(lua_State *L) int width = luaL_optint(L, 1, instance->getWidth()); int height = luaL_optint(L, 2, instance->getHeight()); const char *str = luaL_optstring(L, 3, "normal"); + int fsaa = luaL_optint(L, 4, 0); - Canvas::TextureType texture_type; - if (!Canvas::getConstant(str, texture_type)) - return luaL_error(L, "Invalid canvas type: %s", str); + Texture::Format format; + if (!Texture::getConstant(str, format)) + return luaL_error(L, "Invalid texture format: %s", str); - Canvas *canvas = 0; - EXCEPT_GUARD(canvas = instance->newCanvas(width, height, texture_type);) + Canvas *canvas = nullptr; + EXCEPT_GUARD(canvas = instance->newCanvas(width, height, format, fsaa);) - if (canvas == 0) + if (canvas == nullptr) return luaL_error(L, "Canvas not created, but no error thrown. I don't even..."); luax_pushtype(L, "Canvas", GRAPHICS_CANVAS_T, canvas); @@ -440,8 +450,10 @@ int w_newShader(lua_State *L) int w_newMesh(lua_State *L) { - // Check first argument: mandatory table of vertices. - luaL_checktype(L, 1, LUA_TTABLE); + // Check first argument: table of vertices or number of vertices. + int ttype = lua_type(L, 1); + if (ttype != LUA_TTABLE && ttype != LUA_TNUMBER) + luaL_argerror(L, 1, "table or number expected"); // Second argument: optional texture. Texture *tex = nullptr; @@ -456,52 +468,60 @@ int w_newMesh(lua_State *L) if (str && !Mesh::getConstant(str, mode)) return luaL_error(L, "Invalid mesh draw mode: %s", str); - size_t vertex_count = lua_objlen(L, 1); - std::vector vertices; - vertices.reserve(vertex_count); - - bool use_colors = false; - - // Get the vertices from the table. - for (size_t i = 1; i <= vertex_count; i++) - { - lua_rawgeti(L, 1, i); - - if (lua_type(L, -1) != LUA_TTABLE) - return luax_typerror(L, 1, "table of tables"); - - for (int j = 1; j <= 8; j++) - lua_rawgeti(L, -j, j); - - Vertex v; - - v.x = (float) luaL_checknumber(L, -8); - v.y = (float) luaL_checknumber(L, -7); - - v.s = (float) luaL_checknumber(L, -6); - v.t = (float) luaL_checknumber(L, -5); - - v.r = (unsigned char) luaL_optinteger(L, -4, 255); - v.g = (unsigned char) luaL_optinteger(L, -3, 255); - v.b = (unsigned char) luaL_optinteger(L, -2, 255); - v.a = (unsigned char) luaL_optinteger(L, -1, 255); - - // Enable per-vertex coloring if any color is not the default. - if (!use_colors && (v.r != 255 || v.g != 255 || v.b != 255 || v.a != 255)) - use_colors = true; - - lua_pop(L, 9); - vertices.push_back(v); - } - Mesh *t = nullptr; - EXCEPT_GUARD(t = instance->newMesh(vertices, mode);) + + if (ttype == LUA_TTABLE) + { + size_t vertex_count = lua_objlen(L, 1); + std::vector vertices; + vertices.reserve(vertex_count); + + bool use_colors = false; + + // Get the vertices from the table. + for (size_t i = 1; i <= vertex_count; i++) + { + lua_rawgeti(L, 1, i); + + if (lua_type(L, -1) != LUA_TTABLE) + return luax_typerror(L, 1, "table of tables"); + + for (int j = 1; j <= 8; j++) + lua_rawgeti(L, -j, j); + + Vertex v; + + v.x = (float) luaL_checknumber(L, -8); + v.y = (float) luaL_checknumber(L, -7); + + v.s = (float) luaL_optnumber(L, -6, 0.0); + v.t = (float) luaL_optnumber(L, -5, 0.0); + + v.r = (unsigned char) luaL_optinteger(L, -4, 255); + v.g = (unsigned char) luaL_optinteger(L, -3, 255); + v.b = (unsigned char) luaL_optinteger(L, -2, 255); + v.a = (unsigned char) luaL_optinteger(L, -1, 255); + + // Enable per-vertex coloring if any color is not the default. + if (!use_colors && (v.r != 255 || v.g != 255 || v.b != 255 || v.a != 255)) + use_colors = true; + + lua_pop(L, 9); + vertices.push_back(v); + } + + EXCEPT_GUARD(t = instance->newMesh(vertices, mode);) + t->setVertexColors(use_colors); + } + else + { + int count = luaL_checkint(L, 1); + EXCEPT_GUARD(t = instance->newMesh(count, mode);) + } if (tex) t->setTexture(tex); - t->setVertexColors(use_colors); - luax_pushtype(L, "Mesh", GRAPHICS_MESH_T, t); return 1; } @@ -798,7 +818,19 @@ int w_getPointSize(lua_State *L) int w_getMaxPointSize(lua_State *L) { - lua_pushnumber(L, instance->getMaxPointSize()); + lua_pushnumber(L, instance->getSystemLimit(Graphics::LIMIT_POINT_SIZE)); + return 1; +} + +int w_setWireframe(lua_State *L) +{ + instance->setWireframe(luax_toboolean(L, 1)); + return 0; +} + +int w_isWireframe(lua_State *L) +{ + luax_pushboolean(L, instance->isWireframe()); return 1; } @@ -962,6 +994,14 @@ int w_isSupported(lua_State *L) if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_BC5)) supported = false; break; + case Graphics::SUPPORT_INSTANCING: + if (!GLEE_ARB_draw_instanced) + supported = false; + break; + case Graphics::SUPPORT_SRGB: + if (!Canvas::isSRGBSupported()) + supported = false; + break; default: supported = false; } @@ -989,6 +1029,18 @@ int w_getRendererInfo(lua_State *L) return 4; } +int w_getSystemLimit(lua_State *L) +{ + const char *limitstr = luaL_checkstring(L, 1); + Graphics::SystemLimit limittype; + + if (!Graphics::getConstant(limitstr, limittype)) + return luaL_error(L, "Invalid system limit type: %s", limitstr); + + lua_pushnumber(L, instance->getSystemLimit(limittype)); + return 1; +} + int w_draw(lua_State *L) { Drawable *drawable = nullptr; @@ -1100,8 +1152,9 @@ int w_line(lua_State *L) args = lua_objlen(L, 1); is_table = true; } + if (args % 2 != 0) - return luaL_error(L, "Number of vertices must be a multiple of two"); + return luaL_error(L, "Number of vertex components must be a multiple of two"); else if (args < 4) return luaL_error(L, "Need at least two vertices to draw a line"); @@ -1202,7 +1255,7 @@ int w_polygon(lua_State *L) } if (args % 2 != 0) - return luaL_error(L, "Number of vertices must be a multiple of two"); + return luaL_error(L, "Number of vertex components must be a multiple of two"); else if (args < 6) return luaL_error(L, "Need at least three vertices to draw a polygon"); @@ -1325,6 +1378,8 @@ static const luaL_Reg functions[] = { "getPointSize", w_getPointSize }, { "getMaxPointSize", w_getMaxPointSize }, { "getMaxTextureSize", w_getMaxTextureSize }, + { "setWireframe", w_setWireframe }, + { "isWireframe", w_isWireframe }, { "newScreenshot", w_newScreenshot }, { "setCanvas", w_setCanvas }, { "getCanvas", w_getCanvas }, @@ -1334,6 +1389,7 @@ static const luaL_Reg functions[] = { "isSupported", w_isSupported }, { "getRendererInfo", w_getRendererInfo }, + { "getSystemLimit", w_getSystemLimit }, { "draw", w_draw }, @@ -1369,6 +1425,7 @@ static const luaL_Reg functions[] = // Deprecated since 0.9.1. { "getMaxImageSize", w_getMaxTextureSize }, + { "getMaxPointSize", w_getMaxPointSize }, { 0, 0 } }; diff --git a/src/modules/graphics/opengl/wrap_Graphics.h b/src/modules/graphics/opengl/wrap_Graphics.h index d19559360..396a8e3dd 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.h +++ b/src/modules/graphics/opengl/wrap_Graphics.h @@ -83,6 +83,8 @@ int w_getLineJoin(lua_State *L); int w_setPointSize(lua_State *L); int w_getPointSize(lua_State *L); int w_getMaxPointSize(lua_State *L); +int w_setWireframe(lua_State *L); +int w_isWireframe(lua_State *L); int w_newScreenshot(lua_State *L); int w_setCanvas(lua_State *L); int w_getCanvas(lua_State *L); @@ -90,6 +92,7 @@ int w_setShader(lua_State *L); int w_getShader(lua_State *L); int w_isSupported(lua_State *L); int w_getRendererInfo(lua_State *L); +int w_getSystemLimit(lua_State *L); int w_draw(lua_State *L); int w_print(lua_State *L); int w_printf(lua_State *L); diff --git a/src/modules/graphics/opengl/wrap_Mesh.cpp b/src/modules/graphics/opengl/wrap_Mesh.cpp index 1ea57e755..83e21afd3 100644 --- a/src/modules/graphics/opengl/wrap_Mesh.cpp +++ b/src/modules/graphics/opengl/wrap_Mesh.cpp @@ -53,8 +53,8 @@ int w_Mesh_setVertex(lua_State *L) v.x = luaL_checknumber(L, -8); v.y = luaL_checknumber(L, -7); - v.s = luaL_checknumber(L, -6); - v.t = luaL_checknumber(L, -5); + v.s = luaL_optnumber(L, -6, 0.0); + v.t = luaL_optnumber(L, -5, 0.0); v.r = luaL_optinteger(L, -4, 255); v.g = luaL_optinteger(L, -3, 255); v.b = luaL_optinteger(L, -2, 255); @@ -66,8 +66,8 @@ int w_Mesh_setVertex(lua_State *L) { v.x = luaL_checknumber(L, 3); v.y = luaL_checknumber(L, 4); - v.s = luaL_checknumber(L, 5); - v.t = luaL_checknumber(L, 6); + v.s = luaL_optnumber(L, 5, 0.0); + v.t = luaL_optnumber(L, 6, 0.0); v.r = luaL_optinteger(L, 7, 255); v.g = luaL_optinteger(L, 8, 255); v.b = luaL_optinteger(L, 9, 255); @@ -122,8 +122,8 @@ int w_Mesh_setVertices(lua_State *L) v.x = (float) luaL_checknumber(L, -8); v.y = (float) luaL_checknumber(L, -7); - v.s = (float) luaL_checknumber(L, -6); - v.t = (float) luaL_checknumber(L, -5); + v.s = (float) luaL_optnumber(L, -6, 0.0); + v.t = (float) luaL_optnumber(L, -5, 0.0); v.r = (unsigned char) luaL_optinteger(L, -4, 255); v.g = (unsigned char) luaL_optinteger(L, -3, 255); @@ -220,14 +220,15 @@ int w_Mesh_setVertexMap(lua_State *L) int w_Mesh_getVertexMap(lua_State *L) { Mesh *t = luax_checkmesh(L, 1); - const uint32 *vertex_map = 0; - EXCEPT_GUARD(vertex_map = t->getVertexMap();) - size_t elements = t->getVertexMapCount(); + std::vector vertex_map; + EXCEPT_GUARD(t->getVertexMap(vertex_map);) - lua_createtable(L, elements, 0); + size_t element_count = vertex_map.size(); - for (size_t i = 0; i < elements; i++) + lua_createtable(L, element_count, 0); + + for (size_t i = 0; i < element_count; i++) { lua_pushinteger(L, lua_Integer(vertex_map[i]) + 1); lua_rawseti(L, -2, i + 1); @@ -236,6 +237,20 @@ int w_Mesh_getVertexMap(lua_State *L) return 1; } +int w_Mesh_setInstanceCount(lua_State *L) +{ + Mesh *t = luax_checkmesh(L, 1); + t->setInstanceCount(luaL_checkint(L, 2)); + return 0; +} + +int w_Mesh_getInstanceCount(lua_State *L) +{ + Mesh *t = luax_checkmesh(L, 1); + lua_pushinteger(L, t->getInstanceCount()); + return 1; +} + int w_Mesh_setTexture(lua_State *L) { Mesh *t = luax_checkmesh(L, 1); @@ -301,6 +316,38 @@ int w_Mesh_getDrawMode(lua_State *L) return 1; } +int w_Mesh_setDrawRange(lua_State *L) +{ + Mesh *t = luax_checkmesh(L, 1); + + if (lua_isnoneornil(L, 2)) + t->setDrawRange(); + else + { + int rangemin = luaL_checkint(L, 2) - 1; + int rangemax = luaL_checkint(L, 3) - 1; + EXCEPT_GUARD(t->setDrawRange(rangemin, rangemax);) + } + + return 0; +} + +int w_Mesh_getDrawRange(lua_State *L) +{ + Mesh *t = luax_checkmesh(L, 1); + + int rangemin = -1; + int rangemax = -1; + t->getDrawRange(rangemin, rangemax); + + if (rangemin < 0 || rangemax < 0) + return 0; + + lua_pushinteger(L, rangemin + 1); + lua_pushinteger(L, rangemax + 1); + return 2; +} + int w_Mesh_setVertexColors(lua_State *L) { Mesh *t = luax_checkmesh(L, 1); @@ -315,20 +362,6 @@ int w_Mesh_hasVertexColors(lua_State *L) return 1; } -int w_Mesh_setWireframe(lua_State *L) -{ - Mesh *t = luax_checkmesh(L, 1); - t->setWireframe(luax_toboolean(L, 2)); - return 0; -} - -int w_Mesh_isWireframe(lua_State *L) -{ - Mesh *t = luax_checkmesh(L, 1); - luax_pushboolean(L, t->isWireframe()); - return 1; -} - static const luaL_Reg functions[] = { { "setVertex", w_Mesh_setVertex }, @@ -338,14 +371,16 @@ static const luaL_Reg functions[] = { "getVertexCount", w_Mesh_getVertexCount }, { "setVertexMap", w_Mesh_setVertexMap }, { "getVertexMap", w_Mesh_getVertexMap }, + { "setInstanceCount", w_Mesh_setInstanceCount }, + { "getInstanceCount", w_Mesh_getInstanceCount }, { "setTexture", w_Mesh_setTexture }, { "getTexture", w_Mesh_getTexture }, { "setDrawMode", w_Mesh_setDrawMode }, { "getDrawMode", w_Mesh_getDrawMode }, + { "setDrawRange", w_Mesh_setDrawRange }, + { "getDrawRange", w_Mesh_getDrawRange }, { "setVertexColors", w_Mesh_setVertexColors }, { "hasVertexColors", w_Mesh_hasVertexColors }, - { "setWireframe", w_Mesh_setWireframe }, - { "isWireframe", w_Mesh_isWireframe }, // Deprecated since 0.9.1. { "setImage", w_Mesh_setTexture }, diff --git a/src/modules/graphics/opengl/wrap_Mesh.h b/src/modules/graphics/opengl/wrap_Mesh.h index efae413dc..aa7c0f08b 100644 --- a/src/modules/graphics/opengl/wrap_Mesh.h +++ b/src/modules/graphics/opengl/wrap_Mesh.h @@ -41,14 +41,16 @@ int w_Mesh_getVertices(lua_State *L); int w_Mesh_getVertexCount(lua_State *L); int w_Mesh_setVertexMap(lua_State *L); int w_Mesh_getVertexMap(lua_State *L); +int w_Mesh_setInstanceCount(lua_State *L); +int w_Mesh_getInstanceCount(lua_State *L); int w_Mesh_setTexture(lua_State *L); int w_Mesh_getTexture(lua_State *L); int w_Mesh_setDrawMode(lua_State *L); int w_Mesh_getDrawMode(lua_State *L); +int w_Mesh_setDrawRange(lua_State *L); +int w_Mesh_getDrawRange(lua_State *L); int w_Mesh_setVertexColors(lua_State *L); int w_Mesh_hasVertexColors(lua_State *L); -int w_Mesh_setWireframe(lua_State *L); -int w_Mesh_isWireframe(lua_State *L); extern "C" int luaopen_mesh(lua_State *L); diff --git a/src/modules/graphics/opengl/wrap_ParticleSystem.cpp b/src/modules/graphics/opengl/wrap_ParticleSystem.cpp index 91517d60c..6cb3c0416 100644 --- a/src/modules/graphics/opengl/wrap_ParticleSystem.cpp +++ b/src/modules/graphics/opengl/wrap_ParticleSystem.cpp @@ -127,7 +127,7 @@ int w_ParticleSystem_getInsertMode(lua_State *L) int w_ParticleSystem_setEmissionRate(lua_State *L) { ParticleSystem *t = luax_checkparticlesystem(L, 1); - int arg1 = luaL_checkint(L, 2); + float arg1 = (float) luaL_checknumber(L, 2); EXCEPT_GUARD(t->setEmissionRate(arg1);) return 0; } @@ -135,7 +135,7 @@ int w_ParticleSystem_setEmissionRate(lua_State *L) int w_ParticleSystem_getEmissionRate(lua_State *L) { ParticleSystem *t = luax_checkparticlesystem(L, 1); - lua_pushinteger(L, t->getEmissionRate()); + lua_pushnumber(L, t->getEmissionRate()); return 1; } @@ -191,6 +191,15 @@ int w_ParticleSystem_getPosition(lua_State *L) return 2; } +int w_ParticleSystem_moveTo(lua_State *L) +{ + ParticleSystem *t = luax_checkparticlesystem(L, 1); + float arg1 = (float)luaL_checknumber(L, 2); + float arg2 = (float)luaL_checknumber(L, 3); + t->moveTo(arg1, arg2); + return 0; +} + int w_ParticleSystem_setAreaSpread(lua_State *L) { ParticleSystem *t = luax_checkparticlesystem(L, 1); @@ -560,6 +569,20 @@ int w_ParticleSystem_getColors(lua_State *L) return colors.size(); } +int w_ParticleSystem_setRelativeRotation(lua_State *L) +{ + ParticleSystem *t = luax_checkparticlesystem(L, 1); + t->setRelativeRotation(luax_toboolean(L, 2)); + return 0; +} + +int w_ParticleSystem_hasRelativeRotation(lua_State *L) +{ + ParticleSystem *t = luax_checkparticlesystem(L, 1); + luax_pushboolean(L, t->hasRelativeRotation()); + return 1; +} + int w_ParticleSystem_getCount(lua_State *L) { ParticleSystem *t = luax_checkparticlesystem(L, 1); @@ -649,6 +672,7 @@ static const luaL_Reg functions[] = { "getParticleLifetime", w_ParticleSystem_getParticleLifetime }, { "setPosition", w_ParticleSystem_setPosition }, { "getPosition", w_ParticleSystem_getPosition }, + { "moveTo", w_ParticleSystem_moveTo }, { "setAreaSpread", w_ParticleSystem_setAreaSpread }, { "getAreaSpread", w_ParticleSystem_getAreaSpread }, { "setDirection", w_ParticleSystem_setDirection }, @@ -677,6 +701,8 @@ static const luaL_Reg functions[] = { "getColors", w_ParticleSystem_getColors }, { "setOffset", w_ParticleSystem_setOffset }, { "getOffset", w_ParticleSystem_getOffset }, + { "setRelativeRotation", w_ParticleSystem_setRelativeRotation }, + { "hasRelativeRotation", w_ParticleSystem_hasRelativeRotation }, { "getCount", w_ParticleSystem_getCount }, { "start", w_ParticleSystem_start }, { "stop", w_ParticleSystem_stop }, diff --git a/src/modules/graphics/opengl/wrap_ParticleSystem.h b/src/modules/graphics/opengl/wrap_ParticleSystem.h index 849374745..b3c96a54f 100644 --- a/src/modules/graphics/opengl/wrap_ParticleSystem.h +++ b/src/modules/graphics/opengl/wrap_ParticleSystem.h @@ -48,6 +48,7 @@ int w_ParticleSystem_setParticleLifetime(lua_State *L); int w_ParticleSystem_getParticleLifetime(lua_State *L); int w_ParticleSystem_setPosition(lua_State *L); int w_ParticleSystem_getPosition(lua_State *L); +int w_ParticleSystem_moveTo(lua_State *L); int w_ParticleSystem_setAreaSpread(lua_State *L); int w_ParticleSystem_getAreaSpread(lua_State *L); int w_ParticleSystem_setDirection(lua_State *L); @@ -76,6 +77,8 @@ int w_ParticleSystem_setColors(lua_State *L); int w_ParticleSystem_getColors(lua_State *L); int w_ParticleSystem_setOffset(lua_State *L); int w_ParticleSystem_getOffset(lua_State *L); +int w_ParticleSystem_setRelativeRotation(lua_State *L); +int w_ParticleSystem_hasRelativeRotation(lua_State *L); int w_ParticleSystem_getCount(lua_State *L); int w_ParticleSystem_start(lua_State *L); int w_ParticleSystem_stop(lua_State *L); diff --git a/src/modules/image/Image.h b/src/modules/image/Image.h index b95024475..9a5d50794 100644 --- a/src/modules/image/Image.h +++ b/src/modules/image/Image.h @@ -47,7 +47,7 @@ public: /** * Destructor. **/ - virtual ~Image() {}; + virtual ~Image() {} /** * Creates new ImageData from FileData. @@ -93,4 +93,4 @@ public: } // image } // love -#endif // LOVE_IMAGE_IMAGE_H \ No newline at end of file +#endif // LOVE_IMAGE_IMAGE_H diff --git a/src/modules/image/magpie/DevilHandler.cpp b/src/modules/image/magpie/DevilHandler.cpp index dede18fdc..74abd813b 100644 --- a/src/modules/image/magpie/DevilHandler.cpp +++ b/src/modules/image/magpie/DevilHandler.cpp @@ -23,15 +23,10 @@ // LOVE #include "common/Exception.h" #include "common/math.h" -#include "thread/threads.h" // DevIL #include -using love::thread::Lock; - -static Mutex *devilMutex = 0; - namespace love { namespace image @@ -44,21 +39,22 @@ static inline void ilxClearErrors() while (ilGetError() != IL_NO_ERROR); } -void DevilHandler::init() +DevilHandler::DevilHandler() + : mutex(nullptr) { + // There should only ever be one DevilHandler object (owned by the Image + // module), so we can use the global initialization function here. ilInit(); ilEnable(IL_ORIGIN_SET); ilOriginFunc(IL_ORIGIN_UPPER_LEFT); } -void DevilHandler::quit() +DevilHandler::~DevilHandler() { ilShutDown(); - if (devilMutex) - { - delete devilMutex; - devilMutex = 0; - } + + if (mutex) + delete mutex; } bool DevilHandler::canDecode(love::filesystem::FileData * /*data*/) @@ -85,10 +81,10 @@ bool DevilHandler::canEncode(ImageData::Format format) DevilHandler::DecodedImage DevilHandler::decode(love::filesystem::FileData *data) { - if (!devilMutex) - devilMutex = thread::newMutex(); + if (!mutex) + mutex = love::thread::newMutex(); - Lock lock(devilMutex); + love::thread::Lock lock(mutex); ILuint image = ilGenImage(); ilBindImage(image); @@ -140,10 +136,10 @@ DevilHandler::DecodedImage DevilHandler::decode(love::filesystem::FileData *data DevilHandler::EncodedImage DevilHandler::encode(const DecodedImage &img, ImageData::Format format) { - if (!devilMutex) - devilMutex = thread::newMutex(); + if (!mutex) + mutex = love::thread::newMutex(); - Lock lock(devilMutex); + love::thread::Lock lock(mutex); ILuint tempimage = ilGenImage(); ilBindImage(tempimage); diff --git a/src/modules/image/magpie/DevilHandler.h b/src/modules/image/magpie/DevilHandler.h index 5d1e85019..d1f833abc 100644 --- a/src/modules/image/magpie/DevilHandler.h +++ b/src/modules/image/magpie/DevilHandler.h @@ -24,6 +24,7 @@ // LOVE #include "filesystem/FileData.h" #include "FormatHandler.h" +#include "thread/threads.h" namespace love { @@ -39,16 +40,20 @@ class DevilHandler : public FormatHandler { public: - static void init(); - static void quit(); - // Implements FormatHandler. - static bool canDecode(love::filesystem::FileData *data); - static bool canEncode(ImageData::Format format); + DevilHandler(); + virtual ~DevilHandler(); - static DecodedImage decode(love::filesystem::FileData *data); - static EncodedImage encode(const DecodedImage &img, ImageData::Format format); + virtual bool canDecode(love::filesystem::FileData *data); + virtual bool canEncode(ImageData::Format format); + + virtual DecodedImage decode(love::filesystem::FileData *data); + virtual EncodedImage encode(const DecodedImage &img, ImageData::Format format); + +private: + + Mutex *mutex; }; // DevilHandler diff --git a/src/modules/image/magpie/FormatHandler.cpp b/src/modules/image/magpie/FormatHandler.cpp new file mode 100644 index 000000000..d50e24008 --- /dev/null +++ b/src/modules/image/magpie/FormatHandler.cpp @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2006-2014 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +// LOVE +#include "FormatHandler.h" +#include "common/Exception.h" + +namespace love +{ +namespace image +{ +namespace magpie +{ + +FormatHandler::FormatHandler() +{ +} + +FormatHandler::~FormatHandler() +{ +} + +bool FormatHandler::canDecode(love::filesystem::FileData* /*data*/) +{ + return false; +} + +bool FormatHandler::canEncode(ImageData::Format /*format*/) +{ + return false; +} + +FormatHandler::DecodedImage FormatHandler::decode(love::filesystem::FileData* /*data*/) +{ + throw love::Exception("Image decoding is not implemented for this format backend."); +} + +FormatHandler::EncodedImage FormatHandler::encode(const DecodedImage& /*img*/, ImageData::Format /*format*/) +{ + throw love::Exception("Image encoding is not implemented for this format backend."); +} + +} // magpie +} // image +} // love diff --git a/src/modules/image/magpie/FormatHandler.h b/src/modules/image/magpie/FormatHandler.h index f953e97e3..f115fced6 100644 --- a/src/modules/image/magpie/FormatHandler.h +++ b/src/modules/image/magpie/FormatHandler.h @@ -24,6 +24,7 @@ // LOVE #include "image/ImageData.h" #include "filesystem/FileData.h" +#include "common/Object.h" namespace love { @@ -34,8 +35,9 @@ namespace magpie /** * Base class for all ImageData encoder/decoder library interfaces. + * We inherit from love::Object to take advantage of reference counting... **/ -class FormatHandler +class FormatHandler : public love::Object { public: @@ -56,26 +58,32 @@ public: EncodedImage() : size(0), data(0) {} }; - // Lets pretend we have virtual static methods... + /** + * The default constructor is called when the Image module is initialized. + **/ + FormatHandler(); /** - * Determines whether a particular FileData can be decoded by this handler. - * @param data The data to decode. + * The destructor is called when the Image module is uninitialized. **/ - // virtual static bool canDecode(love::filesystem::FileData *data) = 0; + virtual ~FormatHandler(); /** - * Determines whether this handler can encode to a particular format. - * @param format The format to encode to. + * Whether this format handler can decode a particular FileData. **/ - // virtual static bool canEncode(ImageData::Format format) = 0; + virtual bool canDecode(love::filesystem::FileData *data); + + /** + * Whether this format handler can encode to a particular format. + **/ + virtual bool canEncode(ImageData::Format format); /** * Decodes an image from its encoded form into raw pixel data. * @param data The encoded data to decode. * @return The decoded pixel data. **/ - // virtual static DecodedImage decode(love::filesystem::FileData *data) = 0; + virtual DecodedImage decode(love::filesystem::FileData *data); /** * Encodes an image from raw pixel data into a particular format. @@ -83,7 +91,7 @@ public: * @param format The format to encode to. * @return The encoded image data. **/ - // virtual static EncodedImage encode(const DecodedImage &img, ImageData::Format format) = 0; + virtual EncodedImage encode(const DecodedImage &img, ImageData::Format format); }; // FormatHandler diff --git a/src/modules/image/magpie/Image.cpp b/src/modules/image/magpie/Image.cpp index 7eb8d2519..3953aefdd 100644 --- a/src/modules/image/magpie/Image.cpp +++ b/src/modules/image/magpie/Image.cpp @@ -34,12 +34,15 @@ namespace magpie Image::Image() { - DevilHandler::init(); + formatHandlers.push_back(new DevilHandler); } Image::~Image() { - DevilHandler::quit(); + // ImageData objects reference the FormatHandlers in our list, so we should + // release them instead of deleting them completely here. + for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it) + (*it)->release(); } const char *Image::getName() const @@ -49,17 +52,17 @@ const char *Image::getName() const love::image::ImageData *Image::newImageData(love::filesystem::FileData *data) { - return new ImageData(data); + return new ImageData(formatHandlers, data); } love::image::ImageData *Image::newImageData(int width, int height) { - return new ImageData(width, height); + return new ImageData(formatHandlers, width, height); } love::image::ImageData *Image::newImageData(int width, int height, void *data, bool own) { - return new ImageData(width, height, data, own); + return new ImageData(formatHandlers, width, height, data, own); } love::image::CompressedData *Image::newCompressedData(love::filesystem::FileData *data) diff --git a/src/modules/image/magpie/Image.h b/src/modules/image/magpie/Image.h index 4385e6635..75adfc479 100644 --- a/src/modules/image/magpie/Image.h +++ b/src/modules/image/magpie/Image.h @@ -23,6 +23,10 @@ // LOVE #include "image/Image.h" +#include "FormatHandler.h" + +// C++ +#include namespace love { @@ -54,6 +58,11 @@ public: bool isCompressed(love::filesystem::FileData *data); +private: + + // Image format handlers we can use for decoding and encoding ImageData. + std::list formatHandlers; + }; // Image } // magpie diff --git a/src/modules/image/magpie/ImageData.cpp b/src/modules/image/magpie/ImageData.cpp index db614abbb..7864b55e9 100644 --- a/src/modules/image/magpie/ImageData.cpp +++ b/src/modules/image/magpie/ImageData.cpp @@ -18,11 +18,9 @@ * 3. This notice may not be removed or altered from any source distribution. **/ +// LOVE #include "ImageData.h" -#include "FormatHandler.h" -#include "DevilHandler.h" - namespace love { namespace image @@ -30,23 +28,36 @@ namespace image namespace magpie { -ImageData::ImageData(love::filesystem::FileData *data) +ImageData::ImageData(std::list formats, love::filesystem::FileData *data) + : formatHandlers(formats) { + for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it) + (*it)->retain(); + decode(data); } -ImageData::ImageData(int width, int height) +ImageData::ImageData(std::list formats, int width, int height) + : formatHandlers(formats) { + for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it) + (*it)->retain(); + this->width = width; this->height = height; + create(width, height); // Set to black/transparency. memset(data, 0, width*height*sizeof(pixel)); } -ImageData::ImageData(int width, int height, void *data, bool own) +ImageData::ImageData(std::list formats, int width, int height, void *data, bool own) + : formatHandlers(formats) { + for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it) + (*it)->retain(); + this->width = width; this->height = height; @@ -59,6 +70,9 @@ ImageData::ImageData(int width, int height, void *data, bool own) ImageData::~ImageData() { delete[] data; + + for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it) + (*it)->release(); } void ImageData::create(int width, int height, void *data) @@ -80,16 +94,23 @@ void ImageData::decode(love::filesystem::FileData *data) { FormatHandler::DecodedImage decodedimage; - if (DevilHandler::canDecode(data)) - decodedimage = DevilHandler::decode(data); - else + for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it) + { + if ((*it)->canDecode(data)) + { + decodedimage = (*it)->decode(data); + break; + } + } + + if (decodedimage.data == nullptr) throw love::Exception("Could not decode image: unrecognized format."); // The decoder *must* output a 32 bits-per-pixel image. if (decodedimage.size != decodedimage.width*decodedimage.height*sizeof(pixel)) { delete[] decodedimage.data; - throw love::Exception("Coult not convert image!"); + throw love::Exception("Could not convert image!"); } if (this->data) @@ -114,15 +135,21 @@ void ImageData::encode(love::filesystem::File *f, ImageData::Format format) rawimage.size = width*height*sizeof(pixel); rawimage.data = data; - if (DevilHandler::canEncode(format)) - encodedimage = DevilHandler::encode(rawimage, format); - else + for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it) + { + if ((*it)->canEncode(format)) + { + encodedimage = (*it)->encode(rawimage, format); + break; + } + } + + if (encodedimage.data == nullptr) throw love::Exception("Image format has no suitable encoder."); } try { - f->open(love::filesystem::File::WRITE); f->write(encodedimage.data, encodedimage.size); f->close(); diff --git a/src/modules/image/magpie/ImageData.h b/src/modules/image/magpie/ImageData.h index b7ccbf346..298e82423 100644 --- a/src/modules/image/magpie/ImageData.h +++ b/src/modules/image/magpie/ImageData.h @@ -22,9 +22,13 @@ #define LOVE_IMAGE_MAGPIE_IMAGE_DATA_H // LOVE +#include "FormatHandler.h" #include "filesystem/File.h" #include "image/ImageData.h" +// C++ +#include + namespace love { namespace image @@ -36,9 +40,9 @@ class ImageData : public love::image::ImageData { public: - ImageData(love::filesystem::FileData *data); - ImageData(int width, int height); - ImageData(int width, int height, void *data, bool own); + ImageData(std::list formats, love::filesystem::FileData *data); + ImageData(std::list formats, int width, int height); + ImageData(std::list formats, int width, int height, void *data, bool own); virtual ~ImageData(); // Implements image::ImageData. @@ -52,6 +56,9 @@ private: // Decode and load an encoded format. void decode(love::filesystem::FileData *data); + // Image format handlers we can use for decoding and encoding. + std::list formatHandlers; + }; // ImageData } // magpie diff --git a/src/modules/keyboard/Keyboard.cpp b/src/modules/keyboard/Keyboard.cpp index 6270c108f..16e93005f 100644 --- a/src/modules/keyboard/Keyboard.cpp +++ b/src/modules/keyboard/Keyboard.cpp @@ -220,6 +220,17 @@ StringMap::Entry Keyboard::keyEntries[] = {"audioplay", Keyboard::KEY_AUDIOPLAY}, {"audiomute", Keyboard::KEY_AUDIOMUTE}, {"mediaselect", Keyboard::KEY_MEDIASELECT}, + {"www", Keyboard::KEY_WWW}, + {"mail", Keyboard::KEY_MAIL}, + {"calculator", Keyboard::KEY_CALCULATOR}, + {"computer", Keyboard::KEY_COMPUTER}, + {"appsearch", Keyboard::KEY_APP_SEARCH}, + {"apphome", Keyboard::KEY_APP_HOME}, + {"appback", Keyboard::KEY_APP_BACK}, + {"appforward", Keyboard::KEY_APP_FORWARD}, + {"appstop", Keyboard::KEY_APP_STOP}, + {"apprefresh", Keyboard::KEY_APP_REFRESH}, + {"appbookmarks", Keyboard::KEY_APP_BOOKMARKS}, {"brightnessdown", Keyboard::KEY_BRIGHTNESSDOWN}, {"brightnessup", Keyboard::KEY_BRIGHTNESSUP}, diff --git a/src/modules/keyboard/Keyboard.h b/src/modules/keyboard/Keyboard.h index 5e2e325a0..1b7c80bd1 100644 --- a/src/modules/keyboard/Keyboard.h +++ b/src/modules/keyboard/Keyboard.h @@ -220,6 +220,17 @@ public: KEY_AUDIOPLAY, KEY_AUDIOMUTE, KEY_MEDIASELECT, + KEY_WWW, + KEY_MAIL, + KEY_CALCULATOR, + KEY_COMPUTER, + KEY_APP_SEARCH, + KEY_APP_HOME, + KEY_APP_BACK, + KEY_APP_FORWARD, + KEY_APP_STOP, + KEY_APP_REFRESH, + KEY_APP_BOOKMARKS, KEY_BRIGHTNESSDOWN, KEY_BRIGHTNESSUP, diff --git a/src/modules/keyboard/sdl/Keyboard.cpp b/src/modules/keyboard/sdl/Keyboard.cpp index 4c70ea94a..488632d6a 100644 --- a/src/modules/keyboard/sdl/Keyboard.cpp +++ b/src/modules/keyboard/sdl/Keyboard.cpp @@ -264,6 +264,17 @@ std::map Keyboard::createKeyMap() k[Keyboard::KEY_AUDIOPLAY] = SDLK_AUDIOPLAY; k[Keyboard::KEY_AUDIOMUTE] = SDLK_AUDIOMUTE; k[Keyboard::KEY_MEDIASELECT] = SDLK_MEDIASELECT; + k[Keyboard::KEY_WWW] = SDLK_WWW; + k[Keyboard::KEY_MAIL] = SDLK_MAIL; + k[Keyboard::KEY_CALCULATOR] = SDLK_CALCULATOR; + k[Keyboard::KEY_COMPUTER] = SDLK_COMPUTER; + k[Keyboard::KEY_APP_SEARCH] = SDLK_AC_SEARCH; + k[Keyboard::KEY_APP_HOME] = SDLK_AC_HOME; + k[Keyboard::KEY_APP_BACK] = SDLK_AC_BACK; + k[Keyboard::KEY_APP_FORWARD] = SDLK_AC_FORWARD; + k[Keyboard::KEY_APP_STOP] = SDLK_AC_STOP; + k[Keyboard::KEY_APP_REFRESH] = SDLK_AC_REFRESH; + k[Keyboard::KEY_APP_BOOKMARKS] = SDLK_AC_BOOKMARKS; k[Keyboard::KEY_BRIGHTNESSDOWN] = SDLK_BRIGHTNESSDOWN; k[Keyboard::KEY_BRIGHTNESSUP] = SDLK_BRIGHTNESSUP; diff --git a/src/modules/love/love.cpp b/src/modules/love/love.cpp index 77fe5b208..7bb700b76 100644 --- a/src/modules/love/love.cpp +++ b/src/modules/love/love.cpp @@ -170,6 +170,15 @@ const char *love_codename() return love::VERSION_CODENAME; } +static int w_love_getVersion(lua_State *L) +{ + lua_pushinteger(L, love::VERSION_MAJOR); + lua_pushinteger(L, love::VERSION_MINOR); + lua_pushinteger(L, love::VERSION_REV); + lua_pushstring(L, love::VERSION_CODENAME); + return 4; +} + int luaopen_love(lua_State * L) { love::luax_insistglobal(L, "love"); @@ -203,6 +212,9 @@ int luaopen_love(lua_State * L) lua_setfield(L, -2, "_version_compat"); + lua_pushcfunction(L, w_love_getVersion); + lua_setfield(L, -2, "getVersion"); + #ifdef LOVE_WINDOWS lua_pushstring(L, "Windows"); #elif defined(LOVE_MACOSX) diff --git a/src/modules/math/MathModule.cpp b/src/modules/math/MathModule.cpp index 9ffefe839..ed096ebd7 100644 --- a/src/modules/math/MathModule.cpp +++ b/src/modules/math/MathModule.cpp @@ -193,5 +193,35 @@ bool Math::isConvex(const std::vector &polygon) return true; } +/** + * http://en.wikipedia.org/wiki/SRGB#The_reverse_transformation + **/ +float Math::gammaToLinear(float c) const +{ + if (c > 1.0) + return 1.0; + else if (c < 0.0) + return 0.0; + else if (c <= 0.04045) + return c / 12.92; + else + return powf((c + 0.055) / 1.055, 2.4); +} + +/** + * http://en.wikipedia.org/wiki/SRGB#The_forward_transformation_.28CIE_xyY_or_CIE_XYZ_to_sRGB.29 + **/ +float Math::linearToGamma(float c) const +{ + if (c > 1.0) + return 1.0; + else if (c < 0.0) + return 0.0; + else if (c < 0.0031308) + return c * 12.92; + else + return 1.055 * powf(c, 0.41666) - 0.055; +} + } // math } // love diff --git a/src/modules/math/MathModule.h b/src/modules/math/MathModule.h index 70f420515..e9df716ac 100644 --- a/src/modules/math/MathModule.h +++ b/src/modules/math/MathModule.h @@ -53,26 +53,6 @@ public: virtual ~Math() {} - inline void setRandomSeed(RandomGenerator::Seed seed) - { - rng.setSeed(seed); - } - - inline void setRandomSeed(uint32 low, uint32 high) - { - rng.setSeed(low, high); - } - - inline RandomGenerator::Seed getRandomSeed() const - { - return rng.getSeed(); - } - - inline void getRandomSeed(uint32 &low, uint32 &high) const - { - rng.getSeed(low, high); - } - /** * @copydoc RandomGenerator::random() **/ @@ -105,6 +85,26 @@ public: return rng.randomNormal(stddev); } + inline void setRandomSeed(RandomGenerator::Seed seed) + { + rng.setSeed(seed); + } + + inline RandomGenerator::Seed getRandomSeed() const + { + return rng.getSeed(); + } + + inline void setRandomState(const std::string &statestr) + { + rng.setState(statestr); + } + + inline std::string getRandomState() const + { + return rng.getState(); + } + /** * Create a new random number generator. **/ @@ -136,6 +136,16 @@ public: **/ bool isConvex(const std::vector &polygon); + /** + * Converts a value from the sRGB (gamma) colorspace to linear RGB. + **/ + float gammaToLinear(float c) const; + + /** + * Converts a value from linear RGB to the sRGB (gamma) colorspace. + **/ + float linearToGamma(float c) const; + /** * Calculate Simplex noise for the specified coordinate(s). * diff --git a/src/modules/math/RandomGenerator.cpp b/src/modules/math/RandomGenerator.cpp index 1f7b58a47..0e26d3e8d 100644 --- a/src/modules/math/RandomGenerator.cpp +++ b/src/modules/math/RandomGenerator.cpp @@ -20,8 +20,13 @@ #include "RandomGenerator.h" -// STL +// C++ #include +#include +#include + +// C +#include namespace love { @@ -36,25 +41,10 @@ RandomGenerator::RandomGenerator() { // because it is too big for some compilers to handle ... if you know what // i mean -#ifdef LOVE_BIG_ENDIAN - seed.b32.a = 0x0139408D; - seed.b32.b = 0xCBBF7A44; -#else - seed.b32.b = 0x0139408D; - seed.b32.a = 0xCBBF7A44; -#endif - - rng_state = seed; -} - -void RandomGenerator::setSeed(RandomGenerator::Seed newseed) -{ - // 0 xor 0 is still 0, so Xorshift can't generate new numbers. - if (newseed.b64 == 0) - throw love::Exception("Invalid random seed."); - - seed = newseed; - rng_state = seed; + Seed newseed; + newseed.b32.low = 0xCBBF7A44; + newseed.b32.high = 0x0139408D; + setSeed(newseed); } uint64 RandomGenerator::rand() @@ -83,5 +73,71 @@ double RandomGenerator::randomNormal(double stddev) return r * sin(phi) * stddev; } +void RandomGenerator::setSeed(RandomGenerator::Seed newseed) +{ + // 0 xor 0 is still 0, so Xorshift can't generate new numbers. + if (newseed.b64 == 0) + throw love::Exception("Invalid random seed."); + + seed = newseed; + rng_state = seed; +} + +RandomGenerator::Seed RandomGenerator::getSeed() const +{ + return seed; +} + +void RandomGenerator::setState(const std::string &statestr) +{ + // For this implementation we'll accept a hex string representing the + // 64-bit state integer xorshift uses. + + Seed state = {}; + + // Hex string must start with 0x. + if (statestr.find("0x") != 0 || statestr.size() < 3) + throw love::Exception("Invalid random state."); + + // standardized strtoull (or 64 bit integer support for stringstream) + // requires C++11's standard library, which we can't use yet. + // I use strtol like this not because it's the best solution, but because + // it's "good enough". + + // Convert the hex string to the state integer character-by-character. + for (size_t i = 2; i < statestr.size(); i++) + { + char hex[2] = {statestr[i], 0}; + char *end = nullptr; + + // Convert the current hex character to a number. + int nibble = strtol(hex, &end, 16); + + // Check if strtol failed to convert it. + if (end != nullptr && *end != 0) + throw love::Exception("Invalid random state."); + + state.b64 = (state.b64 << 4) + nibble; + } + + rng_state = state; +} + +std::string RandomGenerator::getState() const +{ + // For this implementation we'll return a hex string representing the 64-bit + // state integer xorshift uses. + + std::stringstream ss; + + ss << "0x"; + + // Again with the stringstream not dealing with 64 bit integers... + ss << std::setfill('0') << std::setw(8) << std::hex << rng_state.b32.high; + ss << std::setfill('0') << std::setw(8) << std::hex << rng_state.b32.low; + + return ss.str(); +} + } // math } // love diff --git a/src/modules/math/RandomGenerator.h b/src/modules/math/RandomGenerator.h index 830a9506c..aab0f9277 100644 --- a/src/modules/math/RandomGenerator.h +++ b/src/modules/math/RandomGenerator.h @@ -28,8 +28,9 @@ #include "common/int.h" #include "common/Object.h" -// STL +// C++ #include +#include namespace love { @@ -45,54 +46,19 @@ public: uint64 b64; struct { - uint32 a; - uint32 b; +#ifdef LOVE_BIG_ENDIAN + uint32 high; + uint32 low; +#else + uint32 low; + uint32 high; +#endif } b32; }; RandomGenerator(); virtual ~RandomGenerator() {} - /** - * Set pseudo-random seed. - * It's up to the implementation how to use this. - **/ - void setSeed(Seed seed); - - /** - * Separately set the low and high bits of the pseudo-random seed. - **/ - inline void setSeed(uint32 low, uint32 high) - { - Seed newseed; - -#ifdef LOVE_BIG_ENDIAN - newseed.b32.a = high; - newseed.b32.b = low; -#else - newseed.b32.b = high; - newseed.b32.a = low; -#endif - - setSeed(newseed); - } - - inline Seed getSeed() const - { - return seed; - } - - inline void getSeed(uint32 &low, uint32 &high) const - { -#ifdef LOVE_BIG_ENDIAN - high = seed.b32.a; - low = seed.b32.b; -#else - high = seed.b32.b; - low = seed.b32.a; -#endif - } - /** * Return uniformly distributed pseudo random integer. * @@ -138,6 +104,28 @@ public: **/ double randomNormal(double stddev); + /** + * Set pseudo-random seed. + * It's up to the implementation how to use this. + **/ + void setSeed(Seed seed); + + /** + * Get the previously set pseudo-random seed. + **/ + Seed getSeed() const; + + /** + * Set the internal implementation-dependent state value based on a string. + **/ + void setState(const std::string &statestr); + + /** + * Get a string representation of the implementation-dependent internal + * state value. + **/ + std::string getState() const; + private: Seed seed; diff --git a/src/modules/math/wrap_Math.cpp b/src/modules/math/wrap_Math.cpp index 95cf37940..50b78f956 100644 --- a/src/modules/math/wrap_Math.cpp +++ b/src/modules/math/wrap_Math.cpp @@ -32,21 +32,6 @@ namespace love namespace math { -int w_setRandomSeed(lua_State *L) -{ - EXCEPT_GUARD(Math::instance.setRandomSeed(luax_checkrandomseed(L, 1));) - return 0; -} - -int w_getRandomSeed(lua_State *L) -{ - uint32 low = 0, high = 0; - Math::instance.getRandomSeed(low, high); - lua_pushnumber(L, (lua_Number) low); - lua_pushnumber(L, (lua_Number) high); - return 2; -} - int w_random(lua_State *L) { return luax_getrandom(L, 1, Math::instance.random()); @@ -62,6 +47,32 @@ int w_randomNormal(lua_State *L) return 1; } +int w_setRandomSeed(lua_State *L) +{ + EXCEPT_GUARD(Math::instance.setRandomSeed(luax_checkrandomseed(L, 1));) + return 0; +} + +int w_getRandomSeed(lua_State *L) +{ + RandomGenerator::Seed s = Math::instance.getRandomSeed(); + lua_pushnumber(L, (lua_Number) s.b32.low); + lua_pushnumber(L, (lua_Number) s.b32.high); + return 2; +} + +int w_setRandomState(lua_State *L) +{ + EXCEPT_GUARD(Math::instance.setRandomState(luax_checkstring(L, 1));) + return 0; +} + +int w_getRandomState(lua_State *L) +{ + luax_pushstring(L, Math::instance.getRandomState()); + return 1; +} + int w_newRandomGenerator(lua_State *L) { RandomGenerator::Seed s; @@ -238,6 +249,70 @@ int w_isConvex(lua_State *L) return 1; } +static int getGammaArgs(lua_State *L, float color[4]) +{ + int numcomponents = 0; + + if (lua_istable(L, 1)) + { + int n = lua_objlen(L, 1); + for (int i = 1; i <= n && i <= 4; i++) + { + lua_rawgeti(L, 1, i); + color[i - 1] = (float) luaL_checknumber(L, -1) / 255.0; + numcomponents++; + } + + lua_pop(L, numcomponents); + } + else + { + int n = lua_gettop(L); + for (int i = 1; i <= n && i <= 4; i++) + { + color[i - 1] = (float) luaL_checknumber(L, i) / 255.0; + numcomponents++; + } + } + + if (numcomponents == 0) + luaL_checknumber(L, 1); + + return numcomponents; +} + +int w_gammaToLinear(lua_State *L) +{ + float color[4]; + int numcomponents = getGammaArgs(L, color); + + for (int i = 0; i < numcomponents; i++) + { + // Alpha should always be linear. + if (i < 3) + color[i] = Math::instance.gammaToLinear(color[i]); + lua_pushnumber(L, color[i] * 255); + } + + return numcomponents; +} + +int w_linearToGamma(lua_State *L) +{ + float color[4]; + int numcomponents = getGammaArgs(L, color); + + for (int i = 0; i < numcomponents; i++) + { + // Alpha should always be linear. + if (i < 3) + color[i] = Math::instance.linearToGamma(color[i]); + lua_pushnumber(L, color[i] * 255); + } + + return numcomponents; +} + int w_noise(lua_State *L) { float w, x, y, z; @@ -277,14 +352,18 @@ int w_noise(lua_State *L) // List of functions to wrap. static const luaL_Reg functions[] = { - { "setRandomSeed", w_setRandomSeed }, - { "getRandomSeed", w_getRandomSeed }, { "random", w_random }, { "randomNormal", w_randomNormal }, + { "setRandomSeed", w_setRandomSeed }, + { "getRandomSeed", w_getRandomSeed }, + { "setRandomState", w_setRandomState }, + { "getRandomState", w_getRandomState }, { "newRandomGenerator", w_newRandomGenerator }, { "newBezierCurve", w_newBezierCurve }, { "triangulate", w_triangulate }, { "isConvex", w_isConvex }, + { "gammaToLinear", w_gammaToLinear }, + { "linearToGamma", w_linearToGamma }, { "noise", w_noise }, { 0, 0 } }; diff --git a/src/modules/math/wrap_Math.h b/src/modules/math/wrap_Math.h index 8693535dc..d75afe4ae 100644 --- a/src/modules/math/wrap_Math.h +++ b/src/modules/math/wrap_Math.h @@ -30,14 +30,18 @@ namespace love namespace math { -int w_setRandomSeed(lua_State *L); -int w_getRandomSeed(lua_State *L); int w_random(lua_State *L); int w_randomNormal(lua_State *L); +int w_setRandomSeed(lua_State *L); +int w_getRandomSeed(lua_State *L); +int w_setRandomState(lua_State *L); +int w_getRandomState(lua_State *L); int w_newRandomGenerator(lua_State *L); int w_newBezierCurve(lua_State *L); int w_triangulate(lua_State *L); int w_isConvex(lua_State *L); +int w_gammaToLinear(lua_State *L); +int w_linearToGamma(lua_State *L); int w_noise(lua_State *L); extern "C" LOVE_EXPORT int luaopen_love_math(lua_State *L); diff --git a/src/modules/math/wrap_RandomGenerator.cpp b/src/modules/math/wrap_RandomGenerator.cpp index 78cf53a04..ea029df2a 100644 --- a/src/modules/math/wrap_RandomGenerator.cpp +++ b/src/modules/math/wrap_RandomGenerator.cpp @@ -47,16 +47,8 @@ RandomGenerator::Seed luax_checkrandomseed(lua_State *L, int idx) if (!lua_isnoneornil(L, idx + 1)) { - uint32 low = checkrandomseed_part(L, idx); - uint32 high = checkrandomseed_part(L, idx + 1); - -#ifdef LOVE_BIG_ENDIAN - s.b32.a = high; - s.b32.b = low; -#else - s.b32.b = high; - s.b32.a = low; -#endif + s.b32.low = checkrandomseed_part(L, idx); + s.b32.high = checkrandomseed_part(L, idx + 1); } else s.b64 = checkrandomseed_part(L, idx); @@ -95,25 +87,6 @@ RandomGenerator *luax_checkrandomgenerator(lua_State *L, int idx) return luax_checktype(L, idx, "RandomGenerator", MATH_RANDOM_GENERATOR_T); } -int w_RandomGenerator_setSeed(lua_State *L) -{ - RandomGenerator *rng = luax_checkrandomgenerator(L, 1); - EXCEPT_GUARD(rng->setSeed(luax_checkrandomseed(L, 2));) - return 0; -} - -int w_RandomGenerator_getSeed(lua_State *L) -{ - RandomGenerator *rng = luax_checkrandomgenerator(L, 1); - - uint32 low = 0, high = 0; - rng->getSeed(low, high); - - lua_pushnumber(L, (lua_Number) low); - lua_pushnumber(L, (lua_Number) high); - return 2; -} - int w_RandomGenerator_random(lua_State *L) { RandomGenerator *rng = luax_checkrandomgenerator(L, 1); @@ -132,12 +105,44 @@ int w_RandomGenerator_randomNormal(lua_State *L) return 1; } +int w_RandomGenerator_setSeed(lua_State *L) +{ + RandomGenerator *rng = luax_checkrandomgenerator(L, 1); + EXCEPT_GUARD(rng->setSeed(luax_checkrandomseed(L, 2));) + return 0; +} + +int w_RandomGenerator_getSeed(lua_State *L) +{ + RandomGenerator *rng = luax_checkrandomgenerator(L, 1); + RandomGenerator::Seed s = rng->getSeed(); + lua_pushnumber(L, (lua_Number) s.b32.low); + lua_pushnumber(L, (lua_Number) s.b32.high); + return 2; +} + +int w_RandomGenerator_setState(lua_State *L) +{ + RandomGenerator *rng = luax_checkrandomgenerator(L, 1); + EXCEPT_GUARD(rng->setState(luax_checkstring(L, 2));) + return 0; +} + +int w_RandomGenerator_getState(lua_State *L) +{ + RandomGenerator *rng = luax_checkrandomgenerator(L, 1); + luax_pushstring(L, rng->getState()); + return 1; +} + static const luaL_Reg functions[] = { - { "setSeed", w_RandomGenerator_setSeed }, - { "getSeed", w_RandomGenerator_getSeed }, { "random", w_RandomGenerator_random }, { "randomNormal", w_RandomGenerator_randomNormal }, + { "setSeed", w_RandomGenerator_setSeed }, + { "getSeed", w_RandomGenerator_getSeed }, + { "setState", w_RandomGenerator_setState }, + { "getState", w_RandomGenerator_getState }, { 0, 0 } }; diff --git a/src/modules/math/wrap_RandomGenerator.h b/src/modules/math/wrap_RandomGenerator.h index 07e82894e..ad5201d17 100644 --- a/src/modules/math/wrap_RandomGenerator.h +++ b/src/modules/math/wrap_RandomGenerator.h @@ -36,10 +36,12 @@ RandomGenerator::Seed luax_checkrandomseed(lua_State *L, int idx); int luax_getrandom(lua_State *L, int startidx, double r); RandomGenerator *luax_checkrandomgenerator(lua_State *L, int idx); -int w_RandomGenerator_setSeed(lua_State *L); -int w_RandomGenerator_getSeed(lua_State *L); int w_RandomGenerator_random(lua_State *L); int w_RandomGenerator_randomNormal(lua_State *L); +int w_RandomGenerator_setSeed(lua_State *L); +int w_RandomGenerator_getSeed(lua_State *L); +int w_RandomGenerator_setState(lua_State *L); +int w_RandomGenerator_getState(lua_State *L); extern "C" int luaopen_randomgenerator(lua_State *L); } // math diff --git a/src/modules/mouse/Mouse.h b/src/modules/mouse/Mouse.h index 3a178fd4d..90d73d727 100644 --- a/src/modules/mouse/Mouse.h +++ b/src/modules/mouse/Mouse.h @@ -49,7 +49,7 @@ public: BUTTON_MAX_ENUM }; - virtual ~Mouse() {}; + virtual ~Mouse() {} virtual Cursor *newCursor(love::image::ImageData *data, int hotx, int hoty) = 0; virtual Cursor *getSystemCursor(Cursor::SystemCursor cursortype) = 0; diff --git a/src/modules/mouse/sdl/Mouse.cpp b/src/modules/mouse/sdl/Mouse.cpp index 94b187a7a..74e86291e 100644 --- a/src/modules/mouse/sdl/Mouse.cpp +++ b/src/modules/mouse/sdl/Mouse.cpp @@ -32,6 +32,39 @@ namespace mouse namespace sdl { +// SDL reports mouse coordinates in the window coordinate system in OS X, but +// we want them in pixel coordinates (may be different with high-DPI enabled.) +static void windowToPixelCoords(int *x, int *y) +{ + double scale = 1.0; + + love::window::Window *window = love::window::sdl::Window::getSingleton(); + if (window != nullptr) + scale = window->getPixelScale(); + + if (x != nullptr) + *x = int(double(*x) * scale); + + if (y != nullptr) + *y = int(double(*y) * scale); +} + +// And vice versa for setting mouse coordinates. +static void pixelToWindowCoords(int *x, int *y) +{ + double scale = 1.0; + + love::window::Window *window = love::window::sdl::Window::getSingleton(); + if (window != nullptr) + scale = window->getPixelScale(); + + if (x != nullptr) + *x = int(double(*x) / scale); + + if (y != nullptr) + *y = int(double(*y) / scale); +} + const char *Mouse::getName() const { return "love.mouse.sdl"; @@ -98,30 +131,40 @@ love::mouse::Cursor *Mouse::getCursor() const int Mouse::getX() const { int x; - SDL_GetMouseState(&x, 0); + SDL_GetMouseState(&x, nullptr); + windowToPixelCoords(&x, nullptr); + return x; } int Mouse::getY() const { int y; - SDL_GetMouseState(0, &y); + SDL_GetMouseState(nullptr, &y); + windowToPixelCoords(nullptr, &y); + return y; } void Mouse::getPosition(int &x, int &y) const { - SDL_GetMouseState(&x, &y); + int mx, my; + SDL_GetMouseState(&mx, &my); + windowToPixelCoords(&mx, &my); + + x = mx; + y = my; } void Mouse::setPosition(int x, int y) { love::window::Window *window = love::window::sdl::Window::getSingleton(); - SDL_Window *handle = NULL; + SDL_Window *handle = nullptr; if (window) handle = (SDL_Window *) window->getHandle(); + pixelToWindowCoords(&x, &y); SDL_WarpMouseInWindow(handle, x, y); } diff --git a/src/modules/mouse/wrap_Cursor.cpp b/src/modules/mouse/wrap_Cursor.cpp index 9e0702937..bb245cfba 100644 --- a/src/modules/mouse/wrap_Cursor.cpp +++ b/src/modules/mouse/wrap_Cursor.cpp @@ -51,7 +51,7 @@ int w_Cursor_getType(lua_State *L) lua_pushstring(L, typestr); return 1; -}; +} static const luaL_Reg functions[] = { @@ -65,4 +65,4 @@ extern "C" int luaopen_cursor(lua_State *L) } } // mouse -} // love \ No newline at end of file +} // love diff --git a/src/modules/physics/box2d/Body.cpp b/src/modules/physics/box2d/Body.cpp index 72565658c..d43830d97 100644 --- a/src/modules/physics/box2d/Body.cpp +++ b/src/modules/physics/box2d/Body.cpp @@ -37,10 +37,14 @@ namespace box2d Body::Body(World *world, b2Vec2 p, Body::Type type) : world(world) + , udata(nullptr) { + udata = new bodyudata(); + udata->ref = nullptr; world->retain(); b2BodyDef def; def.position = Physics::scaleDown(p); + def.userData = (void *) udata; body = world->world->CreateBody(&def); // Box2D body holds a reference to the love Body. this->retain(); @@ -50,7 +54,9 @@ Body::Body(World *world, b2Vec2 p, Body::Type type) Body::Body(b2Body *b) : body(b) + , udata(nullptr) { + udata = (bodyudata *) b->GetUserData(); world = (World *)Memoizer::find(b->GetWorld()); world->retain(); // Box2D body holds a reference to the love Body. @@ -60,8 +66,10 @@ Body::Body(b2Body *b) Body::~Body() { + if (udata != nullptr) + delete udata->ref; + delete udata; world->release(); - body = 0; } float Body::getX() @@ -469,6 +477,40 @@ void Body::destroy() this->release(); } +int Body::setUserData(lua_State *L) +{ + love::luax_assert_argc(L, 1, 1); + + if (udata == nullptr) + { + udata = new bodyudata(); + body->SetUserData((void *) udata); + } + + if (udata->ref != nullptr) + { + // We set the Reference's lua_State to this one before deleting it, so + // it unrefs using the current lua_State's stack. This is necessary + // if setUserData is called in a coroutine. + udata->ref->setL(L); + delete udata->ref; + } + + udata->ref = new Reference(L); + + return 0; +} + +int Body::getUserData(lua_State *L) +{ + if (udata != nullptr && udata->ref != nullptr) + udata->ref->push(L); + else + lua_pushnil(L); + + return 1; +} + } // box2d } // physics } // love diff --git a/src/modules/physics/box2d/Body.h b/src/modules/physics/box2d/Body.h index 30cd1229b..cd4faf11b 100644 --- a/src/modules/physics/box2d/Body.h +++ b/src/modules/physics/box2d/Body.h @@ -41,6 +41,16 @@ class World; class Shape; class Fixture; +/** + * This struct is stored in a void pointer in the Box2D Body class. For now, all + * we need is a Lua reference to arbitrary data, but we might need more later. + **/ +struct bodyudata +{ + // Reference to arbitrary data. + Reference *ref; +}; + /** * A Body is an entity which has position and orientation * in world space. A Body does have collision geometry @@ -388,6 +398,18 @@ public: **/ void destroy(); + /** + * This function stores an in-C reference to + * arbitrary Lua data in the Box2D Body object. + **/ + int setUserData(lua_State *L); + + /** + * Gets the data set with setData. If no + * data is set, nil is returned. + **/ + int getUserData(lua_State *L); + private: /** @@ -408,7 +430,10 @@ private: // This ensures that a World only can be destroyed // once all bodies have been destroyed too. World *world; -}; + + bodyudata *udata; + +}; // Body } // box2d } // physics diff --git a/src/modules/physics/box2d/Fixture.cpp b/src/modules/physics/box2d/Fixture.cpp index f133bac62..9b80ef222 100644 --- a/src/modules/physics/box2d/Fixture.cpp +++ b/src/modules/physics/box2d/Fixture.cpp @@ -39,10 +39,10 @@ namespace box2d Fixture::Fixture(Body *body, Shape *shape, float density) : body(body) - , fixture(NULL) + , fixture(nullptr) { data = new fixtureudata(); - data->ref = 0; + data->ref = nullptr; b2FixtureDef def; def.shape = shape->shape; def.userData = (void *)data; @@ -65,13 +65,10 @@ Fixture::Fixture(b2Fixture *f) Fixture::~Fixture() { - if (data->ref != 0) + if (data != nullptr) delete data->ref; delete data; - data = NULL; - - fixture = NULL; } Shape::Type Fixture::getType() const @@ -127,14 +124,14 @@ Body *Fixture::getBody() const Shape *Fixture::getShape() const { if (!fixture->GetShape()) - return NULL; + return nullptr; return new Shape(fixture->GetShape(), false); } bool Fixture::isValid() const { - return fixture != 0; + return fixture != nullptr; } void Fixture::setFilterData(int *v) @@ -230,21 +227,24 @@ int Fixture::setUserData(lua_State *L) { love::luax_assert_argc(L, 1, 1); - if (data->ref != 0) + if (data->ref != nullptr) { + // We set the Reference's lua_State to this one before deleting it, so + // it unrefs using the current lua_State's stack. This is necessary + // if setUserData is called in a coroutine. + data->ref->setL(L); delete data->ref; - data->ref = 0; } data->ref = new Reference(L); + return 0; } int Fixture::getUserData(lua_State *L) { - love::luax_assert_argc(L, 0, 0); - if (data->ref != 0) - data->ref->push(); + if (data->ref != nullptr) + data->ref->push(L); else lua_pushnil(L); @@ -311,10 +311,10 @@ void Fixture::destroy(bool implicit) return; } - if (!implicit && fixture != 0) + if (!implicit && fixture != nullptr) body->body->DestroyFixture(fixture); Memoizer::remove(fixture); - fixture = NULL; + fixture = nullptr; // Box2D fixture destroyed. Release its reference to the love Fixture. this->release(); diff --git a/src/modules/physics/box2d/Fixture.h b/src/modules/physics/box2d/Fixture.h index e7f18f942..5564406d4 100644 --- a/src/modules/physics/box2d/Fixture.h +++ b/src/modules/physics/box2d/Fixture.h @@ -123,9 +123,6 @@ public: /** * This function stores an in-C reference to * arbitrary Lua data in the Box2D Fixture object. - * - * The data set here will be passed to the collision - * handler when collisions occur. **/ int setUserData(lua_State *L); diff --git a/src/modules/physics/box2d/Physics.cpp b/src/modules/physics/box2d/Physics.cpp index e5c6a03a3..a76aca20f 100644 --- a/src/modules/physics/box2d/Physics.cpp +++ b/src/modules/physics/box2d/Physics.cpp @@ -93,8 +93,9 @@ EdgeShape *Physics::newEdgeShape(float x1, float y1, float x2, float y2) int Physics::newPolygonShape(lua_State *L) { int argc = lua_gettop(L); - if (argc%2 != 0) - return luaL_error(L, "Number of vertices must be a multiple of two."); + if (argc % 2 != 0) + return luaL_error(L, "Number of vertex components must be a multiple of two."); + // 3 to 8 (b2_maxPolygonVertices) vertices int vcount = argc / 2; if (vcount < 3) diff --git a/src/modules/physics/box2d/wrap_Body.cpp b/src/modules/physics/box2d/wrap_Body.cpp index 419d1fb29..61eb7529d 100644 --- a/src/modules/physics/box2d/wrap_Body.cpp +++ b/src/modules/physics/box2d/wrap_Body.cpp @@ -538,6 +538,20 @@ int w_Body_destroy(lua_State *L) return 0; } +int w_Body_setUserData(lua_State *L) +{ + Body *t = luax_checkbody(L, 1); + lua_remove(L, 1); + return t->setUserData(L); +} + +int w_Body_getUserData(lua_State *L) +{ + Body *t = luax_checkbody(L, 1); + lua_remove(L, 1); + return t->getUserData(L); +} + static const luaL_Reg functions[] = { { "getX", w_Body_getX }, @@ -592,6 +606,8 @@ static const luaL_Reg functions[] = { "isFixedRotation", w_Body_isFixedRotation }, { "getFixtureList", w_Body_getFixtureList }, { "destroy", w_Body_destroy }, + { "setUserData", w_Body_setUserData }, + { "getUserData", w_Body_getUserData }, { 0, 0 } }; diff --git a/src/modules/physics/box2d/wrap_Body.h b/src/modules/physics/box2d/wrap_Body.h index f06348809..2a41556f0 100644 --- a/src/modules/physics/box2d/wrap_Body.h +++ b/src/modules/physics/box2d/wrap_Body.h @@ -85,6 +85,8 @@ int w_Body_setFixedRotation(lua_State *L); int w_Body_isFixedRotation(lua_State *L); int w_Body_getFixtureList(lua_State *L); int w_Body_destroy(lua_State *L); +int w_Body_setUserData(lua_State *L); +int w_Body_getUserData(lua_State *L); extern "C" int luaopen_body(lua_State *L); } // box2d diff --git a/src/modules/physics/box2d/wrap_MotorJoint.cpp b/src/modules/physics/box2d/wrap_MotorJoint.cpp index 7286d3504..b264e17a9 100644 --- a/src/modules/physics/box2d/wrap_MotorJoint.cpp +++ b/src/modules/physics/box2d/wrap_MotorJoint.cpp @@ -140,4 +140,4 @@ extern "C" int luaopen_motorjoint(lua_State *L) } // box2d } // phyics -} // love \ No newline at end of file +} // love diff --git a/src/modules/physics/box2d/wrap_World.cpp b/src/modules/physics/box2d/wrap_World.cpp index b4739f24c..fb45ead88 100644 --- a/src/modules/physics/box2d/wrap_World.cpp +++ b/src/modules/physics/box2d/wrap_World.cpp @@ -143,21 +143,27 @@ int w_World_getBodyList(lua_State *L) { World *t = luax_checkworld(L, 1); lua_remove(L, 1); - return t->getBodyList(L); + int ret = 0; + EXCEPT_GUARD(ret = t->getBodyList(L);) + return ret; } int w_World_getJointList(lua_State *L) { World *t = luax_checkworld(L, 1); lua_remove(L, 1); - return t->getJointList(L); + int ret = 0; + EXCEPT_GUARD(ret = t->getJointList(L);) + return ret; } int w_World_getContactList(lua_State *L) { World *t = luax_checkworld(L, 1); lua_remove(L, 1); - return t->getContactList(L); + int ret = 0; + EXCEPT_GUARD(ret = t->getContactList(L);) + return ret; } int w_World_queryBoundingBox(lua_State *L) diff --git a/src/modules/sound/Decoder.h b/src/modules/sound/Decoder.h index 3701c1e8f..50c522c1b 100644 --- a/src/modules/sound/Decoder.h +++ b/src/modules/sound/Decoder.h @@ -67,7 +67,7 @@ public: /** * Destructor. Should free internal buffer. **/ - virtual ~Decoder() {}; + virtual ~Decoder() {} /** * Decodes the next chunk of the music stream, this will usually be diff --git a/src/modules/sound/SoundData.cpp b/src/modules/sound/SoundData.cpp index edc9720a6..cbb9383c1 100644 --- a/src/modules/sound/SoundData.cpp +++ b/src/modules/sound/SoundData.cpp @@ -52,7 +52,7 @@ SoundData::SoundData(Decoder *decoder) { while (bufferSize < (size_t) size + decoded) bufferSize <<= 1; - data = (char *)realloc(data, bufferSize); + data = (int8 *) realloc(data, bufferSize); } if (!data) @@ -76,7 +76,7 @@ SoundData::SoundData(Decoder *decoder) // Shrink buffer if necessary. if (data && bufferSize > (size_t) size) - data = (char *) realloc(data, size); + data = (int8 *) realloc(data, size); channels = decoder->getChannels(); bitDepth = decoder->getBitDepth(); @@ -139,7 +139,7 @@ void SoundData::load(int samples, int sampleRate, int bitDepth, int channels, vo if (realsize > INT_MAX) throw love::Exception("Data is too big!"); - data = (char *)malloc(size); + data = (int8 *) malloc(size); if (!data) throw love::Exception("Not enough memory."); @@ -190,14 +190,12 @@ void SoundData::setSample(int i, float sample) if (bitDepth == 16) { - short *s = (short *)data; - s[i] = (short)(sample*(float)SHRT_MAX); - return; + int16 *s = (int16 *) data; + s[i] = (int16) (sample * (float) LOVE_INT16_MAX); } else { - data[i] = (char)(sample*(float)CHAR_MAX); - return; + data[i] = (int8) (sample * (float) LOVE_INT8_MAX); } } @@ -209,12 +207,12 @@ float SoundData::getSample(int i) const if (bitDepth == 16) { - short *s = (short *)data; - return (float)s[i]/(float)SHRT_MAX; + int16 *s = (int16 *) data; + return (float) s[i] / (float) LOVE_INT16_MAX; } else { - return (float)data[i]/(float)CHAR_MAX; + return (float) data[i] / (float) LOVE_INT8_MAX; } } diff --git a/src/modules/sound/SoundData.h b/src/modules/sound/SoundData.h index d6b7528db..e12e55ba0 100644 --- a/src/modules/sound/SoundData.h +++ b/src/modules/sound/SoundData.h @@ -23,7 +23,7 @@ // LOVE #include "filesystem/File.h" - +#include "common/int.h" #include "Decoder.h" namespace love @@ -59,12 +59,13 @@ private: void load(int samples, int sampleRate, int bitDepth, int channels, void *newData = 0); - char *data; + int8 *data; int size; int sampleRate; int bitDepth; int channels; + }; // SoundData } // sound diff --git a/src/modules/sound/lullaby/FLACDecoder.cpp b/src/modules/sound/lullaby/FLACDecoder.cpp index 1fcd8245a..6762d9f7b 100644 --- a/src/modules/sound/lullaby/FLACDecoder.cpp +++ b/src/modules/sound/lullaby/FLACDecoder.cpp @@ -183,4 +183,4 @@ void FLACDecoder::error_callback(FLAC__StreamDecoderErrorStatus status) } // sound } // love -#endif // 0 \ No newline at end of file +#endif // 0 diff --git a/src/modules/sound/lullaby/GmeDecoder.cpp b/src/modules/sound/lullaby/GmeDecoder.cpp index aa48fc283..98d364829 100644 --- a/src/modules/sound/lullaby/GmeDecoder.cpp +++ b/src/modules/sound/lullaby/GmeDecoder.cpp @@ -146,4 +146,4 @@ int GmeDecoder::getBitDepth() const } // sound } // love -#endif // LOVE_SUPPORT_GME \ No newline at end of file +#endif // LOVE_SUPPORT_GME diff --git a/src/modules/system/wrap_System.cpp b/src/modules/system/wrap_System.cpp index c4df69f07..c17c29c3c 100644 --- a/src/modules/system/wrap_System.cpp +++ b/src/modules/system/wrap_System.cpp @@ -109,4 +109,4 @@ extern "C" int luaopen_love_system(lua_State *L) } } // system -} // love \ No newline at end of file +} // love diff --git a/src/modules/window/Window.cpp b/src/modules/window/Window.cpp index ac9c45a4e..1e3216c8a 100644 --- a/src/modules/window/Window.cpp +++ b/src/modules/window/Window.cpp @@ -26,19 +26,19 @@ namespace love namespace window { -Window *Window::singleton = NULL; +Window *Window::singleton = nullptr; Window::~Window() { if (singleton == this) - singleton = NULL; + singleton = nullptr; } void Window::swapBuffers() { } -WindowAttributes::WindowAttributes() +WindowSettings::WindowSettings() : fullscreen(false) , fstype(Window::FULLSCREEN_TYPE_NORMAL) , vsync(true) @@ -49,6 +49,8 @@ WindowAttributes::WindowAttributes() , borderless(false) , centered(true) , display(0) + , highdpi(false) + , sRGB(false) { } @@ -62,31 +64,33 @@ bool Window::getConstant(Window::FullscreenType in, const char *&out) return fullscreenTypes.find(in, out); } -bool Window::getConstant(const char *in, Window::Attribute &out) +bool Window::getConstant(const char *in, Window::Setting &out) { - return attributes.find(in, out); + return settings.find(in, out); } -bool Window::getConstant(Window::Attribute in, const char *&out) +bool Window::getConstant(Window::Setting in, const char *&out) { - return attributes.find(in, out); + return settings.find(in, out); } -StringMap::Entry Window::attributeEntries[] = +StringMap::Entry Window::settingEntries[] = { - {"fullscreen", ATTRIB_FULLSCREEN}, - {"fullscreentype", ATTRIB_FULLSCREEN_TYPE}, - {"vsync", ATTRIB_VSYNC}, - {"fsaa", ATTRIB_FSAA}, - {"resizable", ATTRIB_RESIZABLE}, - {"minwidth", ATTRIB_MIN_WIDTH}, - {"minheight", ATTRIB_MIN_HEIGHT}, - {"borderless", ATTRIB_BORDERLESS}, - {"centered", ATTRIB_CENTERED}, - {"display", ATTRIB_DISPLAY} + {"fullscreen", SETTING_FULLSCREEN}, + {"fullscreentype", SETTING_FULLSCREEN_TYPE}, + {"vsync", SETTING_VSYNC}, + {"fsaa", SETTING_FSAA}, + {"resizable", SETTING_RESIZABLE}, + {"minwidth", SETTING_MIN_WIDTH}, + {"minheight", SETTING_MIN_HEIGHT}, + {"borderless", SETTING_BORDERLESS}, + {"centered", SETTING_CENTERED}, + {"display", SETTING_DISPLAY}, + {"highdpi", SETTING_HIGHDPI}, + {"srgb", SETTING_SRGB}, }; -StringMap Window::attributes(Window::attributeEntries, sizeof(Window::attributeEntries)); +StringMap Window::settings(Window::settingEntries, sizeof(Window::settingEntries)); StringMap::Entry Window::fullscreenTypeEntries[] = { diff --git a/src/modules/window/Window.h b/src/modules/window/Window.h index 0f84c7c51..b52900c66 100644 --- a/src/modules/window/Window.h +++ b/src/modules/window/Window.h @@ -37,26 +37,28 @@ namespace window // Forward-declared so it can be used in the class methods. We can't define the // whole thing here because it uses the Window::Type enum. -struct WindowAttributes; +struct WindowSettings; class Window : public Module { public: - // Types of window attributes. - enum Attribute + // Different window settings. + enum Setting { - ATTRIB_FULLSCREEN, - ATTRIB_FULLSCREEN_TYPE, - ATTRIB_VSYNC, - ATTRIB_FSAA, - ATTRIB_RESIZABLE, - ATTRIB_MIN_WIDTH, - ATTRIB_MIN_HEIGHT, - ATTRIB_BORDERLESS, - ATTRIB_CENTERED, - ATTRIB_DISPLAY, - ATTRIB_MAX_ENUM + SETTING_FULLSCREEN, + SETTING_FULLSCREEN_TYPE, + SETTING_VSYNC, + SETTING_FSAA, + SETTING_RESIZABLE, + SETTING_MIN_WIDTH, + SETTING_MIN_HEIGHT, + SETTING_BORDERLESS, + SETTING_CENTERED, + SETTING_DISPLAY, + SETTING_HIGHDPI, + SETTING_SRGB, + SETTING_MAX_ENUM }; enum FullscreenType @@ -74,8 +76,8 @@ public: virtual ~Window(); - virtual bool setWindow(int width = 800, int height = 600, WindowAttributes *attribs = 0) = 0; - virtual void getWindow(int &width, int &height, WindowAttributes &attribs) = 0; + virtual bool setWindow(int width = 800, int height = 600, WindowSettings *settings = nullptr) = 0; + virtual void getWindow(int &width, int &height, WindowSettings &settings) = 0; virtual bool setFullscreen(bool fullscreen, FullscreenType fstype) = 0; virtual bool setFullscreen(bool fullscreen) = 0; @@ -113,14 +115,16 @@ public: virtual void setMouseGrab(bool grab) = 0; virtual bool isMouseGrabbed() const = 0; + virtual double getPixelScale() const = 0; + virtual const void *getHandle() const = 0; //virtual static Window *createSingleton() = 0; //virtual static Window *getSingleton() = 0; // No virtual statics, of course, but you are supposed to implement these statics. - static bool getConstant(const char *in, Attribute &out); - static bool getConstant(Attribute in, const char *&out); + static bool getConstant(const char *in, Setting &out); + static bool getConstant(Setting in, const char *&out); static bool getConstant(const char *in, FullscreenType &out); static bool getConstant(FullscreenType in, const char *&out); @@ -131,17 +135,18 @@ protected: private: - static StringMap::Entry attributeEntries[]; - static StringMap attributes; + static StringMap::Entry settingEntries[]; + static StringMap settings; static StringMap::Entry fullscreenTypeEntries[]; static StringMap fullscreenTypes; }; // Window -struct WindowAttributes +struct WindowSettings { - WindowAttributes(); + WindowSettings(); + bool fullscreen; // = false Window::FullscreenType fstype; // = FULLSCREEN_TYPE_NORMAL bool vsync; // = true @@ -152,7 +157,10 @@ struct WindowAttributes bool borderless; // = false bool centered; // = true int display; // = 0 -}; // WindowFlags + bool highdpi; // false + bool sRGB; // false + +}; // WindowSettings } // window } // love diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index 400706687..af8a0940e 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -63,26 +63,26 @@ Window::~Window() Window::_currentMode::_currentMode() : width(800) , height(600) - , attribs() + , settings() , icon(0) { } -bool Window::setWindow(int width, int height, WindowAttributes *attribs) +bool Window::setWindow(int width, int height, WindowSettings *settings) { graphics::Graphics *gfx = (graphics::Graphics *) Module::findInstance("love.graphics."); - if (gfx) + if (gfx != nullptr) gfx->unSetMode(); - WindowAttributes f; + WindowSettings f; - if (attribs) - f = *attribs; + if (settings) + f = *settings; f.minwidth = std::max(f.minwidth, 1); f.minheight = std::max(f.minheight, 1); - f.display = std::min(std::max(f.display, 0), getDisplayCount()); + f.display = std::min(std::max(f.display, 0), getDisplayCount() - 1); // Use the desktop resolution if a width or height of 0 is specified. if (width == 0 || height == 0) @@ -116,15 +116,28 @@ bool Window::setWindow(int width, int height, WindowAttributes *attribs) if (f.borderless) sdlflags |= SDL_WINDOW_BORDERLESS; +#if SDL_VERSION_ATLEAST(2,0,1) + if (f.highdpi) + sdlflags |= SDL_WINDOW_ALLOW_HIGHDPI; +#endif + // Destroy and recreate the window if the dimensions or flags have changed. if (window) { int curdisplay = SDL_GetWindowDisplayIndex(window); Uint32 wflags = SDL_GetWindowFlags(window); - wflags &= (SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_RESIZABLE | SDL_WINDOW_BORDERLESS); + + Uint32 testflags = SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN_DESKTOP + | SDL_WINDOW_RESIZABLE | SDL_WINDOW_BORDERLESS; + +#if SDL_VERSION_ATLEAST(2,0,1) + testflags |= SDL_WINDOW_ALLOW_HIGHDPI; +#endif + + wflags &= testflags; if (sdlflags != wflags || width != curMode.width || height != curMode.height - || f.display != curdisplay || f.fsaa != curMode.attribs.fsaa) + || f.display != curdisplay || f.fsaa != curMode.settings.fsaa) { SDL_DestroyWindow(window); window = 0; @@ -141,7 +154,7 @@ bool Window::setWindow(int width, int height, WindowAttributes *attribs) if (!window) { // In Windows and Linux, some GL attributes are set on window creation. - setWindowGLAttributes(f.fsaa); + setWindowGLAttributes(f.fsaa, f.sRGB); const char *title = windowTitle.c_str(); int pos = f.centered ? centeredpos : uncenteredpos; @@ -177,15 +190,24 @@ bool Window::setWindow(int width, int height, WindowAttributes *attribs) SDL_RaiseWindow(window); - if (!setContext(f.fsaa, f.vsync)) + if (!setContext(f.fsaa, f.vsync, f.sRGB)) return false; created = true; - updateAttributes(f); + updateSettings(f); - if (gfx) - gfx->setMode(curMode.width, curMode.height); + if (gfx != nullptr) + { + int width = curMode.width; + int height = curMode.height; + +#if SDL_VERSION_ATLEAST(2,0,1) + SDL_GL_GetDrawableSize(window, &width, &height); +#endif + + gfx->setMode(width, height, curMode.settings.sRGB); + } // Make sure the mouse keeps its previous grab setting. setMouseGrab(mouseGrabbed); @@ -204,7 +226,7 @@ bool Window::onWindowResize(int width, int height) return true; } -bool Window::setContext(int fsaa, bool vsync) +bool Window::setContext(int fsaa, bool vsync, bool sRGB) { // We would normally only need to recreate the context if FSAA changes or // SDL_GL_MakeCurrent is unsuccessful, but in Windows MakeCurrent can @@ -216,7 +238,7 @@ bool Window::setContext(int fsaa, bool vsync) } // Make sure the proper attributes are set. - setWindowGLAttributes(fsaa); + setWindowGLAttributes(fsaa, sRGB); context = SDL_GL_CreateContext(window); @@ -228,6 +250,18 @@ bool Window::setContext(int fsaa, bool vsync) context = SDL_GL_CreateContext(window); } + if (!context) + { + int flags = 0; + SDL_GL_GetAttribute(SDL_GL_CONTEXT_FLAGS, &flags); + if (flags & SDL_GL_CONTEXT_DEBUG_FLAG) + { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0); + context = SDL_GL_CreateContext(window); + } + } + if (!context) { std::cerr << "Could not set video mode: " << SDL_GetError() << std::endl; @@ -250,13 +284,13 @@ bool Window::setContext(int fsaa, bool vsync) fsaa = (buffers > 0) ? samples : 0; } - curMode.attribs.fsaa = fsaa; - curMode.attribs.vsync = SDL_GL_GetSwapInterval() != 0; + curMode.settings.fsaa = fsaa; + curMode.settings.vsync = SDL_GL_GetSwapInterval() != 0; return true; } -void Window::setWindowGLAttributes(int fsaa) const +void Window::setWindowGLAttributes(int fsaa, bool /* sRGB */) const { // Set GL window attributes. SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); @@ -269,9 +303,31 @@ void Window::setWindowGLAttributes(int fsaa) const // FSAA. SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (fsaa > 0) ? 1 : 0); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, (fsaa > 0) ? fsaa : 0); + + /* FIXME: Enable this code but make sure to try to re-create the window and + * context with this disabled, if creation fails with it enabled. + * We can leave this out for now because in practice the framebuffer will + * already be sRGB-capable (on desktops at least.) +#if SDL_VERSION_ATLEAST(2,0,1) + SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, sRGB ? 1 : 0); +#endif + */ + + // Do we want a debug context? + const char *debugenv = SDL_GetHint("LOVE_GRAPHICS_DEBUG"); + if (debugenv && *debugenv == '1') + { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); + } + else + { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0); + } } -void Window::updateAttributes(const WindowAttributes &newattribs) +void Window::updateSettings(const WindowSettings &newsettings) { Uint32 wflags = SDL_GetWindowFlags(window); @@ -280,54 +336,59 @@ void Window::updateAttributes(const WindowAttributes &newattribs) if ((wflags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) { - curMode.attribs.fullscreen = true; - curMode.attribs.fstype = FULLSCREEN_TYPE_DESKTOP; + curMode.settings.fullscreen = true; + curMode.settings.fstype = FULLSCREEN_TYPE_DESKTOP; } else if ((wflags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN) { - curMode.attribs.fullscreen = true; - curMode.attribs.fstype = FULLSCREEN_TYPE_NORMAL; + curMode.settings.fullscreen = true; + curMode.settings.fstype = FULLSCREEN_TYPE_NORMAL; } else { - curMode.attribs.fullscreen = false; - curMode.attribs.fstype = newattribs.fstype; + curMode.settings.fullscreen = false; + curMode.settings.fstype = newsettings.fstype; } // The min width/height is set to 0 internally in SDL when in fullscreen. - if (curMode.attribs.fullscreen) + if (curMode.settings.fullscreen) { - curMode.attribs.minwidth = newattribs.minwidth; - curMode.attribs.minheight = newattribs.minheight; + curMode.settings.minwidth = newsettings.minwidth; + curMode.settings.minheight = newsettings.minheight; } else - SDL_GetWindowMinimumSize(window, &curMode.attribs.minwidth, &curMode.attribs.minheight); + SDL_GetWindowMinimumSize(window, &curMode.settings.minwidth, &curMode.settings.minheight); - curMode.attribs.resizable = (wflags & SDL_WINDOW_RESIZABLE) != 0; - curMode.attribs.borderless = (wflags & SDL_WINDOW_BORDERLESS) != 0; - curMode.attribs.centered = newattribs.centered; - curMode.attribs.display = std::max(SDL_GetWindowDisplayIndex(window), 0); + curMode.settings.resizable = (wflags & SDL_WINDOW_RESIZABLE) != 0; + curMode.settings.borderless = (wflags & SDL_WINDOW_BORDERLESS) != 0; + curMode.settings.centered = newsettings.centered; + curMode.settings.display = std::max(SDL_GetWindowDisplayIndex(window), 0); + +#if SDL_VERSION_ATLEAST(2,0,1) + curMode.settings.highdpi = (wflags & SDL_WINDOW_ALLOW_HIGHDPI) != 0; +#else + curMode.settings.highdpi = false; +#endif // Only minimize on focus loss if the window is in exclusive-fullscreen - // mode (mimics behaviour of SDL 2.0.2+). - // In OS X we always disable this to prevent dock minimization weirdness. -#ifndef LOVE_MACOSX - if (curMode.attribs.fullscreen && curMode.attribs.fstype == FULLSCREEN_TYPE_NORMAL) + // mode. + if (curMode.settings.fullscreen && curMode.settings.fstype == FULLSCREEN_TYPE_NORMAL) SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "1"); else -#endif SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); + + curMode.settings.sRGB = newsettings.sRGB; } -void Window::getWindow(int &width, int &height, WindowAttributes &attribs) +void Window::getWindow(int &width, int &height, WindowSettings &settings) { - // Window position may be different from creation - update display index. + // The window might have been modified (moved, resized, etc.) by the user. if (window) - curMode.attribs.display = std::max(SDL_GetWindowDisplayIndex(window), 0); + updateSettings(curMode.settings); width = curMode.width; height = curMode.height; - attribs = curMode.attribs; + settings = curMode.settings; } bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype) @@ -335,9 +396,9 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype) if (!window) return false; - WindowAttributes newattribs = curMode.attribs; - newattribs.fullscreen = fullscreen; - newattribs.fstype = fstype; + WindowSettings newsettings = curMode.settings; + newsettings.fullscreen = fullscreen; + newsettings.fstype = fstype; Uint32 sdlflags = 0; @@ -361,12 +422,21 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype) if (SDL_SetWindowFullscreen(window, sdlflags) == 0) { SDL_GL_MakeCurrent(window, context); - updateAttributes(newattribs); + updateSettings(newsettings); // Update the viewport size now instead of waiting for event polling. graphics::Graphics *gfx = (graphics::Graphics *) Module::findInstance("love.graphics."); - if (gfx) - gfx->setViewportSize(curMode.width, curMode.height); + if (gfx != nullptr) + { + int width = curMode.width; + int height = curMode.height; + +#if SDL_VERSION_ATLEAST(2,0,1) + SDL_GL_GetDrawableSize(window, &width, &height); +#endif + + gfx->setViewportSize(width, height); + } return true; } @@ -376,7 +446,7 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype) bool Window::setFullscreen(bool fullscreen) { - return setFullscreen(fullscreen, curMode.attribs.fstype); + return setFullscreen(fullscreen, curMode.settings.fstype); } int Window::getDisplayCount() const @@ -559,6 +629,26 @@ bool Window::isMouseGrabbed() const return mouseGrabbed; } +double Window::getPixelScale() const +{ + double scale = 1.0; + +#if SDL_VERSION_ATLEAST(2,0,1) + if (window) + { + int wheight; + SDL_GetWindowSize(window, nullptr, &wheight); + + int dheight = wheight; + SDL_GL_GetDrawableSize(window, nullptr, &dheight); + + scale = (double) dheight / wheight; + } +#endif + + return scale; +} + const void *Window::getHandle() const { return window; diff --git a/src/modules/window/sdl/Window.h b/src/modules/window/sdl/Window.h index b6e7ca953..3af5d46c5 100644 --- a/src/modules/window/sdl/Window.h +++ b/src/modules/window/sdl/Window.h @@ -41,8 +41,8 @@ public: Window(); ~Window(); - bool setWindow(int width = 800, int height = 600, WindowAttributes *attribs = 0); - void getWindow(int &width, int &height, WindowAttributes &attribs); + bool setWindow(int width = 800, int height = 600, WindowSettings *settings = nullptr); + void getWindow(int &width, int &height, WindowSettings &settings); bool setFullscreen(bool fullscreen, FullscreenType fstype); bool setFullscreen(bool fullscreen); @@ -79,6 +79,8 @@ public: void setMouseGrab(bool grab); bool isMouseGrabbed() const; + double getPixelScale() const; + const void *getHandle() const; static love::window::Window *createSingleton(); @@ -88,11 +90,11 @@ public: private: - bool setContext(int fsaa, bool vsync); - void setWindowGLAttributes(int fsaa) const; + bool setContext(int fsaa, bool vsync, bool sRGB); + void setWindowGLAttributes(int fsaa, bool sRGB) const; - // Update the saved window attribs based on the window's actual state. - void updateAttributes(const WindowAttributes &newattribs); + // Update the saved window settings based on the window's actual state. + void updateSettings(const WindowSettings &newsettings); std::string windowTitle; @@ -102,7 +104,7 @@ private: int width; int height; - WindowAttributes attribs; + WindowSettings settings; love::image::ImageData *icon; } curMode; diff --git a/src/modules/window/wrap_Window.cpp b/src/modules/window/wrap_Window.cpp index 799c5e93d..42c14fa74 100644 --- a/src/modules/window/wrap_Window.cpp +++ b/src/modules/window/wrap_Window.cpp @@ -26,7 +26,7 @@ namespace love namespace window { -static Window *instance = 0; +static Window *instance = nullptr; int w_getDisplayCount(lua_State *L) { @@ -34,10 +34,10 @@ int w_getDisplayCount(lua_State *L) return 1; } -static const char *attribName(Window::Attribute attrib) +static const char *settingName(Window::Setting setting) { const char *name = nullptr; - Window::getConstant(attrib, name); + Window::getConstant(setting, name); return name; } @@ -62,91 +62,98 @@ int w_setMode(lua_State *L) return luax_typerror(L, -2, "string"); const char *key = luaL_checkstring(L, -2); - Window::Attribute attrib; + Window::Setting setting; - if (!Window::getConstant(key, attrib)) - return luaL_error(L, "Invalid window attribute: %s", key); + if (!Window::getConstant(key, setting)) + return luaL_error(L, "Invalid window setting: %s", key); lua_pop(L, 1); } - WindowAttributes attribs; + WindowSettings settings; - lua_getfield(L, 3, attribName(Window::ATTRIB_FULLSCREEN_TYPE)); + lua_getfield(L, 3, settingName(Window::SETTING_FULLSCREEN_TYPE)); if (!lua_isnoneornil(L, -1)) { const char *typestr = luaL_checkstring(L, -1); - - if (!Window::getConstant(typestr, attribs.fstype)) + if (!Window::getConstant(typestr, settings.fstype)) return luaL_error(L, "Invalid fullscreen type: %s", typestr); } else { // Default to "normal" fullscreen. - attribs.fstype = Window::FULLSCREEN_TYPE_NORMAL; + settings.fstype = Window::FULLSCREEN_TYPE_NORMAL; } lua_pop(L, 1); - attribs.fullscreen = luax_boolflag(L, 3, attribName(Window::ATTRIB_FULLSCREEN), false); - attribs.vsync = luax_boolflag(L, 3, attribName(Window::ATTRIB_VSYNC), true); - attribs.fsaa = luax_intflag(L, 3, attribName(Window::ATTRIB_FSAA), 0); - attribs.resizable = luax_boolflag(L, 3, attribName(Window::ATTRIB_RESIZABLE), false); - attribs.minwidth = luax_intflag(L, 3, attribName(Window::ATTRIB_MIN_WIDTH), 1); - attribs.minheight = luax_intflag(L, 3, attribName(Window::ATTRIB_MIN_HEIGHT), 1); - attribs.borderless = luax_boolflag(L, 3, attribName(Window::ATTRIB_BORDERLESS), false); - attribs.centered = luax_boolflag(L, 3, attribName(Window::ATTRIB_CENTERED), true); - attribs.display = luax_intflag(L, 3, attribName(Window::ATTRIB_DISPLAY), 1); + settings.fullscreen = luax_boolflag(L, 3, settingName(Window::SETTING_FULLSCREEN), false); + settings.vsync = luax_boolflag(L, 3, settingName(Window::SETTING_VSYNC), true); + settings.fsaa = luax_intflag(L, 3, settingName(Window::SETTING_FSAA), 0); + settings.resizable = luax_boolflag(L, 3, settingName(Window::SETTING_RESIZABLE), false); + settings.minwidth = luax_intflag(L, 3, settingName(Window::SETTING_MIN_WIDTH), 1); + settings.minheight = luax_intflag(L, 3, settingName(Window::SETTING_MIN_HEIGHT), 1); + settings.borderless = luax_boolflag(L, 3, settingName(Window::SETTING_BORDERLESS), false); + settings.centered = luax_boolflag(L, 3, settingName(Window::SETTING_CENTERED), true); + settings.display = luax_intflag(L, 3, settingName(Window::SETTING_DISPLAY), 1); + settings.highdpi = luax_boolflag(L, 3, settingName(Window::SETTING_HIGHDPI), false); + settings.sRGB = luax_boolflag(L, 3, settingName(Window::SETTING_SRGB), false); // Display index is 1-based in Lua and 0-based internally. - attribs.display--; + settings.display--; - EXCEPT_GUARD(luax_pushboolean(L, instance->setWindow(w, h, &attribs));) + EXCEPT_GUARD(luax_pushboolean(L, instance->setWindow(w, h, &settings));) return 1; } int w_getMode(lua_State *L) { int w, h; - WindowAttributes attribs; - instance->getWindow(w, h, attribs); + WindowSettings settings; + instance->getWindow(w, h, settings); lua_pushnumber(L, w); lua_pushnumber(L, h); lua_newtable(L); const char *fstypestr = "normal"; - Window::getConstant(attribs.fstype, fstypestr); + Window::getConstant(settings.fstype, fstypestr); lua_pushstring(L, fstypestr); - lua_setfield(L, -2, attribName(Window::ATTRIB_FULLSCREEN_TYPE)); + lua_setfield(L, -2, settingName(Window::SETTING_FULLSCREEN_TYPE)); - luax_pushboolean(L, attribs.fullscreen); - lua_setfield(L, -2, attribName(Window::ATTRIB_FULLSCREEN)); + luax_pushboolean(L, settings.fullscreen); + lua_setfield(L, -2, settingName(Window::SETTING_FULLSCREEN)); - luax_pushboolean(L, attribs.vsync); - lua_setfield(L, -2, attribName(Window::ATTRIB_VSYNC)); + luax_pushboolean(L, settings.vsync); + lua_setfield(L, -2, settingName(Window::SETTING_VSYNC)); - lua_pushinteger(L, attribs.fsaa); - lua_setfield(L, -2, attribName(Window::ATTRIB_FSAA)); + lua_pushinteger(L, settings.fsaa); + lua_setfield(L, -2, settingName(Window::SETTING_FSAA)); - luax_pushboolean(L, attribs.resizable); - lua_setfield(L, -2, attribName(Window::ATTRIB_RESIZABLE)); + luax_pushboolean(L, settings.resizable); + lua_setfield(L, -2, settingName(Window::SETTING_RESIZABLE)); - lua_pushinteger(L, attribs.minwidth); - lua_setfield(L, -2, attribName(Window::ATTRIB_MIN_WIDTH)); + lua_pushinteger(L, settings.minwidth); + lua_setfield(L, -2, settingName(Window::SETTING_MIN_WIDTH)); - lua_pushinteger(L, attribs.minheight); - lua_setfield(L, -2, attribName(Window::ATTRIB_MIN_HEIGHT)); + lua_pushinteger(L, settings.minheight); + lua_setfield(L, -2, settingName(Window::SETTING_MIN_HEIGHT)); - luax_pushboolean(L, attribs.borderless); - lua_setfield(L, -2, attribName(Window::ATTRIB_BORDERLESS)); + luax_pushboolean(L, settings.borderless); + lua_setfield(L, -2, settingName(Window::SETTING_BORDERLESS)); - luax_pushboolean(L, attribs.centered); - lua_setfield(L, -2, attribName(Window::ATTRIB_CENTERED)); + luax_pushboolean(L, settings.centered); + lua_setfield(L, -2, settingName(Window::SETTING_CENTERED)); // Display index is 0-based internally and 1-based in Lua. - lua_pushinteger(L, attribs.display + 1); - lua_setfield(L, -2, attribName(Window::ATTRIB_DISPLAY)); + lua_pushinteger(L, settings.display + 1); + lua_setfield(L, -2, settingName(Window::SETTING_DISPLAY)); + + luax_pushboolean(L, settings.highdpi); + lua_setfield(L, -2, settingName(Window::SETTING_HIGHDPI)); + + luax_pushboolean(L, settings.sRGB); + lua_setfield(L, -2, settingName(Window::SETTING_SRGB)); return 3; } @@ -202,14 +209,14 @@ int w_setFullscreen(lua_State *L) int w_getFullscreen(lua_State *L) { int w, h; - WindowAttributes attribs; - instance->getWindow(w, h, attribs); + WindowSettings settings; + instance->getWindow(w, h, settings); const char *typestr; - if (!Window::getConstant(attribs.fstype, typestr)) + if (!Window::getConstant(settings.fstype, typestr)) luaL_error(L, "Unknown fullscreen type."); - luax_pushboolean(L, attribs.fullscreen); + luax_pushboolean(L, settings.fullscreen); lua_pushstring(L, typestr); return 2; } @@ -300,6 +307,12 @@ int w_isVisible(lua_State *L) return 1; } +int w_getPixelScale(lua_State *L) +{ + lua_pushnumber(L, instance->getPixelScale()); + return 1; +} + static const luaL_Reg functions[] = { { "getDisplayCount", w_getDisplayCount }, @@ -320,6 +333,7 @@ static const luaL_Reg functions[] = { "hasFocus", w_hasFocus }, { "hasMouseFocus", w_hasMouseFocus }, { "isVisible", w_isVisible }, + { "getPixelScale", w_getPixelScale }, { 0, 0 } }; diff --git a/src/modules/window/wrap_Window.h b/src/modules/window/wrap_Window.h index 6bba9a574..760374c14 100644 --- a/src/modules/window/wrap_Window.h +++ b/src/modules/window/wrap_Window.h @@ -47,6 +47,7 @@ int w_getTitle(lua_State *L); int w_hasFocus(lua_State *L); int w_hasMouseFocus(lua_State *L); int w_isVisible(lua_State *L); +int w_getPixelScale(lua_State *L); extern "C" LOVE_EXPORT int luaopen_love_window(lua_State *L); } // window diff --git a/src/scripts/boot.lua b/src/scripts/boot.lua index a02e698b8..183b1725f 100644 --- a/src/scripts/boot.lua +++ b/src/scripts/boot.lua @@ -223,15 +223,14 @@ function love.createhandlers() end -local is_fused_game = false -local no_game_code = false - local function uridecode(s) return s:gsub("%%%x%x", function(str) return string.char(tonumber(str:sub(2), 16)) end) end +local no_game_code = false + -- This can't be overriden. function love.boot() @@ -248,35 +247,45 @@ function love.boot() -- Is this one of those fancy "fused" games? local can_has_game = pcall(love.filesystem.setSource, arg0) - is_fused_game = can_has_game + local is_fused_game = can_has_game if love.arg.options.fused.set then is_fused_game = true end + love.filesystem.setFused(is_fused_game) + + local identity = "" if not can_has_game and o.game.set and o.game.arg[1] then local nouri = o.game.arg[1] if nouri:sub(1, 7) == "file://" then nouri = uridecode(nouri:sub(8)) end local full_source = love.path.getfull(nouri) - local leaf = love.path.leaf(full_source) - leaf = leaf:gsub("^([%.]+)", "") -- strip leading "."'s - leaf = leaf:gsub("%.([^%.]+)$", "") -- strip extension - leaf = leaf:gsub("%.", "_") -- replace remaining "."'s with "_" - love.filesystem.setIdentity(leaf) can_has_game = pcall(love.filesystem.setSource, full_source) + + -- Use the name of the source .love as the identity for now. + identity = love.path.leaf(full_source) + else + -- Use the name of the exe as the identity for now. + identity = love.path.leaf(arg0) end + identity = identity:gsub("^([%.]+)", "") -- strip leading "."'s + identity = identity:gsub("%.([^%.]+)$", "") -- strip extension + identity = identity:gsub("%.", "_") -- replace remaining "."'s with "_" + identity = #identity > 0 and identity or "lovegame" + + -- When conf.lua is initially loaded, the main source should be checked + -- before the save directory (the identity should be appended.) + pcall(love.filesystem.setIdentity, identity, true) + if can_has_game and not (love.filesystem.exists("main.lua") or love.filesystem.exists("conf.lua")) then no_game_code = true end - love.filesystem.setFused(is_fused_game) - if not can_has_game then love.nogame() end - end function love.init() @@ -300,6 +309,8 @@ function love.init() borderless = false, resizable = false, centered = true, + highdpi = false, + srgb = false, }, modules = { event = true, @@ -342,6 +353,11 @@ function love.init() c.console = true end + -- Console hack + if c.console and love._openConsole then + love._openConsole() + end + -- Gets desired modules. for k,v in ipairs{ "thread", @@ -369,11 +385,6 @@ function love.init() love.createhandlers() end - -- Console hack - if c.console and love._openConsole then - love._openConsole() - end - -- Setup window here. if c.window and c.modules.window then assert(love.window.setMode(c.window.width, c.window.height, @@ -388,6 +399,8 @@ function love.init() borderless = c.window.borderless, centered = c.window.centered, display = c.window.display, + highdpi = c.window.highdpi, + srgb = c.window.srgb, }), "Could not set window mode") love.window.setTitle(c.window.title or c.title) if c.window.icon then @@ -1373,8 +1386,9 @@ function love.nogame() local ox = rain.ox local oy = rain.oy - local batch_w = 2 * math.ceil(love.graphics.getWidth() / sx) + 2 - local batch_h = 2 * math.ceil(love.graphics.getHeight() / sy) + 2 + local m = 1 / love.window.getPixelScale() + local batch_w = 2 * math.ceil(m * love.graphics.getWidth() / sx) + 2 + local batch_h = 2 * math.ceil(m * love.graphics.getHeight() / sy) + 2 batch:clear() @@ -1409,8 +1423,9 @@ function love.nogame() g_time = g_time + dt / 2 local int, frac = math.modf(g_time) update_rain(frac) - inspector.x = love.graphics.getWidth() * 0.45 - inspector.y = love.graphics.getHeight() * 0.55 + local scale = love.window.getPixelScale() + inspector.x = love.graphics.getWidth() * 0.45 / scale + inspector.y = love.graphics.getHeight() * 0.55 / scale end local function draw_grid() @@ -1481,8 +1496,13 @@ function love.nogame() function love.draw() love.graphics.setColor(255, 255, 255) + love.graphics.push() + love.graphics.scale(love.window.getPixelScale()) + draw_grid() draw_inspector() + + love.graphics.pop() end function love.keyreleased(key) @@ -1498,6 +1518,7 @@ function love.nogame() t.modules.physics = false t.modules.joystick = false t.window.resizable = true + t.window.highdpi = true end end diff --git a/src/scripts/boot.lua.h b/src/scripts/boot.lua.h index 24349aacc..5e6f539ad 100644 --- a/src/scripts/boot.lua.h +++ b/src/scripts/boot.lua.h @@ -27,7 +27,7 @@ const unsigned char boot_lua[] = 0x2d, 0x2d, 0x5b, 0x5b, 0x0a, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, - 0x2d, 0x32, 0x30, 0x31, 0x33, 0x20, 0x4c, 0x4f, 0x56, 0x45, 0x20, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, + 0x2d, 0x32, 0x30, 0x31, 0x34, 0x20, 0x4c, 0x4f, 0x56, 0x45, 0x20, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x54, 0x65, 0x61, 0x6d, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x27, 0x61, 0x73, 0x2d, 0x69, 0x73, 0x27, 0x2c, 0x20, 0x77, @@ -404,10 +404,6 @@ const unsigned char boot_lua[] = 0x09, 0x09, 0x65, 0x6e, 0x64, 0x2c, 0x0a, 0x09, 0x7d, 0x29, 0x0a, 0x65, 0x6e, 0x64, 0x0a, - 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x69, 0x73, 0x5f, 0x66, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x67, 0x61, 0x6d, - 0x65, 0x20, 0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x0a, - 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6e, 0x6f, 0x5f, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x20, 0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x75, 0x72, 0x69, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x28, 0x73, 0x29, 0x0a, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x73, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x25, 0x25, @@ -418,6 +414,8 @@ const unsigned char boot_lua[] = 0x62, 0x28, 0x32, 0x29, 0x2c, 0x20, 0x31, 0x36, 0x29, 0x29, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x29, 0x0a, 0x65, 0x6e, 0x64, 0x0a, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6e, 0x6f, 0x5f, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x20, 0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x0a, 0x2d, 0x2d, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x27, 0x74, 0x20, 0x62, 0x65, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x6e, 0x2e, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x62, 0x6f, 0x6f, 0x74, @@ -442,13 +440,18 @@ const unsigned char boot_lua[] = 0x65, 0x20, 0x3d, 0x20, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x73, 0x65, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2c, 0x20, 0x61, 0x72, 0x67, 0x30, 0x29, 0x0a, - 0x09, 0x69, 0x73, 0x5f, 0x66, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x67, 0x61, 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x63, - 0x61, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x5f, 0x67, 0x61, 0x6d, 0x65, 0x0a, + 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x69, 0x73, 0x5f, 0x66, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x67, 0x61, + 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x63, 0x61, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x5f, 0x67, 0x61, 0x6d, 0x65, 0x0a, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x61, 0x72, 0x67, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x66, 0x75, 0x73, 0x65, 0x64, 0x2e, 0x73, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x09, 0x09, 0x69, 0x73, 0x5f, 0x66, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x67, 0x61, 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x73, + 0x65, 0x74, 0x46, 0x75, 0x73, 0x65, 0x64, 0x28, 0x69, 0x73, 0x5f, 0x66, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x67, + 0x61, 0x6d, 0x65, 0x29, 0x0a, + 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x3d, 0x20, + 0x22, 0x22, 0x0a, 0x09, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x5f, 0x67, 0x61, 0x6d, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6f, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x73, 0x65, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6f, 0x2e, 0x67, 0x61, 0x6d, 0x65, 0x2e, 0x61, 0x72, 0x67, 0x5b, 0x31, 0x5d, 0x20, @@ -464,28 +467,51 @@ const unsigned char boot_lua[] = 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x3d, 0x20, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x67, 0x65, 0x74, 0x66, 0x75, 0x6c, 0x6c, 0x28, 0x6e, 0x6f, 0x75, 0x72, 0x69, 0x29, 0x0a, - 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, - 0x65, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x6c, 0x65, 0x61, 0x66, 0x28, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x29, 0x0a, - 0x09, 0x09, 0x6c, 0x65, 0x61, 0x66, 0x20, 0x3d, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x3a, 0x67, 0x73, 0x75, 0x62, - 0x28, 0x22, 0x5e, 0x28, 0x5b, 0x25, 0x2e, 0x5d, 0x2b, 0x29, 0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x20, 0x2d, - 0x2d, 0x20, 0x73, 0x74, 0x72, 0x69, 0x70, 0x20, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x22, 0x2e, - 0x22, 0x27, 0x73, 0x0a, - 0x09, 0x09, 0x6c, 0x65, 0x61, 0x66, 0x20, 0x3d, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x3a, 0x67, 0x73, 0x75, 0x62, - 0x28, 0x22, 0x25, 0x2e, 0x28, 0x5b, 0x5e, 0x25, 0x2e, 0x5d, 0x2b, 0x29, 0x24, 0x22, 0x2c, 0x20, 0x22, 0x22, - 0x29, 0x20, 0x2d, 0x2d, 0x20, 0x73, 0x74, 0x72, 0x69, 0x70, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, - 0x6f, 0x6e, 0x0a, - 0x09, 0x09, 0x6c, 0x65, 0x61, 0x66, 0x20, 0x3d, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x3a, 0x67, 0x73, 0x75, 0x62, - 0x28, 0x22, 0x25, 0x2e, 0x22, 0x2c, 0x20, 0x22, 0x5f, 0x22, 0x29, 0x20, 0x2d, 0x2d, 0x20, 0x72, 0x65, 0x70, - 0x6c, 0x61, 0x63, 0x65, 0x20, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x22, 0x2e, 0x22, - 0x27, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x22, 0x5f, 0x22, 0x0a, - 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, - 0x73, 0x65, 0x74, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x28, 0x6c, 0x65, 0x61, 0x66, 0x29, 0x0a, 0x09, 0x09, 0x63, 0x61, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x5f, 0x67, 0x61, 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x73, 0x65, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2c, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x29, 0x0a, + 0x09, 0x09, 0x0a, + 0x09, 0x09, 0x2d, 0x2d, 0x20, 0x55, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x2e, 0x6c, 0x6f, 0x76, + 0x65, 0x20, 0x61, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x6e, 0x6f, 0x77, 0x2e, 0x0a, + 0x09, 0x09, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, + 0x70, 0x61, 0x74, 0x68, 0x2e, 0x6c, 0x65, 0x61, 0x66, 0x28, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x29, 0x0a, + 0x09, 0x65, 0x6c, 0x73, 0x65, 0x0a, + 0x09, 0x09, 0x2d, 0x2d, 0x20, 0x55, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x78, 0x65, 0x20, 0x61, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6e, 0x6f, 0x77, 0x2e, 0x0a, + 0x09, 0x09, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, + 0x70, 0x61, 0x74, 0x68, 0x2e, 0x6c, 0x65, 0x61, 0x66, 0x28, 0x61, 0x72, 0x67, 0x30, 0x29, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x3d, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5e, 0x28, 0x5b, 0x25, 0x2e, 0x5d, 0x2b, 0x29, 0x22, + 0x2c, 0x20, 0x22, 0x22, 0x29, 0x20, 0x2d, 0x2d, 0x20, 0x73, 0x74, 0x72, 0x69, 0x70, 0x20, 0x6c, 0x65, 0x61, + 0x64, 0x69, 0x6e, 0x67, 0x20, 0x22, 0x2e, 0x22, 0x27, 0x73, 0x0a, + 0x09, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x3d, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x25, 0x2e, 0x28, 0x5b, 0x5e, 0x25, 0x2e, 0x5d, 0x2b, + 0x29, 0x24, 0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x20, 0x2d, 0x2d, 0x20, 0x73, 0x74, 0x72, 0x69, 0x70, 0x20, + 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x0a, + 0x09, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x3d, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x25, 0x2e, 0x22, 0x2c, 0x20, 0x22, 0x5f, 0x22, 0x29, + 0x20, 0x2d, 0x2d, 0x20, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x20, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, + 0x69, 0x6e, 0x67, 0x20, 0x22, 0x2e, 0x22, 0x27, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x22, 0x5f, 0x22, 0x0a, + 0x09, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x3d, 0x20, 0x23, 0x69, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x20, 0x3e, 0x20, 0x30, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x20, 0x6f, 0x72, 0x20, 0x22, 0x6c, 0x6f, 0x76, 0x65, 0x67, 0x61, 0x6d, 0x65, 0x22, 0x0a, + 0x09, 0x2d, 0x2d, 0x20, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x2e, 0x6c, 0x75, 0x61, 0x20, + 0x69, 0x73, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x65, + 0x64, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x0a, + 0x09, 0x2d, 0x2d, 0x20, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x76, + 0x65, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x28, 0x74, 0x68, 0x65, 0x20, 0x69, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, + 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x2e, 0x29, 0x0a, + 0x09, 0x70, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x73, 0x65, 0x74, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2c, 0x20, + 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2c, 0x20, 0x74, 0x72, 0x75, 0x65, 0x29, 0x0a, 0x09, 0x69, 0x66, 0x20, 0x63, 0x61, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x5f, 0x67, 0x61, 0x6d, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x28, 0x22, 0x6d, 0x61, 0x69, 0x6e, @@ -495,9 +521,6 @@ const unsigned char boot_lua[] = 0x09, 0x09, 0x6e, 0x6f, 0x5f, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x0a, - 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x73, - 0x65, 0x74, 0x46, 0x75, 0x73, 0x65, 0x64, 0x28, 0x69, 0x73, 0x5f, 0x66, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x67, - 0x61, 0x6d, 0x65, 0x29, 0x0a, 0x09, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x5f, 0x67, 0x61, 0x6d, 0x65, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6e, 0x6f, 0x67, 0x61, 0x6d, 0x65, 0x28, 0x29, 0x0a, @@ -537,6 +560,9 @@ const unsigned char boot_lua[] = 0x73, 0x65, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x0a, + 0x09, 0x09, 0x09, 0x68, 0x69, 0x67, 0x68, 0x64, 0x70, 0x69, 0x20, 0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, + 0x2c, 0x0a, + 0x09, 0x09, 0x09, 0x73, 0x72, 0x67, 0x62, 0x20, 0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x0a, 0x09, 0x09, 0x7d, 0x2c, 0x0a, 0x09, 0x09, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x0a, @@ -599,6 +625,13 @@ const unsigned char boot_lua[] = 0x6e, 0x0a, 0x09, 0x09, 0x63, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x2d, 0x2d, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x20, 0x68, 0x61, 0x63, 0x6b, 0x0a, + 0x09, 0x69, 0x66, 0x20, 0x63, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, + 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x20, + 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x28, 0x29, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x2d, 0x2d, 0x20, 0x47, 0x65, 0x74, 0x73, 0x20, 0x64, 0x65, 0x73, 0x69, 0x72, 0x65, 0x64, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2e, 0x0a, 0x09, 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x2c, 0x76, 0x20, 0x69, 0x6e, 0x20, 0x69, 0x70, 0x61, 0x69, 0x72, 0x73, @@ -630,13 +663,6 @@ const unsigned char boot_lua[] = 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x28, 0x29, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x0a, - 0x09, 0x2d, 0x2d, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x20, 0x68, 0x61, 0x63, 0x6b, 0x0a, - 0x09, 0x69, 0x66, 0x20, 0x63, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, - 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x20, - 0x74, 0x68, 0x65, 0x6e, 0x0a, - 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, - 0x65, 0x28, 0x29, 0x0a, - 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x2d, 0x2d, 0x20, 0x53, 0x65, 0x74, 0x75, 0x70, 0x20, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x68, 0x65, 0x72, 0x65, 0x2e, 0x0a, 0x09, 0x69, 0x66, 0x20, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x63, @@ -668,6 +694,10 @@ const unsigned char boot_lua[] = 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x64, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x2c, 0x0a, + 0x09, 0x09, 0x09, 0x68, 0x69, 0x67, 0x68, 0x64, 0x70, 0x69, 0x20, 0x3d, 0x20, 0x63, 0x2e, 0x77, 0x69, 0x6e, + 0x64, 0x6f, 0x77, 0x2e, 0x68, 0x69, 0x67, 0x68, 0x64, 0x70, 0x69, 0x2c, 0x0a, + 0x09, 0x09, 0x09, 0x73, 0x72, 0x67, 0x62, 0x20, 0x3d, 0x20, 0x63, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, + 0x2e, 0x73, 0x72, 0x67, 0x62, 0x2c, 0x0a, 0x09, 0x09, 0x7d, 0x29, 0x2c, 0x20, 0x22, 0x43, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x65, 0x74, 0x20, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0x29, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x73, 0x65, 0x74, 0x54, @@ -4924,14 +4954,17 @@ const unsigned char boot_lua[] = 0x6f, 0x78, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6f, 0x79, 0x20, 0x3d, 0x20, 0x72, 0x61, 0x69, 0x6e, 0x2e, 0x6f, 0x79, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6d, 0x20, 0x3d, 0x20, 0x31, 0x20, 0x2f, 0x20, 0x6c, 0x6f, + 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x67, 0x65, 0x74, 0x50, 0x69, 0x78, 0x65, 0x6c, + 0x53, 0x63, 0x61, 0x6c, 0x65, 0x28, 0x29, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x77, 0x20, 0x3d, 0x20, - 0x32, 0x20, 0x2a, 0x20, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x63, 0x65, 0x69, 0x6c, 0x28, 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, 0x2f, 0x20, 0x73, 0x78, 0x29, 0x20, 0x2b, 0x20, 0x32, 0x0a, + 0x32, 0x20, 0x2a, 0x20, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x63, 0x65, 0x69, 0x6c, 0x28, 0x6d, 0x20, 0x2a, 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, 0x2f, 0x20, 0x73, 0x78, 0x29, 0x20, 0x2b, 0x20, 0x32, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x68, 0x20, 0x3d, 0x20, - 0x32, 0x20, 0x2a, 0x20, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x63, 0x65, 0x69, 0x6c, 0x28, 0x6c, 0x6f, 0x76, 0x65, - 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x28, 0x29, 0x20, 0x2f, 0x20, 0x73, 0x79, 0x29, 0x20, 0x2b, 0x20, 0x32, 0x0a, + 0x32, 0x20, 0x2a, 0x20, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x63, 0x65, 0x69, 0x6c, 0x28, 0x6d, 0x20, 0x2a, 0x20, + 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x48, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x28, 0x29, 0x20, 0x2f, 0x20, 0x73, 0x79, 0x29, 0x20, 0x2b, 0x20, 0x32, 0x0a, 0x09, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x28, 0x29, 0x0a, 0x09, 0x09, 0x69, 0x66, 0x20, 0x62, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x67, 0x65, 0x74, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x28, 0x29, 0x20, 0x3c, 0x20, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x77, @@ -4977,12 +5010,17 @@ const unsigned char boot_lua[] = 0x29, 0x0a, 0x09, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x61, 0x69, 0x6e, 0x28, 0x66, 0x72, 0x61, 0x63, 0x29, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0x6c, 0x6f, + 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x67, 0x65, 0x74, 0x50, 0x69, 0x78, 0x65, 0x6c, + 0x53, 0x63, 0x61, 0x6c, 0x65, 0x28, 0x29, 0x0a, 0x09, 0x09, 0x69, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x78, 0x20, 0x3d, 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, 0x2a, 0x20, 0x30, 0x2e, 0x34, 0x35, 0x0a, + 0x74, 0x68, 0x28, 0x29, 0x20, 0x2a, 0x20, 0x30, 0x2e, 0x34, 0x35, 0x20, 0x2f, 0x20, 0x73, 0x63, 0x61, 0x6c, + 0x65, 0x0a, 0x09, 0x09, 0x69, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x79, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x48, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x28, 0x29, 0x20, 0x2a, 0x20, 0x30, 0x2e, 0x35, 0x35, 0x0a, + 0x67, 0x68, 0x74, 0x28, 0x29, 0x20, 0x2a, 0x20, 0x30, 0x2e, 0x35, 0x35, 0x20, 0x2f, 0x20, 0x73, 0x63, 0x61, + 0x6c, 0x65, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x67, 0x72, 0x69, 0x64, 0x28, 0x29, 0x0a, @@ -5126,8 +5164,15 @@ const unsigned char boot_lua[] = 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x29, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x75, + 0x73, 0x68, 0x28, 0x29, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x63, + 0x61, 0x6c, 0x65, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x67, 0x65, + 0x74, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x28, 0x29, 0x29, 0x0a, 0x09, 0x09, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x67, 0x72, 0x69, 0x64, 0x28, 0x29, 0x0a, 0x09, 0x09, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x28, 0x29, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x6f, + 0x70, 0x28, 0x29, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x0a, 0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6b, 0x65, 0x79, @@ -5155,6 +5200,8 @@ const unsigned char boot_lua[] = 0x63, 0x6b, 0x20, 0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x0a, 0x09, 0x09, 0x74, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, + 0x09, 0x09, 0x74, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x68, 0x69, 0x67, 0x68, 0x64, 0x70, 0x69, + 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, diff --git a/src/scripts/graphics.lua b/src/scripts/graphics.lua index 2af520bcb..4b2b9b814 100644 --- a/src/scripts/graphics.lua +++ b/src/scripts/graphics.lua @@ -1304,7 +1304,7 @@ do #define TransformProjectionMatrix gl_ModelViewProjectionMatrix #define NormalMatrix gl_NormalMatrix uniform sampler2D _tex0_; -uniform vec2 love_ScreenParams;]] +uniform vec4 love_ScreenSize;]] local GLSL_VERTEX = { HEADER = [[ @@ -1315,7 +1315,15 @@ uniform vec2 love_ScreenParams;]] #define VertexColor gl_Color #define VaryingTexCoord gl_TexCoord[0] -#define VaryingColor gl_FrontColor]], +#define VaryingColor gl_FrontColor + +#if defined(GL_ARB_draw_instanced) + #extension GL_ARB_draw_instanced : enable + #define love_InstanceID gl_InstanceIDARB +#else + attribute float love_PseudoInstanceID; + int love_InstanceID = int(love_PseudoInstanceID); +#endif]], FOOTER = [[ void main() { @@ -1338,7 +1346,7 @@ void main() { float dummy = texture2D(_tex0_, vec2(.5)).r; // See Shader::checkSetScreenParams in Shader.cpp. - vec2 pixelcoord = vec2(gl_FragCoord.x, (gl_FragCoord.y * love_ScreenParams[0]) + love_ScreenParams[1]); + vec2 pixelcoord = vec2(gl_FragCoord.x, (gl_FragCoord.y * love_ScreenSize.z) + love_ScreenSize.w); gl_FragColor = effect(VaryingColor, _tex0_, VaryingTexCoord.st, pixelcoord); }]], @@ -1349,7 +1357,7 @@ void main() { float dummy = texture2D(_tex0_, vec2(.5)).r; // See Shader::checkSetScreenParams in Shader.cpp. - vec2 pixelcoord = vec2(gl_FragCoord.x, (gl_FragCoord.y * love_ScreenParams[0]) + love_ScreenParams[1]); + vec2 pixelcoord = vec2(gl_FragCoord.x, (gl_FragCoord.y * love_ScreenSize.z) + love_ScreenSize.w); effects(VaryingColor, _tex0_, VaryingTexCoord.st, pixelcoord); }]], diff --git a/src/scripts/graphics.lua.h b/src/scripts/graphics.lua.h index c08ac6326..b89461c99 100644 --- a/src/scripts/graphics.lua.h +++ b/src/scripts/graphics.lua.h @@ -27,7 +27,7 @@ const unsigned char graphics_lua[] = 0x2d, 0x2d, 0x5b, 0x5b, 0x0a, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, - 0x2d, 0x32, 0x30, 0x31, 0x33, 0x20, 0x4c, 0x4f, 0x56, 0x45, 0x20, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, + 0x2d, 0x32, 0x30, 0x31, 0x34, 0x20, 0x4c, 0x4f, 0x56, 0x45, 0x20, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x54, 0x65, 0x61, 0x6d, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x27, 0x61, 0x73, 0x2d, 0x69, 0x73, 0x27, 0x2c, 0x20, 0x77, @@ -6295,8 +6295,8 @@ const unsigned char graphics_lua[] = 0x69, 0x78, 0x20, 0x67, 0x6c, 0x5f, 0x4e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x5f, 0x74, 0x65, 0x78, 0x30, 0x5f, 0x3b, 0x0a, - 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, - 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3b, 0x5d, 0x5d, 0x0a, + 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, + 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x53, 0x69, 0x7a, 0x65, 0x3b, 0x5d, 0x5d, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x5f, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x20, 0x3d, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x20, 0x3d, 0x20, 0x5b, 0x5b, 0x0a, @@ -6312,8 +6312,23 @@ const unsigned char graphics_lua[] = 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x67, 0x6c, 0x5f, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x5b, 0x30, 0x5d, 0x0a, 0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6c, - 0x6f, 0x72, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x5d, 0x5d, - 0x2c, 0x0a, + 0x6f, 0x72, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x0a, + 0x23, 0x69, 0x66, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x28, 0x47, 0x4c, 0x5f, 0x41, 0x52, 0x42, + 0x5f, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x64, 0x29, 0x0a, + 0x09, 0x23, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x47, 0x4c, 0x5f, 0x41, 0x52, 0x42, + 0x5f, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x64, 0x20, 0x3a, 0x20, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x0a, + 0x09, 0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x49, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x20, 0x67, 0x6c, 0x5f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x49, 0x44, 0x41, 0x52, 0x42, 0x0a, + 0x23, 0x65, 0x6c, 0x73, 0x65, 0x0a, + 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6c, + 0x6f, 0x76, 0x65, 0x5f, 0x50, 0x73, 0x65, 0x75, 0x64, 0x6f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x49, 0x44, 0x3b, 0x0a, + 0x09, 0x69, 0x6e, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x49, 0x44, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x50, 0x73, 0x65, 0x75, + 0x64, 0x6f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x29, 0x3b, 0x0a, + 0x23, 0x65, 0x6e, 0x64, 0x69, 0x66, 0x5d, 0x5d, 0x2c, 0x0a, 0x09, 0x09, 0x46, 0x4f, 0x4f, 0x54, 0x45, 0x52, 0x20, 0x3d, 0x20, 0x5b, 0x5b, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3d, @@ -6350,9 +6365,9 @@ const unsigned char graphics_lua[] = 0x09, 0x76, 0x65, 0x63, 0x32, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x2c, 0x20, 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, - 0x79, 0x20, 0x2a, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x73, 0x5b, 0x30, 0x5d, 0x29, 0x20, 0x2b, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x53, 0x63, 0x72, - 0x65, 0x65, 0x6e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x5b, 0x31, 0x5d, 0x29, 0x3b, 0x0a, + 0x79, 0x20, 0x2a, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x53, 0x69, 0x7a, + 0x65, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, + 0x53, 0x69, 0x7a, 0x65, 0x2e, 0x77, 0x29, 0x3b, 0x0a, 0x09, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x28, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x2c, 0x20, 0x5f, 0x74, 0x65, 0x78, 0x30, 0x5f, 0x2c, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x54, 0x65, @@ -6375,9 +6390,9 @@ const unsigned char graphics_lua[] = 0x09, 0x76, 0x65, 0x63, 0x32, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x78, 0x2c, 0x20, 0x28, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, - 0x79, 0x20, 0x2a, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x73, 0x5b, 0x30, 0x5d, 0x29, 0x20, 0x2b, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x53, 0x63, 0x72, - 0x65, 0x65, 0x6e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x5b, 0x31, 0x5d, 0x29, 0x3b, 0x0a, + 0x79, 0x20, 0x2a, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x53, 0x69, 0x7a, + 0x65, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, + 0x53, 0x69, 0x7a, 0x65, 0x2e, 0x77, 0x29, 0x3b, 0x0a, 0x09, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x73, 0x28, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x2c, 0x20, 0x5f, 0x74, 0x65, 0x78, 0x30, 0x5f, 0x2c, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x2e, 0x73, 0x74, 0x2c, 0x20, 0x70, 0x69, 0x78,