mirror of
https://github.com/love2d/love.git
synced 2026-08-12 08:30:56 +02:00
Merge branch '12.0-development' into metal
This commit is contained in:
@@ -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]" = (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
**/
|
||||
|
||||
@@ -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
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ enum BufferType
|
||||
BUFFERTYPE_INDEX,
|
||||
BUFFERTYPE_UNIFORM,
|
||||
BUFFERTYPE_TEXEL,
|
||||
BUFFERTYPE_SHADER_STORAGE,
|
||||
BUFFERTYPE_MAX_ENUM
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))));
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user