From 0706b6243cbe2815ad38aeff9aa87b9585dbed4b Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sat, 3 Oct 2020 19:28:57 -0300 Subject: [PATCH 01/32] Add love.filesystem.mountFullPath. --- .../xcode/liblove.xcodeproj/project.pbxproj | 8 + src/common/StringMap.h | 15 + src/common/apple.h | 50 +++ src/common/apple.mm | 76 +++++ src/common/ios.h | 15 - src/common/ios.mm | 47 +-- src/common/macos.h | 7 - src/common/macos.mm | 28 -- src/modules/filesystem/Filesystem.cpp | 47 ++- src/modules/filesystem/Filesystem.h | 35 +- src/modules/filesystem/physfs/Filesystem.cpp | 299 +++++++++++++----- src/modules/filesystem/physfs/Filesystem.h | 7 + src/modules/filesystem/wrap_Filesystem.cpp | 68 ++++ 13 files changed, 494 insertions(+), 208 deletions(-) create mode 100644 src/common/apple.h create mode 100644 src/common/apple.mm diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index ce4d481ca..dbb37e172 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -960,6 +960,8 @@ FAAA3FDA1F64B3AD00F89E99 /* lstrlib.h in Headers */ = {isa = PBXBuildFile; fileRef = FAAA3FD51F64B3AD00F89E99 /* lstrlib.h */; }; FAAA3FDB1F64B3AD00F89E99 /* lutf8lib.c in Sources */ = {isa = PBXBuildFile; fileRef = FAAA3FD61F64B3AD00F89E99 /* lutf8lib.c */; }; FAAA3FDC1F64B3AD00F89E99 /* lutf8lib.h in Headers */ = {isa = PBXBuildFile; fileRef = FAAA3FD71F64B3AD00F89E99 /* lutf8lib.h */; }; + FAAC2F79251A9D2200BCB81B /* apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = FAAC2F78251A9D2200BCB81B /* apple.mm */; }; + FAAC2F7A251A9D2200BCB81B /* apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = FAAC2F78251A9D2200BCB81B /* apple.mm */; }; FAAFF04416CB11C700CCDE45 /* OpenAL-Soft.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FAAFF04316CB11C700CCDE45 /* OpenAL-Soft.framework */; }; FAB17BE61ABFAA9000F9BA27 /* lz4.c in Sources */ = {isa = PBXBuildFile; fileRef = FAB17BE41ABFAA9000F9BA27 /* lz4.c */; }; FAB17BE71ABFAA9000F9BA27 /* lz4.c in Sources */ = {isa = PBXBuildFile; fileRef = FAB17BE41ABFAA9000F9BA27 /* lz4.c */; }; @@ -1916,6 +1918,8 @@ FAAA3FD51F64B3AD00F89E99 /* lstrlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lstrlib.h; sourceTree = ""; }; FAAA3FD61F64B3AD00F89E99 /* lutf8lib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lutf8lib.c; sourceTree = ""; }; FAAA3FD71F64B3AD00F89E99 /* lutf8lib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lutf8lib.h; sourceTree = ""; }; + FAAC2F78251A9D2200BCB81B /* apple.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = apple.mm; sourceTree = ""; }; + FAAC2F7F251A9D3E00BCB81B /* apple.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = apple.h; sourceTree = ""; }; FAAFF04316CB11C700CCDE45 /* OpenAL-Soft.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "OpenAL-Soft.framework"; path = "/Library/Frameworks/OpenAL-Soft.framework"; sourceTree = ""; }; FAB17BE41ABFAA9000F9BA27 /* lz4.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lz4.c; sourceTree = ""; }; FAB17BE51ABFAA9000F9BA27 /* lz4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lz4.h; sourceTree = ""; }; @@ -2189,6 +2193,8 @@ children = ( FAA3A9AC1B7D465A00CED060 /* android.cpp */, FAA3A9AD1B7D465A00CED060 /* android.h */, + FAAC2F7F251A9D3E00BCB81B /* apple.h */, + FAAC2F78251A9D2200BCB81B /* apple.mm */, FA0B78F71A958E3B000E1D17 /* b64.cpp */, FA0B78F81A958E3B000E1D17 /* b64.h */, FA6BDE5B1F31725300786805 /* Color.h */, @@ -4649,6 +4655,7 @@ FA0B7E1F1A95902C000E1D17 /* Physics.cpp in Sources */, FA6A2B7B1F60B8250074C308 /* wrap_ByteData.cpp in Sources */, FA0B7E821A95902C000E1D17 /* Shape.cpp in Sources */, + FAAC2F7A251A9D2200BCB81B /* apple.mm in Sources */, FA0B7ACE1A958EA3000E1D17 /* packet.c in Sources */, FAF140891E20934C00F898D2 /* PoolAlloc.cpp in Sources */, FA27B3B41B498151008A9DCE /* wrap_Video.cpp in Sources */, @@ -5045,6 +5052,7 @@ FAF140881E20934C00F898D2 /* PoolAlloc.cpp in Sources */, FA0B7AAD1A958EA3000E1D17 /* b2WheelJoint.cpp in Sources */, FA0B7DEE1A95902C000E1D17 /* Mouse.cpp in Sources */, + FAAC2F79251A9D2200BCB81B /* apple.mm in Sources */, FAA54ACC1F91660400A8FA7B /* TheoraVideoStream.cpp in Sources */, FA1E887E1DF363CD00E808AA /* Filter.cpp in Sources */, FA0B7D281A95902C000E1D17 /* wrap_GlyphData.cpp in Sources */, diff --git a/src/common/StringMap.h b/src/common/StringMap.h index 4e6dc25de..b373e8f32 100644 --- a/src/common/StringMap.h +++ b/src/common/StringMap.h @@ -197,6 +197,21 @@ bool getConstant(const char *in, type &out) { return name##s.find(in, out); } \ bool getConstant(type in, const char *&out) { return name##s.find(in, out); } \ std::vector getConstants(type) { return name##s.getNames(); } +#define STRINGMAP_CLASS_DECLARE(type) \ +static bool getConstant(const char *in, type &out); \ +static bool getConstant(type in, const char *&out); \ +static std::vector getConstants(type); \ + +#define STRINGMAP_CLASS_BEGIN(classname, type, count, name) \ +static StringMap::Entry name##Entries[] = + +#define STRINGMAP_CLASS_END(classname, type, count, name) \ +; \ +static StringMap name##s(name##Entries, sizeof(name##Entries)); \ +bool classname::getConstant(const char *in, type &out) { return name##s.find(in, out); } \ +bool classname::getConstant(type in, const char *&out) { return name##s.find(in, out); } \ +std::vector classname::getConstants(type) { return name##s.getNames(); } + } // love #endif // LOVE_STRING_MAP_H diff --git a/src/common/apple.h b/src/common/apple.h new file mode 100644 index 000000000..a2edcda6b --- /dev/null +++ b/src/common/apple.h @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2006-2020 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#pragma once + +#include "config.h" + +#if defined(LOVE_IOS) || defined(LOVE_MACOS) + +#include + +namespace love +{ +namespace apple +{ + +enum UserDirectory +{ + USER_DIRECTORY_HOME, + USER_DIRECTORY_APPSUPPORT, + USER_DIRECTORY_DOCUMENTS, + USER_DIRECTORY_DESKTOP, + USER_DIRECTORY_CACHES, +}; + +std::string getUserDirectory(UserDirectory dir); + +std::string getExecutablePath(); + +} // apple +} // love + +#endif // defined(LOVE_IOS) || defined(LOVE_MACOS) diff --git a/src/common/apple.mm b/src/common/apple.mm new file mode 100644 index 000000000..9b27152dc --- /dev/null +++ b/src/common/apple.mm @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2006-2020 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#include "apple.h" + +#if defined(LOVE_IOS) || defined(LOVE_MACOS) + +#import + +namespace love +{ +namespace apple +{ + +std::string getUserDirectory(UserDirectory dir) +{ + std::string path; + NSSearchPathDirectory nsdir = NSTrashDirectory; + + @autoreleasepool + { + switch (dir) + { + case USER_DIRECTORY_HOME: + return NSHomeDirectory().UTF8String; + case USER_DIRECTORY_APPSUPPORT: + nsdir = NSApplicationSupportDirectory; + break; + case USER_DIRECTORY_DOCUMENTS: + nsdir = NSDocumentDirectory; + break; + case USER_DIRECTORY_DESKTOP: + nsdir = NSDesktopDirectory; + break; + case USER_DIRECTORY_CACHES: + nsdir = NSCachesDirectory; + break; + } + + NSArray *dirs = [[NSFileManager defaultManager] URLsForDirectory:nsdir inDomains:NSUserDomainMask]; + if (dirs.count > 0) + path = [dirs[0].path UTF8String]; + } + + return path; +} + +std::string getExecutablePath() +{ + @autoreleasepool + { + return std::string([NSBundle mainBundle].executablePath.UTF8String); + } +} + +} // apple +} // love + +#endif // defined(LOVE_IOS) || defined(LOVE_MACOS) diff --git a/src/common/ios.h b/src/common/ios.h index 8e436d94b..b07a05609 100644 --- a/src/common/ios.h +++ b/src/common/ios.h @@ -42,27 +42,12 @@ namespace ios **/ std::string getLoveInResources(bool &fused); -/** - * Gets the directory path where files should be stored. - **/ -std::string getAppdataDirectory(); - -/** - * Get the home directory (on iOS, this really means the app's sandbox dir.) - **/ -std::string getHomeDirectory(); - /** * Opens the specified URL with the default program associated with the URL's * scheme. **/ bool openURL(const std::string &url); -/** - * Returns the full path to the executable. - **/ -std::string getExecutablePath(); - /** * Causes devices with vibration support to vibrate for about 0.5 seconds. **/ diff --git a/src/common/ios.mm b/src/common/ios.mm index 648b56c61..bcc0bdbfc 100644 --- a/src/common/ios.mm +++ b/src/common/ios.mm @@ -19,6 +19,7 @@ **/ #include "ios.h" +#include "apple.h" #ifdef LOVE_IOS @@ -125,12 +126,6 @@ static bool deleteFileInDocuments(NSString *filename); @end -static NSString *getDocumentsDirectory() -{ - NSArray *docdirs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - return docdirs[0]; -} - static NSArray *getLovesInDocuments() { NSMutableArray *paths = [NSMutableArray new]; @@ -287,38 +282,6 @@ std::string getLoveInResources(bool &fused) return path; } -static std::string getUserDirectory(NSSearchPathDirectory dir) -{ - std::string path; - - @autoreleasepool - { - NSArray *dirs = [[NSFileManager defaultManager] URLsForDirectory:dir inDomains:NSUserDomainMask]; - - if (dirs.count > 0) - path = [dirs[0].path UTF8String]; - } - - return path; -} - -std::string getAppdataDirectory() -{ - return getUserDirectory(NSApplicationSupportDirectory); -} - -std::string getHomeDirectory() -{ - std::string path; - - @autoreleasepool - { - path = [NSHomeDirectory() UTF8String]; - } - - return path; -} - bool openURL(const std::string &url) { bool success = false; @@ -335,14 +298,6 @@ bool openURL(const std::string &url) return success; } -std::string getExecutablePath() -{ - @autoreleasepool - { - return std::string([NSBundle mainBundle].executablePath.UTF8String); - } -} - void vibrate() { @autoreleasepool diff --git a/src/common/macos.h b/src/common/macos.h index a3fcbdd9d..297c8f116 100644 --- a/src/common/macos.h +++ b/src/common/macos.h @@ -31,8 +31,6 @@ namespace love namespace macos { -std::string getAppdataDirectory(); - /** * Returns the filepath of the first detected love file in the Resources folder * in the main bundle (love.app.) @@ -46,11 +44,6 @@ std::string getLoveInResources(); **/ std::string checkDropEvents(); -/** - * Returns the full path to the executable. - **/ -std::string getExecutablePath(); - /** * Bounce the dock icon, if the app isn't in the foreground. **/ diff --git a/src/common/macos.mm b/src/common/macos.mm index 1ee165a5b..06edf8a37 100644 --- a/src/common/macos.mm +++ b/src/common/macos.mm @@ -36,26 +36,6 @@ namespace love namespace macos { -static std::string getUserDirectory(NSSearchPathDirectory dir) -{ - std::string path; - - @autoreleasepool - { - NSArray *dirs = [[NSFileManager defaultManager] URLsForDirectory:dir inDomains:NSUserDomainMask]; - - if (dirs.count > 0) - path = [dirs[0].path UTF8String]; - } - - return path; -} - -std::string getAppdataDirectory() -{ - return getUserDirectory(NSApplicationSupportDirectory); -} - std::string getLoveInResources() { std::string path; @@ -94,14 +74,6 @@ std::string checkDropEvents() return dropstr; } -std::string getExecutablePath() -{ - @autoreleasepool - { - return std::string([NSBundle mainBundle].executablePath.UTF8String); - } -} - void requestAttention(bool continuous) { @autoreleasepool diff --git a/src/modules/filesystem/Filesystem.cpp b/src/modules/filesystem/Filesystem.cpp index 109b3c942..56f028ae9 100644 --- a/src/modules/filesystem/Filesystem.cpp +++ b/src/modules/filesystem/Filesystem.cpp @@ -26,10 +26,8 @@ #include #include -#if defined(LOVE_MACOS) -#include "common/macos.h" -#elif defined(LOVE_IOS) -#include "common/ios.h" +#if defined(LOVE_MACOS) || defined(LOVE_IOS) +#include "common/apple.h" #elif defined(LOVE_WINDOWS) #include #include "common/utf8.h" @@ -92,10 +90,8 @@ bool Filesystem::isRealDirectory(const std::string &path) const std::string Filesystem::getExecutablePath() const { -#if defined(LOVE_MACOS) - return love::macos::getExecutablePath(); -#elif defined(LOVE_IOS) - return love::ios::getExecutablePath(); +#if defined(LOVE_MACOS) || defined(LOVE_IOS) + return love::apple::getExecutablePath(); #elif defined(LOVE_WINDOWS) wchar_t buffer[MAX_PATH + 1] = {0}; @@ -120,30 +116,33 @@ std::string Filesystem::getExecutablePath() const #endif } -bool Filesystem::getConstant(const char *in, FileType &out) +STRINGMAP_CLASS_BEGIN(Filesystem, Filesystem::FileType, Filesystem::FILETYPE_MAX_ENUM, fileType) { - return fileTypes.find(in, out); + { "file", Filesystem::FILETYPE_FILE }, + { "directory", Filesystem::FILETYPE_DIRECTORY }, + { "symlink", Filesystem::FILETYPE_SYMLINK }, + { "other", Filesystem::FILETYPE_OTHER }, } +STRINGMAP_CLASS_END(Filesystem, Filesystem::FileType, Filesystem::FILETYPE_MAX_ENUM, fileType) -bool Filesystem::getConstant(FileType in, const char *&out) +STRINGMAP_CLASS_BEGIN(Filesystem, Filesystem::CommonPath, Filesystem::COMMONPATH_MAX_ENUM, commonPath) { - return fileTypes.find(in, out); + { "appidentity", Filesystem::COMMONPATH_APP_IDENTITY }, + { "appdocuments", Filesystem::COMMONPATH_APP_DOCUMENTS }, + { "apptemp", Filesystem::COMMONPATH_APP_TEMP }, + { "userhome", Filesystem::COMMONPATH_USER_HOME }, + { "userappdata", Filesystem::COMMONPATH_USER_APPDATA }, + { "userdesktop", Filesystem::COMMONPATH_USER_DESKTOP }, + { "userdocuments", Filesystem::COMMONPATH_USER_DOCUMENTS }, } +STRINGMAP_CLASS_END(Filesystem, Filesystem::CommonPath, Filesystem::COMMONPATH_MAX_ENUM, commonPath) -std::vector Filesystem::getConstants(FileType) +STRINGMAP_CLASS_BEGIN(Filesystem, Filesystem::MountPermissions, Filesystem::MOUNT_PERMISSIONS_MAX_ENUM, mountPermissions) { - return fileTypes.getNames(); + { "read", Filesystem::MOUNT_PERMISSIONS_READ }, + { "readwrite", Filesystem::MOUNT_PERMISSIONS_READWRITE }, } - -StringMap::Entry Filesystem::fileTypeEntries[] = -{ - { "file", FILETYPE_FILE }, - { "directory", FILETYPE_DIRECTORY }, - { "symlink", FILETYPE_SYMLINK }, - { "other", FILETYPE_OTHER }, -}; - -StringMap Filesystem::fileTypes(Filesystem::fileTypeEntries, sizeof(Filesystem::fileTypeEntries)); +STRINGMAP_CLASS_END(Filesystem, Filesystem::MountPermissions, Filesystem::MOUNT_PERMISSIONS_MAX_ENUM, mountPermissions) } // filesystem } // love diff --git a/src/modules/filesystem/Filesystem.h b/src/modules/filesystem/Filesystem.h index 715a35f80..444b18d41 100644 --- a/src/modules/filesystem/Filesystem.h +++ b/src/modules/filesystem/Filesystem.h @@ -71,6 +71,25 @@ public: FILETYPE_MAX_ENUM }; + enum CommonPath + { + COMMONPATH_APP_IDENTITY, + COMMONPATH_APP_DOCUMENTS, + COMMONPATH_APP_TEMP, + COMMONPATH_USER_HOME, + COMMONPATH_USER_APPDATA, + COMMONPATH_USER_DESKTOP, + COMMONPATH_USER_DOCUMENTS, + COMMONPATH_MAX_ENUM + }; + + enum MountPermissions + { + MOUNT_PERMISSIONS_READ, + MOUNT_PERMISSIONS_READWRITE, + MOUNT_PERMISSIONS_MAX_ENUM + }; + struct Info { // Numbers will be -1 if they cannot be determined. @@ -136,8 +155,13 @@ public: virtual bool mount(const char *archive, const char *mountpoint, bool appendToPath = false) = 0; virtual bool mount(Data *data, const char *archivename, const char *mountpoint, bool appendToPath = false) = 0; + + virtual bool mountFullPath(const char *archive, const char *mountpoint, MountPermissions permissions, bool appendToPath = false) = 0; + virtual bool mountCommonPath(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath = false) = 0; + virtual bool unmount(const char *archive) = 0; virtual bool unmount(Data *data) = 0; + virtual bool unmount(CommonPath path) = 0; /** * Creates a new file. @@ -152,6 +176,8 @@ public: **/ virtual FileData *newFileData(const void *data, size_t size, const char *filename) const; + virtual std::string getFullCommonPath(CommonPath path) = 0; + /** * Gets the current working directory. **/ @@ -263,18 +289,15 @@ public: **/ virtual std::string getExecutablePath() const; - static bool getConstant(const char *in, FileType &out); - static bool getConstant(FileType in, const char *&out); - static std::vector getConstants(FileType); + STRINGMAP_CLASS_DECLARE(FileType); + STRINGMAP_CLASS_DECLARE(CommonPath); + STRINGMAP_CLASS_DECLARE(MountPermissions); private: // Should we save external or internal for Android bool useExternal; - static StringMap::Entry fileTypeEntries[]; - static StringMap fileTypes; - }; // Filesystem } // filesystem diff --git a/src/modules/filesystem/physfs/Filesystem.cpp b/src/modules/filesystem/physfs/Filesystem.cpp index 99b0d6ff5..a5f8a042a 100644 --- a/src/modules/filesystem/physfs/Filesystem.cpp +++ b/src/modules/filesystem/physfs/Filesystem.cpp @@ -45,6 +45,10 @@ # include #endif +#if defined(LOVE_IOS) || defined(LOVE_MACOS) +# include "common/apple.h" +#endif + #ifdef LOVE_IOS # include "common/ios.h" #endif @@ -60,44 +64,6 @@ #include "common/android.h" #endif -namespace -{ - size_t getDriveDelim(const std::string &input) - { - for (size_t i = 0; i < input.size(); ++i) - if (input[i] == '/' || input[i] == '\\') - return i; - // Something's horribly wrong - return 0; - } - - std::string getDriveRoot(const std::string &input) - { - return input.substr(0, getDriveDelim(input)+1); - } - - std::string skipDriveRoot(const std::string &input) - { - return input.substr(getDriveDelim(input)+1); - } - - std::string normalize(const std::string &input) - { - std::stringstream out; - bool seenSep = false, isSep = false; - for (size_t i = 0; i < input.size(); ++i) - { - isSep = (input[i] == LOVE_PATH_SEPARATOR[0]); - if (!isSep || !seenSep) - out << input[i]; - seenSep = isSep; - } - - return out.str(); - } - -} - namespace love { namespace filesystem @@ -105,6 +71,53 @@ namespace filesystem namespace physfs { +static size_t getDriveDelim(const std::string &input) +{ + for (size_t i = 0; i < input.size(); ++i) + if (input[i] == '/' || input[i] == '\\') + return i; + // Something's horribly wrong + return 0; +} + +static std::string getDriveRoot(const std::string &input) +{ + return input.substr(0, getDriveDelim(input)+1); +} + +static std::string skipDriveRoot(const std::string &input) +{ + return input.substr(getDriveDelim(input)+1); +} + +static std::string normalize(const std::string &input) +{ + std::stringstream out; + bool seenSep = false, isSep = false; + for (size_t i = 0; i < input.size(); ++i) + { + isSep = (input[i] == LOVE_PATH_SEPARATOR[0]); + if (!isSep || !seenSep) + out << input[i]; + seenSep = isSep; + } + + return out.str(); +} + +static bool isAppCommonPath(Filesystem::CommonPath path) +{ + switch (path) + { + case Filesystem::COMMONPATH_APP_IDENTITY: + case Filesystem::COMMONPATH_APP_DOCUMENTS: + case Filesystem::COMMONPATH_APP_TEMP: + return true; + default: + return false; + } +} + Filesystem::Filesystem() : fused(false) , fusedSet(false) @@ -159,15 +172,13 @@ bool Filesystem::setIdentity(const char *ident, bool appendToPath) save_identity = std::string(ident); // Generate the relative path to the game save folder. - save_path_relative = std::string(LOVE_APPDATA_PREFIX LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + save_identity; + if (fused) + save_path_relative = std::string(LOVE_APPDATA_PREFIX) + save_identity; + else + save_path_relative = std::string(LOVE_APPDATA_PREFIX LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + save_identity; // Generate the full path to the game save folder. - save_path_full = std::string(getAppdataDirectory()) + std::string(LOVE_PATH_SEPARATOR); - if (fused) - save_path_full += std::string(LOVE_APPDATA_PREFIX) + save_identity; - else - save_path_full += save_path_relative; - + save_path_full = std::string(getAppdataDirectory()) + std::string(LOVE_PATH_SEPARATOR) + save_path_relative; save_path_full = normalize(save_path_full); #ifdef LOVE_ANDROID @@ -370,10 +381,35 @@ bool Filesystem::mount(const char *archive, const char *mountpoint, bool appendT realPath += archive; } - if (realPath.length() == 0) + return mountFullPath(realPath.c_str(), mountpoint, MOUNT_PERMISSIONS_READ, appendToPath); +} + +bool Filesystem::mountFullPath(const char *archive, const char *mountpoint, MountPermissions permissions, bool appendToPath) +{ + if (!PHYSFS_isInit() || !archive || !mountpoint) return false; - return PHYSFS_mount(realPath.c_str(), mountpoint, appendToPath) != 0; + if (permissions == MOUNT_PERMISSIONS_READWRITE && strlen(mountpoint) == 0) + return false; + + // TODO: readwrite mount + return PHYSFS_mount(archive, mountpoint, appendToPath) != 0; +} + +bool Filesystem::mountCommonPath(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath) +{ + std::string fullpath = getFullCommonPath(path); + if (fullpath.empty()) + return false; + + bool success = mountFullPath(fullpath.c_str(), mountpoint, permissions, appendToPath); + + if (!success && isAppCommonPath(path)) + { + + } + + return success; } bool Filesystem::mount(Data *data, const char *archivename, const char *mountpoint, bool appendToPath) @@ -403,42 +439,35 @@ bool Filesystem::unmount(const char *archive) return true; } - std::string realPath; - std::string sourceBase = getSourceBaseDirectory(); + if (PHYSFS_getRealDir(archive) != nullptr) + return PHYSFS_unmount(archive) != 0; - // Check whether the given archive path is in the list of allowed full paths. - auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive); + if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0) + return false; - if (it != allowedMountPaths.end()) - realPath = *it; - else if (isFused() && sourceBase.compare(archive) == 0) - { - // Special case: if the game is fused and the archive is the source's - // base directory, unmount it even though it's outside of the save dir. - realPath = sourceBase; - } - else - { - // Not allowed for safety reasons. - if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0) - return false; + const char *realDir = PHYSFS_getRealDir(archive); + if (!realDir) + return false; - const char *realDir = PHYSFS_getRealDir(archive); - if (!realDir) - return false; + std::string realPath = realDir; + realPath += LOVE_PATH_SEPARATOR; + realPath += archive; - realPath = realDir; - realPath += LOVE_PATH_SEPARATOR; - realPath += archive; - } - - const char *mountPoint = PHYSFS_getMountPoint(realPath.c_str()); - if (!mountPoint) + if (PHYSFS_getMountPoint(realPath.c_str()) == nullptr) return false; return PHYSFS_unmount(realPath.c_str()) != 0; } +bool Filesystem::unmount(CommonPath path) +{ + std::string fullpath = getFullCommonPath(path); + if (fullpath.empty()) + return false; + + return unmount(fullpath.c_str()); +} + bool Filesystem::unmount(Data *data) { for (const auto &datapair : mountedData) @@ -458,6 +487,115 @@ love::filesystem::File *Filesystem::newFile(const char *filename) const return new File(filename); } +std::string Filesystem::getFullCommonPath(CommonPath path) +{ + if (!fullCommonPaths[path].empty()) + return fullCommonPaths[path]; + + if (path == COMMONPATH_APP_IDENTITY || path == COMMONPATH_APP_DOCUMENTS || path == COMMONPATH_APP_TEMP) + { + + } + +#if defined(LOVE_MACOS) || defined(LOVE_IOS) + + switch (path) + { + case COMMONPATH_APP_IDENTITY: + case COMMONPATH_APP_DOCUMENTS: + case COMMONPATH_APP_TEMP: + // Handled above. + break; + case COMMONPATH_USER_HOME: + fullCommonPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_HOME); + break; + case COMMONPATH_USER_APPDATA: + fullCommonPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_APPSUPPORT); + break; + case COMMONPATH_USER_DESKTOP: + fullCommonPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_DESKTOP); + break; + case COMMONPATH_USER_DOCUMENTS: + fullCommonPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_DOCUMENTS); + break; + case COMMONPATH_MAX_ENUM: + break; + } + +#elif defined(LOVE_WINDOWS) + + PWSTR winpath = nullptr; + HRESULT hr = E_FAIL; + + switch (path) + { + case COMMONPATH_APP_IDENTITY: + case COMMONPATH_APP_DOCUMENTS: + case COMMONPATH_APP_TEMP: + // Handled above. + break; + case COMMONPATH_USER_HOME: + hr = SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &winpath); + break; + case COMMONPATH_USER_APPDATA: + hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &winpath); + break; + case COMMONPATH_USER_DESKTOP: + hr = SHGetKnownFolderPath(FOLDERID_Desktop, 0, nullptr, &winpath); + break; + case COMMONPATH_USER_DOCUMENTS: + hr = SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &winpath); + break; + case COMMONPATH_MAX_ENUM: + break; + } + + if (SUCCEEDED(hr)) + { + fullCommonPaths[path] = to_utf8(winpath); + CoTaskMemFree(winpath); + } + else + { + + } + +#elif defined(LOVE_ANDROID) + +#elif defined(LOVE_LINUX) + + const char *xdgdir = nullptr; + + switch (path) + { + case COMMONPATH_APP_IDENTITY: + case COMMONPATH_APP_DOCUMENTS: + case COMMONPATH_APP_TEMP: + // Handled above. + break; + case COMMONPATH_USER_HOME: + fullCommonPaths[path] = normalize(PHYSFS_getUserDir()); + break; + case COMMONPATH_USER_APPDATA: + xdgdir = getenv("XDG_DATA_HOME"); + if (!xdgdir) + fullCommonPaths[path] = normalize(std::string(getUserDirectory()) + "/.local/share/"); + else + fullCommonPaths[path] = xdgdir; + break; + case COMMONPATH_USER_DESKTOP: + break; + case COMMONPATH_USER_DOCUMENTS: + break; + case COMMONPATH_MAX_ENUM: + break; + } + +#endif + + return fullCommonPaths[path]; +} + const char *Filesystem::getWorkingDirectory() { if (cwd.empty()) @@ -474,7 +612,7 @@ const char *Filesystem::getWorkingDirectory() if (getcwd(cwd_char, LOVE_MAX_PATH)) cwd = cwd_char; // if getcwd fails, cwd_char (and thus cwd) will still be empty - delete [] cwd_char; + delete[] cwd_char; #endif } @@ -483,9 +621,9 @@ const char *Filesystem::getWorkingDirectory() std::string Filesystem::getUserDirectory() { -#ifdef LOVE_IOS +#if defined(LOVE_IOS) || defined(LOVE_MACOS) // PHYSFS_getUserDir doesn't give exactly the path we want on iOS. - static std::string userDir = normalize(love::ios::getHomeDirectory()); + static std::string userDir = normalize(apple::getUserDirectory(apple::USER_DIRECTORY_HOME)); #else static std::string userDir = normalize(PHYSFS_getUserDir()); #endif @@ -512,10 +650,8 @@ std::string Filesystem::getAppdataDirectory() appdata = to_utf8(w_appdata); } replace_char(appdata, '\\', '/'); -#elif defined(LOVE_MACOS) - appdata = normalize(love::macos::getAppdataDirectory()); -#elif defined(LOVE_IOS) - appdata = normalize(love::ios::getAppdataDirectory()); +#elif defined(LOVE_MACOS) || defined(LOVE_IOS) + appdata = normalize(apple::getUserDirectory(apple::USER_DIRECTORY_APPSUPPORT)); #elif defined(LOVE_LINUX) char *xdgdatahome = getenv("XDG_DATA_HOME"); if (!xdgdatahome) @@ -529,7 +665,6 @@ std::string Filesystem::getAppdataDirectory() return appdata; } - const char *Filesystem::getSaveDirectory() { return save_path_full.c_str(); diff --git a/src/modules/filesystem/physfs/Filesystem.h b/src/modules/filesystem/physfs/Filesystem.h index b66417f02..2ccfa9dc7 100644 --- a/src/modules/filesystem/physfs/Filesystem.h +++ b/src/modules/filesystem/physfs/Filesystem.h @@ -63,11 +63,16 @@ public: bool mount(const char *archive, const char *mountpoint, bool appendToPath = false) override; bool mount(Data *data, const char *archivename, const char *mountpoint, bool appendToPath = false) override; + bool mountFullPath(const char *archive, const char *mountpoint, MountPermissions permissions, bool appendToPath = false) override; + bool mountCommonPath(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath = false) override; + bool unmount(const char *archive) override; bool unmount(Data *data) override; + bool unmount(CommonPath path) override; love::filesystem::File *newFile(const char *filename) const override; + std::string getFullCommonPath(CommonPath path) override; const char *getWorkingDirectory() override; std::string getUserDirectory() override; std::string getAppdataDirectory() override; @@ -129,6 +134,8 @@ private: std::map> mountedData; + std::string fullCommonPaths[COMMONPATH_MAX_ENUM]; + }; // Filesystem } // physfs diff --git a/src/modules/filesystem/wrap_Filesystem.cpp b/src/modules/filesystem/wrap_Filesystem.cpp index 4ccb04670..59bd2ada7 100644 --- a/src/modules/filesystem/wrap_Filesystem.cpp +++ b/src/modules/filesystem/wrap_Filesystem.cpp @@ -148,6 +148,48 @@ int w_mount(lua_State *L) return 1; } +int w_mountFullPath(lua_State *L) +{ + const char *fullpath = luaL_checkstring(L, 1); + const char *mountpoint = luaL_checkstring(L, 2); + + auto permissions = Filesystem::MOUNT_PERMISSIONS_READ; + if (!lua_isnoneornil(L, 3)) + { + const char *permissionstr = luaL_checkstring(L, 3); + if (!Filesystem::getConstant(permissionstr, permissions)) + return luax_enumerror(L, "mount permissions", Filesystem::getConstants(permissions), permissionstr); + } + + bool append = luax_optboolean(L, 4, false); + + luax_pushboolean(L, instance()->mountFullPath(fullpath, mountpoint, permissions, append)); + return 1; +} + +int w_mountCommonPath(lua_State *L) +{ + const char *commonpathstr = luaL_checkstring(L, 1); + Filesystem::CommonPath commonpath; + if (!Filesystem::getConstant(commonpathstr, commonpath)) + return luax_enumerror(L, "common path", Filesystem::getConstants(commonpath), commonpathstr); + + const char *mountpoint = luaL_checkstring(L, 2); + + auto permissions = Filesystem::MOUNT_PERMISSIONS_READ; + if (!lua_isnoneornil(L, 3)) + { + const char *permissionstr = luaL_checkstring(L, 3); + if (!Filesystem::getConstant(permissionstr, permissions)) + return luax_enumerror(L, "mount permissions", Filesystem::getConstants(permissions), permissionstr); + } + + bool append = luax_optboolean(L, 4, false); + + luax_pushboolean(L, instance()->mountCommonPath(commonpath, mountpoint, permissions, append)); + return 1; +} + int w_unmount(lua_State *L) { if (luax_istype(L, 1, Data::type)) @@ -163,6 +205,17 @@ int w_unmount(lua_State *L) return 1; } +int w_unmountCommonPath(lua_State *L) +{ + const char *commonpathstr = luaL_checkstring(L, 1); + Filesystem::CommonPath commonpath; + if (!Filesystem::getConstant(commonpathstr, commonpath)) + return luax_enumerror(L, "common path", Filesystem::getConstants(commonpath), commonpathstr); + + luax_pushboolean(L, instance()->unmount(commonpath)); + return 1; +} + int w_newFile(lua_State *L) { const char *filename = luaL_checkstring(L, 1); @@ -331,6 +384,17 @@ int w_newFileData(lua_State *L) return 1; } +int w_getFullCommonPath(lua_State *L) +{ + const char *commonpathstr = luaL_checkstring(L, 1); + Filesystem::CommonPath commonpath; + if (!Filesystem::getConstant(commonpathstr, commonpath)) + return luax_enumerror(L, "common path", Filesystem::getConstants(commonpath), commonpathstr); + + luax_pushstring(L, instance()->getFullCommonPath(commonpath)); + return 1; +} + int w_getWorkingDirectory(lua_State *L) { lua_pushstring(L, instance()->getWorkingDirectory()); @@ -839,8 +903,12 @@ static const luaL_Reg functions[] = { "setSource", w_setSource }, { "getSource", w_getSource }, { "mount", w_mount }, + { "mountFullPath", w_mountFullPath }, +// { "mountCommonPath", w_mountCommonPath }, { "unmount", w_unmount }, +// { "unmountCommonPath", w_unmountCommonPath }, { "newFile", w_newFile }, +// { "getFullCommonPath", w_getFullCommonPath }, { "getWorkingDirectory", w_getWorkingDirectory }, { "getUserDirectory", w_getUserDirectory }, { "getAppdataDirectory", w_getAppdataDirectory }, From 0fd53e2e3d3242f88fdb4f7c7074340dc596902d Mon Sep 17 00:00:00 2001 From: Jordan Christiansen Date: Wed, 9 Dec 2020 19:54:36 -0600 Subject: [PATCH 02/32] Disable GL_EXT_texture_array on OpenGL ES 2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Nvidia Tegra 3 driver for Android (used by Ouya) erroneously claims to support the desktop-oriented GL_EXT_texture_array extension. On desktop GPUs using (non ES) OpenGL, the glTexImage3D and similar functions are always present, and the GL_EXT_texture_array extension simply adds support for texture arrays to those functions. On OpenGL ES 2.0, those functions do not exist, and GL_OES_texture_3D is required to define them. Calling glTexImage3D on the Tegra 3 causes a segfault. This change works around the issue by manually setting GLAD_EXT_texture_array to 0 on OpenGL ES 2.0 systems, which causes LÖVE to avoid codepaths that assume that the 3D texture functions are available. It doesn't make sense for any OpenGL ES 2.0 GPU to claim support for GL_EXT_texture_array. Fixes #1647 --- src/modules/graphics/opengl/OpenGL.cpp | 24 +++++++++++++------- src/modules/graphics/wrap_GraphicsShader.lua | 9 ++++---- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index f2fcbb01a..cae7e74ec 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -391,15 +391,23 @@ void OpenGL::initOpenGLFunctions() } } - if (GLAD_ES_VERSION_2_0 && GLAD_OES_texture_3D && !GLAD_ES_VERSION_3_0) + if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0) { - // Function signatures don't match, we'll have to conditionally call it - //fp_glTexImage3D = fp_glTexImage3DOES; - fp_glTexSubImage3D = fp_glTexSubImage3DOES; - fp_glCopyTexSubImage3D = fp_glCopyTexSubImage3DOES; - fp_glCompressedTexImage3D = fp_glCompressedTexImage3DOES; - fp_glCompressedTexSubImage3D = fp_glCompressedTexSubImage3DOES; - fp_glFramebufferTexture3D = fp_glFramebufferTexture3DOES; + // The Nvidia Tegra 3 driver (used by Ouya) claims to support GL_EXT_texture_array but + // segfaults if you actually try to use it. OpenGL ES 2.0 devices should use OES_texture_3D. + // GL_EXT_texture_array is for desktops. + GLAD_EXT_texture_array = false; + + if (GLAD_OES_texture_3D) + { + // Function signatures don't match, we'll have to conditionally call it + //fp_glTexImage3D = fp_glTexImage3DOES; + fp_glTexSubImage3D = fp_glTexSubImage3DOES; + fp_glCopyTexSubImage3D = fp_glCopyTexSubImage3DOES; + fp_glCompressedTexImage3D = fp_glCompressedTexImage3DOES; + fp_glCompressedTexSubImage3D = fp_glCompressedTexSubImage3DOES; + fp_glFramebufferTexture3D = fp_glFramebufferTexture3DOES; + } } if (!GLAD_VERSION_3_2 && !GLAD_ES_VERSION_3_2 && !GLAD_ARB_draw_elements_base_vertex) diff --git a/src/modules/graphics/wrap_GraphicsShader.lua b/src/modules/graphics/wrap_GraphicsShader.lua index 095f21a19..2b4335240 100644 --- a/src/modules/graphics/wrap_GraphicsShader.lua +++ b/src/modules/graphics/wrap_GraphicsShader.lua @@ -54,7 +54,8 @@ GLSL.SYNTAX = [[ #define DepthCubeImage samplerCubeShadow #endif #define extern uniform -#ifdef GL_EXT_texture_array +#if defined(GL_EXT_texture_array) && (!defined(GL_ES) || __VERSION__ > 100) +#define texture_arrays_enabled #extension GL_EXT_texture_array : enable #endif #ifdef GL_OES_texture_3D @@ -85,7 +86,7 @@ uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_ScreenSize; GLSL.FUNCTIONS = [[ #ifdef GL_ES - #if __VERSION__ >= 300 || defined(GL_EXT_texture_array) + #if __VERSION__ >= 300 || defined(texture_arrays_enabled) precision lowp sampler2DArray; #endif #if __VERSION__ >= 300 || defined(GL_OES_texture_3D) @@ -121,7 +122,7 @@ GLSL.FUNCTIONS = [[ #if __VERSION__ > 100 || defined(GL_OES_texture_3D) vec4 Texel(sampler3D s, vec3 c) { return love_texture3D(s, c); } #endif - #if __VERSION__ >= 130 || defined(GL_EXT_texture_array) + #if __VERSION__ >= 130 || defined(texture_arrays_enabled) vec4 Texel(sampler2DArray s, vec3 c) { return love_texture2DArray(s, c); } #endif #ifdef PIXEL @@ -130,7 +131,7 @@ GLSL.FUNCTIONS = [[ #if __VERSION__ > 100 || defined(GL_OES_texture_3D) vec4 Texel(sampler3D s, vec3 c, float b) { return love_texture3D(s, c, b); } #endif - #if __VERSION__ >= 130 || defined(GL_EXT_texture_array) + #if __VERSION__ >= 130 || defined(texture_arrays_enabled) vec4 Texel(sampler2DArray s, vec3 c, float b) { return love_texture2DArray(s, c, b); } #endif #endif From f3afdee70a3666fb774be99389c92da994f48666 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Wed, 23 Dec 2020 12:58:26 -0400 Subject: [PATCH 03/32] Keep math.mod and string.gfind when LuaJIT 2.1 is used Code written assuming LuaJIT 2.0 is being used might rely on them, even though they're deprecated (and removed in LuaJIT 2.1). --- src/modules/love/love.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/modules/love/love.cpp b/src/modules/love/love.cpp index 1dce17077..693952a68 100644 --- a/src/modules/love/love.cpp +++ b/src/modules/love/love.cpp @@ -362,6 +362,23 @@ static int w_deprecation__gc(lua_State *) return 0; } +static void luax_addcompatibilityalias(lua_State *L, const char *module, const char *name, const char *alias) +{ + lua_getglobal(L, module); + if (lua_istable(L, -1)) + { + lua_getfield(L, -1, alias); + bool hasalias = !lua_isnoneornil(L, -1); + lua_pop(L, 1); + if (!hasalias) + { + lua_getfield(L, -1, name); + lua_setfield(L, -2, alias); + } + } + lua_pop(L, 1); +} + int luaopen_love(lua_State *L) { love::luax_insistpinnedthread(L); @@ -469,6 +486,13 @@ int luaopen_love(lua_State *L) love::luax_require(L, "love.data"); lua_pop(L, 1); +#if LUA_VERSION_NUM <= 501 + // These are deprecated in Lua 5.1. LuaJIT 2.1 removes them, but code + // written assuming LuaJIT 2.0 or Lua 5.1 is used might still rely on them. + luax_addcompatibilityalias(L, "math", "fmod", "mod"); + luax_addcompatibilityalias(L, "string", "gmatch", "gfind"); +#endif + #ifdef LOVE_ENABLE_LUASOCKET love::luasocket::__open(L); #endif From ae4fe5bff9d024af98d2cf33a5cf553d7a3b3bf5 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Wed, 23 Dec 2020 13:00:24 -0400 Subject: [PATCH 04/32] Update Xcode project files --- platform/xcode/liblove.xcodeproj/project.pbxproj | 2 +- platform/xcode/love.xcodeproj/project.pbxproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index 04886b3a2..1abcea551 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -4149,7 +4149,7 @@ 08FB7793FE84155DC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1000; + LastUpgradeCheck = 1230; TargetAttributes = { FA0B78DC1A958B90000E1D17 = { CreatedOnToolsVersion = 6.1.1; diff --git a/platform/xcode/love.xcodeproj/project.pbxproj b/platform/xcode/love.xcodeproj/project.pbxproj index 38e3e5605..20e6eb592 100644 --- a/platform/xcode/love.xcodeproj/project.pbxproj +++ b/platform/xcode/love.xcodeproj/project.pbxproj @@ -328,7 +328,7 @@ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1000; + LastUpgradeCheck = 1230; TargetAttributes = { FA0B7F051A95AAF3000E1D17 = { CreatedOnToolsVersion = 6.1.1; From 48a13d443b739d98f6ba95b6db73f102b5b787ec Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Wed, 23 Dec 2020 20:42:12 -0400 Subject: [PATCH 05/32] Fix validating glsl1 shaders using array textures on GLES3 --- src/modules/graphics/wrap_GraphicsShader.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/modules/graphics/wrap_GraphicsShader.lua b/src/modules/graphics/wrap_GraphicsShader.lua index 3fbe9e10b..81f793482 100644 --- a/src/modules/graphics/wrap_GraphicsShader.lua +++ b/src/modules/graphics/wrap_GraphicsShader.lua @@ -54,8 +54,11 @@ GLSL.SYNTAX = [[ #define DepthCubeImage samplerCubeShadow #endif #define extern uniform -#if defined(GL_EXT_texture_array) && (!defined(GL_ES) || __VERSION__ > 100) -#define texture_arrays_enabled +#if defined(GL_EXT_texture_array) && (!defined(GL_ES) || __VERSION__ > 100 || defined(GL_OES_gpu_shader5)) +// Only used when !GLSLES1 to work around Ouya driver bug. But we still want it +// enabled for glslang validation when glsl 1-on-3 is used, so also enable it if +// OES_gpu_shader5 exists. +#define LOVE_EXT_TEXTURE_ARRAY_ENABLED #extension GL_EXT_texture_array : enable #endif #ifdef GL_OES_texture_3D @@ -86,7 +89,7 @@ uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_ScreenSize; GLSL.FUNCTIONS = [[ #ifdef GL_ES - #if __VERSION__ >= 300 || defined(texture_arrays_enabled) + #if __VERSION__ >= 300 || defined(LOVE_EXT_TEXTURE_ARRAY_ENABLED) precision lowp sampler2DArray; #endif #if __VERSION__ >= 300 || defined(GL_OES_texture_3D) @@ -122,7 +125,7 @@ GLSL.FUNCTIONS = [[ #if __VERSION__ > 100 || defined(GL_OES_texture_3D) vec4 Texel(sampler3D s, vec3 c) { return love_texture3D(s, c); } #endif - #if __VERSION__ >= 130 || defined(texture_arrays_enabled) + #if __VERSION__ >= 130 || defined(LOVE_EXT_TEXTURE_ARRAY_ENABLED) vec4 Texel(sampler2DArray s, vec3 c) { return love_texture2DArray(s, c); } #endif #ifdef PIXEL @@ -131,7 +134,7 @@ GLSL.FUNCTIONS = [[ #if __VERSION__ > 100 || defined(GL_OES_texture_3D) vec4 Texel(sampler3D s, vec3 c, float b) { return love_texture3D(s, c, b); } #endif - #if __VERSION__ >= 130 || defined(texture_arrays_enabled) + #if __VERSION__ >= 130 || defined(LOVE_EXT_TEXTURE_ARRAY_ENABLED) vec4 Texel(sampler2DArray s, vec3 c, float b) { return love_texture2DArray(s, c, b); } #endif #endif From 57a2a88a09f43d989592119531c2b006b6cea466 Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Thu, 24 Dec 2020 22:15:15 +0800 Subject: [PATCH 06/32] Android: Rename "openURL" Java method to "openURLFromLOVE" SDL 2.0.14 adds openURL method which conflicts with LOVE one. --- src/common/android.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/common/android.cpp b/src/common/android.cpp index 0b7724ef0..22a6ccefa 100644 --- a/src/common/android.cpp +++ b/src/common/android.cpp @@ -148,7 +148,14 @@ bool openURL(const std::string &url) JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv(); jclass activity = env->FindClass("org/love2d/android/GameActivity"); - jmethodID openURL = env->GetStaticMethodID(activity, "openURL", "(Ljava/lang/String;)Z"); + jmethodID openURL = env->GetStaticMethodID(activity, "openURLFromLOVE", "(Ljava/lang/String;)Z"); + + if (openURL == nullptr) + { + env->ExceptionClear(); + openURL = env->GetStaticMethodID(activity, "openURL", "(Ljava/lang/String;)Z"); + } + jstring url_jstring = (jstring) env->NewStringUTF(url.c_str()); jboolean result = env->CallStaticBooleanMethod(activity, openURL, url_jstring); From 9e460ba53e5d4c56d7a45c7580580f3a22ff12d0 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Thu, 24 Dec 2020 12:29:00 -0400 Subject: [PATCH 07/32] Fix explicit window position not being restored in some cases when exiting fullscreen Fixes #1426 --- src/modules/window/sdl/Window.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index 81fccc233..2b1d635b5 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -481,7 +481,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) int x = f.x; int y = f.y; - if (f.useposition && !f.fullscreen) + if (f.useposition) { // The position needs to be in the global coordinate space. SDL_Rect displaybounds = {}; @@ -511,7 +511,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) // Enforce minimum window dimensions. SDL_SetWindowMinimumSize(window, f.minwidth, f.minheight); - if ((f.useposition || f.centered) && !f.fullscreen) + if (f.useposition || f.centered) SDL_SetWindowPosition(window, x, y); SDL_RaiseWindow(window); From 90bd8f18112d5ceb44a8303a6cba239c08865d55 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Thu, 24 Dec 2020 12:45:08 -0400 Subject: [PATCH 08/32] Update version --- extra/appveyor/appveyor.yml | 2 +- extra/windows/love.rc | Bin 3632 -> 3632 bytes platform/unix/love.6 | 2 +- platform/xcode/ios/love-ios.plist | 2 +- .../xcode/liblove.xcodeproj/project.pbxproj | 3 +++ platform/xcode/love.xcodeproj/project.pbxproj | 6 ++++++ platform/xcode/macosx/liblove-macosx.plist | 2 +- platform/xcode/macosx/love-macosx.plist | 2 +- src/common/version.h | 6 +++--- 9 files changed, 17 insertions(+), 8 deletions(-) diff --git a/extra/appveyor/appveyor.yml b/extra/appveyor/appveyor.yml index b081aa9a3..639d824db 100644 --- a/extra/appveyor/appveyor.yml +++ b/extra/appveyor/appveyor.yml @@ -1,4 +1,4 @@ -version: 11.3.{build} +version: 11.4.{build} image: Visual Studio 2013 diff --git a/extra/windows/love.rc b/extra/windows/love.rc index dd0b6b9976921f3430d23acd9a283a5aa19cb8c1..fc8777529416fbc735952c8ca6de3f7d5a493e82 100644 GIT binary patch delta 26 icmdlWvq5Hq3JbFdgVN+i7Wv6;ELMyrn;Tiya{>Tls0Td& delta 26 icmdlWvq5Hq3JbF_gVN+i7Wv6;ELM!hn;Tiya{>TlkOw;e diff --git a/platform/unix/love.6 b/platform/unix/love.6 index b621dd229..a2eb4c3d6 100644 --- a/platform/unix/love.6 +++ b/platform/unix/love.6 @@ -12,7 +12,7 @@ .\" 3. This notice may not be removed or altered from any source distribution. .Dd March 31, 2018 .Dt LOVE 6 -.Os LÖVE 11.3 +.Os LÖVE 11.4 .Sh NAME .Nm love .Nd 2D game development framework diff --git a/platform/xcode/ios/love-ios.plist b/platform/xcode/ios/love-ios.plist index 6abf7e335..21b2c64e5 100644 --- a/platform/xcode/ios/love-ios.plist +++ b/platform/xcode/ios/love-ios.plist @@ -36,7 +36,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 11.3 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index 1abcea551..0b3dbf5be 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -5330,6 +5330,7 @@ "$(inherited)", "$(PROJECT_DIR)/ios/libraries/freetype", ); + MARKETING_VERSION = 11.4; OTHER_LDFLAGS = ( "-undefined", dynamic_lookup, @@ -5373,6 +5374,7 @@ "$(inherited)", "$(PROJECT_DIR)/ios/libraries/freetype", ); + MARKETING_VERSION = 11.4; OTHER_LDFLAGS = ( "-undefined", dynamic_lookup, @@ -5417,6 +5419,7 @@ "$(inherited)", "$(PROJECT_DIR)/ios/libraries/freetype", ); + MARKETING_VERSION = 11.4; OTHER_LDFLAGS = ( "-undefined", dynamic_lookup, diff --git a/platform/xcode/love.xcodeproj/project.pbxproj b/platform/xcode/love.xcodeproj/project.pbxproj index 20e6eb592..67ea81532 100644 --- a/platform/xcode/love.xcodeproj/project.pbxproj +++ b/platform/xcode/love.xcodeproj/project.pbxproj @@ -462,6 +462,7 @@ INFOPLIST_FILE = "macosx/love-macosx.plist"; INSTALL_PATH = /Applications; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MARKETING_VERSION = 11.4; PRODUCT_BUNDLE_IDENTIFIER = org.love2d.love; PRODUCT_NAME = love; }; @@ -495,6 +496,7 @@ INFOPLIST_FILE = "macosx/love-macosx.plist"; INSTALL_PATH = /Applications; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MARKETING_VERSION = 11.4; PRODUCT_BUNDLE_IDENTIFIER = org.love2d.love; PRODUCT_NAME = love; }; @@ -704,6 +706,7 @@ ); INFOPLIST_FILE = "$(SRCROOT)/ios/love-ios.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 11.4; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = org.love2d.love; PRODUCT_NAME = love; @@ -750,6 +753,7 @@ ); INFOPLIST_FILE = "$(SRCROOT)/ios/love-ios.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 11.4; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = org.love2d.love; PRODUCT_NAME = love; @@ -797,6 +801,7 @@ ); INFOPLIST_FILE = "$(SRCROOT)/ios/love-ios.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 11.4; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = org.love2d.love; PRODUCT_NAME = love; @@ -918,6 +923,7 @@ INFOPLIST_FILE = "macosx/love-macosx.plist"; INSTALL_PATH = /Applications; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MARKETING_VERSION = 11.4; PRODUCT_BUNDLE_IDENTIFIER = org.love2d.love; PRODUCT_NAME = love; }; diff --git a/platform/xcode/macosx/liblove-macosx.plist b/platform/xcode/macosx/liblove-macosx.plist index b76e815fe..606144ce4 100644 --- a/platform/xcode/macosx/liblove-macosx.plist +++ b/platform/xcode/macosx/liblove-macosx.plist @@ -17,7 +17,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 11.3 + $(MARKETING_VERSION) CFBundleSignature LoVe NSPrincipalClass diff --git a/platform/xcode/macosx/love-macosx.plist b/platform/xcode/macosx/love-macosx.plist index 479346944..a59b85fe2 100644 --- a/platform/xcode/macosx/love-macosx.plist +++ b/platform/xcode/macosx/love-macosx.plist @@ -62,7 +62,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 11.3 + $(MARKETING_VERSION) CFBundleSignature LoVe LSApplicationCategoryType diff --git a/src/common/version.h b/src/common/version.h index f72740fcd..b41a3d083 100644 --- a/src/common/version.h +++ b/src/common/version.h @@ -25,12 +25,12 @@ namespace love { // Version stuff. -#define LOVE_VERSION_STRING "11.3" +#define LOVE_VERSION_STRING "11.4" static const int VERSION_MAJOR = 11; -static const int VERSION_MINOR = 3; +static const int VERSION_MINOR = 4; static const int VERSION_REV = 0; static const char *VERSION = LOVE_VERSION_STRING; -static const char *VERSION_COMPATIBILITY[] = { VERSION, "11.0", "11.1", "11.2", 0 }; +static const char *VERSION_COMPATIBILITY[] = { VERSION, "11.0", "11.1", "11.2", "11.3", 0 }; static const char *VERSION_CODENAME = "Mysterious Mysteries"; } // love From fe23c1619897eb357e2f064a1cafe4f5f8926ef9 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Thu, 24 Dec 2020 20:31:33 -0400 Subject: [PATCH 09/32] Disable gamma correction on Windows + Intel HD 2/3000 / Intel HD Graphics It's completely broken there. Closes #1592 --- src/modules/graphics/opengl/Canvas.cpp | 9 --------- src/modules/graphics/opengl/Graphics.cpp | 4 ++-- src/modules/graphics/opengl/OpenGL.cpp | 16 ++++++++++++++-- src/modules/graphics/opengl/OpenGL.h | 7 +++++++ 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index 9bd14ab51..9a38c0305 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/src/modules/graphics/opengl/Canvas.cpp @@ -39,11 +39,6 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo glGenFramebuffers(1, &framebuffer); gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, framebuffer); - // Might work around an Intel driver bug: https://github.com/love2d/love/issues/1592 - bool current_srgb = gl.isStateEnabled(OpenGL::ENABLE_FRAMEBUFFER_SRGB); - if (current_srgb && isPixelFormatDepthStencil(format)) - gl.setEnableState(OpenGL::ENABLE_FRAMEBUFFER_SRGB, false); - if (texture != 0) { if (isPixelFormatDepthStencil(format) && (GLAD_ES_VERSION_3_0 || !GLAD_ES_VERSION_2_0)) @@ -110,10 +105,6 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); - // Restore sRGB state if we turned it off above. - if (current_srgb && isPixelFormatDepthStencil(format)) - gl.setEnableState(OpenGL::ENABLE_FRAMEBUFFER_SRGB, current_srgb); - return status; } diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 6b5e229be..d826479ee 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -198,8 +198,8 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Set whether drawing converts input from linear -> sRGB colorspace. - if (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB - || GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB) + if (!gl.bugs.brokenSRGB && (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB + || GLAD_EXT_framebuffer_sRGB || GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB)) { if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control) gl.setEnableState(OpenGL::ENABLE_FRAMEBUFFER_SRGB, isGammaCorrect()); diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 44d7f8767..a5757798f 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -155,6 +155,16 @@ bool OpenGL::initContext() if (strstr(device, "HD Graphics 4000") || strstr(device, "HD Graphics 2500")) bugs.clientWaitSyncStalls = true; } + + if (getVendor() == VENDOR_INTEL) + { + const char *device = (const char *) glGetString(GL_RENDERER); + if (strstr(device, "HD Graphics 3000") || strstr(device, "HD Graphics 2000") + || !strcmp(device, "Intel(R) HD Graphics") || !strcmp(device, "Intel(R) HD Graphics Family")) + { + bugs.brokenSRGB = true; + } + } #endif #ifdef LOVE_WINDOWS @@ -215,8 +225,8 @@ void OpenGL::setupContext() setEnableState(ENABLE_SCISSOR_TEST, state.enableState[ENABLE_SCISSOR_TEST]); setEnableState(ENABLE_FACE_CULL, state.enableState[ENABLE_FACE_CULL]); - if (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB - || GLAD_EXT_sRGB_write_control) + if (!bugs.brokenSRGB && (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB + || GLAD_EXT_framebuffer_sRGB || GLAD_EXT_sRGB_write_control)) { setEnableState(ENABLE_FRAMEBUFFER_SRGB, state.enableState[ENABLE_FRAMEBUFFER_SRGB]); } @@ -1717,6 +1727,8 @@ bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget, else return true; case PIXELFORMAT_sRGBA8: + if (gl.bugs.brokenSRGB) + return false; if (rendertarget) { if (GLAD_VERSION_1_0) diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index a1a6cb61a..a2467ad25 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/src/modules/graphics/opengl/OpenGL.h @@ -177,6 +177,13 @@ public: **/ bool brokenR8PixelFormat; + /** + * Intel HD Graphics drivers on Windows prior to the HD 2500/4000 have + * completely broken sRGB support. + * https://github.com/love2d/love/issues/1592 + **/ + bool brokenSRGB; + /** * Other bugs which have workarounds that don't use conditional code at * the moment: From 9c4db00e9490742317d4a493f5c11d7f7e200124 Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Fri, 25 Dec 2020 21:45:32 +0800 Subject: [PATCH 10/32] Android: Fix changing orientation with t.window.fullscreen = true Fixes love2d/love-android#196 --- src/modules/window/sdl/Window.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index 2b1d635b5..159c83550 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -440,8 +440,16 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) Uint32 sdlflags = SDL_WINDOW_OPENGL; - // On Android we always must have fullscreen type FULLSCREEN_TYPE_DESKTOP + // On Android, disable fullscreen first on window creation so it's + // possible to change the orientation by specifying portait width and + // height, otherwise SDL will pick the current orientation dimensions when + // fullscreen flag is set. Don't worry, we'll set it back later when user + // also requested fullscreen after the window is created. + // See https://github.com/love2d/love-android/issues/196 #ifdef LOVE_ANDROID + bool fullscreen = f.fullscreen; + + f.fullscreen = false; f.fstype = FULLSCREEN_DESKTOP; #endif @@ -527,8 +535,11 @@ bool Window::setWindow(int width, int height, WindowSettings *settings) graphics->setMode((int) scaledw, (int) scaledh, pixelWidth, pixelHeight, f.stencil); } + // Set fullscreen when user requested it before. + // See above for explanation. #ifdef LOVE_ANDROID - love::android::setImmersive(f.fullscreen); + setFullscreen(fullscreen); + love::android::setImmersive(fullscreen); #endif return true; From 4f730e930be0f7e814cadb4ac103e2457533ce2b Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Fri, 25 Dec 2020 16:13:29 -0400 Subject: [PATCH 11/32] Potential workaround for driver bug related to canvas mipmaps --- src/modules/graphics/opengl/Canvas.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index 9a38c0305..947c51d20 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/src/modules/graphics/opengl/Canvas.cpp @@ -60,12 +60,26 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo // Make sure all faces and layers of the texture are initialized to // transparent black. This is unfortunately probably pretty slow for // 2D-array and 3D textures with a lot of layers... + // Iterate backwards to make sure mip/layer/face 0 is bound at the end. for (int mip = nb_mips - 1; mip >= 0; mip--) { int nlayers = layers; if (texType == TEXTURE_VOLUME) nlayers = std::max(layers >> mip, 1); + GLuint tempframebuffer = 0; + if (mip > 0) + { + // Some Intel drivers on Windows don't like reusing the same + // FBO for different sized attachments, so use a temporary one + // to clear smaller mips. + // https://github.com/love2d/love/issues/1585 + glGenFramebuffers(1, &tempframebuffer); + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, tempframebuffer); + } + else + gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, framebuffer); + for (int layer = nlayers - 1; layer >= 0; layer--) { for (int face = faces - 1; face >= 0; face--) @@ -98,6 +112,9 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo } } } + + if (tempframebuffer != 0) + gl.deleteFramebuffer(tempframebuffer); } } From 95ab4054ff355b669f400f077feb31f09d3cdc59 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sat, 26 Dec 2020 16:37:37 -0400 Subject: [PATCH 12/32] Fix a freeze on Intel drivers when using mipmapped Canvases Fixes #1585 Also disallow generateMipmaps on depth/stencil canvases because it's not really supported. --- src/modules/graphics/Canvas.cpp | 3 +++ src/modules/graphics/opengl/Canvas.cpp | 34 ++++++++++++------------ src/modules/graphics/opengl/Graphics.cpp | 4 --- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/modules/graphics/Canvas.cpp b/src/modules/graphics/Canvas.cpp index e8179271b..682967c27 100644 --- a/src/modules/graphics/Canvas.cpp +++ b/src/modules/graphics/Canvas.cpp @@ -69,6 +69,9 @@ Canvas::Canvas(const Settings &settings) filter.mipmap = defaultMipmapFilter; } + if (settings.mipmaps == MIPMAPS_AUTO && isPixelFormatDepthStencil(format)) + throw love::Exception("Automatic mipmap generation cannot be used for depth/stencil Canvases."); + auto gfx = Module::getInstance(Module::M_GRAPHICS); const Graphics::Capabilities &caps = gfx->getCapabilities(); diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index 947c51d20..6d1a99745 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/src/modules/graphics/opengl/Canvas.cpp @@ -67,19 +67,6 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo if (texType == TEXTURE_VOLUME) nlayers = std::max(layers >> mip, 1); - GLuint tempframebuffer = 0; - if (mip > 0) - { - // Some Intel drivers on Windows don't like reusing the same - // FBO for different sized attachments, so use a temporary one - // to clear smaller mips. - // https://github.com/love2d/love/issues/1585 - glGenFramebuffers(1, &tempframebuffer); - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, tempframebuffer); - } - else - gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, framebuffer); - for (int layer = nlayers - 1; layer >= 0; layer--) { for (int face = faces - 1; face >= 0; face--) @@ -112,9 +99,6 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo } } } - - if (tempframebuffer != 0) - gl.deleteFramebuffer(tempframebuffer); } } @@ -278,8 +262,18 @@ bool Canvas::loadVolatile() return false; } + // All mipmap levels need to be initialized - for color formats we can + // clear the base mip and use glGenerateMipmap after that's done. Depth + // and stencil formats don't always support glGenerateMipmap so we need + // to individually clear each mip level in that case. We avoid doing that + // for color formats because of an Intel driver bug: + // https://github.com/love2d/love/issues/1585 + int clearmips = 1; + if (isPixelFormatDepthStencil(format)) + clearmips = mipmapCount; + // Create a canvas-local FBO used for glReadPixels as well as MSAA blitting. - status = createFBO(fbo, texType, format, texture, texType == TEXTURE_VOLUME ? depth : layers, mipmapCount); + status = createFBO(fbo, texType, format, texture, texType == TEXTURE_VOLUME ? depth : layers, clearmips); if (status != GL_FRAMEBUFFER_COMPLETE) { @@ -290,6 +284,9 @@ bool Canvas::loadVolatile() } return false; } + + if (clearmips < mipmapCount && getMipmapMode() != MIPMAPS_NONE) + generateMipmaps(); } if (!isReadable() || actualSamples > 0) @@ -488,6 +485,9 @@ void Canvas::generateMipmaps() if (getMipmapCount() == 1 || getMipmapMode() == MIPMAPS_NONE) throw love::Exception("generateMipmaps can only be called on a Canvas which was created with mipmaps enabled."); + if (isPixelFormatDepthStencil(format)) + throw love::Exception("generateMipmaps cannot be called on a depth/stencil Canvas."); + gl.bindTextureToUnit(this, 0, false); GLenum gltextype = OpenGL::getGLTextureType(texType); diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index d826479ee..5f11db41c 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -587,10 +587,6 @@ void Graphics::endPass() if (rt.canvas->getMipmapMode() == Canvas::MIPMAPS_AUTO && rt.mipmap == 0) rt.canvas->generateMipmaps(); } - - int dsmipmap = rts.depthStencil.mipmap; - if (depthstencil != nullptr && depthstencil->getMipmapMode() == Canvas::MIPMAPS_AUTO && dsmipmap == 0) - depthstencil->generateMipmaps(); } void Graphics::clear(OptionalColorf c, OptionalInt stencil, OptionalDouble depth) From 2682b75ebcef25f9a35a66dbedf692677e8ae846 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sat, 26 Dec 2020 22:55:06 -0400 Subject: [PATCH 13/32] Change macOS/iOS deployment targets to Xcode 12's minimums --- platform/xcode/liblove.xcodeproj/project.pbxproj | 12 ++++++------ platform/xcode/love.xcodeproj/project.pbxproj | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index b503078da..d9218572f 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -5103,10 +5103,10 @@ "\"$(SRCROOT)/../../src/modules\"", "\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"", ); - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "@rpath"; LIBRARY_SEARCH_PATHS = ""; - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = NO; SDKROOT = macosx; USE_HEADERMAP = NO; @@ -5169,10 +5169,10 @@ "\"$(SRCROOT)/../../src/modules\"", "\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"", ); - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "@rpath"; LIBRARY_SEARCH_PATHS = ""; - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; USE_HEADERMAP = NO; @@ -5352,11 +5352,11 @@ "\"$(SRCROOT)/../../src/modules\"", "\"$(SRCROOT)/../../src/libraries/enet/libenet/include\"", ); - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "@rpath"; LIBRARY_SEARCH_PATHS = ""; LLVM_LTO = YES; - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = NO; SDKROOT = macosx; USE_HEADERMAP = NO; diff --git a/platform/xcode/love.xcodeproj/project.pbxproj b/platform/xcode/love.xcodeproj/project.pbxproj index 7b78fdd03..e54d93fc6 100644 --- a/platform/xcode/love.xcodeproj/project.pbxproj +++ b/platform/xcode/love.xcodeproj/project.pbxproj @@ -557,9 +557,9 @@ "\"$(SRCROOT)/../../src/modules\"", ); INFOPLIST_FILE = "love-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = ""; "OTHER_LDFLAGS[arch=x86_64]" = ( @@ -638,10 +638,10 @@ "\"$(SRCROOT)/../../src/modules\"", ); INFOPLIST_FILE = "love-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LLVM_LTO = YES; - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = NO; OTHER_LDFLAGS = ""; "OTHER_LDFLAGS[arch=x86_64]" = ( @@ -869,10 +869,10 @@ "\"$(SRCROOT)/../../src/modules\"", ); INFOPLIST_FILE = "love-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LLVM_LTO = YES; - MACOSX_DEPLOYMENT_TARGET = 10.7; + MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = NO; OTHER_LDFLAGS = ""; "OTHER_LDFLAGS[arch=x86_64]" = ( From 769fcf5fc6b2588ea1f527a37cfc20ca799cc207 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sun, 27 Dec 2020 15:01:09 -0400 Subject: [PATCH 14/32] macOS/iOS: allow c++17 language features --- platform/xcode/liblove.xcodeproj/project.pbxproj | 6 +++--- platform/xcode/love.xcodeproj/project.pbxproj | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index d9218572f..a17af7108 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -5055,7 +5055,7 @@ 10D5479E63C26BB35EB5482E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -5117,7 +5117,7 @@ 64274E785071353E1A1D0D4B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -5304,7 +5304,7 @@ FA5326C4189719C700F7BBF4 /* Distribution */ = { isa = XCBuildConfiguration; buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; diff --git a/platform/xcode/love.xcodeproj/project.pbxproj b/platform/xcode/love.xcodeproj/project.pbxproj index e54d93fc6..326654779 100644 --- a/platform/xcode/love.xcodeproj/project.pbxproj +++ b/platform/xcode/love.xcodeproj/project.pbxproj @@ -503,7 +503,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -582,7 +582,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -813,7 +813,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; From 2a5a90ccef1286a8018f082831cfcdcdd8cf0504 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sun, 27 Dec 2020 19:17:47 -0400 Subject: [PATCH 15/32] Add 'vertexmain' and 'pixelmain' shader entry points. These are low level 'raw' entry points which don't declare any inputs or outputs themselves. They require GLSL 3 to use. --- src/modules/graphics/Graphics.cpp | 4 +- src/modules/graphics/Shader.cpp | 120 ++++++++++++++++++++++-------- src/modules/graphics/Shader.h | 11 ++- 3 files changed, 98 insertions(+), 37 deletions(-) diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index fe2ae7339..f0c2cdfd7 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -268,7 +268,7 @@ Shader *Graphics::newShader(const std::vector &stagessource) if (!validstages[i]) continue; - if (info.isStage[i]) + if (info.stages[i] != Shader::ENTRYPOINT_NONE) { isanystage = true; stages[i].set(newShaderStage((ShaderStage::StageType) i, source, info), Acquire::NORETAIN); @@ -347,7 +347,7 @@ bool Graphics::validateShader(bool gles, const std::vector &stagess if (!validstages[i]) continue; - if (info.isStage[i]) + if (info.stages[i] != Shader::ENTRYPOINT_NONE) { isanystage = true; std::string glsl = Shader::createShaderStageCode(this, stype, source, info); diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index c45689e9c..c64770640 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -38,6 +38,7 @@ namespace graphics namespace glsl { + static const char global_syntax[] = R"( #if !defined(GL_ES) && __VERSION__ < 140 #define lowp @@ -275,6 +276,16 @@ void main() { } )"; +static const char vertex_main_raw[] = R"( +void vertexmain(); + +void main() { + love_initializeBuiltinUniforms(); + setPointSize(); + vertexmain(); +} +)"; + static const char pixel_header[] = R"( #ifdef GL_ES precision mediump float; @@ -284,29 +295,10 @@ static const char pixel_header[] = R"( #if __VERSION__ >= 130 #define varying in - // Some drivers seem to make the pixel shader do more work when multiple - // pixel shader outputs are defined, even when only one is actually used. - // TODO: We should use reflection or something instead of this, to determine - // how many outputs are actually used in the shader code. - #ifdef LOVE_MULTI_RENDER_TARGETS - layout(location = 0) out vec4 love_RenderTargets[love_MaxRenderTargets]; - #define love_PixelColor love_RenderTargets[0] - #else - layout(location = 0) out vec4 love_PixelColor; - #endif -#else - #ifdef LOVE_MULTI_RENDER_TARGETS - #define love_RenderTargets gl_FragData - #endif - #define love_PixelColor gl_FragColor #endif // Legacy #define love_MaxCanvases love_MaxRenderTargets -#define love_Canvases love_RenderTargets -#ifdef LOVE_MULTI_RENDER_TARGETS -#define LOVE_MULTI_CANVASES 1 -#endif // See Shader::updateScreenParams in Shader.cpp. #define love_PixelCoord (vec2(gl_FragCoord.x, (gl_FragCoord.y * love_ScreenSize.z) + love_ScreenSize.w)) @@ -335,6 +327,12 @@ vec4 VideoTexel(vec2 texcoords) { )"; static const char pixel_main[] = R"( +#if __VERSION__ >= 130 + layout(location = 0) out vec4 love_PixelColor; +#else + #define love_PixelColor gl_FragColor +#endif + uniform sampler2D MainTex; varying LOVE_HIGHP_OR_MEDIUMP vec4 VaryingTexCoord; varying mediump vec4 VaryingColor; @@ -348,6 +346,30 @@ void main() { )"; static const char pixel_main_custom[] = R"( +#if __VERSION__ >= 130 + // Some drivers seem to make the pixel shader do more work when multiple + // pixel shader outputs are defined, even when only one is actually used. + // TODO: We should use reflection or something instead of this, to determine + // how many outputs are actually used in the shader code. + #ifdef LOVE_MULTI_RENDER_TARGETS + layout(location = 0) out vec4 love_RenderTargets[love_MaxRenderTargets]; + #define love_PixelColor love_RenderTargets[0] + #else + layout(location = 0) out vec4 love_PixelColor; + #endif +#else + #ifdef LOVE_MULTI_RENDER_TARGETS + #define love_RenderTargets gl_FragData + #endif + #define love_PixelColor gl_FragColor +#endif + +// Legacy +#define love_Canvases love_RenderTargets +#ifdef LOVE_MULTI_RENDER_TARGETS +#define LOVE_MULTI_CANVASES 1 +#endif + varying LOVE_HIGHP_OR_MEDIUMP vec4 VaryingTexCoord; varying mediump vec4 VaryingColor; @@ -359,6 +381,15 @@ void main() { } )"; +static const char pixel_main_raw[] = R"( +void pixelmain(); + +void main() { + love_initializeBuiltinUniforms(); + pixelmain(); +} +)"; + struct StageInfo { const char *name; @@ -366,12 +397,13 @@ struct StageInfo const char *functions; const char *main; const char *main_custom; + const char *main_raw; }; static const StageInfo stageInfo[] = { - { "VERTEX", vertex_header, vertex_functions, vertex_main, vertex_main }, - { "PIXEL", pixel_header, pixel_functions, pixel_main, pixel_main_custom }, + { "VERTEX", vertex_header, vertex_functions, vertex_main, vertex_main, vertex_main_raw }, + { "PIXEL", pixel_header, pixel_functions, pixel_main, pixel_main_custom, pixel_main_raw }, }; static_assert((sizeof(stageInfo) / sizeof(StageInfo)) == ShaderStage::STAGE_MAX_ENUM, "Stages array size must match ShaderStage enum."); @@ -400,30 +432,38 @@ static Shader::Language getTargetLanguage(const std::string &src) return lang; } -static bool isVertexCode(const std::string &src) +static Shader::EntryPoint getVertexEntryPoint(const std::string &src) { - std::regex r("vec4\\s+position\\s*\\("); std::smatch m; - return std::regex_search(src, m, r); + + if (std::regex_search(src, m, std::regex("void\\s+vertexmain\\s*\\("))) + return Shader::ENTRYPOINT_RAW; + + if (std::regex_search(src, m, std::regex("vec4\\s+position\\s*\\("))) + return Shader::ENTRYPOINT_HIGHLEVEL; + + return Shader::ENTRYPOINT_NONE; } -static bool isPixelCode(const std::string &src, bool &custompixel, bool &mrt) +static Shader::EntryPoint getPixelEntryPoint(const std::string &src, bool &mrt) { - custompixel = false; mrt = false; std::smatch m; + + if (std::regex_search(src, m, std::regex("void\\s+pixelmain\\s*\\("))) + return Shader::ENTRYPOINT_RAW; + if (std::regex_search(src, m, std::regex("vec4\\s+effect\\s*\\("))) - return true; + return Shader::ENTRYPOINT_HIGHLEVEL; if (std::regex_search(src, m, std::regex("void\\s+effect\\s*\\("))) { - custompixel = true; if (src.find("love_RenderTargets") != std::string::npos || src.find("love_Canvases") != std::string::npos) mrt = true; - return true; + return Shader::ENTRYPOINT_CUSTOM; } - return false; + return Shader::ENTRYPOINT_NONE; } } // glsl @@ -439,8 +479,8 @@ Shader::SourceInfo Shader::getSourceInfo(const std::string &src) { SourceInfo info = {}; info.language = glsl::getTargetLanguage(src); - info.isStage[ShaderStage::STAGE_VERTEX] = glsl::isVertexCode(src); - info.isStage[ShaderStage::STAGE_PIXEL] = glsl::isPixelCode(src, info.customPixelFunction, info.usesMRT); + info.stages[ShaderStage::STAGE_VERTEX] = glsl::getVertexEntryPoint(src); + info.stages[ShaderStage::STAGE_PIXEL] = glsl::getPixelEntryPoint(src, info.usesMRT); return info; } @@ -449,6 +489,12 @@ std::string Shader::createShaderStageCode(Graphics *gfx, ShaderStage::StageType if (info.language == Shader::LANGUAGE_MAX_ENUM) throw love::Exception("Invalid shader language"); + if (info.stages[stage] == ENTRYPOINT_NONE) + throw love::Exception("Cannot find entry point for shader stage."); + + if (info.stages[stage] == ENTRYPOINT_RAW && info.language == LANGUAGE_GLSL1) + throw love::Exception("Shaders using a raw entry point must use GLSL 3 or greater."); + const auto &features = gfx->getCapabilities().features; if (info.language == LANGUAGE_GLSL3 && !features[Graphics::FEATURE_GLSL3]) @@ -481,7 +527,15 @@ std::string Shader::createShaderStageCode(Graphics *gfx, ShaderStage::StageType ss << glsl::global_uniforms; ss << glsl::global_functions; ss << stageinfo.functions; - ss << (info.customPixelFunction ? stageinfo.main_custom : stageinfo.main); + + if (info.stages[stage] == ENTRYPOINT_HIGHLEVEL) + ss << stageinfo.main; + else if (info.stages[stage] == ENTRYPOINT_CUSTOM) + ss << stageinfo.main_custom; + else if (info.stages[stage] == ENTRYPOINT_RAW) + ss << stageinfo.main_raw; + else + throw love::Exception("Unknown shader entry point %d", info.stages[stage]); ss << ((!gles && (lang == Shader::LANGUAGE_GLSL1 || glsl1on3)) ? "#line 0\n" : "#line 1\n"); ss << code; diff --git a/src/modules/graphics/Shader.h b/src/modules/graphics/Shader.h index 2c4717626..d75a6100c 100644 --- a/src/modules/graphics/Shader.h +++ b/src/modules/graphics/Shader.h @@ -90,11 +90,18 @@ public: STANDARD_MAX_ENUM }; + enum EntryPoint + { + ENTRYPOINT_NONE, + ENTRYPOINT_HIGHLEVEL, + ENTRYPOINT_CUSTOM, + ENTRYPOINT_RAW, + }; + struct SourceInfo { Language language; - bool isStage[ShaderStage::STAGE_MAX_ENUM]; - bool customPixelFunction; + EntryPoint stages[ShaderStage::STAGE_MAX_ENUM]; bool usesMRT; }; From 2fb88cb4a5f08f34675f6020c618b210dd66a862 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sun, 27 Dec 2020 19:52:27 -0400 Subject: [PATCH 16/32] Remove some obsolete code --- src/modules/graphics/opengl/Texture.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/modules/graphics/opengl/Texture.cpp b/src/modules/graphics/opengl/Texture.cpp index dd027702d..2d7c8a62f 100644 --- a/src/modules/graphics/opengl/Texture.cpp +++ b/src/modules/graphics/opengl/Texture.cpp @@ -42,11 +42,6 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo glGenFramebuffers(1, &framebuffer); gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, framebuffer); - // Intel driver bug: https://github.com/love2d/love/issues/1592 - bool current_srgb = gl.isStateEnabled(OpenGL::ENABLE_FRAMEBUFFER_SRGB); - if (current_srgb && isPixelFormatDepthStencil(format)) - gl.setEnableState(OpenGL::ENABLE_FRAMEBUFFER_SRGB, false); - if (texture != 0) { if (isPixelFormatDepthStencil(format) && (GLAD_ES_VERSION_3_0 || !GLAD_ES_VERSION_2_0)) @@ -112,10 +107,6 @@ static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat fo gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, current_fbo); - // Restore sRGB state if we turned it off above. - if (current_srgb && isPixelFormatDepthStencil(format)) - gl.setEnableState(OpenGL::ENABLE_FRAMEBUFFER_SRGB, current_srgb); - return status; } From 562cc6b1cbbba34a6d9db100a4cb563d56993cee Mon Sep 17 00:00:00 2001 From: Qais Patankar Date: Wed, 30 Dec 2020 04:27:48 +0000 Subject: [PATCH 17/32] Fix #1614: add Body:getLocalPoints --- src/modules/physics/box2d/Body.cpp | 24 ++++++++++++++++++++++++ src/modules/physics/box2d/Body.h | 6 ++++++ src/modules/physics/box2d/wrap_Body.cpp | 8 ++++++++ 3 files changed, 38 insertions(+) diff --git a/src/modules/physics/box2d/Body.cpp b/src/modules/physics/box2d/Body.cpp index 2f8301be0..446be6b8b 100644 --- a/src/modules/physics/box2d/Body.cpp +++ b/src/modules/physics/box2d/Body.cpp @@ -346,6 +346,30 @@ void Body::getLocalVector(float x, float y, float &x_o, float &y_o) y_o = v.y; } +int Body::getLocalPoints(lua_State *L) +{ + int argc = lua_gettop(L); + int vcount = (int)argc/2; + // at least one point + love::luax_assert_argc(L, 2); + + for (int i = 0; iGetLocalPoint(Physics::scaleDown(b2Vec2(x, y)))); + // And then we push the result + lua_pushnumber(L, point.x); + lua_pushnumber(L, point.y); + } + + return argc; +} + void Body::getLinearVelocityFromWorldPoint(float x, float y, float &x_o, float &y_o) { b2Vec2 v = Physics::scaleUp(body->GetLinearVelocityFromWorldPoint(Physics::scaleDown(b2Vec2(x, y)))); diff --git a/src/modules/physics/box2d/Body.h b/src/modules/physics/box2d/Body.h index 6c2edcc62..b279fa0d6 100644 --- a/src/modules/physics/box2d/Body.h +++ b/src/modules/physics/box2d/Body.h @@ -317,6 +317,12 @@ public: **/ void getLocalVector(float x, float y, float &x_o, float &y_o); + /** + * Transforms a series of points (x, y) from world coordinates + * to local coordinates. + **/ + int getLocalPoints(lua_State *L); + /** * Gets the velocity on the Body for the given world point. * @param x The x-coordinate of the world point. diff --git a/src/modules/physics/box2d/wrap_Body.cpp b/src/modules/physics/box2d/wrap_Body.cpp index af57ce766..2b303a63d 100644 --- a/src/modules/physics/box2d/wrap_Body.cpp +++ b/src/modules/physics/box2d/wrap_Body.cpp @@ -444,6 +444,13 @@ int w_Body_getLocalVector(lua_State *L) return 2; } +int w_Body_getLocalPoints(lua_State *L) +{ + Body *t = luax_checkbody(L, 1); + lua_remove(L, 1); + return t->getLocalPoints(L); +} + int w_Body_getLinearVelocityFromWorldPoint(lua_State *L) { Body *t = luax_checkbody(L, 1); @@ -679,6 +686,7 @@ static const luaL_Reg w_Body_functions[] = { "getWorldPoints", w_Body_getWorldPoints }, { "getLocalPoint", w_Body_getLocalPoint }, { "getLocalVector", w_Body_getLocalVector }, + { "getLocalPoints", w_Body_getLocalPoints }, { "getLinearVelocityFromWorldPoint", w_Body_getLinearVelocityFromWorldPoint }, { "getLinearVelocityFromLocalPoint", w_Body_getLinearVelocityFromLocalPoint }, { "isBullet", w_Body_isBullet }, From 0ec7ea73de77660608fba8e054e671310f2199ae Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Wed, 30 Dec 2020 11:46:00 -0400 Subject: [PATCH 18/32] Improve error message slightly --- src/modules/graphics/Shader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index c64770640..df1990fc3 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -493,7 +493,7 @@ std::string Shader::createShaderStageCode(Graphics *gfx, ShaderStage::StageType throw love::Exception("Cannot find entry point for shader stage."); if (info.stages[stage] == ENTRYPOINT_RAW && info.language == LANGUAGE_GLSL1) - throw love::Exception("Shaders using a raw entry point must use GLSL 3 or greater."); + throw love::Exception("Shaders using a raw entry point (vertexmain or pixelmain) must use GLSL 3 or greater."); const auto &features = gfx->getCapabilities().features; From 7524677d4c8c09fd1cabec9a36b89df6c4279036 Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Thu, 31 Dec 2020 10:32:05 +0800 Subject: [PATCH 19/32] Don't use `feof` to check for end-of-file in DroppedFile. Fixes #1551 Apparently `feof` is only set when `fread(..., size, count, ...) < size * count` happends, however we actually have code that automatically adjust amount of bytes to read (and at "eof" it really happends to be 0) so fread is never able to set the EOF flag. --- src/modules/filesystem/DroppedFile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/filesystem/DroppedFile.cpp b/src/modules/filesystem/DroppedFile.cpp index 5ed9773f1..5a602ff04 100644 --- a/src/modules/filesystem/DroppedFile.cpp +++ b/src/modules/filesystem/DroppedFile.cpp @@ -165,7 +165,7 @@ bool DroppedFile::flush() bool DroppedFile::isEOF() { - return file == nullptr || feof(file) != 0; + return file == nullptr || tell() >= getSize(); } int64 DroppedFile::tell() From 9e9fa715899cbff178be7a6fe4b758fd6b4bef2e Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Wed, 30 Dec 2020 22:54:08 -0400 Subject: [PATCH 20/32] physfs: Add PHYSFS_mountRW --- src/libraries/physfs/physfs.c | 79 ++++++++++++++++++++++++++--------- src/libraries/physfs/physfs.h | 8 ++++ 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/src/libraries/physfs/physfs.c b/src/libraries/physfs/physfs.c index eec40b2bf..f2b241f47 100644 --- a/src/libraries/physfs/physfs.c +++ b/src/libraries/physfs/physfs.c @@ -49,6 +49,7 @@ typedef struct __PHYSFS_DIRHANDLE__ char *root; /* subdirectory of archiver to use as root of archive (NULL for actual root) */ size_t rootlen; /* subdirectory of archiver to use as root of archive (NULL for actual root) */ const PHYSFS_Archiver *funcs; /* Ptr to archiver info for this handle. */ + int forWriting; /* Whether this was opened for writing or not. */ struct __PHYSFS_DIRHANDLE__ *next; /* linked list stuff. */ } DirHandle; @@ -862,6 +863,7 @@ static DirHandle *tryOpenDir(PHYSFS_Io *io, const PHYSFS_Archiver *funcs, retval->mountPoint = NULL; retval->funcs = funcs; retval->opaque = opaque; + retval->forWriting = forWriting; } /* else */ } /* if */ @@ -1749,7 +1751,7 @@ int PHYSFS_setRoot(const char *archive, const char *subdir) static int doMount(PHYSFS_Io *io, const char *fname, - const char *mountPoint, int appendToPath) + const char *mountPoint, int appendToPath, int forWriting) { DirHandle *dh; DirHandle *prev = NULL; @@ -1770,7 +1772,7 @@ static int doMount(PHYSFS_Io *io, const char *fname, prev = i; } /* for */ - dh = createDirHandle(io, fname, mountPoint, 0); + dh = createDirHandle(io, fname, mountPoint, forWriting); BAIL_IF_MUTEX_ERRPASS(!dh, stateLock, 0); if (appendToPath) @@ -1797,7 +1799,7 @@ int PHYSFS_mountIo(PHYSFS_Io *io, const char *fname, BAIL_IF(!io, PHYSFS_ERR_INVALID_ARGUMENT, 0); BAIL_IF(!fname, PHYSFS_ERR_INVALID_ARGUMENT, 0); BAIL_IF(io->version != 0, PHYSFS_ERR_UNSUPPORTED, 0); - return doMount(io, fname, mountPoint, appendToPath); + return doMount(io, fname, mountPoint, appendToPath, 0); } /* PHYSFS_mountIo */ @@ -1813,7 +1815,7 @@ int PHYSFS_mountMemory(const void *buf, PHYSFS_uint64 len, void (*del)(void *), io = __PHYSFS_createMemoryIo(buf, len, del); BAIL_IF_ERRPASS(!io, 0); - retval = doMount(io, fname, mountPoint, appendToPath); + retval = doMount(io, fname, mountPoint, appendToPath, 0); if (!retval) { /* docs say not to call (del) in case of failure, so cheat. */ @@ -1837,7 +1839,7 @@ int PHYSFS_mountHandle(PHYSFS_File *file, const char *fname, io = __PHYSFS_createHandleIo(file); BAIL_IF_ERRPASS(!io, 0); - retval = doMount(io, fname, mountPoint, appendToPath); + retval = doMount(io, fname, mountPoint, appendToPath, 0); if (!retval) { /* docs say not to destruct in case of failure, so cheat. */ @@ -1852,7 +1854,14 @@ int PHYSFS_mountHandle(PHYSFS_File *file, const char *fname, int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath) { BAIL_IF(!newDir, PHYSFS_ERR_INVALID_ARGUMENT, 0); - return doMount(NULL, newDir, mountPoint, appendToPath); + return doMount(NULL, newDir, mountPoint, appendToPath, 0); +} /* PHYSFS_mount */ + + +int PHYSFS_mountRW(const char *newDir, const char *mountPoint, int appendToPath) +{ + BAIL_IF(!newDir, PHYSFS_ERR_INVALID_ARGUMENT, 0); + return doMount(NULL, newDir, mountPoint, appendToPath, 1); } /* PHYSFS_mount */ @@ -2159,10 +2168,39 @@ static int verifyPath(DirHandle *h, char **_fname, int allowMissing) } /* verifyPath */ -/* This must hold the stateLock before calling. */ -static int doMkdir(const char *_dname, char *dname) +static DirHandle *findWriteHandle(const char *_fname) +{ + DirHandle *i = NULL; + char *allocated_fname; + char *fname; + size_t len; + + len = strlen(_fname) + longest_root + 1; + allocated_fname = (char *) __PHYSFS_smallAlloc(len); + BAIL_IF(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + fname = allocated_fname + longest_root; + + if (sanitizePlatformIndependentPath(_fname, fname)) + { + for (i = searchPath; i != NULL; i = i->next) + { + char *arcfname = fname; + if (i->forWriting && verifyPath(i, &arcfname, 0)) + { + break; + } /* if */ + } /* for */ + } + + __PHYSFS_smallFree(allocated_fname); + + return i != NULL ? i : writeDir; +} /* findWriteHandle */ + + +/* This must hold the stateLock before calling. */ +static int doMkdir(const char *_dname, char *dname, DirHandle *h) { - DirHandle *h = writeDir; char *start; char *end; int retval = 0; @@ -2212,15 +2250,17 @@ int PHYSFS_mkdir(const char *_dname) int retval = 0; char *dname; size_t len; + DirHandle *h = NULL; BAIL_IF(!_dname, PHYSFS_ERR_INVALID_ARGUMENT, 0); __PHYSFS_platformGrabMutex(stateLock); - BAIL_IF_MUTEX(!writeDir, PHYSFS_ERR_NO_WRITE_DIR, stateLock, 0); - len = strlen(_dname) + dirHandleRootLen(writeDir) + 1; + h = findWriteHandle(_dname); + BAIL_IF_MUTEX(!h, PHYSFS_ERR_NO_WRITE_DIR, stateLock, 0); + len = strlen(_dname) + dirHandleRootLen(h) + 1; dname = (char *) __PHYSFS_smallAlloc(len); BAIL_IF_MUTEX(!dname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0); - retval = doMkdir(_dname, dname); + retval = doMkdir(_dname, dname, h); __PHYSFS_platformReleaseMutex(stateLock); __PHYSFS_smallFree(dname); return retval; @@ -2228,9 +2268,8 @@ int PHYSFS_mkdir(const char *_dname) /* This must hold the stateLock before calling. */ -static int doDelete(const char *_fname, char *fname) +static int doDelete(const char *_fname, char *fname, DirHandle *h) { - DirHandle *h = writeDir; BAIL_IF_ERRPASS(!sanitizePlatformIndependentPathWithRoot(h, _fname, fname), 0); BAIL_IF_ERRPASS(!verifyPath(h, &fname, 0), 0); return h->funcs->remove(h->opaque, fname); @@ -2242,13 +2281,15 @@ int PHYSFS_delete(const char *_fname) int retval; char *fname; size_t len; + DirHandle *h = NULL; __PHYSFS_platformGrabMutex(stateLock); - BAIL_IF_MUTEX(!writeDir, PHYSFS_ERR_NO_WRITE_DIR, stateLock, 0); - len = strlen(_fname) + dirHandleRootLen(writeDir) + 1; + h = findWriteHandle(_fname); + BAIL_IF_MUTEX(!h, PHYSFS_ERR_NO_WRITE_DIR, stateLock, 0); + len = strlen(_fname) + dirHandleRootLen(h) + 1; fname = (char *) __PHYSFS_smallAlloc(len); BAIL_IF_MUTEX(!fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0); - retval = doDelete(_fname, fname); + retval = doDelete(_fname, fname, h); __PHYSFS_platformReleaseMutex(stateLock); __PHYSFS_smallFree(fname); return retval; @@ -2631,7 +2672,7 @@ static PHYSFS_File *doOpenWrite(const char *_fname, const int appending) __PHYSFS_platformGrabMutex(stateLock); - h = writeDir; + h = findWriteHandle(_fname); BAIL_IF_MUTEX(!h, PHYSFS_ERR_NO_WRITE_DIR, stateLock, 0); len = strlen(_fname) + dirHandleRootLen(h) + 1; @@ -3115,7 +3156,7 @@ int PHYSFS_stat(const char *_fname, PHYSFS_Stat *stat) if (exists) { stat->filetype = PHYSFS_FILETYPE_DIRECTORY; - stat->readonly = 1; + stat->readonly = !i->forWriting; retval = 1; } /* if */ else if (verifyPath(i, &arcfname, 0)) diff --git a/src/libraries/physfs/physfs.h b/src/libraries/physfs/physfs.h index 49d49dae4..9ec5e79e7 100644 --- a/src/libraries/physfs/physfs.h +++ b/src/libraries/physfs/physfs.h @@ -2204,6 +2204,14 @@ PHYSFS_DECL int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath); +/** + * Just like mount() but attempts to open the directory for writing as well as + * reading. + */ +PHYSFS_DECL int PHYSFS_mountRW(const char *newDir, + const char *mountPoint, + int appendToPath); + /** * \fn int PHYSFS_getMountPoint(const char *dir) * \brief Determine a mounted archive's mountpoint. From f61a650a879b48d58e2fc4fa8e1e132106ed0317 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Thu, 31 Dec 2020 00:06:41 -0400 Subject: [PATCH 21/32] Add 'readonly' field to love.filesystem.getInfo's returned table --- src/modules/filesystem/Filesystem.h | 1 + src/modules/filesystem/physfs/Filesystem.cpp | 1 + src/modules/filesystem/wrap_Filesystem.cpp | 3 +++ 3 files changed, 5 insertions(+) diff --git a/src/modules/filesystem/Filesystem.h b/src/modules/filesystem/Filesystem.h index 444b18d41..e2c8c3d50 100644 --- a/src/modules/filesystem/Filesystem.h +++ b/src/modules/filesystem/Filesystem.h @@ -96,6 +96,7 @@ public: int64 size; int64 modtime; FileType type; + bool readonly; }; static love::Type type; diff --git a/src/modules/filesystem/physfs/Filesystem.cpp b/src/modules/filesystem/physfs/Filesystem.cpp index a5f8a042a..7833e051e 100644 --- a/src/modules/filesystem/physfs/Filesystem.cpp +++ b/src/modules/filesystem/physfs/Filesystem.cpp @@ -720,6 +720,7 @@ bool Filesystem::getInfo(const char *filepath, Info &info) const info.size = (int64) stat.filesize; info.modtime = (int64) stat.modtime; + info.readonly = stat.readonly != 0; if (stat.filetype == PHYSFS_FILETYPE_REGULAR) info.type = FILETYPE_FILE; diff --git a/src/modules/filesystem/wrap_Filesystem.cpp b/src/modules/filesystem/wrap_Filesystem.cpp index 59bd2ada7..da03f2a0f 100644 --- a/src/modules/filesystem/wrap_Filesystem.cpp +++ b/src/modules/filesystem/wrap_Filesystem.cpp @@ -485,6 +485,9 @@ int w_getInfo(lua_State *L) lua_pushstring(L, typestr); lua_setfield(L, -2, "type"); + luax_pushboolean(L, info.readonly); + lua_setfield(L, -2, "readonly"); + // Lua numbers (doubles) can't fit the full range of 64 bit ints. info.size = std::min(info.size, 0x20000000000000LL); if (info.size >= 0) From 781acf5c4d9875080ca1e9802c5f2a37968e298c Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Thu, 31 Dec 2020 16:48:42 +0800 Subject: [PATCH 22/32] Support for >2GB DroppedFile in desktops. --- src/modules/filesystem/DroppedFile.cpp | 43 +++++++++++++++++++++----- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/modules/filesystem/DroppedFile.cpp b/src/modules/filesystem/DroppedFile.cpp index 5a602ff04..1dea290a3 100644 --- a/src/modules/filesystem/DroppedFile.cpp +++ b/src/modules/filesystem/DroppedFile.cpp @@ -106,14 +106,25 @@ bool DroppedFile::isOpen() const int64 DroppedFile::getSize() { + int fd = file ? fileno(file) : -1; + #ifdef LOVE_WINDOWS + + struct _stat64 buf; - // make sure non-ASCII filenames work. - std::wstring wfilename = to_widestr(filename); + if (fd != -1) + { + if (_fstat64(fd, &buf) != 0) + return -1; + } + else + { + // make sure non-ASCII filenames work. + std::wstring wfilename = to_widestr(filename); - struct _stat buf; - if (_wstat(wfilename.c_str(), &buf) != 0) - return -1; + if (_wstat64(wfilename.c_str(), &buf) != 0) + return -1; + } return (int64) buf.st_size; @@ -121,7 +132,13 @@ int64 DroppedFile::getSize() // Assume POSIX support... struct stat buf; - if (stat(filename.c_str(), &buf) != 0) + + if (fd != -1) + { + if (fstat(fd, &buf) != 0) + return -1; + } + else if (stat(filename.c_str(), &buf) != 0) return -1; return (int64) buf.st_size; @@ -173,12 +190,22 @@ int64 DroppedFile::tell() if (file == nullptr) return -1; - return (int64) ftell(file); +#ifdef LOVE_WINDOWS + return (int64) _ftelli64(file); +#else + return (int64) ftello(file); +#endif } bool DroppedFile::seek(uint64 pos) { - return file != nullptr && fseek(file, (long) pos, SEEK_SET) == 0; + return file != nullptr && + +#ifdef LOVE_WINDOWS + _fseeki64(file, (int64) pos, SEEK_SET) == 0; +#else + fseeko(file, (off_t) pos, SEEK_SET) == 0; +#endif } bool DroppedFile::setBuffer(BufferMode bufmode, int64 size) From 2bee42893b8bb073e314022315c65ff5472b1d0e Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Thu, 31 Dec 2020 14:10:39 -0400 Subject: [PATCH 23/32] physfs: fix indentation --- src/libraries/physfs/physfs.c | 60 +++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/libraries/physfs/physfs.c b/src/libraries/physfs/physfs.c index f2b241f47..6540fd86e 100644 --- a/src/libraries/physfs/physfs.c +++ b/src/libraries/physfs/physfs.c @@ -49,7 +49,7 @@ typedef struct __PHYSFS_DIRHANDLE__ char *root; /* subdirectory of archiver to use as root of archive (NULL for actual root) */ size_t rootlen; /* subdirectory of archiver to use as root of archive (NULL for actual root) */ const PHYSFS_Archiver *funcs; /* Ptr to archiver info for this handle. */ - int forWriting; /* Whether this was opened for writing or not. */ + int forWriting; /* Whether this was opened for writing or not. */ struct __PHYSFS_DIRHANDLE__ *next; /* linked list stuff. */ } DirHandle; @@ -863,7 +863,7 @@ static DirHandle *tryOpenDir(PHYSFS_Io *io, const PHYSFS_Archiver *funcs, retval->mountPoint = NULL; retval->funcs = funcs; retval->opaque = opaque; - retval->forWriting = forWriting; + retval->forWriting = forWriting; } /* else */ } /* if */ @@ -1860,8 +1860,8 @@ int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath) int PHYSFS_mountRW(const char *newDir, const char *mountPoint, int appendToPath) { - BAIL_IF(!newDir, PHYSFS_ERR_INVALID_ARGUMENT, 0); - return doMount(NULL, newDir, mountPoint, appendToPath, 1); + BAIL_IF(!newDir, PHYSFS_ERR_INVALID_ARGUMENT, 0); + return doMount(NULL, newDir, mountPoint, appendToPath, 1); } /* PHYSFS_mount */ @@ -2170,31 +2170,31 @@ static int verifyPath(DirHandle *h, char **_fname, int allowMissing) static DirHandle *findWriteHandle(const char *_fname) { - DirHandle *i = NULL; - char *allocated_fname; - char *fname; - size_t len; + DirHandle *i = NULL; + char *allocated_fname; + char *fname; + size_t len; - len = strlen(_fname) + longest_root + 1; - allocated_fname = (char *) __PHYSFS_smallAlloc(len); - BAIL_IF(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, NULL); - fname = allocated_fname + longest_root; + len = strlen(_fname) + longest_root + 1; + allocated_fname = (char *) __PHYSFS_smallAlloc(len); + BAIL_IF(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + fname = allocated_fname + longest_root; - if (sanitizePlatformIndependentPath(_fname, fname)) - { - for (i = searchPath; i != NULL; i = i->next) - { - char *arcfname = fname; - if (i->forWriting && verifyPath(i, &arcfname, 0)) - { - break; - } /* if */ - } /* for */ - } + if (sanitizePlatformIndependentPath(_fname, fname)) + { + for (i = searchPath; i != NULL; i = i->next) + { + char *arcfname = fname; + if (i->forWriting && verifyPath(i, &arcfname, 0)) + { + break; + } /* if */ + } /* for */ + } - __PHYSFS_smallFree(allocated_fname); - - return i != NULL ? i : writeDir; + __PHYSFS_smallFree(allocated_fname); + + return i != NULL ? i : writeDir; } /* findWriteHandle */ @@ -2250,12 +2250,12 @@ int PHYSFS_mkdir(const char *_dname) int retval = 0; char *dname; size_t len; - DirHandle *h = NULL; + DirHandle *h = NULL; BAIL_IF(!_dname, PHYSFS_ERR_INVALID_ARGUMENT, 0); __PHYSFS_platformGrabMutex(stateLock); - h = findWriteHandle(_dname); + h = findWriteHandle(_dname); BAIL_IF_MUTEX(!h, PHYSFS_ERR_NO_WRITE_DIR, stateLock, 0); len = strlen(_dname) + dirHandleRootLen(h) + 1; dname = (char *) __PHYSFS_smallAlloc(len); @@ -2281,10 +2281,10 @@ int PHYSFS_delete(const char *_fname) int retval; char *fname; size_t len; - DirHandle *h = NULL; + DirHandle *h = NULL; __PHYSFS_platformGrabMutex(stateLock); - h = findWriteHandle(_fname); + h = findWriteHandle(_fname); BAIL_IF_MUTEX(!h, PHYSFS_ERR_NO_WRITE_DIR, stateLock, 0); len = strlen(_fname) + dirHandleRootLen(h) + 1; fname = (char *) __PHYSFS_smallAlloc(len); From 61580c7a0d416cb6dc0c79bc5fdd33ee0806be20 Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Fri, 1 Jan 2021 20:48:15 +0800 Subject: [PATCH 24/32] Update src/modules/filesystem/DroppedFile.cpp as suggested Co-authored-by: Qais Patankar --- src/modules/filesystem/DroppedFile.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/modules/filesystem/DroppedFile.cpp b/src/modules/filesystem/DroppedFile.cpp index 1dea290a3..2917525d8 100644 --- a/src/modules/filesystem/DroppedFile.cpp +++ b/src/modules/filesystem/DroppedFile.cpp @@ -199,12 +199,13 @@ int64 DroppedFile::tell() bool DroppedFile::seek(uint64 pos) { - return file != nullptr && + if (file == nullptr) + return false; #ifdef LOVE_WINDOWS - _fseeki64(file, (int64) pos, SEEK_SET) == 0; + return _fseeki64(file, (int64) pos, SEEK_SET) == 0; #else - fseeko(file, (off_t) pos, SEEK_SET) == 0; + return fseeko(file, (off_t) pos, SEEK_SET) == 0; #endif } From f00f5cb51a31a6faac7902f15fe37f1eb9ca7d8e Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Fri, 1 Jan 2021 16:25:50 -0400 Subject: [PATCH 25/32] physfs: add PHYSFS_canUnmount Fix unmount on a RW archive not checking if writable files are open --- src/libraries/physfs/physfs.c | 41 +++++++++++++++++++++++++++++++++++ src/libraries/physfs/physfs.h | 6 +++++ 2 files changed, 47 insertions(+) diff --git a/src/libraries/physfs/physfs.c b/src/libraries/physfs/physfs.c index 6540fd86e..4b5decf07 100644 --- a/src/libraries/physfs/physfs.c +++ b/src/libraries/physfs/physfs.c @@ -1104,6 +1104,23 @@ static int freeDirHandle(DirHandle *dh, FileHandle *openList) } /* freeDirHandle */ +static int dirHandleFilesOpen(DirHandle *dh, FileHandle *openList) +{ + FileHandle *i; + + if (dh == NULL) + return 0; + + for (i = openList; i != NULL; i = i->next) + { + if (i->dirHandle == dh) + return 1; + } + + return 0; +} /* dirHandleFilesOpen */ + + static char *calculateBaseDir(const char *argv0) { const char dirsep = __PHYSFS_platformDirSeparator; @@ -1891,6 +1908,8 @@ int PHYSFS_unmount(const char *oldDir) if (strcmp(i->dirName, oldDir) == 0) { next = i->next; + if (i->forWriting && dirHandleFilesOpen(i, openWriteList)) + BAIL_MUTEX(PHYSFS_ERR_FILES_STILL_OPEN, stateLock, 0); BAIL_IF_MUTEX_ERRPASS(!freeDirHandle(i, openReadList), stateLock, 0); @@ -1908,6 +1927,28 @@ int PHYSFS_unmount(const char *oldDir) } /* PHYSFS_unmount */ +int PHYSFS_canUnmount(const char *oldDir) +{ + DirHandle *i; + + BAIL_IF(oldDir == NULL, PHYSFS_ERR_INVALID_ARGUMENT, 0); + + __PHYSFS_platformGrabMutex(stateLock); + for (i = searchPath; i != NULL; i = i->next) + { + if (strcmp(i->dirName, oldDir) == 0) + { + if (i->forWriting && dirHandleFilesOpen(i, openWriteList)) + BAIL_MUTEX(PHYSFS_ERR_OK, stateLock, 0); + if (dirHandleFilesOpen(i, openReadList)) + BAIL_MUTEX(PHYSFS_ERR_OK, stateLock, 0); + BAIL_MUTEX(PHYSFS_ERR_OK, stateLock, 1); + } + } + + BAIL_MUTEX(PHYSFS_ERR_NOT_MOUNTED, stateLock, 0); +} /* PHYSFS_canUnmount */ + char **PHYSFS_getSearchPath(void) { return doEnumStringList(PHYSFS_getSearchPathCallback); diff --git a/src/libraries/physfs/physfs.h b/src/libraries/physfs/physfs.h index 9ec5e79e7..b08c02ce7 100644 --- a/src/libraries/physfs/physfs.h +++ b/src/libraries/physfs/physfs.h @@ -2787,6 +2787,12 @@ PHYSFS_DECL int PHYSFS_enumerate(const char *dir, PHYSFS_EnumerateCallback c, */ PHYSFS_DECL int PHYSFS_unmount(const char *oldDir); +/** + * \fn int PHYSFS_canUnmount(const char *oldDir) + * \brief Check whether a directory or archive can be unmounted. + */ +PHYSFS_DECL int PHYSFS_canUnmount(const char *oldDir); + /** * \fn const PHYSFS_Allocator *PHYSFS_getAllocator(void) From f1511905487c9de774502e7890fc6c48d4e9042c Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Fri, 1 Jan 2021 16:29:40 -0400 Subject: [PATCH 26/32] filesystem restructure work. Add mount/unmountCommonPath, mount/unmountFullPath, getFullCommonPath. Reimplement identity and save directory code in terms of new commonpath code. Add internal createRealDirectory function (not exposed, currently). --- src/common/apple.h | 2 +- src/common/apple.mm | 3 + src/modules/filesystem/Filesystem.cpp | 87 +++- src/modules/filesystem/Filesystem.h | 16 +- src/modules/filesystem/physfs/File.cpp | 3 +- src/modules/filesystem/physfs/Filesystem.cpp | 395 +++++++++---------- src/modules/filesystem/physfs/Filesystem.h | 37 +- src/modules/filesystem/wrap_Filesystem.cpp | 16 +- 8 files changed, 330 insertions(+), 229 deletions(-) diff --git a/src/common/apple.h b/src/common/apple.h index a2edcda6b..207f75663 100644 --- a/src/common/apple.h +++ b/src/common/apple.h @@ -38,10 +38,10 @@ enum UserDirectory USER_DIRECTORY_DOCUMENTS, USER_DIRECTORY_DESKTOP, USER_DIRECTORY_CACHES, + USER_DIRECTORY_TEMP, }; std::string getUserDirectory(UserDirectory dir); - std::string getExecutablePath(); } // apple diff --git a/src/common/apple.mm b/src/common/apple.mm index 9b27152dc..dcffa4d76 100644 --- a/src/common/apple.mm +++ b/src/common/apple.mm @@ -52,6 +52,9 @@ std::string getUserDirectory(UserDirectory dir) case USER_DIRECTORY_CACHES: nsdir = NSCachesDirectory; break; + case USER_DIRECTORY_TEMP: + nsdir = NSItemReplacementDirectory; + break; } NSArray *dirs = [[NSFileManager defaultManager] URLsForDirectory:nsdir inDomains:NSUserDomainMask]; diff --git a/src/modules/filesystem/Filesystem.cpp b/src/modules/filesystem/Filesystem.cpp index 56f028ae9..a44703308 100644 --- a/src/modules/filesystem/Filesystem.cpp +++ b/src/modules/filesystem/Filesystem.cpp @@ -28,8 +28,10 @@ #if defined(LOVE_MACOS) || defined(LOVE_IOS) #include "common/apple.h" +#include #elif defined(LOVE_WINDOWS) #include +#include #include "common/utf8.h" #elif defined(LOVE_LINUX) #include @@ -68,6 +70,14 @@ FileData *Filesystem::newFileData(const void *data, size_t size, const char *fil } bool Filesystem::isRealDirectory(const std::string &path) const +{ + FileType ftype = FILETYPE_MAX_ENUM; + if (!getRealPathType(path, ftype)) + return false; + return ftype == FILETYPE_DIRECTORY; +} + +bool Filesystem::getRealPathType(const std::string &path, FileType &ftype) const { #ifdef LOVE_WINDOWS // make sure non-ASCII paths work. @@ -77,15 +87,85 @@ bool Filesystem::isRealDirectory(const std::string &path) const if (_wstat(wpath.c_str(), &buf) != 0) return false; - return (buf.st_mode & _S_IFDIR) == _S_IFDIR; + if ((buf.st_mode & _S_IFREG) == _S_IFREG) + ftype = FILETYPE_FILE; + else if ((buf.st_mode & _S_IFDIR) == _S_IFDIR) + ftype = FILETYPE_DIRECTORY; + else if ((buf.st_mode & _S_IFLNK) == _S_IFLNK) + ftype = FILETYPE_SYMLINK; + else + ftype = FILETYPE_OTHER; #else // Assume POSIX support... struct stat buf; if (stat(path.c_str(), &buf) != 0) return false; - return S_ISDIR(buf.st_mode) != 0; + if (S_ISREG(buf.st_mode)) + ftype = FILETYPE_FILE; + else if (S_ISDIR(buf.st_mode)) + ftype = FILETYPE_DIRECTORY; + else if (S_ISLNK(buf.st_mode)) + ftype = FILETYPE_SYMLINK; + else + ftype = FILETYPE_OTHER; #endif + + return true; +} + +static bool getContainingDirectory(const std::string &path, std::string &newpath) +{ + size_t index = path.find_last_of("/\\"); + + if (index == std::string::npos) + return false; + + newpath = path.substr(0, index); + + // Bail if the root has been stripped out. + return newpath.find("/\\") != std::string::npos; +} + +static bool createDirectoryRaw(const std::string &path) +{ +#ifdef LOVE_WINDOWS + std::wstring wpath = to_widestr(path); + return CreateDirectoryW(wpath, nullptr) != 0; +#else + return mkdir(path.c_str(), S_IRWXU) == 0; +#endif +} + +bool Filesystem::createRealDirectory(const std::string &path) +{ + FileType ftype = FILETYPE_MAX_ENUM; + if (getRealPathType(path, ftype)) + return ftype == FILETYPE_DIRECTORY; + + std::vector createpaths = {path}; + + // Find the deepest subdirectory in the given path that actually exists. + while (true) + { + std::string subpath; + if (!getContainingDirectory(createpaths[0], subpath)) + break; + + if (isRealDirectory(subpath)) + break; + + createpaths.insert(createpaths.begin(), subpath); + } + + // Try to create missing subdirectories starting from that existing one. + for (const std::string &p : createpaths) + { + if (!createDirectoryRaw(p)) + return false; + } + + return true; } std::string Filesystem::getExecutablePath() const @@ -127,9 +207,8 @@ STRINGMAP_CLASS_END(Filesystem, Filesystem::FileType, Filesystem::FILETYPE_MAX_E STRINGMAP_CLASS_BEGIN(Filesystem, Filesystem::CommonPath, Filesystem::COMMONPATH_MAX_ENUM, commonPath) { - { "appidentity", Filesystem::COMMONPATH_APP_IDENTITY }, + { "appsavedir", Filesystem::COMMONPATH_APP_SAVEDIR }, { "appdocuments", Filesystem::COMMONPATH_APP_DOCUMENTS }, - { "apptemp", Filesystem::COMMONPATH_APP_TEMP }, { "userhome", Filesystem::COMMONPATH_USER_HOME }, { "userappdata", Filesystem::COMMONPATH_USER_APPDATA }, { "userdesktop", Filesystem::COMMONPATH_USER_DESKTOP }, diff --git a/src/modules/filesystem/Filesystem.h b/src/modules/filesystem/Filesystem.h index e2c8c3d50..e58c9ad13 100644 --- a/src/modules/filesystem/Filesystem.h +++ b/src/modules/filesystem/Filesystem.h @@ -73,9 +73,8 @@ public: enum CommonPath { - COMMONPATH_APP_IDENTITY, + COMMONPATH_APP_SAVEDIR, COMMONPATH_APP_DOCUMENTS, - COMMONPATH_APP_TEMP, COMMONPATH_USER_HOME, COMMONPATH_USER_APPDATA, COMMONPATH_USER_DESKTOP, @@ -163,6 +162,7 @@ public: virtual bool unmount(const char *archive) = 0; virtual bool unmount(Data *data) = 0; virtual bool unmount(CommonPath path) = 0; + virtual bool unmountFullPath(const char *fullpath) = 0; /** * Creates a new file. @@ -177,6 +177,9 @@ public: **/ virtual FileData *newFileData(const void *data, size_t size, const char *filename) const; + /** + * Gets the full path for the given common path. + */ virtual std::string getFullCommonPath(CommonPath path) = 0; /** @@ -199,7 +202,7 @@ public: /** * Gets the full path of the save folder. **/ - virtual const char *getSaveDirectory() = 0; + virtual std::string getSaveDirectory() = 0; /** * Gets the full path to the directory containing the game source. @@ -285,6 +288,11 @@ public: **/ virtual bool isRealDirectory(const std::string &path) const; + /** + * Recursively creates a directory at the given full OS-dependent path. + **/ + virtual bool createRealDirectory(const std::string &path); + /** * Gets the full platform-dependent path to the executable. **/ @@ -296,6 +304,8 @@ public: private: + bool getRealPathType(const std::string &path, FileType &ftype) const; + // Should we save external or internal for Android bool useExternal; diff --git a/src/modules/filesystem/physfs/File.cpp b/src/modules/filesystem/physfs/File.cpp index 451be301f..c20626f21 100644 --- a/src/modules/filesystem/physfs/File.cpp +++ b/src/modules/filesystem/physfs/File.cpp @@ -69,14 +69,13 @@ bool File::open(Mode mode) throw love::Exception("Could not open file %s. Does not exist.", filename.c_str()); // Check whether the write directory is set. - if ((mode == MODE_APPEND || mode == MODE_WRITE) && (PHYSFS_getWriteDir() == nullptr) && !setupWriteDirectory()) + if ((mode == MODE_APPEND || mode == MODE_WRITE) && !setupWriteDirectory()) throw love::Exception("Could not set write directory."); // File already open? if (file != nullptr) return false; - PHYSFS_getLastErrorCode(); PHYSFS_File *handle = nullptr; switch (mode) diff --git a/src/modules/filesystem/physfs/Filesystem.cpp b/src/modules/filesystem/physfs/Filesystem.cpp index 7833e051e..b329344ca 100644 --- a/src/modules/filesystem/physfs/Filesystem.cpp +++ b/src/modules/filesystem/physfs/Filesystem.cpp @@ -71,25 +71,6 @@ namespace filesystem namespace physfs { -static size_t getDriveDelim(const std::string &input) -{ - for (size_t i = 0; i < input.size(); ++i) - if (input[i] == '/' || input[i] == '\\') - return i; - // Something's horribly wrong - return 0; -} - -static std::string getDriveRoot(const std::string &input) -{ - return input.substr(0, getDriveDelim(input)+1); -} - -static std::string skipDriveRoot(const std::string &input) -{ - return input.substr(getDriveDelim(input)+1); -} - static std::string normalize(const std::string &input) { std::stringstream out; @@ -105,13 +86,18 @@ static std::string normalize(const std::string &input) return out.str(); } +static const Filesystem::CommonPath appCommonPaths[] = +{ + Filesystem::COMMONPATH_APP_SAVEDIR, + Filesystem::COMMONPATH_APP_DOCUMENTS +}; + static bool isAppCommonPath(Filesystem::CommonPath path) { switch (path) { - case Filesystem::COMMONPATH_APP_IDENTITY: + case Filesystem::COMMONPATH_APP_SAVEDIR: case Filesystem::COMMONPATH_APP_DOCUMENTS: - case Filesystem::COMMONPATH_APP_TEMP: return true; default: return false; @@ -119,8 +105,12 @@ static bool isAppCommonPath(Filesystem::CommonPath path) } Filesystem::Filesystem() - : fused(false) + : appendIdentityToPath(false) + , fused(false) , fusedSet(false) + , fullPaths() + , commonPathMountInfo() + , saveDirectoryNeedsMounting(false) { requirePath = {"?.lua", "?/init.lua"}; cRequirePath = {"??"}; @@ -166,65 +156,65 @@ bool Filesystem::setIdentity(const char *ident, bool appendToPath) if (!PHYSFS_isInit()) return false; - std::string old_save_path = save_path_full; + if (ident == nullptr || strlen(ident) == 0) + return false; - // Store the save directory. - save_identity = std::string(ident); + // Validate whether re-mounting will work. + for (CommonPath p : appCommonPaths) + { + if (!commonPathMountInfo[p].mounted) + continue; - // Generate the relative path to the game save folder. - if (fused) - save_path_relative = std::string(LOVE_APPDATA_PREFIX) + save_identity; - else - save_path_relative = std::string(LOVE_APPDATA_PREFIX LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + save_identity; + // If a file is still open, unmount will fail. + std::string fullPath = getFullCommonPath(p); + if (!fullPath.empty() && !PHYSFS_canUnmount(fullPath.c_str())) + return false; + } - // Generate the full path to the game save folder. - save_path_full = std::string(getAppdataDirectory()) + std::string(LOVE_PATH_SEPARATOR) + save_path_relative; - save_path_full = normalize(save_path_full); - -#ifdef LOVE_ANDROID - if (save_identity == "") - save_identity = "unnamed"; + bool oldMountedCommonPaths[COMMONPATH_MAX_ENUM] = {false}; - std::string storage_path; - if (isAndroidSaveExternal()) - storage_path = SDL_AndroidGetExternalStoragePath(); - else - storage_path = SDL_AndroidGetInternalStoragePath(); + // We don't want old save paths to accumulate when we set a new identity. + for (CommonPath p : appCommonPaths) + { + oldMountedCommonPaths[p] = commonPathMountInfo[p].mounted; + if (commonPathMountInfo[p].mounted) + unmount(p); + } - std::string save_directory = storage_path + "/save"; + // These will be re-populated by getFullCommonPath. + for (CommonPath p : appCommonPaths) + fullPaths[p].clear(); - save_path_full = storage_path + std::string("/save/") + save_identity; + // Store the save directory. getFullCommonPath(COMMONPATH_APP_*) uses this. + saveIdentity = std::string(ident); + appendIdentityToPath = appendToPath; - if (!love::android::directoryExists(save_path_full.c_str()) && - !love::android::mkdir(save_path_full.c_str())) - SDL_Log("Error: Could not create save directory %s!", save_path_full.c_str()); -#endif + // Try to mount as readwrite without creating missing directories in the + // path hierarchy. If this fails, setupWriteDirectory will attempt to create + // them and try again. + // This is done so the save directory is only created on-demand. + if (!mountCommonPathInternal(COMMONPATH_APP_SAVEDIR, nullptr, MOUNT_PERMISSIONS_READWRITE, appendToPath, false)) + saveDirectoryNeedsMounting = true; - // We now have something like: - // save_identity: game - // save_path_relative: ./LOVE/game - // save_path_full: C:\Documents and Settings\user\Application Data/LOVE/game - - // We don't want old read-only save paths to accumulate when we set a new - // identity. - if (!old_save_path.empty()) - PHYSFS_unmount(old_save_path.c_str()); - - // Try to add the save directory to the search path. - // (No error on fail, it means that the path doesn't exist). - PHYSFS_mount(save_path_full.c_str(), nullptr, appendToPath); - - // HACK: This forces setupWriteDirectory to be called the next time a file - // is opened for writing - otherwise it won't be called at all if it was - // already called at least once before. - PHYSFS_setWriteDir(nullptr); + // Mount any other app common paths with directory creation immediately + // instead of on-demand, since to get to this point they would have to be + // explicitly mounted already beforehand. + for (CommonPath p : appCommonPaths) + { + if (oldMountedCommonPaths[p] && p != COMMONPATH_APP_SAVEDIR) + { + // TODO: error handling? + auto info = commonPathMountInfo[p]; + mountCommonPathInternal(p, info.mountPoint.c_str(), info.permissions, appendToPath, true); + } + } return true; } const char *Filesystem::getIdentity() const { - return save_identity.c_str(); + return saveIdentity.c_str(); } bool Filesystem::setSource(const char *source) @@ -233,7 +223,7 @@ bool Filesystem::setSource(const char *source) return false; // Check whether directory is already set. - if (!game_source.empty()) + if (!gameSource.empty()) return false; std::string new_search_path = source; @@ -275,14 +265,14 @@ bool Filesystem::setSource(const char *source) #endif // Save the game source. - game_source = new_search_path; + gameSource = new_search_path; return true; } const char *Filesystem::getSource() const { - return game_source.c_str(); + return gameSource.c_str(); } bool Filesystem::setupWriteDirectory() @@ -290,54 +280,19 @@ bool Filesystem::setupWriteDirectory() if (!PHYSFS_isInit()) return false; - // These must all be set. - if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty()) + if (!saveDirectoryNeedsMounting) + return true; + + if (saveIdentity.empty()) return false; - // We need to make sure the write directory is created. To do that, we also - // need to make sure all its parent directories are also created. - std::string temp_writedir = getDriveRoot(save_path_full); - std::string temp_createdir = skipDriveRoot(save_path_full); - - // On some sandboxed platforms, physfs will break when its write directory - // is the root of the drive and it tries to create a folder (even if the - // folder's path is in a writable location.) If the user's home folder is - // in the save path, we'll try starting from there instead. - if (save_path_full.find(getUserDirectory()) == 0) - { - temp_writedir = getUserDirectory(); - temp_createdir = save_path_full.substr(getUserDirectory().length()); - - // Strip leading '/' characters from the path we want to create. - size_t startpos = temp_createdir.find_first_not_of('/'); - if (startpos != std::string::npos) - temp_createdir = temp_createdir.substr(startpos); - } - - // Set either '/' or the user's home as a writable directory. - // (We must create the save folder before mounting it). - if (!PHYSFS_setWriteDir(temp_writedir.c_str())) + // Only the save directory is mounted on-demand if it doesn't exist yet. + // Other app common paths are immediately re-mounted in setIdentity. + bool createdir = true; + if (!mountCommonPathInternal(COMMONPATH_APP_SAVEDIR, nullptr, MOUNT_PERMISSIONS_READWRITE, appendIdentityToPath, createdir)) return false; - // Create the save folder. (We're now "at" either '/' or the user's home). - if (!createDirectory(temp_createdir.c_str())) - { - // Clear the write directory in case of error. - PHYSFS_setWriteDir(nullptr); - return false; - } - - // Set the final write directory. - if (!PHYSFS_setWriteDir(save_path_full.c_str())) - return false; - - // Add the directory. (Will not be readded if already present). - if (!PHYSFS_mount(save_path_full.c_str(), nullptr, 0)) - { - PHYSFS_setWriteDir(nullptr); // Clear the write directory in case of error. - return false; - } - + saveDirectoryNeedsMounting = false; return true; } @@ -374,7 +329,7 @@ bool Filesystem::mount(const char *archive, const char *mountpoint, bool appendT // Always disallow mounting of files inside the game source, since it // won't work anyway if the game source is a zipped .love file. - if (realPath.find(game_source) == 0) + if (realPath.find(gameSource) == 0) return false; realPath += LOVE_PATH_SEPARATOR; @@ -386,30 +341,40 @@ bool Filesystem::mount(const char *archive, const char *mountpoint, bool appendT bool Filesystem::mountFullPath(const char *archive, const char *mountpoint, MountPermissions permissions, bool appendToPath) { - if (!PHYSFS_isInit() || !archive || !mountpoint) + if (!PHYSFS_isInit() || !archive) return false; - if (permissions == MOUNT_PERMISSIONS_READWRITE && strlen(mountpoint) == 0) - return false; + if (permissions == MOUNT_PERMISSIONS_READWRITE) + return PHYSFS_mountRW(archive, mountpoint, appendToPath) != 0; - // TODO: readwrite mount return PHYSFS_mount(archive, mountpoint, appendToPath) != 0; } -bool Filesystem::mountCommonPath(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath) +bool Filesystem::mountCommonPathInternal(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath, bool createDir) { std::string fullpath = getFullCommonPath(path); if (fullpath.empty()) return false; - bool success = mountFullPath(fullpath.c_str(), mountpoint, permissions, appendToPath); - - if (!success && isAppCommonPath(path)) + if (createDir && isAppCommonPath(path) && !isRealDirectory(fullpath)) { - + if (!createRealDirectory(fullpath)) + return false; } - return success; + if (mountFullPath(fullpath.c_str(), mountpoint, permissions, appendToPath)) + { + std::string mp = mountpoint != nullptr ? mountpoint : "/"; + commonPathMountInfo[path] = {true, mp, permissions}; + return true; + } + + return false; +} + +bool Filesystem::mountCommonPath(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath) +{ + return mountCommonPathInternal(path, mountpoint, permissions, appendToPath, true); } bool Filesystem::mount(Data *data, const char *archivename, const char *mountpoint, bool appendToPath) @@ -439,8 +404,13 @@ bool Filesystem::unmount(const char *archive) return true; } - if (PHYSFS_getRealDir(archive) != nullptr) - return PHYSFS_unmount(archive) != 0; + auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive); + if (it != allowedMountPaths.end()) + return unmountFullPath(archive); + + std::string sourceBase = getSourceBaseDirectory(); + if (isFused() && sourceBase.compare(archive) == 0) + return unmountFullPath(archive); if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0) return false; @@ -459,13 +429,25 @@ bool Filesystem::unmount(const char *archive) return PHYSFS_unmount(realPath.c_str()) != 0; } +bool Filesystem::unmountFullPath(const char *fullpath) +{ + if (!PHYSFS_isInit() || !fullpath) + return false; + + return PHYSFS_unmount(fullpath) != 0; +} + bool Filesystem::unmount(CommonPath path) { std::string fullpath = getFullCommonPath(path); - if (fullpath.empty()) - return false; - return unmount(fullpath.c_str()); + if (!fullpath.empty() && unmountFullPath(fullpath.c_str())) + { + commonPathMountInfo[path].mounted = false; + return true; + } + + return false; } bool Filesystem::unmount(Data *data) @@ -489,34 +471,60 @@ love::filesystem::File *Filesystem::newFile(const char *filename) const std::string Filesystem::getFullCommonPath(CommonPath path) { - if (!fullCommonPaths[path].empty()) - return fullCommonPaths[path]; + if (!fullPaths[path].empty()) + return fullPaths[path]; - if (path == COMMONPATH_APP_IDENTITY || path == COMMONPATH_APP_DOCUMENTS || path == COMMONPATH_APP_TEMP) + if (isAppCommonPath(path)) { - + if (saveIdentity.empty()) + return fullPaths[path]; + + std::string rootpath; + switch (path) + { + case COMMONPATH_APP_SAVEDIR: + rootpath = getFullCommonPath(COMMONPATH_USER_APPDATA); + break; + case COMMONPATH_APP_DOCUMENTS: + rootpath = getFullCommonPath(COMMONPATH_USER_DOCUMENTS); + break; + default: + break; + } + + if (rootpath.empty()) + return fullPaths[path]; + + std::string suffix; + if (isFused()) + suffix = std::string(LOVE_PATH_SEPARATOR) + saveIdentity; + else + suffix = std::string(LOVE_PATH_SEPARATOR LOVE_APPDATA_FOLDER LOVE_PATH_SEPARATOR) + saveIdentity; + + fullPaths[path] = normalize(rootpath + suffix); + + return fullPaths[path]; } #if defined(LOVE_MACOS) || defined(LOVE_IOS) switch (path) { - case COMMONPATH_APP_IDENTITY: + case COMMONPATH_APP_SAVEDIR: case COMMONPATH_APP_DOCUMENTS: - case COMMONPATH_APP_TEMP: // Handled above. break; case COMMONPATH_USER_HOME: - fullCommonPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_HOME); + fullPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_HOME); break; case COMMONPATH_USER_APPDATA: - fullCommonPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_APPSUPPORT); + fullPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_APPSUPPORT); break; case COMMONPATH_USER_DESKTOP: - fullCommonPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_DESKTOP); + fullPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_DESKTOP); break; case COMMONPATH_USER_DOCUMENTS: - fullCommonPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_DOCUMENTS); + fullPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_DOCUMENTS); break; case COMMONPATH_MAX_ENUM: break; @@ -529,9 +537,8 @@ std::string Filesystem::getFullCommonPath(CommonPath path) switch (path) { - case COMMONPATH_APP_IDENTITY: + case COMMONPATH_APP_SAVEDIR: case COMMONPATH_APP_DOCUMENTS: - case COMMONPATH_APP_TEMP: // Handled above. break; case COMMONPATH_USER_HOME: @@ -552,40 +559,66 @@ std::string Filesystem::getFullCommonPath(CommonPath path) if (SUCCEEDED(hr)) { - fullCommonPaths[path] = to_utf8(winpath); + fullPaths[path] = to_utf8(winpath); CoTaskMemFree(winpath); } - else - { - - } #elif defined(LOVE_ANDROID) + std::string storagepath; + if (isAndroidSaveExternal()) + storagepath = SDL_AndroidGetExternalStoragePath(); + else + storagepath = SDL_AndroidGetInternalStoragePath(); + + switch (path) + { + case COMMONPATH_APP_SAVEDIR: + case COMMONPATH_APP_DOCUMENTS: + // Handled above. + break; + case COMMONPATH_USER_HOME: + fullPaths[path] = normalize(PHYSFS_getUserDir()); + break; + case COMMONPATH_USER_APPDATA: + fullPaths[path] = normalize(storagepath + "/save/"); + break; + case COMMONPATH_USER_DESKTOP: + // No such thing on Android? + break; + case COMMONPATH_USER_DOCUMENTS: + // TODO: something more idiomatic / useful? + fullPaths[path] = normalize(storagepath + "/Documents/"); + break; + case COMMONPATH_MAX_ENUM: + break; + } + #elif defined(LOVE_LINUX) const char *xdgdir = nullptr; switch (path) { - case COMMONPATH_APP_IDENTITY: + case COMMONPATH_APP_SAVEDIR: case COMMONPATH_APP_DOCUMENTS: - case COMMONPATH_APP_TEMP: // Handled above. break; case COMMONPATH_USER_HOME: - fullCommonPaths[path] = normalize(PHYSFS_getUserDir()); + fullPaths[path] = normalize(PHYSFS_getUserDir()); break; case COMMONPATH_USER_APPDATA: xdgdir = getenv("XDG_DATA_HOME"); if (!xdgdir) - fullCommonPaths[path] = normalize(std::string(getUserDirectory()) + "/.local/share/"); + fullPaths[path] = normalize(std::string(getUserDirectory()) + "/.local/share/"); else - fullCommonPaths[path] = xdgdir; + fullPaths[path] = xdgdir; break; case COMMONPATH_USER_DESKTOP: + fullPaths[path] = normalize(std::string(getUserDirectory()) + "/Desktop/"); break; case COMMONPATH_USER_DOCUMENTS: + fullPaths[path] = normalize(std::string(getUserDirectory()) + "/Documents/"); break; case COMMONPATH_MAX_ENUM: break; @@ -593,7 +626,7 @@ std::string Filesystem::getFullCommonPath(CommonPath path) #endif - return fullCommonPaths[path]; + return fullPaths[path]; } const char *Filesystem::getWorkingDirectory() @@ -621,58 +654,22 @@ const char *Filesystem::getWorkingDirectory() std::string Filesystem::getUserDirectory() { -#if defined(LOVE_IOS) || defined(LOVE_MACOS) - // PHYSFS_getUserDir doesn't give exactly the path we want on iOS. - static std::string userDir = normalize(apple::getUserDirectory(apple::USER_DIRECTORY_HOME)); -#else - static std::string userDir = normalize(PHYSFS_getUserDir()); -#endif - - return userDir; + return getFullCommonPath(COMMONPATH_USER_HOME); } std::string Filesystem::getAppdataDirectory() { - if (appdata.empty()) - { -#ifdef LOVE_WINDOWS_UWP - appdata = getUserDirectory(); -#elif defined(LOVE_WINDOWS) - PWSTR path = nullptr; - if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &path))) - { - appdata = to_utf8(path); - CoTaskMemFree(path); - } - else - { - wchar_t *w_appdata = _wgetenv(L"APPDATA"); - appdata = to_utf8(w_appdata); - } - replace_char(appdata, '\\', '/'); -#elif defined(LOVE_MACOS) || defined(LOVE_IOS) - appdata = normalize(apple::getUserDirectory(apple::USER_DIRECTORY_APPSUPPORT)); -#elif defined(LOVE_LINUX) - char *xdgdatahome = getenv("XDG_DATA_HOME"); - if (!xdgdatahome) - appdata = normalize(std::string(getUserDirectory()) + "/.local/share/"); - else - appdata = xdgdatahome; -#else - appdata = getUserDirectory(); -#endif - } - return appdata; + return getFullCommonPath(COMMONPATH_USER_APPDATA); } -const char *Filesystem::getSaveDirectory() +std::string Filesystem::getSaveDirectory() { - return save_path_full.c_str(); + return getFullCommonPath(COMMONPATH_APP_SAVEDIR); } std::string Filesystem::getSourceBaseDirectory() const { - size_t source_len = game_source.length(); + size_t source_len = gameSource.length(); if (source_len == 0) return ""; @@ -681,9 +678,9 @@ std::string Filesystem::getSourceBaseDirectory() const // symbols (i.e. '..' and '.') #ifdef LOVE_WINDOWS // In windows, delimiters can be either '/' or '\'. - size_t base_end_pos = game_source.find_last_of("/\\", source_len - 2); + size_t base_end_pos = gameSource.find_last_of("/\\", source_len - 2); #else - size_t base_end_pos = game_source.find_last_of('/', source_len - 2); + size_t base_end_pos = gameSource.find_last_of('/', source_len - 2); #endif if (base_end_pos == std::string::npos) @@ -693,7 +690,7 @@ std::string Filesystem::getSourceBaseDirectory() const if (base_end_pos == 0) base_end_pos = 1; - return game_source.substr(0, base_end_pos); + return gameSource.substr(0, base_end_pos); } std::string Filesystem::getRealDirectory(const char *filename) const @@ -739,7 +736,7 @@ bool Filesystem::createDirectory(const char *dir) if (!PHYSFS_isInit()) return false; - if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory()) + if (!setupWriteDirectory()) return false; if (!PHYSFS_mkdir(dir)) @@ -753,7 +750,7 @@ bool Filesystem::remove(const char *file) if (!PHYSFS_isInit()) return false; - if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory()) + if (!setupWriteDirectory()) return false; if (!PHYSFS_delete(file)) diff --git a/src/modules/filesystem/physfs/Filesystem.h b/src/modules/filesystem/physfs/Filesystem.h index 2ccfa9dc7..1ad915981 100644 --- a/src/modules/filesystem/physfs/Filesystem.h +++ b/src/modules/filesystem/physfs/Filesystem.h @@ -69,6 +69,7 @@ public: bool unmount(const char *archive) override; bool unmount(Data *data) override; bool unmount(CommonPath path) override; + bool unmountFullPath(const char *fullpath) override; love::filesystem::File *newFile(const char *filename) const override; @@ -76,7 +77,7 @@ public: const char *getWorkingDirectory() override; std::string getUserDirectory() override; std::string getAppdataDirectory() override; - const char *getSaveDirectory() override; + std::string getSaveDirectory() override; std::string getSourceBaseDirectory() const override; std::string getRealDirectory(const char *filename) const override; @@ -103,26 +104,26 @@ public: private: + struct CommonPathMountInfo + { + bool mounted; + std::string mountPoint; + MountPermissions permissions; + }; + + bool mountCommonPathInternal(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath, bool createDir); + // Contains the current working directory (UTF8). std::string cwd; - // %APPDATA% on Windows. - std::string appdata; - - // This name will be used to create the folder - // in the appdata/userdata folder. - std::string save_identity; - - // Full and relative paths of the game save folder. - // (Relative to the %APPDATA% folder, meaning that the - // relative string will look something like: ./LOVE/game) - std::string save_path_relative, save_path_full; + // This name will be used to create the folder in the appdata folder. + std::string saveIdentity; + bool appendIdentityToPath; // The full path to the source of the game. - std::string game_source; + std::string gameSource; - // Allow saving outside of the LOVE_APPDATA_FOLDER - // for release 'builds' + // Allow saving outside of the LOVE_APPDATA_FOLDER for release 'builds' bool fused; bool fusedSet; @@ -134,7 +135,11 @@ private: std::map> mountedData; - std::string fullCommonPaths[COMMONPATH_MAX_ENUM]; + std::string fullPaths[COMMONPATH_MAX_ENUM]; + + CommonPathMountInfo commonPathMountInfo[COMMONPATH_MAX_ENUM]; + + bool saveDirectoryNeedsMounting; }; // Filesystem diff --git a/src/modules/filesystem/wrap_Filesystem.cpp b/src/modules/filesystem/wrap_Filesystem.cpp index da03f2a0f..45bf3e889 100644 --- a/src/modules/filesystem/wrap_Filesystem.cpp +++ b/src/modules/filesystem/wrap_Filesystem.cpp @@ -205,6 +205,13 @@ int w_unmount(lua_State *L) return 1; } +int w_unmountFullPath(lua_State *L) +{ + const char *fullpath = luaL_checkstring(L, 1); + luax_pushboolean(L, instance()->unmountFullPath(fullpath)); + return 1; +} + int w_unmountCommonPath(lua_State *L) { const char *commonpathstr = luaL_checkstring(L, 1); @@ -415,7 +422,7 @@ int w_getAppdataDirectory(lua_State *L) int w_getSaveDirectory(lua_State *L) { - lua_pushstring(L, instance()->getSaveDirectory()); + luax_pushstring(L, instance()->getSaveDirectory()); return 1; } @@ -907,11 +914,12 @@ static const luaL_Reg functions[] = { "getSource", w_getSource }, { "mount", w_mount }, { "mountFullPath", w_mountFullPath }, -// { "mountCommonPath", w_mountCommonPath }, + { "mountCommonPath", w_mountCommonPath }, { "unmount", w_unmount }, -// { "unmountCommonPath", w_unmountCommonPath }, + { "unmountFullPath", w_unmountFullPath }, + { "unmountCommonPath", w_unmountCommonPath }, { "newFile", w_newFile }, -// { "getFullCommonPath", w_getFullCommonPath }, + { "getFullCommonPath", w_getFullCommonPath }, { "getWorkingDirectory", w_getWorkingDirectory }, { "getUserDirectory", w_getUserDirectory }, { "getAppdataDirectory", w_getAppdataDirectory }, From 6e6292ccb2e57b95335e8d2d5893486b8e8f492e Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Fri, 1 Jan 2021 16:32:34 -0400 Subject: [PATCH 27/32] Fix a potential compile error on Windows --- src/modules/filesystem/Filesystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/filesystem/Filesystem.cpp b/src/modules/filesystem/Filesystem.cpp index a44703308..116dab199 100644 --- a/src/modules/filesystem/Filesystem.cpp +++ b/src/modules/filesystem/Filesystem.cpp @@ -131,7 +131,7 @@ static bool createDirectoryRaw(const std::string &path) { #ifdef LOVE_WINDOWS std::wstring wpath = to_widestr(path); - return CreateDirectoryW(wpath, nullptr) != 0; + return CreateDirectoryW(wpath.c_str(), nullptr) != 0; #else return mkdir(path.c_str(), S_IRWXU) == 0; #endif From cf67e972908fb9b6f19ee085971e94ba23e8e76f Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Fri, 1 Jan 2021 16:41:39 -0400 Subject: [PATCH 28/32] Fix another windows compile error --- src/modules/filesystem/Filesystem.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/filesystem/Filesystem.cpp b/src/modules/filesystem/Filesystem.cpp index 116dab199..391bf732c 100644 --- a/src/modules/filesystem/Filesystem.cpp +++ b/src/modules/filesystem/Filesystem.cpp @@ -91,8 +91,6 @@ bool Filesystem::getRealPathType(const std::string &path, FileType &ftype) const ftype = FILETYPE_FILE; else if ((buf.st_mode & _S_IFDIR) == _S_IFDIR) ftype = FILETYPE_DIRECTORY; - else if ((buf.st_mode & _S_IFLNK) == _S_IFLNK) - ftype = FILETYPE_SYMLINK; else ftype = FILETYPE_OTHER; #else From ff90a0dea62407e9fc613813db99e5f929dc2e7f Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Fri, 1 Jan 2021 19:36:13 -0400 Subject: [PATCH 29/32] physfs: improve mounted archive search when opening a file for write --- src/libraries/physfs/physfs.c | 45 +++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/libraries/physfs/physfs.c b/src/libraries/physfs/physfs.c index 4b5decf07..3dac3291f 100644 --- a/src/libraries/physfs/physfs.c +++ b/src/libraries/physfs/physfs.c @@ -2209,9 +2209,39 @@ static int verifyPath(DirHandle *h, char **_fname, int allowMissing) } /* verifyPath */ +static int countPathComponents(const char *path) +{ + int components = 0; + const char *start; + const char *end; + + if (path == NULL) + return 0; + + if (*path != '/') + components = 1; + + start = path; + while (1) + { + end = strchr(start, '/'); + + if (end == NULL) + break; + + components++; + start = end + 1; + } + + return components; +} /* countPathComponents */ + + static DirHandle *findWriteHandle(const char *_fname) { DirHandle *i = NULL; + int deepest_path_components = -1; + DirHandle *deepest_dirhandle = NULL; char *allocated_fname; char *fname; size_t len; @@ -2228,14 +2258,21 @@ static DirHandle *findWriteHandle(const char *_fname) char *arcfname = fname; if (i->forWriting && verifyPath(i, &arcfname, 0)) { - break; + int path_components = 0; + if (i->mountPoint != NULL) + path_components = countPathComponents(i->mountPoint); + if (path_components > deepest_path_components) + { + deepest_path_components = path_components; + deepest_dirhandle = i; + } /* if */ } /* if */ } /* for */ - } + } /* if */ __PHYSFS_smallFree(allocated_fname); - - return i != NULL ? i : writeDir; + + return deepest_dirhandle != NULL ? deepest_dirhandle : writeDir; } /* findWriteHandle */ From 9742adb1b9a8f9036497ac17f17d6a2768e97847 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Tue, 5 Jan 2021 17:37:01 -0400 Subject: [PATCH 30/32] macOS: add search paths for mpg123's headers Needed for newer versions of mpg123 --- platform/xcode/liblove.xcodeproj/project.pbxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index 0b3dbf5be..4b815e49c 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -5320,9 +5320,11 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", + "$(PROJECT_DIR)/macosx/Frameworks/mpg123.framework/Headers", /Library/Frameworks/freetype.framework/Headers, /Library/Frameworks/Lua.framework/Headers, /Library/Frameworks/SDL2.framework/Headers, + /Library/Frameworks/mpg123.framework/Headers, ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; @@ -5364,9 +5366,11 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", + "$(PROJECT_DIR)/macosx/Frameworks/mpg123.framework/Headers", /Library/Frameworks/freetype.framework/Headers, /Library/Frameworks/Lua.framework/Headers, /Library/Frameworks/SDL2.framework/Headers, + /Library/Frameworks/mpg123.framework/Headers, ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; @@ -5409,9 +5413,11 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", + "$(PROJECT_DIR)/macosx/Frameworks/mpg123.framework/Headers", /Library/Frameworks/freetype.framework/Headers, /Library/Frameworks/Lua.framework/Headers, /Library/Frameworks/SDL2.framework/Headers, + /Library/Frameworks/mpg123.framework/Headers, ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; From 6fede802b374deda2a5131fab27861e7a540d5a3 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Wed, 6 Jan 2021 21:55:06 -0400 Subject: [PATCH 31/32] mpg123 isn't used in 12.x --- platform/xcode/liblove.xcodeproj/project.pbxproj | 6 ------ 1 file changed, 6 deletions(-) diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index 380243514..54071ed82 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -5395,11 +5395,9 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", - "$(PROJECT_DIR)/macosx/Frameworks/mpg123.framework/Headers", /Library/Frameworks/freetype.framework/Headers, /Library/Frameworks/Lua.framework/Headers, /Library/Frameworks/SDL2.framework/Headers, - /Library/Frameworks/mpg123.framework/Headers, ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; @@ -5441,11 +5439,9 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", - "$(PROJECT_DIR)/macosx/Frameworks/mpg123.framework/Headers", /Library/Frameworks/freetype.framework/Headers, /Library/Frameworks/Lua.framework/Headers, /Library/Frameworks/SDL2.framework/Headers, - /Library/Frameworks/mpg123.framework/Headers, ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; @@ -5488,11 +5484,9 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", - "$(PROJECT_DIR)/macosx/Frameworks/mpg123.framework/Headers", /Library/Frameworks/freetype.framework/Headers, /Library/Frameworks/Lua.framework/Headers, /Library/Frameworks/SDL2.framework/Headers, - /Library/Frameworks/mpg123.framework/Headers, ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; From 50ff86bd9226bbe02c3777c9af8f7277b8ce9a2f Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sun, 10 Jan 2021 17:53:12 -0400 Subject: [PATCH 32/32] Graphics Buffers can be used as Shader Storage Buffers. --- src/modules/graphics/Buffer.cpp | 67 +++++++- src/modules/graphics/Buffer.h | 3 + src/modules/graphics/Graphics.cpp | 19 +-- src/modules/graphics/Graphics.h | 1 + src/modules/graphics/Shader.cpp | 66 +++++++- src/modules/graphics/Shader.h | 18 +++ src/modules/graphics/opengl/Buffer.cpp | 2 + src/modules/graphics/opengl/Graphics.cpp | 22 ++- src/modules/graphics/opengl/OpenGL.cpp | 83 ++++++++-- src/modules/graphics/opengl/OpenGL.h | 22 ++- src/modules/graphics/opengl/Shader.cpp | 192 ++++++++++++++++++++--- src/modules/graphics/opengl/Shader.h | 9 ++ src/modules/graphics/vertex.cpp | 7 +- src/modules/graphics/vertex.h | 1 + src/modules/graphics/wrap_Shader.cpp | 7 +- 15 files changed, 451 insertions(+), 68 deletions(-) diff --git a/src/modules/graphics/Buffer.cpp b/src/modules/graphics/Buffer.cpp index c9c3f8a5f..aaf9b2228 100644 --- a/src/modules/graphics/Buffer.cpp +++ b/src/modules/graphics/Buffer.cpp @@ -20,6 +20,7 @@ #include "Buffer.h" #include "Graphics.h" +#include "common/memory.h" namespace love { @@ -48,15 +49,20 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector 0) + throw love::Exception("Arrays are not currently supported in shader storage buffers."); + + if (info.baseType == DATA_BASETYPE_BOOL) + throw love::Exception("Bool types are not supported in shader storage buffers."); + + if (info.baseType == DATA_BASETYPE_UNORM || info.baseType == DATA_BASETYPE_SNORM) + throw love::Exception("Normalized formats are not supported in shader storage buffers."); + + size_t alignment = 1; + + // GLSL's std430 packing rules. We also assume all matrices are + // column-major. + if (info.isMatrix) + alignment = info.matrixRows * info.componentSize; + else + alignment = info.components * info.componentSize; + + structurealignment = std::max(structurealignment, alignment); + + memberoffset = alignUp(memberoffset, alignment); + + if (memberoffset != offset && (indexbuffer || vertexbuffer || texelbuffer)) + throw love::Exception("Cannot create Buffer:\nInternal alignment of member '%s' is preventing Buffer from being created as both a shader storage buffer and other buffer types\nMember byte offset needed for shader storage buffer: %d\nMember byte offset needed for other buffer types: %d", + member.decl.name.c_str(), memberoffset, offset); + } + + member.offset = memberoffset; + member.size = membersize; + + offset = member.offset + member.size; dataMembers.push_back(member); } - stride = offset; + stride = alignUp(offset, structurealignment); + + if (storagebuffer && (indexbuffer || vertexbuffer || texelbuffer)) + { + if (stride != offset) + throw love::Exception("Cannot create Buffer:\nBuffer used as a shader storage buffer would have a different number of bytes per array element (%d) than when used as other buffer types (%d)", + stride, offset); + } + + if (storagebuffer && stride > SHADER_STORAGE_BUFFER_MAX_STRIDE) + throw love::Exception("Shader storage buffers cannot have more than %d bytes within each array element.", SHADER_STORAGE_BUFFER_MAX_STRIDE); if (size != 0) { @@ -144,7 +194,8 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vectorsize = size; if (texelbuffer && arraylength * dataMembers.size() > caps.limits[Graphics::LIMIT_TEXEL_BUFFER_SIZE]) - throw love::Exception("Cannot create texel buffer: total number of values in the buffer (%d * %d) is too large for this system (maximum %d).", (int) dataMembers.size(), (int) arraylength, caps.limits[Graphics::LIMIT_TEXEL_BUFFER_SIZE]); + throw love::Exception("Cannot create texel buffer: total number of values in the buffer (%d * %d) is too large for this system (maximum %d).", + (int) dataMembers.size(), (int) arraylength, caps.limits[Graphics::LIMIT_TEXEL_BUFFER_SIZE]); } Buffer::~Buffer() diff --git a/src/modules/graphics/Buffer.h b/src/modules/graphics/Buffer.h index 40f98fb1e..483b9679e 100644 --- a/src/modules/graphics/Buffer.h +++ b/src/modules/graphics/Buffer.h @@ -48,6 +48,8 @@ public: static love::Type type; + static const size_t SHADER_STORAGE_BUFFER_MAX_STRIDE = 2048; + enum MapType { MAP_WRITE_INVALIDATE, @@ -59,6 +61,7 @@ public: TYPEFLAG_VERTEX = 1 << BUFFERTYPE_VERTEX, TYPEFLAG_INDEX = 1 << BUFFERTYPE_INDEX, TYPEFLAG_TEXEL = 1 << BUFFERTYPE_TEXEL, + TYPEFLAG_SHADER_STORAGE = 1 << BUFFERTYPE_SHADER_STORAGE, }; struct DataDeclaration diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index f0c2cdfd7..25b1ad775 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -1944,15 +1944,16 @@ StringMap Graphics::features(Grap StringMap::Entry Graphics::systemLimitEntries[] = { - { "pointsize", LIMIT_POINT_SIZE }, - { "texturesize", LIMIT_TEXTURE_SIZE }, - { "texturelayers", LIMIT_TEXTURE_LAYERS }, - { "volumetexturesize", LIMIT_VOLUME_TEXTURE_SIZE }, - { "cubetexturesize", LIMIT_CUBE_TEXTURE_SIZE }, - { "texelbuffersize", LIMIT_TEXEL_BUFFER_SIZE }, - { "rendertargets", LIMIT_RENDER_TARGETS }, - { "texturemsaa", LIMIT_TEXTURE_MSAA }, - { "anisotropy", LIMIT_ANISOTROPY }, + { "pointsize", LIMIT_POINT_SIZE }, + { "texturesize", LIMIT_TEXTURE_SIZE }, + { "texturelayers", LIMIT_TEXTURE_LAYERS }, + { "volumetexturesize", LIMIT_VOLUME_TEXTURE_SIZE }, + { "cubetexturesize", LIMIT_CUBE_TEXTURE_SIZE }, + { "texelbuffersize", LIMIT_TEXEL_BUFFER_SIZE }, + { "shaderstoragebuffersize", LIMIT_SHADER_STORAGE_BUFFER_SIZE }, + { "rendertargets", LIMIT_RENDER_TARGETS }, + { "texturemsaa", LIMIT_TEXTURE_MSAA }, + { "anisotropy", LIMIT_ANISOTROPY }, }; StringMap Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries)); diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 0ad0c5528..88258532d 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -162,6 +162,7 @@ public: LIMIT_CUBE_TEXTURE_SIZE, LIMIT_TEXTURE_LAYERS, LIMIT_TEXEL_BUFFER_SIZE, + LIMIT_SHADER_STORAGE_BUFFER_SIZE, LIMIT_RENDER_TARGETS, LIMIT_TEXTURE_MSAA, LIMIT_ANISOTROPY, diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index df1990fc3..605d74bd6 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -26,6 +26,9 @@ // glslang #include "libraries/glslang/glslang/Public/ShaderLang.h" +// Needed for reflection information. +#include "libraries/glslang/glslang/Include/Types.h" + // C++ #include #include @@ -546,7 +549,7 @@ Shader::Shader(ShaderStage *vertex, ShaderStage *pixel) : stages() { std::string err; - if (!validate(vertex, pixel, err)) + if (!validateInternal(vertex, pixel, err, validationReflection)) throw love::Exception("%s", err.c_str()); stages[ShaderStage::STAGE_VERTEX] = vertex; @@ -629,7 +632,13 @@ void Shader::checkMainTexture(Texture *tex) const checkMainTextureType(tex->getTextureType(), tex->getSamplerState().depthSampleMode.hasValue); } -bool Shader::validate(ShaderStage *vertex, ShaderStage *pixel, std::string &err) +bool Shader::validate(ShaderStage* vertex, ShaderStage* pixel, std::string& err) +{ + ValidationReflection reflection; + return validateInternal(vertex, pixel, err, reflection); +} + +bool Shader::validateInternal(ShaderStage *vertex, ShaderStage *pixel, std::string &err, ValidationReflection &reflection) { glslang::TProgram program; @@ -645,6 +654,59 @@ bool Shader::validate(ShaderStage *vertex, ShaderStage *pixel, std::string &err) return false; } + if (!program.buildReflection(EShReflectionSeparateBuffers)) + { + err = "Cannot get reflection information for shader."; + return false; + } + + for (int i = 0; i < program.getNumBufferBlocks(); i++) + { + const glslang::TObjectReflection &info = program.getBufferBlock(i); + const glslang::TType *type = info.getType(); + if (type != nullptr) + { + const glslang::TQualifier &qualifiers = type->getQualifier(); + + if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && (info.stages & (EShLangVertexMask | EShLangFragmentMask))) + { + err = "Shader validation error:\nStorage Buffer block '" + info.name + "' must be marked as readonly in vertex and pixel shaders."; + return false; + } + + if (qualifiers.layoutPacking != glslang::ElpStd430) + { + err = "Shader validation error:\nStorage Buffer block '" + info.name + "' must use the std430 packing layout."; + return false; + } + + const glslang::TTypeList *structure = type->getStruct(); + if (structure == nullptr || structure->size() != 1) + { + err = "Shader validation error:\nStorage Buffer block '" + info.name + "' must contain a single unsized array of structs."; + return false; + } + + const glslang::TType* structtype = (*structure)[0].type; + if (structtype == nullptr || structtype->getBasicType() != glslang::EbtStruct || !structtype->isUnsizedArray()) + { + err = "Shader validation error:\nStorage Buffer block '" + info.name + "' must contain a single unsized array of structs."; + return false; + } + + BufferReflection bufferReflection = {}; + bufferReflection.stride = (size_t) info.size; + bufferReflection.memberCount = (size_t) info.numMembers; + + reflection.storageBuffers[info.name] = bufferReflection; + } + else + { + err = "Shader validation error:\nCannot retrieve type information for Storage Buffer Block '" + info.name + "'."; + return false; + } + } + return true; } diff --git a/src/modules/graphics/Shader.h b/src/modules/graphics/Shader.h index d75a6100c..c04c51bb8 100644 --- a/src/modules/graphics/Shader.h +++ b/src/modules/graphics/Shader.h @@ -78,6 +78,7 @@ public: UNIFORM_BOOL, UNIFORM_SAMPLER, UNIFORM_TEXELBUFFER, + UNIFORM_STORAGEBUFFER, UNIFORM_UNKNOWN, UNIFORM_MAX_ENUM }; @@ -126,6 +127,8 @@ public: TextureType textureType; DataBaseType texelBufferType; bool isDepthSampler; + size_t bufferStride; + size_t bufferMemberCount; std::string name; union @@ -227,8 +230,23 @@ public: protected: + struct BufferReflection + { + size_t stride; + size_t memberCount; + }; + + struct ValidationReflection + { + std::map storageBuffers; + }; + + static bool validateInternal(ShaderStage* vertex, ShaderStage* pixel, std::string& err, ValidationReflection &reflection); + StrongRef stages[ShaderStage::STAGE_MAX_ENUM]; + ValidationReflection validationReflection; + }; // Shader } // graphics diff --git a/src/modules/graphics/opengl/Buffer.cpp b/src/modules/graphics/opengl/Buffer.cpp index 43276e59f..a9e33c0f9 100644 --- a/src/modules/graphics/opengl/Buffer.cpp +++ b/src/modules/graphics/opengl/Buffer.cpp @@ -78,6 +78,8 @@ Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const st mapType = BUFFERTYPE_VERTEX; else if (typeFlags & TYPEFLAG_INDEX) mapType = BUFFERTYPE_INDEX; + else if (typeFlags & TYPEFLAG_SHADER_STORAGE) + mapType = BUFFERTYPE_SHADER_STORAGE; target = OpenGL::getGLBufferType(mapType); diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 3c6437a62..94b3deab7 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -345,7 +345,19 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b const float texel[] = {0.0f, 0.0f, 0.0f, 1.0f}; - love::graphics::Buffer *buffer = newBuffer(settings, format, texel, sizeof(texel), 1); + auto buffer = newBuffer(settings, format, texel, sizeof(texel), 1); + defaultBuffers[BUFFERTYPE_TEXEL].set(buffer, Acquire::NORETAIN); + } + + if (capabilities.features[FEATURE_GLSL4] && defaultBuffers[BUFFERTYPE_SHADER_STORAGE].get() == nullptr) + { + Buffer::Settings settings(Buffer::TYPEFLAG_SHADER_STORAGE, BUFFERUSAGE_STATIC); + std::vector format = {{"", DATAFORMAT_FLOAT, 0}}; + + std::vector data; + data.resize(Buffer::SHADER_STORAGE_BUFFER_MAX_STRIDE / 4); + + auto buffer = newBuffer(settings, format, data.data(), data.size() * sizeof(float), data.size()); defaultBuffers[BUFFERTYPE_TEXEL].set(buffer, Acquire::NORETAIN); } @@ -359,6 +371,9 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b if (defaultBuffers[BUFFERTYPE_TEXEL].get()) gl.setDefaultTexelBuffer((GLuint) defaultBuffers[BUFFERTYPE_TEXEL]->getTexelBufferHandle()); + if (defaultBuffers[BUFFERTYPE_SHADER_STORAGE].get()) + gl.setDefaultStorageBuffer((GLuint) defaultBuffers[BUFFERTYPE_SHADER_STORAGE]->getHandle()); + // Reload all volatile objects. if (!Volatile::loadAll()) ::printf("Could not reload all volatile objects.\n"); @@ -1515,7 +1530,7 @@ void Graphics::initCapabilities() capabilities.features[FEATURE_GLSL3] = GLAD_ES_VERSION_3_0 || gl.isCoreProfile(); capabilities.features[FEATURE_GLSL4] = GLAD_ES_VERSION_3_1 || (gl.isCoreProfile() && GLAD_VERSION_4_3); capabilities.features[FEATURE_INSTANCING] = gl.isInstancingSupported(); - capabilities.features[FEATURE_TEXEL_BUFFER] = gl.areTexelBuffersSupported(); + capabilities.features[FEATURE_TEXEL_BUFFER] = gl.isBufferTypeSupported(BUFFERTYPE_TEXEL); static_assert(FEATURE_MAX_ENUM == 11, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize(); @@ -1524,10 +1539,11 @@ void Graphics::initCapabilities() capabilities.limits[LIMIT_VOLUME_TEXTURE_SIZE] = gl.getMax3DTextureSize(); capabilities.limits[LIMIT_CUBE_TEXTURE_SIZE] = gl.getMaxCubeTextureSize(); capabilities.limits[LIMIT_TEXEL_BUFFER_SIZE] = gl.getMaxTexelBufferSize(); + capabilities.limits[LIMIT_SHADER_STORAGE_BUFFER_SIZE] = gl.getMaxShaderStorageBufferSize(); capabilities.limits[LIMIT_RENDER_TARGETS] = gl.getMaxRenderTargets(); capabilities.limits[LIMIT_TEXTURE_MSAA] = gl.getMaxSamples(); capabilities.limits[LIMIT_ANISOTROPY] = gl.getMaxAnisotropy(); - static_assert(LIMIT_MAX_ENUM == 9, "Graphics::initCapabilities must be updated when adding a new system limit!"); + static_assert(LIMIT_MAX_ENUM == 10, "Graphics::initCapabilities must be updated when adding a new system limit!"); for (int i = 0; i < TEXTURE_MAX_ENUM; i++) capabilities.textureTypes[i] = gl.isTextureTypeSupported((TextureType) i); diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 7d6c0faab..0974da473 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -101,9 +101,12 @@ OpenGL::OpenGL() , max3DTextureSize(0) , maxCubeTextureSize(0) , maxTextureArrayLayers(0) + , maxTexelBufferSize(0) + , maxShaderStorageBufferSize(0) , maxRenderTargets(1) , maxSamples(1) , maxTextureUnits(1) + , maxShaderStorageBufferBindings(0) , maxPointSize(1) , coreProfile(false) , vendor(VENDOR_UNKNOWN) @@ -235,9 +238,13 @@ void OpenGL::setupContext() for (int i = 0; i < (int) BUFFERTYPE_MAX_ENUM; i++) { state.boundBuffers[i] = 0; - glBindBuffer(getGLBufferType((BufferType) i), 0); + if (isBufferTypeSupported((BufferType) i)) + glBindBuffer(getGLBufferType((BufferType) i), 0); } + if (isBufferTypeSupported(BUFFERTYPE_SHADER_STORAGE)) + state.boundIndexedBuffers[BUFFERTYPE_SHADER_STORAGE].resize(maxShaderStorageBufferBindings, 0); + // Initialize multiple texture unit support for shaders. for (int i = 0; i < TEXTURE_MAX_ENUM + 1; i++) { @@ -290,10 +297,6 @@ void OpenGL::deInitContext() } } - if (state.defaultTexelBuffer != 0) - gl.deleteTexture(state.defaultTexelBuffer); - state.defaultTexelBuffer = 0; - contextInitialized = false; } @@ -481,11 +484,22 @@ void OpenGL::initMaxValues() else maxTextureArrayLayers = 0; - if (areTexelBuffersSupported()) + if (isBufferTypeSupported(BUFFERTYPE_TEXEL)) glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTexelBufferSize); else maxTexelBufferSize = 0; + if (isBufferTypeSupported(BUFFERTYPE_SHADER_STORAGE)) + { + glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &maxShaderStorageBufferSize); + glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &maxShaderStorageBufferBindings); + } + else + { + maxShaderStorageBufferSize = 0; + maxShaderStorageBufferBindings = 0; + } + int maxattachments = 1; int maxdrawbuffers = 1; @@ -606,6 +620,7 @@ GLenum OpenGL::getGLBufferType(BufferType type) case BUFFERTYPE_VERTEX: return GL_ARRAY_BUFFER; case BUFFERTYPE_INDEX: return GL_ELEMENT_ARRAY_BUFFER; case BUFFERTYPE_TEXEL: return GL_TEXTURE_BUFFER; + case BUFFERTYPE_SHADER_STORAGE: return GL_SHADER_STORAGE_BUFFER; case BUFFERTYPE_MAX_ENUM: return GL_ZERO; } @@ -803,6 +818,12 @@ void OpenGL::deleteBuffer(GLuint buffer) { if (state.boundBuffers[i] == buffer) state.boundBuffers[i] = 0; + + for (GLuint &bufferid : state.boundIndexedBuffers[i]) + { + if (bufferid == buffer) + bufferid = 0; + } } } @@ -1142,6 +1163,19 @@ void OpenGL::bindTextureToUnit(Texture *texture, int textureunit, bool restorepr bindTextureToUnit(textype, handle, textureunit, restoreprev, bindforedit); } +void OpenGL::bindIndexedBuffer(GLuint buffer, BufferType type, int index) +{ + auto &bindings = state.boundIndexedBuffers[type]; + if (bindings.size() > (size_t) index && buffer != bindings[index]) + { + bindings[index] = buffer; + glBindBufferBase(getGLBufferType(type), index, buffer); + + // glBindBufferBase affects glBindBuffer as well... for some reason. + state.boundBuffers[type] = buffer; + } +} + void OpenGL::deleteTexture(GLuint texture) { // glDeleteTextures binds texture 0 to all texture units the deleted texture @@ -1392,9 +1426,28 @@ bool OpenGL::isTextureTypeSupported(TextureType type) const return GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_EXT_texture_array; case TEXTURE_CUBE: return GLAD_VERSION_1_3 || GLAD_ES_VERSION_2_0; - default: + case TEXTURE_MAX_ENUM: return false; } + return false; +} + +bool OpenGL::isBufferTypeSupported(BufferType type) const +{ + switch (type) + { + case BUFFERTYPE_VERTEX: + case BUFFERTYPE_INDEX: + return true; + case BUFFERTYPE_TEXEL: + // Not supported in ES until 3.2, which we don't support shaders for... + return GLAD_VERSION_3_1; + case BUFFERTYPE_SHADER_STORAGE: + return (GLAD_VERSION_4_3 && isCoreProfile()) || GLAD_ES_VERSION_3_1; + case BUFFERTYPE_MAX_ENUM: + return false; + } + return false; } bool OpenGL::isClampZeroOneTextureWrapSupported() const @@ -1435,12 +1488,6 @@ bool OpenGL::isMultiFormatMRTSupported() const return getMaxRenderTargets() > 1 && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object); } -bool OpenGL::areTexelBuffersSupported() const -{ - // Not supported in ES until 3.2, which we don't support shaders for... - return GLAD_VERSION_3_1; -} - int OpenGL::getMax2DTextureSize() const { return std::max(max2DTextureSize, 1); @@ -1466,6 +1513,11 @@ int OpenGL::getMaxTexelBufferSize() const return maxTexelBufferSize; } +int OpenGL::getMaxShaderStorageBufferSize() const +{ + return maxShaderStorageBufferSize; +} + int OpenGL::getMaxRenderTargets() const { return std::min(maxRenderTargets, MAX_COLOR_RENDER_TARGETS); @@ -1481,6 +1533,11 @@ int OpenGL::getMaxTextureUnits() const return maxTextureUnits; } +int OpenGL::getMaxShaderStorageBufferBindings() const +{ + return maxShaderStorageBufferBindings; +} + float OpenGL::getMaxPointSize() const { return maxPointSize; diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index df7317f53..d1ac0ae86 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/src/modules/graphics/opengl/OpenGL.h @@ -319,6 +319,9 @@ public: GLuint getDefaultTexelBuffer() const { return state.defaultTexelBuffer; } void setDefaultTexelBuffer(GLuint tex) { state.defaultTexelBuffer = tex; } + GLuint getDefaultStorageBuffer() const { return state.defaultStorageBuffer; } + void setDefaultStorageBuffer(GLuint buf) { state.defaultStorageBuffer = buf; } + /** * Helper for setting the active texture unit. * @@ -338,6 +341,8 @@ public: void bindBufferTextureToUnit(GLuint texture, int textureunit, bool restoreprev, bool bindforedit); + void bindIndexedBuffer(GLuint buffer, BufferType type, int index); + /** * Helper for deleting an OpenGL texture. * Cleans up if the texture is currently bound. @@ -357,6 +362,7 @@ public: bool rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth = 1); bool isTextureTypeSupported(TextureType type) const; + bool isBufferTypeSupported(BufferType type) const; bool isClampZeroOneTextureWrapSupported() const; bool isPixelShaderHighpSupported() const; bool isInstancingSupported() const; @@ -364,7 +370,6 @@ public: bool isSamplerLODBiasSupported() const; bool isBaseVertexSupported() const; bool isMultiFormatMRTSupported() const; - bool areTexelBuffersSupported() const; /** * Returns the maximum supported width or height of a texture. @@ -379,6 +384,11 @@ public: **/ int getMaxTexelBufferSize() const; + /** + * Returns the maximum number of bytes in a shader storage buffer. + **/ + int getMaxShaderStorageBufferSize() const; + /** * Returns the maximum supported number of simultaneous render targets. **/ @@ -394,6 +404,11 @@ public: **/ int getMaxTextureUnits() const; + /** + * Returns the maximum number of shader storage buffer bindings. + **/ + int getMaxShaderStorageBufferBindings() const; + /** * Returns the maximum point size. **/ @@ -458,9 +473,11 @@ private: int maxCubeTextureSize; int maxTextureArrayLayers; int maxTexelBufferSize; + int maxShaderStorageBufferSize; int maxRenderTargets; int maxSamples; int maxTextureUnits; + int maxShaderStorageBufferBindings; float maxPointSize; bool coreProfile; @@ -475,6 +492,8 @@ private: // Texture unit state (currently bound texture for each texture unit.) std::vector boundTextures[TEXTURE_MAX_ENUM + 1]; + std::vector boundIndexedBuffers[BUFFERTYPE_MAX_ENUM]; + bool enableState[ENABLE_MAX_ENUM]; GLenum faceCullMode; @@ -495,6 +514,7 @@ private: GLuint defaultTexture[TEXTURE_MAX_ENUM]; GLuint defaultTexelBuffer; + GLuint defaultStorageBuffer; } state; diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 9f518e204..458091db2 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -37,6 +37,11 @@ namespace graphics namespace opengl { +static bool isBuffer(Shader::UniformType utype) +{ + return utype == Shader::UNIFORM_TEXELBUFFER || utype == Shader::UNIFORM_STORAGEBUFFER; +} + Shader::Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel) : love::graphics::Shader(vertex, pixel) , program(0) @@ -69,7 +74,7 @@ Shader::~Shader() delete[] p.second.textures; } - else if (p.second.baseType == UNIFORM_TEXELBUFFER) + else if (isBuffer(p.second.baseType)) { for (int i = 0; i < p.second.count; i++) { @@ -195,7 +200,7 @@ void Shader::mapActiveUniforms() u.data = malloc(u.dataSize); break; case UNIFORM_MATRIX: - u.dataSize = sizeof(float) * (u.matrix.rows * u.matrix.columns) * u.count; + u.dataSize = sizeof(float) * ((size_t)u.matrix.rows * u.matrix.columns) * u.count; u.data = malloc(u.dataSize); break; default: @@ -267,7 +272,7 @@ void Shader::mapActiveUniforms() break; case UNIFORM_MATRIX: glGetUniformfv(program, location, &u.floats[offset]); - offset += u.matrix.rows * u.matrix.columns; + offset += (size_t)u.matrix.rows * u.matrix.columns; break; default: break; @@ -310,13 +315,90 @@ void Shader::mapActiveUniforms() } } + if (gl.isBufferTypeSupported(BUFFERTYPE_SHADER_STORAGE)) + { + GLint numstoragebuffers = 0; + glGetProgramInterfaceiv(program, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &numstoragebuffers); + + char namebuffer[2048] = { '\0' }; + + for (int sindex = 0; sindex < numstoragebuffers; sindex++) + { + UniformInfo u = {}; + u.baseType = UNIFORM_STORAGEBUFFER; + + GLsizei namelength = 0; + glGetProgramResourceName(program, GL_SHADER_STORAGE_BLOCK, sindex, 2048, &namelength, namebuffer); + + u.name = std::string(namebuffer, namelength); + u.count = 1; + + const auto reflectionit = validationReflection.storageBuffers.find(u.name); + if (reflectionit != validationReflection.storageBuffers.end()) + { + u.bufferStride = reflectionit->second.stride; + u.bufferMemberCount = reflectionit->second.memberCount; + } + + // Make sure previously set uniform data is preserved, and shader- + // initialized values are retrieved. + auto oldu = olduniforms.find(u.name); + if (oldu != olduniforms.end()) + { + u.data = oldu->second.data; + u.dataSize = oldu->second.dataSize; + u.buffers = oldu->second.buffers; + } + else + { + u.dataSize = sizeof(int) * 1; + u.data = malloc(u.dataSize); + + u.ints[0] = -1; + + u.buffers = new love::graphics::Buffer * [u.count]; + memset(u.buffers, 0, sizeof(Buffer*)* u.count); + } + + GLenum props[] = { GL_BUFFER_BINDING }; + glGetProgramResourceiv(program, GL_SHADER_STORAGE_BLOCK, sindex, 1, props, 1, nullptr, u.ints); + + BufferBinding binding; + binding.bindingindex = u.ints[0]; + binding.buffer = gl.getDefaultStorageBuffer(); + + if (binding.bindingindex >= 0) + { + int activeindex = (int)activeStorageBufferBindings.size(); + + storageBufferBindingIndexToActiveBinding[binding.bindingindex] = activeindex; + + activeStorageBufferBindings.push_back(binding); + } + + uniforms[u.name] = u; + + for (int i = 0; i < u.count; i++) + { + if (u.buffers[i] == nullptr) + continue; + Volatile* v = dynamic_cast(u.buffers[i]); + if (v != nullptr) + v->loadVolatile(); + } + + sendBuffers(&u, u.buffers, u.count, true); + } + } + // Make sure uniforms that existed before but don't exist anymore are // cleaned up. This theoretically shouldn't happen, but... for (const auto &p : olduniforms) { if (uniforms.find(p.first) == uniforms.end()) { - free(p.second.data); + if (p.second.data != nullptr) + free(p.second.data); if (p.second.baseType == UNIFORM_SAMPLER) { @@ -328,7 +410,7 @@ void Shader::mapActiveUniforms() delete[] p.second.textures; } - else if (p.second.baseType == UNIFORM_TEXELBUFFER) + else if (isBuffer(p.second.baseType)) { for (int i = 0; i < p.second.count; i++) { @@ -354,6 +436,9 @@ bool Shader::loadVolatile() textureUnits.clear(); textureUnits.push_back(TextureUnit()); + storageBufferBindingIndexToActiveBinding.resize(gl.getMaxShaderStorageBufferBindings(), -1); + activeStorageBufferBindings.clear(); + for (const auto &stage : stages) { if (stage.get() != nullptr) @@ -500,6 +585,9 @@ void Shader::attach() } } + for (auto bufferbinding : activeStorageBufferBindings) + gl.bindIndexedBuffer(bufferbinding.buffer, BUFFERTYPE_SHADER_STORAGE, bufferbinding.bindingindex); + // send any pending uniforms to the shader program. for (const auto &p : pendingUniformUpdates) updateUniform(p.first, p.second, true); @@ -722,10 +810,18 @@ static bool isTexelBufferTypeCompatible(DataBaseType a, DataBaseType b) void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count, bool internalUpdate) { - if (info->baseType != UNIFORM_TEXELBUFFER) - return; + uint32 requiredtypeflags = 0; - uint32 requiredtypeflags = Buffer::TYPEFLAG_TEXEL; + bool texelbinding = info->baseType == UNIFORM_TEXELBUFFER; + bool storagebinding = info->baseType == UNIFORM_STORAGEBUFFER; + + if (texelbinding) + requiredtypeflags = Buffer::TYPEFLAG_TEXEL; + else if (storagebinding) + requiredtypeflags = Buffer::TYPEFLAG_SHADER_STORAGE; + + if (requiredtypeflags == 0) + return; bool shaderactive = current == this; @@ -745,17 +841,43 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe { if (internalUpdate) continue; - else + else if (texelbinding) throw love::Exception("Shader uniform '%s' is a texel buffer, but the given Buffer was not created with texel buffer capabilities.", info->name.c_str()); + else if (storagebinding) + throw love::Exception("Shader uniform '%s' is a shader storage buffer block, but the given Buffer was not created with shader storage buffer capabilities.", info->name.c_str()); + else + throw love::Exception("Shader uniform '%s' does not match the types supported by the given Buffer.", info->name.c_str()); } - DataBaseType basetype = buffer->getDataMember(0).info.baseType; - if (!isTexelBufferTypeCompatible(basetype, info->texelBufferType)) + if (texelbinding) { - if (internalUpdate) - continue; - else - throw love::Exception("Texel buffer's data format base type must match the variable declared in the shader."); + DataBaseType basetype = buffer->getDataMember(0).info.baseType; + if (!isTexelBufferTypeCompatible(basetype, info->texelBufferType)) + { + if (internalUpdate) + continue; + else + throw love::Exception("Texel buffer's data format base type must match the variable declared in the shader."); + } + } + else if (storagebinding) + { + if (info->bufferStride != buffer->getArrayStride()) + { + if (internalUpdate) + continue; + else + throw love::Exception("Shader storage block '%s' has an array stride of %d bytes, but the given Buffer has an array stride of %d bytes.", + info->name.c_str(), info->bufferStride, buffer->getArrayStride()); + } + else if (info->bufferMemberCount != buffer->getDataMembers().size()) + { + if (internalUpdate) + continue; + else + throw love::Exception("Shader storage block '%s' has a struct with %d fields, but the given Buffer has a format with %d members.", + info->name.c_str(), info->bufferMemberCount, buffer->getDataMembers().size()); + } } buffer->retain(); @@ -766,19 +888,39 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe info->buffers[i] = buffer; - GLuint gltex = 0; - if (buffers[i] != nullptr) - gltex = (GLuint) buffer->getTexelBufferHandle(); - else - gltex = gl.getDefaultTexelBuffer(); + if (texelbinding) + { + GLuint gltex = 0; + if (buffers[i] != nullptr) + gltex = (GLuint) buffer->getTexelBufferHandle(); + else + gltex = gl.getDefaultTexelBuffer(); - int texunit = info->ints[i]; + int texunit = info->ints[i]; - if (shaderactive) - gl.bindBufferTextureToUnit(gltex, texunit, false, false); + if (shaderactive) + gl.bindBufferTextureToUnit(gltex, texunit, false, false); - // Store texture id so it can be re-bound to the texture unit later. - textureUnits[texunit].texture = gltex; + // Store texture id so it can be re-bound to the texture unit later. + textureUnits[texunit].texture = gltex; + } + else if (storagebinding) + { + int bindingindex = info->ints[i]; + + GLuint glbuffer = 0; + if (buffers[i] != nullptr) + glbuffer = (GLuint) buffer->getHandle(); + else + glbuffer = gl.getDefaultStorageBuffer(); + + if (shaderactive) + gl.bindIndexedBuffer(glbuffer, BUFFERTYPE_SHADER_STORAGE, bindingindex); + + int activeindex = storageBufferBindingIndexToActiveBinding[bindingindex]; + if (activeindex >= 0) + activeStorageBufferBindings[activeindex].buffer = glbuffer; + } } } diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index 393c7fb6c..2f215c687 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/src/modules/graphics/opengl/Shader.h @@ -80,6 +80,12 @@ private: bool active = false; }; + struct BufferBinding + { + int bindingindex = 0; + GLuint buffer = 0; + }; + // Map active uniform names to their locations. void mapActiveUniforms(); @@ -117,6 +123,9 @@ private: // Texture unit pool for setting textures std::vector textureUnits; + std::vector storageBufferBindingIndexToActiveBinding; + std::vector activeStorageBufferBindings; + std::vector> pendingUniformUpdates; float lastPointSize; diff --git a/src/modules/graphics/vertex.cpp b/src/modules/graphics/vertex.cpp index 15d16891e..d448b9895 100644 --- a/src/modules/graphics/vertex.cpp +++ b/src/modules/graphics/vertex.cpp @@ -339,9 +339,10 @@ const char *getConstant(BuiltinVertexAttribute attrib) STRINGMAP_BEGIN(BufferType, BUFFERTYPE_MAX_ENUM, bufferTypeName) { - { "vertex", BUFFERTYPE_VERTEX }, - { "index", BUFFERTYPE_INDEX }, - { "texel", BUFFERTYPE_TEXEL }, + { "vertex", BUFFERTYPE_VERTEX }, + { "index", BUFFERTYPE_INDEX }, + { "texel", BUFFERTYPE_TEXEL }, + { "shaderstorage", BUFFERTYPE_SHADER_STORAGE }, } STRINGMAP_END(BufferType, BUFFERTYPE_MAX_ENUM, bufferTypeName) diff --git a/src/modules/graphics/vertex.h b/src/modules/graphics/vertex.h index f1479ccf8..21ff230ce 100644 --- a/src/modules/graphics/vertex.h +++ b/src/modules/graphics/vertex.h @@ -59,6 +59,7 @@ enum BufferType BUFFERTYPE_VERTEX = 0, BUFFERTYPE_INDEX, BUFFERTYPE_TEXEL, + BUFFERTYPE_SHADER_STORAGE, BUFFERTYPE_MAX_ENUM }; diff --git a/src/modules/graphics/wrap_Shader.cpp b/src/modules/graphics/wrap_Shader.cpp index ad73f5c91..bf7a3a8e1 100644 --- a/src/modules/graphics/wrap_Shader.cpp +++ b/src/modules/graphics/wrap_Shader.cpp @@ -278,8 +278,6 @@ int w_Shader_sendTextures(lua_State *L, int startidx, Shader *shader, const Shad for (int i = 0; i < count; i++) { Texture *tex = luax_checktexture(L, startidx + i); - if (tex->getTextureType() != info->textureType) - return luaL_argerror(L, startidx + i, "invalid texture type for uniform"); textures.push_back(tex); } @@ -321,6 +319,7 @@ static int w_Shader_sendLuaValues(lua_State *L, int startidx, Shader *shader, co case Shader::UNIFORM_SAMPLER: return w_Shader_sendTextures(L, startidx, shader, info); case Shader::UNIFORM_TEXELBUFFER: + case Shader::UNIFORM_STORAGEBUFFER: return w_Shader_sendBuffers(L, startidx, shader, info); default: return luaL_error(L, "Unknown variable type for shader uniform '%s", name); @@ -329,8 +328,8 @@ static int w_Shader_sendLuaValues(lua_State *L, int startidx, Shader *shader, co static int w_Shader_sendData(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info, bool colors) { - if (info->baseType == Shader::UNIFORM_SAMPLER) - return luaL_error(L, "Uniform sampler values (textures) cannot be sent to Shaders via Data objects."); + if (info->baseType == Shader::UNIFORM_SAMPLER || info->baseType == Shader::UNIFORM_TEXELBUFFER || info->baseType == Shader::UNIFORM_STORAGEBUFFER) + return luaL_error(L, "Only value types (floats, ints, vectors, matrices, etc) be sent to Shaders via Data objects."); math::Transform::MatrixLayout layout = math::Transform::MATRIX_ROW_MAJOR; int dataidx = startidx;