Merge branch '12.0-development' into metal

This commit is contained in:
Alex Szpakowski
2021-01-14 20:33:45 -04:00
40 changed files with 1692 additions and 574 deletions
@@ -884,6 +884,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 */; };
@@ -1936,6 +1938,8 @@
FAAA3FD51F64B3AD00F89E99 /* lstrlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lstrlib.h; sourceTree = "<group>"; };
FAAA3FD61F64B3AD00F89E99 /* lutf8lib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lutf8lib.c; sourceTree = "<group>"; };
FAAA3FD71F64B3AD00F89E99 /* lutf8lib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lutf8lib.h; sourceTree = "<group>"; };
FAAC2F78251A9D2200BCB81B /* apple.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = apple.mm; sourceTree = "<group>"; };
FAAC2F7F251A9D3E00BCB81B /* apple.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = apple.h; sourceTree = "<group>"; };
FAAFF04316CB11C700CCDE45 /* OpenAL-Soft.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "OpenAL-Soft.framework"; path = "/Library/Frameworks/OpenAL-Soft.framework"; sourceTree = "<absolute>"; };
FAB17BE41ABFAA9000F9BA27 /* lz4.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lz4.c; sourceTree = "<group>"; };
FAB17BE51ABFAA9000F9BA27 /* lz4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lz4.h; sourceTree = "<group>"; };
@@ -2308,6 +2312,8 @@
children = (
FAA3A9AC1B7D465A00CED060 /* android.cpp */,
FAA3A9AD1B7D465A00CED060 /* android.h */,
FAAC2F7F251A9D3E00BCB81B /* apple.h */,
FAAC2F78251A9D2200BCB81B /* apple.mm */,
FA0B78F71A958E3B000E1D17 /* b64.cpp */,
FA0B78F81A958E3B000E1D17 /* b64.h */,
FA6BDE5B1F31725300786805 /* Color.h */,
@@ -4435,7 +4441,7 @@
08FB7793FE84155DC02AAC07 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1000;
LastUpgradeCheck = 1230;
TargetAttributes = {
FA0B78DC1A958B90000E1D17 = {
CreatedOnToolsVersion = 6.1.1;
@@ -4873,6 +4879,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 */,
FABDA9872552448200B5C523 /* b2_body.cpp in Sources */,
FAF140891E20934C00F898D2 /* PoolAlloc.cpp in Sources */,
@@ -5283,6 +5290,7 @@
217DFC0B1D9F6D490055D849 /* unixtcp.c in Sources */,
FAF140881E20934C00F898D2 /* PoolAlloc.cpp in Sources */,
FA0B7DEE1A95902C000E1D17 /* Mouse.cpp in Sources */,
FAAC2F79251A9D2200BCB81B /* apple.mm in Sources */,
FAA54ACC1F91660400A8FA7B /* TheoraVideoStream.cpp in Sources */,
FA1E887E1DF363CD00E808AA /* Filter.cpp in Sources */,
FABDA9862552448200B5C523 /* b2_body.cpp in Sources */,
@@ -5314,7 +5322,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;
@@ -5362,10 +5370,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;
@@ -5376,7 +5384,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;
@@ -5428,10 +5436,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;
@@ -5563,7 +5571,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;
@@ -5611,11 +5619,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;
@@ -5656,6 +5664,7 @@
"$(inherited)",
"$(PROJECT_DIR)/ios/libraries/freetype",
);
MARKETING_VERSION = 11.4;
OTHER_LDFLAGS = (
"-undefined",
dynamic_lookup,
@@ -5699,6 +5708,7 @@
"$(inherited)",
"$(PROJECT_DIR)/ios/libraries/freetype",
);
MARKETING_VERSION = 11.4;
OTHER_LDFLAGS = (
"-undefined",
dynamic_lookup,
@@ -5743,6 +5753,7 @@
"$(inherited)",
"$(PROJECT_DIR)/ios/libraries/freetype",
);
MARKETING_VERSION = 11.4;
OTHER_LDFLAGS = (
"-undefined",
dynamic_lookup,
@@ -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;
@@ -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]" = (
@@ -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;
@@ -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]" = (
@@ -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;
@@ -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]" = (
+15
View File
@@ -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<std::string> 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<std::string> getConstants(type); \
#define STRINGMAP_CLASS_BEGIN(classname, type, count, name) \
static StringMap<type, count>::Entry name##Entries[] =
#define STRINGMAP_CLASS_END(classname, type, count, name) \
; \
static StringMap<type, count> 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<std::string> classname::getConstants(type) { return name##s.getNames(); }
} // love
#endif // LOVE_STRING_MAP_H
+8 -1
View File
@@ -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);
+50
View File
@@ -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 <string>
namespace love
{
namespace apple
{
enum UserDirectory
{
USER_DIRECTORY_HOME,
USER_DIRECTORY_APPSUPPORT,
USER_DIRECTORY_DOCUMENTS,
USER_DIRECTORY_DESKTOP,
USER_DIRECTORY_CACHES,
USER_DIRECTORY_TEMP,
};
std::string getUserDirectory(UserDirectory dir);
std::string getExecutablePath();
} // apple
} // love
#endif // defined(LOVE_IOS) || defined(LOVE_MACOS)
+79
View File
@@ -0,0 +1,79 @@
/**
* 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 <Foundation/Foundation.h>
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;
case USER_DIRECTORY_TEMP:
nsdir = NSItemReplacementDirectory;
break;
}
NSArray<NSURL *> *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)
-15
View File
@@ -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.
**/
+1 -46
View File
@@ -19,6 +19,7 @@
**/
#include "ios.h"
#include "apple.h"
#ifdef LOVE_IOS
@@ -127,12 +128,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];
@@ -339,38 +334,6 @@ std::string getLoveInResources(bool &fused)
return path;
}
static std::string getUserDirectory(NSSearchPathDirectory dir)
{
std::string path;
@autoreleasepool
{
NSArray<NSURL *> *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;
@@ -387,14 +350,6 @@ bool openURL(const std::string &url)
return success;
}
std::string getExecutablePath()
{
@autoreleasepool
{
return std::string([NSBundle mainBundle].executablePath.UTF8String);
}
}
void vibrate()
{
@autoreleasepool
-7
View File
@@ -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.
**/
-28
View File
@@ -36,26 +36,6 @@ namespace love
namespace macos
{
static std::string getUserDirectory(NSSearchPathDirectory dir)
{
std::string path;
@autoreleasepool
{
NSArray<NSURL *> *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
+138 -19
View File
@@ -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 */
@@ -1102,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;
@@ -1749,7 +1768,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 +1789,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 +1816,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 +1832,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 +1856,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 +1871,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 */
@@ -1882,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);
@@ -1899,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);
@@ -2159,10 +2209,76 @@ 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 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;
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))
{
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 deepest_dirhandle != NULL ? deepest_dirhandle : 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 +2328,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 +2346,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 +2359,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 +2750,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 +3234,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))
+14
View File
@@ -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.
@@ -2779,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)
+102 -26
View File
@@ -26,12 +26,12 @@
#include <sys/types.h>
#include <sys/stat.h>
#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"
#include <unistd.h>
#elif defined(LOVE_WINDOWS)
#include <windows.h>
#include <fileapi.h>
#include "common/utf8.h"
#elif defined(LOVE_LINUX)
#include <unistd.h>
@@ -70,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.
@@ -79,23 +87,89 @@ 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
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.c_str(), 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<std::string> 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
{
#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 +194,32 @@ 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);
{ "appsavedir", Filesystem::COMMONPATH_APP_SAVEDIR },
{ "appdocuments", Filesystem::COMMONPATH_APP_DOCUMENTS },
{ "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<std::string> 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<Filesystem::FileType, Filesystem::FILETYPE_MAX_ENUM>::Entry Filesystem::fileTypeEntries[] =
{
{ "file", FILETYPE_FILE },
{ "directory", FILETYPE_DIRECTORY },
{ "symlink", FILETYPE_SYMLINK },
{ "other", FILETYPE_OTHER },
};
StringMap<Filesystem::FileType, Filesystem::FILETYPE_MAX_ENUM> Filesystem::fileTypes(Filesystem::fileTypeEntries, sizeof(Filesystem::fileTypeEntries));
STRINGMAP_CLASS_END(Filesystem, Filesystem::MountPermissions, Filesystem::MOUNT_PERMISSIONS_MAX_ENUM, mountPermissions)
} // filesystem
} // love
+41 -7
View File
@@ -71,12 +71,31 @@ public:
FILETYPE_MAX_ENUM
};
enum CommonPath
{
COMMONPATH_APP_SAVEDIR,
COMMONPATH_APP_DOCUMENTS,
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.
int64 size;
int64 modtime;
FileType type;
bool readonly;
};
static love::Type type;
@@ -136,8 +155,14 @@ 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;
virtual bool unmountFullPath(const char *fullpath) = 0;
/**
* Creates a new file.
@@ -152,6 +177,11 @@ 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;
/**
* Gets the current working directory.
**/
@@ -172,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.
@@ -258,23 +288,27 @@ 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.
**/
virtual std::string getExecutablePath() const;
static bool getConstant(const char *in, FileType &out);
static bool getConstant(FileType in, const char *&out);
static std::vector<std::string> getConstants(FileType);
STRINGMAP_CLASS_DECLARE(FileType);
STRINGMAP_CLASS_DECLARE(CommonPath);
STRINGMAP_CLASS_DECLARE(MountPermissions);
private:
bool getRealPathType(const std::string &path, FileType &ftype) const;
// Should we save external or internal for Android
bool useExternal;
static StringMap<FileType, FILETYPE_MAX_ENUM>::Entry fileTypeEntries[];
static StringMap<FileType, FILETYPE_MAX_ENUM> fileTypes;
}; // Filesystem
} // filesystem
+37 -9
View File
@@ -106,14 +106,25 @@ bool NativeFile::isOpen() const
int64 NativeFile::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 NativeFile::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;
@@ -165,7 +182,7 @@ bool NativeFile::flush()
bool NativeFile::isEOF()
{
return file == nullptr || feof(file) != 0;
return file == nullptr || tell() >= getSize();
}
int64 NativeFile::tell()
@@ -173,12 +190,23 @@ int64 NativeFile::tell()
if (file == nullptr)
return -1;
return (int64) ftell(file);
#ifdef LOVE_WINDOWS
return (int64) _ftelli64(file);
#else
return (int64) ftello(file);
#endif
}
bool NativeFile::seek(uint64 pos)
{
return file != nullptr && fseek(file, (long) pos, SEEK_SET) == 0;
if (file == nullptr)
return false;
#ifdef LOVE_WINDOWS
return _fseeki64(file, (int64) pos, SEEK_SET) == 0;
#else
return fseeko(file, (off_t) pos, SEEK_SET) == 0;
#endif
}
bool NativeFile::setBuffer(BufferMode bufmode, int64 size)
+1 -2
View File
@@ -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)
+345 -212
View File
@@ -45,6 +45,10 @@
# include <unistd.h>
#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,9 +71,46 @@ namespace filesystem
namespace physfs
{
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 const Filesystem::CommonPath appCommonPaths[] =
{
Filesystem::COMMONPATH_APP_SAVEDIR,
Filesystem::COMMONPATH_APP_DOCUMENTS
};
static bool isAppCommonPath(Filesystem::CommonPath path)
{
switch (path)
{
case Filesystem::COMMONPATH_APP_SAVEDIR:
case Filesystem::COMMONPATH_APP_DOCUMENTS:
return true;
default:
return false;
}
}
Filesystem::Filesystem()
: fused(false)
: appendIdentityToPath(false)
, fused(false)
, fusedSet(false)
, fullPaths()
, commonPathMountInfo()
, saveDirectoryNeedsMounting(false)
{
requirePath = {"?.lua", "?/init.lua"};
cRequirePath = {"??"};
@@ -153,67 +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.
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);
if (fused)
save_path_full += std::string(LOVE_APPDATA_PREFIX) + save_identity;
else
save_path_full += save_path_relative;
bool oldMountedCommonPaths[COMMONPATH_MAX_ENUM] = {false};
save_path_full = normalize(save_path_full);
#ifdef LOVE_ANDROID
if (save_identity == "")
save_identity = "unnamed";
// 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 storage_path;
if (isAndroidSaveExternal())
storage_path = SDL_AndroidGetExternalStoragePath();
else
storage_path = SDL_AndroidGetInternalStoragePath();
// These will be re-populated by getFullCommonPath.
for (CommonPath p : appCommonPaths)
fullPaths[p].clear();
std::string save_directory = storage_path + "/save";
// Store the save directory. getFullCommonPath(COMMONPATH_APP_*) uses this.
saveIdentity = std::string(ident);
appendIdentityToPath = appendToPath;
save_path_full = storage_path + std::string("/save/") + save_identity;
// 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;
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
// 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)
@@ -222,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;
@@ -264,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()
@@ -279,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;
}
@@ -363,17 +329,52 @@ 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;
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)
return false;
return PHYSFS_mount(realPath.c_str(), mountpoint, appendToPath) != 0;
if (permissions == MOUNT_PERMISSIONS_READWRITE)
return PHYSFS_mountRW(archive, mountpoint, appendToPath) != 0;
return PHYSFS_mount(archive, mountpoint, appendToPath) != 0;
}
bool Filesystem::mountCommonPathInternal(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath, bool createDir)
{
std::string fullpath = getFullCommonPath(path);
if (fullpath.empty())
return false;
if (createDir && isAppCommonPath(path) && !isRealDirectory(fullpath))
{
if (!createRealDirectory(fullpath))
return false;
}
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)
@@ -403,42 +404,52 @@ bool Filesystem::unmount(const char *archive)
return true;
}
std::string realPath;
std::string sourceBase = getSourceBaseDirectory();
// Check whether the given archive path is in the list of allowed full paths.
auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
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;
return unmountFullPath(archive);
const char *realDir = PHYSFS_getRealDir(archive);
if (!realDir)
return false;
std::string sourceBase = getSourceBaseDirectory();
if (isFused() && sourceBase.compare(archive) == 0)
return unmountFullPath(archive);
realPath = realDir;
realPath += LOVE_PATH_SEPARATOR;
realPath += archive;
}
if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0)
return false;
const char *mountPoint = PHYSFS_getMountPoint(realPath.c_str());
if (!mountPoint)
const char *realDir = PHYSFS_getRealDir(archive);
if (!realDir)
return false;
std::string realPath = realDir;
realPath += LOVE_PATH_SEPARATOR;
realPath += archive;
if (PHYSFS_getMountPoint(realPath.c_str()) == nullptr)
return false;
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() && unmountFullPath(fullpath.c_str()))
{
commonPathMountInfo[path].mounted = false;
return true;
}
return false;
}
bool Filesystem::unmount(Data *data)
{
for (const auto &datapair : mountedData)
@@ -458,6 +469,166 @@ love::filesystem::File *Filesystem::newFile(const char *filename) const
return new File(filename);
}
std::string Filesystem::getFullCommonPath(CommonPath path)
{
if (!fullPaths[path].empty())
return fullPaths[path];
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_SAVEDIR:
case COMMONPATH_APP_DOCUMENTS:
// Handled above.
break;
case COMMONPATH_USER_HOME:
fullPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_HOME);
break;
case COMMONPATH_USER_APPDATA:
fullPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_APPSUPPORT);
break;
case COMMONPATH_USER_DESKTOP:
fullPaths[path] = apple::getUserDirectory(apple::USER_DIRECTORY_DESKTOP);
break;
case COMMONPATH_USER_DOCUMENTS:
fullPaths[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_SAVEDIR:
case COMMONPATH_APP_DOCUMENTS:
// 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))
{
fullPaths[path] = to_utf8(winpath);
CoTaskMemFree(winpath);
}
#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_SAVEDIR:
case COMMONPATH_APP_DOCUMENTS:
// Handled above.
break;
case COMMONPATH_USER_HOME:
fullPaths[path] = normalize(PHYSFS_getUserDir());
break;
case COMMONPATH_USER_APPDATA:
xdgdir = getenv("XDG_DATA_HOME");
if (!xdgdir)
fullPaths[path] = normalize(std::string(getUserDirectory()) + "/.local/share/");
else
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;
}
#endif
return fullPaths[path];
}
const char *Filesystem::getWorkingDirectory()
{
if (cwd.empty())
@@ -474,7 +645,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,61 +654,22 @@ const char *Filesystem::getWorkingDirectory()
std::string Filesystem::getUserDirectory()
{
#ifdef LOVE_IOS
// PHYSFS_getUserDir doesn't give exactly the path we want on iOS.
static std::string userDir = normalize(love::ios::getHomeDirectory());
#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)
appdata = normalize(love::macos::getAppdataDirectory());
#elif defined(LOVE_IOS)
appdata = normalize(love::ios::getAppdataDirectory());
#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 "";
@@ -546,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)
@@ -558,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
@@ -585,6 +717,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;
@@ -603,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))
@@ -617,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))
+27 -15
View File
@@ -63,15 +63,21 @@ 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;
bool unmountFullPath(const char *fullpath) 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;
const char *getSaveDirectory() override;
std::string getSaveDirectory() override;
std::string getSourceBaseDirectory() const override;
std::string getRealDirectory(const char *filename) const override;
@@ -98,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;
@@ -129,6 +135,12 @@ private:
std::map<std::string, StrongRef<Data>> mountedData;
std::string fullPaths[COMMONPATH_MAX_ENUM];
CommonPathMountInfo commonPathMountInfo[COMMONPATH_MAX_ENUM];
bool saveDirectoryNeedsMounting;
}; // Filesystem
} // physfs
+80 -1
View File
@@ -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,24 @@ 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);
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 +391,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());
@@ -351,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;
}
@@ -421,6 +492,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<int64>(info.size, 0x20000000000000LL);
if (info.size >= 0)
@@ -839,8 +913,13 @@ static const luaL_Reg functions[] =
{ "setSource", w_setSource },
{ "getSource", w_getSource },
{ "mount", w_mount },
{ "mountFullPath", w_mountFullPath },
{ "mountCommonPath", w_mountCommonPath },
{ "unmount", w_unmount },
{ "unmountFullPath", w_unmountFullPath },
{ "unmountCommonPath", w_unmountCommonPath },
{ "newFile", w_newFile },
{ "getFullCommonPath", w_getFullCommonPath },
{ "getWorkingDirectory", w_getWorkingDirectory },
{ "getUserDirectory", w_getUserDirectory },
{ "getAppdataDirectory", w_getAppdataDirectory },
+59 -8
View File
@@ -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<DataDe
bool indexbuffer = settings.typeFlags & TYPEFLAG_INDEX;
bool vertexbuffer = settings.typeFlags & TYPEFLAG_VERTEX;
bool texelbuffer = settings.typeFlags & TYPEFLAG_TEXEL;
bool storagebuffer = settings.typeFlags & TYPEFLAG_SHADER_STORAGE;
if (!indexbuffer && !vertexbuffer && !texelbuffer)
throw love::Exception("Buffer must be created with at least one buffer type (index, vertex, or texel).");
if (!indexbuffer && !vertexbuffer && !texelbuffer && !storagebuffer)
throw love::Exception("Buffer must be created with at least one buffer type (index, vertex, texel, or shaderstorage).");
if (texelbuffer && !caps.features[Graphics::FEATURE_TEXEL_BUFFER])
throw love::Exception("Texel buffers are not supported on this system.");
if (storagebuffer && !caps.features[Graphics::FEATURE_GLSL4])
throw love::Exception("Shader Storage buffers are not supported on this system (GLSL 4 support is necessary.)");
size_t offset = 0;
size_t stride = 0;
size_t structurealignment = 1;
for (const DataDeclaration &decl : bufferformat)
{
@@ -116,16 +122,60 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
throw love::Exception("Signed normalized formats are not supported in texel buffers.");
}
// TODO: alignment
member.offset = offset;
member.size = member.info.size;
size_t memberoffset = offset;
size_t membersize = member.info.size;
offset += member.size;
// Storage buffers are always treated as being an array of a structure.
// The structure's contents are the buffer format declaration.
if (storagebuffer)
{
// TODO: We can support these.
if (decl.arrayLength > 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::vector<DataDe
this->size = 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()
+3
View File
@@ -48,6 +48,8 @@ public:
static love::Type type;
static const size_t SHADER_STORAGE_BUFFER_MAX_STRIDE = 2048;
enum MapType
{
MAP_WRITE_INVALIDATE,
@@ -60,6 +62,7 @@ public:
TYPEFLAG_INDEX = 1 << BUFFERTYPE_INDEX,
TYPEFLAG_UNIFORM = 1 << BUFFERTYPE_UNIFORM,
TYPEFLAG_TEXEL = 1 << BUFFERTYPE_TEXEL,
TYPEFLAG_SHADER_STORAGE = 1 << BUFFERTYPE_SHADER_STORAGE,
};
struct DataDeclaration
+12 -11
View File
@@ -298,7 +298,7 @@ Shader *Graphics::newShader(const std::vector<std::string> &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);
@@ -377,7 +377,7 @@ bool Graphics::validateShader(bool gles, const std::vector<std::string> &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);
@@ -1987,15 +1987,16 @@ StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM> Graphics::features(Grap
StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM>::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::SystemLimit, Graphics::LIMIT_MAX_ENUM> Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries));
+1
View File
@@ -163,6 +163,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,
+159 -39
View File
@@ -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 <string>
#include <regex>
@@ -38,6 +41,7 @@ namespace graphics
namespace glsl
{
static const char global_syntax[] = R"(
#if !defined(GL_ES) && __VERSION__ < 140
#define lowp
@@ -65,7 +69,11 @@ static const char global_syntax[] = R"(
#define DepthCubeImage samplerCubeShadow
#endif
#define extern uniform
#ifdef GL_EXT_texture_array
#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
@@ -133,7 +141,7 @@ void love_initializeBuiltinUniforms() {
static const char global_functions[] = R"(
#ifdef GL_ES
#if __VERSION__ >= 300 || defined(GL_EXT_texture_array)
#if __VERSION__ >= 300 || defined(LOVE_EXT_TEXTURE_ARRAY_ENABLED)
precision lowp sampler2DArray;
#endif
#if __VERSION__ >= 300 || defined(GL_OES_texture_3D)
@@ -169,7 +177,7 @@ static const char global_functions[] = R"(
#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(LOVE_EXT_TEXTURE_ARRAY_ENABLED)
vec4 Texel(sampler2DArray s, vec3 c) { return love_texture2DArray(s, c); }
#endif
#ifdef PIXEL
@@ -178,7 +186,7 @@ static const char global_functions[] = R"(
#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(LOVE_EXT_TEXTURE_ARRAY_ENABLED)
vec4 Texel(sampler2DArray s, vec3 c, float b) { return love_texture2DArray(s, c, b); }
#endif
#endif
@@ -282,6 +290,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;
@@ -291,29 +309,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
LOVE_IO_LOCATION(0) out vec4 love_RenderTargets[love_MaxRenderTargets];
#define love_PixelColor love_RenderTargets[0]
#else
LOVE_IO_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))
@@ -342,6 +341,12 @@ vec4 VideoTexel(vec2 texcoords) {
)";
static const char pixel_main[] = R"(
#if __VERSION__ >= 130
LOVE_IO_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;
@@ -355,6 +360,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
LOVE_IO_LOCATION(0) out vec4 love_RenderTargets[love_MaxRenderTargets];
#define love_PixelColor love_RenderTargets[0]
#else
LOVE_IO_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;
@@ -366,6 +395,15 @@ void main() {
}
)";
static const char pixel_main_raw[] = R"(
void pixelmain();
void main() {
love_initializeBuiltinUniforms();
pixelmain();
}
)";
struct StageInfo
{
const char *name;
@@ -373,12 +411,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.");
@@ -407,30 +446,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
@@ -446,8 +493,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;
}
@@ -456,6 +503,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 (vertexmain or pixelmain) must use GLSL 3 or greater.");
const auto &features = gfx->getCapabilities().features;
if (info.language == LANGUAGE_GLSL3 && !features[Graphics::FEATURE_GLSL3])
@@ -490,7 +543,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;
@@ -501,7 +562,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;
@@ -584,7 +645,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;
@@ -600,6 +667,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;
}
+27 -2
View File
@@ -78,6 +78,7 @@ public:
UNIFORM_BOOL,
UNIFORM_SAMPLER,
UNIFORM_TEXELBUFFER,
UNIFORM_STORAGEBUFFER,
UNIFORM_UNKNOWN,
UNIFORM_MAX_ENUM
};
@@ -90,11 +91,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;
};
@@ -119,6 +127,8 @@ public:
TextureType textureType;
DataBaseType texelBufferType;
bool isDepthSampler;
size_t bufferStride;
size_t bufferMemberCount;
std::string name;
union
@@ -220,8 +230,23 @@ public:
protected:
struct BufferReflection
{
size_t stride;
size_t memberCount;
};
struct ValidationReflection
{
std::map<std::string, BufferReflection> storageBuffers;
};
static bool validateInternal(ShaderStage* vertex, ShaderStage* pixel, std::string& err, ValidationReflection &reflection);
StrongRef<ShaderStage> stages[ShaderStage::STAGE_MAX_ENUM];
ValidationReflection validationReflection;
}; // Shader
} // graphics
+2
View File
@@ -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);
+21 -5
View File
@@ -333,8 +333,8 @@ bool Graphics::setMode(void */*context*/, int width, int height, int pixelwidth,
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)
if (!gl.bugs.brokenSRGB && (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB
|| GLAD_EXT_framebuffer_sRGB || GLAD_ES_VERSION_3_0))
{
if (GLAD_VERSION_1_0 || GLAD_EXT_sRGB_write_control)
gl.setEnableState(OpenGL::ENABLE_FRAMEBUFFER_SRGB, isGammaCorrect());
@@ -362,7 +362,19 @@ bool Graphics::setMode(void */*context*/, int width, int height, int pixelwidth,
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<Buffer::DataDeclaration> format = {{"", DATAFORMAT_FLOAT, 0}};
std::vector<float> 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);
}
@@ -376,6 +388,9 @@ bool Graphics::setMode(void */*context*/, int width, int height, int pixelwidth,
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");
@@ -1538,7 +1553,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();
@@ -1547,10 +1562,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);
+100 -23
View File
@@ -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)
@@ -150,6 +153,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
@@ -210,8 +223,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]);
}
@@ -225,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++)
{
@@ -280,10 +297,6 @@ void OpenGL::deInitContext()
}
}
if (state.defaultTexelBuffer != 0)
gl.deleteTexture(state.defaultTexelBuffer);
state.defaultTexelBuffer = 0;
contextInitialized = false;
}
@@ -391,15 +404,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)
@@ -463,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;
@@ -589,6 +621,7 @@ GLenum OpenGL::getGLBufferType(BufferType type)
case BUFFERTYPE_INDEX: return GL_ELEMENT_ARRAY_BUFFER;
case BUFFERTYPE_TEXEL: return GL_TEXTURE_BUFFER;
case BUFFERTYPE_UNIFORM: return GL_UNIFORM_BUFFER;
case BUFFERTYPE_SHADER_STORAGE: return GL_SHADER_STORAGE_BUFFER;
case BUFFERTYPE_MAX_ENUM: return GL_ZERO;
}
@@ -786,6 +819,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;
}
}
}
@@ -1125,6 +1164,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
@@ -1375,9 +1427,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
@@ -1418,12 +1489,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);
@@ -1449,6 +1514,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);
@@ -1464,6 +1534,11 @@ int OpenGL::getMaxTextureUnits() const
return maxTextureUnits;
}
int OpenGL::getMaxShaderStorageBufferBindings() const
{
return maxShaderStorageBufferBindings;
}
float OpenGL::getMaxPointSize() const
{
return maxPointSize;
@@ -1882,6 +1957,8 @@ bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget,
else
return true;
case PIXELFORMAT_RGBA8_UNORM_sRGB:
if (gl.bugs.brokenSRGB)
return false;
if (rendertarget)
{
if (GLAD_VERSION_1_0)
+28 -1
View File
@@ -178,6 +178,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:
@@ -312,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.
*
@@ -331,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.
@@ -350,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;
@@ -357,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.
@@ -372,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.
**/
@@ -387,6 +404,11 @@ public:
**/
int getMaxTextureUnits() const;
/**
* Returns the maximum number of shader storage buffer bindings.
**/
int getMaxShaderStorageBufferBindings() const;
/**
* Returns the maximum point size.
**/
@@ -451,9 +473,11 @@ private:
int maxCubeTextureSize;
int maxTextureArrayLayers;
int maxTexelBufferSize;
int maxShaderStorageBufferSize;
int maxRenderTargets;
int maxSamples;
int maxTextureUnits;
int maxShaderStorageBufferBindings;
float maxPointSize;
bool coreProfile;
@@ -468,6 +492,8 @@ private:
// Texture unit state (currently bound texture for each texture unit.)
std::vector<GLuint> boundTextures[TEXTURE_MAX_ENUM + 1];
std::vector<GLuint> boundIndexedBuffers[BUFFERTYPE_MAX_ENUM];
bool enableState[ENABLE_MAX_ENUM];
GLenum faceCullMode;
@@ -488,6 +514,7 @@ private:
GLuint defaultTexture[TEXTURE_MAX_ENUM];
GLuint defaultTexelBuffer;
GLuint defaultStorageBuffer;
} state;
+167 -25
View File
@@ -38,6 +38,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<Volatile*>(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)
@@ -491,6 +576,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);
@@ -713,10 +801,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;
@@ -736,17 +832,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();
@@ -757,19 +879,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;
}
}
}
+9
View File
@@ -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();
@@ -114,6 +120,9 @@ private:
// Texture unit pool for setting textures
std::vector<TextureUnit> textureUnits;
std::vector<int> storageBufferBindingIndexToActiveBinding;
std::vector<BufferBinding> activeStorageBufferBindings;
std::vector<std::pair<const UniformInfo *, int>> pendingUniformUpdates;
float lastPointSize;
+51 -42
View File
@@ -34,7 +34,7 @@ namespace graphics
namespace opengl
{
static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat format, GLuint texture, int layers, bool clear)
static GLenum createFBO(GLuint &framebuffer, TextureType texType, PixelFormat format, GLuint texture, int mips, int layers, bool clear)
{
// get currently bound fbo to reset to it later
GLuint current_fbo = gl.getFramebuffer(OpenGL::FRAMEBUFFER_ALL);
@@ -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))
@@ -68,37 +63,40 @@ 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...
for (int layer = layers - 1; layer >= 0; layer--)
for (int mip = mips - 1; mip >= 0; mip--)
{
for (int face = faces - 1; face >= 0; face--)
for (int layer = layers - 1; layer >= 0; layer--)
{
for (GLenum attachment : fmt.framebufferAttachments)
for (int face = faces - 1; face >= 0; face--)
{
if (attachment == GL_NONE)
continue;
gl.framebufferTexture(attachment, texType, texture, 0, layer, face);
}
if (clear)
{
if (isPixelFormatDepthStencil(format))
for (GLenum attachment : fmt.framebufferAttachments)
{
bool hadDepthWrites = gl.hasDepthWrites();
if (!hadDepthWrites) // glDepthMask also affects glClear.
gl.setDepthWrites(true);
if (attachment == GL_NONE)
continue;
gl.clearDepth(1.0);
glClearStencil(0);
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
if (!hadDepthWrites)
gl.setDepthWrites(hadDepthWrites);
gl.framebufferTexture(attachment, texType, texture, mip, layer, face);
}
else
if (clear)
{
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (isPixelFormatDepthStencil(format))
{
bool hadDepthWrites = gl.hasDepthWrites();
if (!hadDepthWrites) // glDepthMask also affects glClear.
gl.setDepthWrites(true);
gl.clearDepth(1.0);
glClearStencil(0);
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
if (!hadDepthWrites)
gl.setDepthWrites(hadDepthWrites);
}
else
{
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
}
}
}
@@ -109,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;
}
@@ -328,28 +322,43 @@ void Texture::createTexture()
bool hasdata = slices.get(0, 0) != nullptr;
// 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 local FBO used for glReadPixels as well as MSAA blitting.
if (isRenderTarget())
{
bool clear = !hasdata;
int slices = texType == TEXTURE_VOLUME ? depth : layers;
framebufferStatus = createFBO(fbo, texType, format, texture, slices, clear);
framebufferStatus = createFBO(fbo, texType, format, texture, clearmips, slices, clear);
}
else if (!hasdata)
{
// Initialize all slices to transparent black.
std::vector<uint8> emptydata(getPixelFormatSliceSize(format, w, h));
for (int mip = 0; mip < clearmips; mip++)
{
int mipw = getPixelWidth(mip);
int miph = getPixelHeight(mip);
std::vector<uint8> emptydata(getPixelFormatSliceSize(format, mipw, miph));
Rect r = {0, 0, w, h};
int slices = texType == TEXTURE_VOLUME ? depth : layers;
slices = texType == TEXTURE_CUBE ? 6 : slices;
for (int i = 0; i < slices; i++)
uploadByteData(format, emptydata.data(), emptydata.size(), 0, i, r);
Rect r = {0, 0, mipw, miph};
int slices = texType == TEXTURE_VOLUME ? getDepth(mip) : layers;
slices = texType == TEXTURE_CUBE ? 6 : slices;
for (int i = 0; i < slices; i++)
uploadByteData(format, emptydata.data(), emptydata.size(), mip, i, r);
}
}
// Non-readable textures can't have mipmaps (enforced in the base class),
// so generateMipmaps here is fine - when they aren't already initialized.
if (getMipmapCount() > 1 && slices.getMipmapCount() <= 1)
if (clearmips < mipmapCount && slices.getMipmapCount() <= 1 && getMipmapsMode() != MIPMAPS_NONE)
generateMipmaps();
}
+4 -3
View File
@@ -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)
+1
View File
@@ -60,6 +60,7 @@ enum BufferType
BUFFERTYPE_INDEX,
BUFFERTYPE_UNIFORM,
BUFFERTYPE_TEXEL,
BUFFERTYPE_SHADER_STORAGE,
BUFFERTYPE_MAX_ENUM
};
+3 -4
View File
@@ -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;
+24
View File
@@ -371,6 +371,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);
@@ -480,6 +497,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
+24
View File
@@ -361,6 +361,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; i<vcount; i++)
{
float x = (float)lua_tonumber(L, 1);
float y = (float)lua_tonumber(L, 2);
// Remove them, so we don't run out of stack space
lua_remove(L, 1);
lua_remove(L, 1);
// Time for scaling
b2Vec2 point = Physics::scaleUp(body->GetLocalPoint(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))));
+6
View File
@@ -327,6 +327,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.
+8
View File
@@ -472,6 +472,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);
@@ -691,6 +698,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 },
+15 -4
View File
@@ -489,15 +489,23 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
height = mode.h;
}
// 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
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 = {};
@@ -596,7 +604,7 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
// Enforce minimum window dimensions.
SDL_SetWindowMinimumSize(window, f.minwidth, f.minheight);
if (this->settings.display != f.display || ((f.useposition || f.centered) && !f.fullscreen))
if (this->settings.display != f.display || f.useposition || f.centered)
SDL_SetWindowPosition(window, x, y);
SDL_RaiseWindow(window);
@@ -629,8 +637,11 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
}
}
// 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;