Merge branch 'master' into TemporaryHackishBranch

# Conflicts:
#	Core/Config.cpp
#	Core/Config.h
#	UI/ControlMappingScreen.cpp
#	UI/EmuScreen.cpp
#	UI/GameSettingsScreen.cpp
#	android/assets/ui_atlas.meta
#	android/assets/ui_atlas.zim
#	assets/ui_atlas.meta
#	assets/ui_atlas.zim
This commit is contained in:
LunaMoo committed 2021-07-17 19:08:58 +02:00
commit 4fb6e0c242
198 files changed
+6323 -2862

No files matched your search

+2 -3
View File
@@ -64,10 +64,9 @@ PPSSPPControls.dat
.idea
*.iml
build
build.ios
/build*/
versionname.txt
versioncode.txt
build*/
android/.cxx
# Temp file used by jenkins windows build (TODO: remove)
@@ -75,7 +74,7 @@ desc.txt
Logs
Memstick
memstick*
/memstick*
Cheats
/git-version.cpp
+2
View File
@@ -89,6 +89,8 @@ libretro-build-linux-i686:
# MacOS 64-bit
libretro-build-osx-x64:
tags:
- macosx-packaging
extends:
- .libretro-osx-x64-make-default
- .core-defs
+47 -4
View File
@@ -270,6 +270,9 @@ foreach (LANGUAGE C CXX)
endforeach()
if(NOT MSVC)
# NEON optimizations in libpng17 seem to cause PNG load errors, see #14485.
add_definitions(-DPNG_ARM_NEON_OPT=0)
if(ANDROID)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
endif()
@@ -499,6 +502,8 @@ add_library(Common STATIC
Common/File/VFS/VFS.cpp
Common/File/VFS/AssetReader.cpp
Common/File/VFS/AssetReader.h
Common/File/AndroidStorage.h
Common/File/AndroidStorage.cpp
Common/File/DiskFree.h
Common/File/DiskFree.cpp
Common/File/Path.h
@@ -603,12 +608,14 @@ add_library(Common STATIC
Common/Render/Text/draw_text_uwp.h
Common/System/Display.cpp
Common/System/Display.h
Common/Thread/PrioritizedWorkQueue.cpp
Common/Thread/PrioritizedWorkQueue.h
Common/Thread/Channel.h
Common/Thread/ParallelLoop.cpp
Common/Thread/ParallelLoop.h
Common/Thread/Promise.h
Common/Thread/ThreadUtil.cpp
Common/Thread/ThreadUtil.h
Common/Thread/ThreadPool.cpp
Common/Thread/ThreadPool.h
Common/Thread/ThreadManager.cpp
Common/Thread/ThreadManager.h
Common/UI/Root.cpp
Common/UI/Root.h
Common/UI/Screen.cpp
@@ -644,6 +651,8 @@ add_library(Common STATIC
Common/Log.cpp
Common/LogManager.cpp
Common/LogManager.h
Common/LogReporting.cpp
Common/LogReporting.h
Common/MakeUnique.h
Common/MemArenaAndroid.cpp
Common/MemArenaDarwin.cpp
@@ -1125,34 +1134,65 @@ list(APPEND NativeAppSource
android/jni/TestRunner.cpp
UI/DiscordIntegration.cpp
UI/NativeApp.cpp
UI/BackgroundAudio.h
UI/BackgroundAudio.cpp
UI/ChatScreen.h
UI/ChatScreen.cpp
UI/DevScreens.cpp
UI/DevScreens.h
UI/DisplayLayoutEditor.cpp
UI/DisplayLayoutEditor.h
UI/DisplayLayoutScreen.cpp
UI/DisplayLayoutScreen.h
UI/EmuScreen.h
UI/EmuScreen.cpp
UI/GameInfoCache.h
UI/GameInfoCache.cpp
UI/MainScreen.h
UI/MainScreen.cpp
UI/MiscScreens.h
UI/MiscScreens.cpp
UI/PauseScreen.h
UI/PauseScreen.cpp
UI/GameScreen.h
UI/GameScreen.cpp
UI/GameSettingsScreen.h
UI/GameSettingsScreen.cpp
UI/GPUDriverTestScreen.h
UI/GPUDriverTestScreen.cpp
UI/TiltAnalogSettingsScreen.h
UI/TiltAnalogSettingsScreen.cpp
UI/TiltEventProcessor.h
UI/TiltEventProcessor.cpp
UI/TouchControlLayoutScreen.h
UI/TouchControlLayoutScreen.cpp
UI/TouchControlVisibilityScreen.h
UI/TouchControlVisibilityScreen.cpp
UI/GamepadEmu.h
UI/GamepadEmu.cpp
UI/OnScreenDisplay.h
UI/OnScreenDisplay.cpp
UI/ControlMappingScreen.h
UI/ControlMappingScreen.cpp
UI/RemoteISOScreen.h
UI/RemoteISOScreen.cpp
UI/ReportScreen.h
UI/ReportScreen.cpp
UI/SavedataScreen.h
UI/SavedataScreen.cpp
UI/Store.h
UI/Store.cpp
UI/CwCheatScreen.h
UI/CwCheatScreen.cpp
UI/InstallZipScreen.h
UI/InstallZipScreen.cpp
UI/MemStickScreen.h
UI/MemStickScreen.cpp
UI/ProfilerDraw.h
UI/ProfilerDraw.cpp
UI/TextureUtil.h
UI/TextureUtil.cpp
UI/ComboKeyMappingScreen.h
UI/ComboKeyMappingScreen.cpp
)
@@ -1525,6 +1565,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/Config.cpp
Core/Config.h
Core/ConfigValues.h
Core/ControlMapper.cpp
Core/ControlMapper.h
Core/Core.cpp
Core/Core.h
Core/Compatibility.cpp
@@ -2194,6 +2236,7 @@ if(UNITTEST)
unittest/TestArm64Emitter.cpp
unittest/TestX64Emitter.cpp
unittest/TestVertexJit.cpp
unittest/TestThreadManager.cpp
unittest/JitHarness.cpp
Core/MIPS/ARM/ArmRegCache.cpp
Core/MIPS/ARM/ArmRegCacheFPU.cpp
+1
View File
@@ -248,6 +248,7 @@ void CPUInfo::Detect()
Mode64bit = false;
#endif
vendor = VENDOR_ARM;
logical_cpu_count = 1;
// Get the information about the CPU
#if !PPSSPP_PLATFORM(LINUX)
+3
View File
@@ -39,7 +39,10 @@ struct CPUInfo {
bool Mode64bit;
bool HTT;
// Number of real CPU cores.
int num_cores;
// Number of logical CPUs per core.
int logical_cpu_count;
bool bAtom;
+11 -4
View File
@@ -409,6 +409,7 @@
<ClInclude Include="Data\Text\Parsers.h" />
<ClInclude Include="Data\Text\WrapText.h" />
<ClInclude Include="FakeEmitter.h" />
<ClInclude Include="File\AndroidStorage.h" />
<ClInclude Include="File\DirListing.h" />
<ClInclude Include="File\DiskFree.h" />
<ClInclude Include="File\FileDescriptor.h" />
@@ -469,6 +470,7 @@
<ClInclude Include="Render\Text\draw_text_qt.h" />
<ClInclude Include="Render\Text\draw_text_uwp.h" />
<ClInclude Include="Render\Text\draw_text_win.h" />
<ClInclude Include="LogReporting.h" />
<ClInclude Include="Serialize\SerializeDeque.h" />
<ClInclude Include="Serialize\SerializeFuncs.h" />
<ClInclude Include="Data\Convert\ColorConvNEON.h" />
@@ -525,8 +527,11 @@
<ClInclude Include="System\Display.h" />
<ClInclude Include="System\NativeApp.h" />
<ClInclude Include="System\System.h" />
<ClInclude Include="Thread\PrioritizedWorkQueue.h" />
<ClInclude Include="Thread\ThreadPool.h" />
<ClInclude Include="Thread\Channel.h" />
<ClInclude Include="Thread\Event.h" />
<ClInclude Include="Thread\ParallelLoop.h" />
<ClInclude Include="Thread\Promise.h" />
<ClInclude Include="Thread\ThreadManager.h" />
<ClInclude Include="Thread\ThreadUtil.h" />
<ClInclude Include="Thunk.h" />
<ClInclude Include="TimeUtil.h" />
@@ -830,6 +835,7 @@
<ClCompile Include="Data\Text\I18n.cpp" />
<ClCompile Include="Data\Text\Parsers.cpp" />
<ClCompile Include="Data\Text\WrapText.cpp" />
<ClCompile Include="File\AndroidStorage.cpp" />
<ClCompile Include="File\DirListing.cpp" />
<ClCompile Include="File\DiskFree.cpp" />
<ClCompile Include="File\FileDescriptor.cpp" />
@@ -891,6 +897,7 @@
<ClCompile Include="Render\Text\draw_text_qt.cpp" />
<ClCompile Include="Render\Text\draw_text_uwp.cpp" />
<ClCompile Include="Render\Text\draw_text_win.cpp" />
<ClCompile Include="LogReporting.cpp" />
<ClCompile Include="Serialize\Serializer.cpp" />
<ClCompile Include="Data\Convert\ColorConv.cpp" />
<ClCompile Include="ConsoleListener.cpp" />
@@ -960,8 +967,8 @@
<ClCompile Include="OSVersion.cpp" />
<ClCompile Include="StringUtils.cpp" />
<ClCompile Include="System\Display.cpp" />
<ClCompile Include="Thread\PrioritizedWorkQueue.cpp" />
<ClCompile Include="Thread\ThreadPool.cpp" />
<ClCompile Include="Thread\ParallelLoop.cpp" />
<ClCompile Include="Thread\ThreadManager.cpp" />
<ClCompile Include="Thread\ThreadUtil.cpp" />
<ClCompile Include="Thunk.cpp" />
<ClCompile Include="TimeUtil.cpp" />
+29 -12
View File
@@ -90,12 +90,6 @@
<ClInclude Include="..\ext\libpng17\pngstruct.h">
<Filter>ext\libpng17</Filter>
</ClInclude>
<ClInclude Include="Thread\PrioritizedWorkQueue.h">
<Filter>Thread</Filter>
</ClInclude>
<ClInclude Include="Thread\ThreadPool.h">
<Filter>Thread</Filter>
</ClInclude>
<ClInclude Include="Thread\ThreadUtil.h">
<Filter>Thread</Filter>
</ClInclude>
@@ -394,6 +388,25 @@
<ClInclude Include="File\Path.h">
<Filter>File</Filter>
</ClInclude>
<ClInclude Include="LogReporting.h" />
<ClInclude Include="File\AndroidStorage.h">
<Filter>File</Filter>
</ClInclude>
<ClInclude Include="Thread\ThreadManager.h">
<Filter>Thread</Filter>
</ClInclude>
<ClInclude Include="Thread\Channel.h">
<Filter>Thread</Filter>
</ClInclude>
<ClInclude Include="Thread\Promise.h">
<Filter>Thread</Filter>
</ClInclude>
<ClInclude Include="Thread\ParallelLoop.h">
<Filter>Thread</Filter>
</ClInclude>
<ClInclude Include="Thread\Event.h">
<Filter>Thread</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ABI.cpp" />
@@ -489,12 +502,6 @@
<ClCompile Include="..\ext\libpng17\pngwutil.c">
<Filter>ext\libpng17</Filter>
</ClCompile>
<ClCompile Include="Thread\PrioritizedWorkQueue.cpp">
<Filter>Thread</Filter>
</ClCompile>
<ClCompile Include="Thread\ThreadPool.cpp">
<Filter>Thread</Filter>
</ClCompile>
<ClCompile Include="Thread\ThreadUtil.cpp">
<Filter>Thread</Filter>
</ClCompile>
@@ -760,6 +767,16 @@
<ClCompile Include="File\Path.cpp">
<Filter>File</Filter>
</ClCompile>
<ClCompile Include="LogReporting.cpp" />
<ClCompile Include="File\AndroidStorage.cpp">
<Filter>File</Filter>
</ClCompile>
<ClCompile Include="Thread\ThreadManager.cpp">
<Filter>Thread</Filter>
</ClCompile>
<ClCompile Include="Thread\ParallelLoop.cpp">
<Filter>Thread</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="Crypto">
+25
View File
@@ -170,6 +170,10 @@ void Section::Set(const char* key, uint32_t newValue) {
Set(key, StringFromFormat("0x%08x", newValue).c_str());
}
void Section::Set(const char* key, uint64_t newValue) {
Set(key, StringFromFormat("0x%016lx", newValue).c_str());
}
void Section::Set(const char* key, float newValue) {
Set(key, StringFromFormat("%f", newValue).c_str());
}
@@ -311,6 +315,16 @@ bool Section::Get(const char* key, uint32_t* value, uint32_t defaultValue)
return false;
}
bool Section::Get(const char* key, uint64_t* value, uint64_t defaultValue)
{
std::string temp;
bool retval = Get(key, &temp, 0);
if (retval && TryParse(temp, value))
return true;
*value = defaultValue;
return false;
}
bool Section::Get(const char* key, bool* value, bool defaultValue)
{
std::string temp;
@@ -659,6 +673,17 @@ bool IniFile::Get(const char* sectionName, const char* key, uint32_t* value, uin
}
}
bool IniFile::Get(const char* sectionName, const char* key, uint64_t* value, uint64_t defaultValue)
{
Section *section = GetSection(sectionName);
if (!section) {
*value = defaultValue;
return false;
} else {
return section->Get(key, value, defaultValue);
}
}
bool IniFile::Get(const char* sectionName, const char* key, bool* value, bool defaultValue)
{
Section *section = GetSection(sectionName);
+6
View File
@@ -35,6 +35,7 @@ public:
bool Get(const char* key, std::string* value, const char* defaultValue);
void Set(const char* key, uint32_t newValue);
void Set(const char* key, uint64_t newValue);
void Set(const char* key, float newValue);
void Set(const char* key, const float newValue, const float defaultValue);
void Set(const char* key, double newValue);
@@ -58,6 +59,7 @@ public:
bool Get(const char* key, int* value, int defaultValue = 0);
bool Get(const char* key, uint32_t* value, uint32_t defaultValue = 0);
bool Get(const char* key, uint64_t* value, uint64_t defaultValue = 0);
bool Get(const char* key, bool* value, bool defaultValue = false);
bool Get(const char* key, float* value, float defaultValue = false);
bool Get(const char* key, double* value, double defaultValue = false);
@@ -103,6 +105,9 @@ public:
void Set(const char* sectionName, const char* key, uint32_t newValue) {
GetOrCreateSection(sectionName)->Set(key, newValue);
}
void Set(const char* sectionName, const char* key, uint64_t newValue) {
GetOrCreateSection(sectionName)->Set(key, newValue);
}
void Set(const char* sectionName, const char* key, bool newValue) {
GetOrCreateSection(sectionName)->Set(key, newValue);
}
@@ -114,6 +119,7 @@ public:
bool Get(const char* sectionName, const char* key, std::string* value, const char* defaultValue = "");
bool Get(const char* sectionName, const char* key, int* value, int defaultValue = 0);
bool Get(const char* sectionName, const char* key, uint32_t* value, uint32_t defaultValue = 0);
bool Get(const char* sectionName, const char* key, uint64_t* value, uint64_t defaultValue = 0);
bool Get(const char* sectionName, const char* key, bool* value, bool defaultValue = false);
bool Get(const char* sectionName, const char* key, std::vector<std::string>& values);
+20
View File
@@ -71,6 +71,26 @@ bool TryParse(const std::string &str, uint32_t *const output) {
return true;
}
bool TryParse(const std::string &str, uint64_t *const output) {
char *endptr = NULL;
// Holy crap this is ugly.
// Reset errno to a value other than ERANGE
errno = 0;
uint64_t value = strtoul(str.c_str(), &endptr, 0);
if (!endptr || *endptr)
return false;
if (errno == ERANGE)
return false;
*output = value;
return true;
}
bool TryParse(const std::string &str, bool *const output) {
if ("1" == str || !strcasecmp("true", str.c_str()))
*output = true;
+1
View File
@@ -57,6 +57,7 @@ bool ParseMacAddress(std::string str, uint8_t macAddr[6]);
bool TryParse(const std::string &str, bool *const output);
bool TryParse(const std::string &str, uint32_t *const output);
bool TryParse(const std::string &str, uint64_t *const output);
template <typename N>
static bool TryParse(const std::string &str, N *const output) {
+190
View File
@@ -0,0 +1,190 @@
#include "Common/File/AndroidStorage.h"
#include "Common/StringUtils.h"
#include "Common/Log.h"
#include "Common/TimeUtil.h"
#include "android/jni/app-android.h"
#if PPSSPP_PLATFORM(ANDROID) && !defined(__LIBRETRO__)
static jmethodID openContentUri;
static jmethodID listContentUriDir;
static jmethodID contentUriCreateFile;
static jmethodID contentUriCreateDirectory;
static jmethodID contentUriRemoveFile;
static jmethodID contentUriGetFileInfo;
static jmethodID contentUriGetFreeStorageSpace;
static jmethodID filePathGetFreeStorageSpace;
static jobject g_nativeActivity;
void Android_StorageSetNativeActivity(jobject nativeActivity) {
g_nativeActivity = nativeActivity;
}
void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj) {
openContentUri = env->GetMethodID(env->GetObjectClass(obj), "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I");
_dbg_assert_(openContentUri);
listContentUriDir = env->GetMethodID(env->GetObjectClass(obj), "listContentUriDir", "(Ljava/lang/String;)[Ljava/lang/String;");
_dbg_assert_(listContentUriDir);
contentUriCreateDirectory = env->GetMethodID(env->GetObjectClass(obj), "contentUriCreateDirectory", "(Ljava/lang/String;Ljava/lang/String;)Z");
_dbg_assert_(contentUriCreateDirectory);
contentUriCreateFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriCreateFile", "(Ljava/lang/String;Ljava/lang/String;)Z");
_dbg_assert_(contentUriCreateFile);
contentUriRemoveFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriRemoveFile", "(Ljava/lang/String;)Z");
_dbg_assert_(contentUriRemoveFile);
contentUriGetFileInfo = env->GetMethodID(env->GetObjectClass(obj), "contentUriGetFileInfo", "(Ljava/lang/String;)Ljava/lang/String;");
_dbg_assert_(contentUriGetFileInfo);
contentUriGetFreeStorageSpace = env->GetMethodID(env->GetObjectClass(obj), "contentUriGetFreeStorageSpace", "(Ljava/lang/String;)J");
_dbg_assert_(contentUriGetFreeStorageSpace);
filePathGetFreeStorageSpace = env->GetMethodID(env->GetObjectClass(obj), "filePathGetFreeStorageSpace", "(Ljava/lang/String;)J");
_dbg_assert_(filePathGetFreeStorageSpace);
}
bool Android_IsContentUri(const std::string &filename) {
return startsWith(filename, "content://");
}
int Android_OpenContentUriFd(const std::string &filename, Android_OpenContentUriMode mode) {
if (!g_nativeActivity) {
return -1;
}
std::string fname = filename;
// PPSSPP adds an ending slash to directories before looking them up.
// TODO: Fix that in the caller (or don't call this for directories).
if (fname.back() == '/')
fname.pop_back();
auto env = getEnv();
const char *modeStr = "";
switch (mode) {
case Android_OpenContentUriMode::READ: modeStr = "r"; break;
case Android_OpenContentUriMode::READ_WRITE: modeStr = "rw"; break;
case Android_OpenContentUriMode::READ_WRITE_TRUNCATE: modeStr = "rwt"; break;
}
jstring j_filename = env->NewStringUTF(fname.c_str());
jstring j_mode = env->NewStringUTF(modeStr);
int fd = env->CallIntMethod(g_nativeActivity, openContentUri, j_filename, j_mode);
return fd;
}
bool Android_CreateDirectory(const std::string &rootTreeUri, const std::string &dirName) {
if (!g_nativeActivity) {
return false;
}
auto env = getEnv();
jstring paramRoot = env->NewStringUTF(rootTreeUri.c_str());
jstring paramDirName = env->NewStringUTF(dirName.c_str());
return env->CallBooleanMethod(g_nativeActivity, contentUriCreateDirectory, paramRoot, paramDirName);
}
bool Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName) {
if (!g_nativeActivity) {
return false;
}
auto env = getEnv();
jstring paramRoot = env->NewStringUTF(parentTreeUri.c_str());
jstring paramFileName = env->NewStringUTF(fileName.c_str());
return env->CallBooleanMethod(g_nativeActivity, contentUriCreateFile, paramRoot, paramFileName);
}
bool Android_RemoveFile(const std::string &fileUri) {
if (!g_nativeActivity) {
return false;
}
auto env = getEnv();
jstring paramFileName = env->NewStringUTF(fileUri.c_str());
return env->CallBooleanMethod(g_nativeActivity, contentUriRemoveFile, paramFileName);
}
static bool ParseFileInfo(const std::string &line, File::FileInfo *fileInfo) {
std::vector<std::string> parts;
SplitString(line, '|', parts);
if (parts.size() != 5) {
ERROR_LOG(FILESYS, "Bad format: %s", line.c_str());
return false;
}
fileInfo->name = std::string(parts[2]);
fileInfo->isDirectory = parts[0][0] == 'D';
fileInfo->exists = true;
sscanf(parts[1].c_str(), "%" PRIu64, &fileInfo->size);
fileInfo->fullName = Path(parts[3]);
fileInfo->isWritable = true; // TODO: Should be passed as part of the string.
sscanf(parts[4].c_str(), "%" PRIu64, &fileInfo->lastModified);
return true;
}
bool Android_GetFileInfo(const std::string &fileUri, File::FileInfo *fileInfo) {
if (!g_nativeActivity) {
return false;
}
auto env = getEnv();
jstring paramFileUri = env->NewStringUTF(fileUri.c_str());
jstring str = (jstring)env->CallObjectMethod(g_nativeActivity, contentUriGetFileInfo, paramFileUri);
if (!str) {
return false;
}
const char *charArray = env->GetStringUTFChars(str, 0);
bool retval = ParseFileInfo(std::string(charArray), fileInfo);
env->DeleteLocalRef(str);
return retval && fileInfo->exists;
}
std::vector<File::FileInfo> Android_ListContentUri(const std::string &path) {
if (!g_nativeActivity) {
return std::vector<File::FileInfo>();
}
auto env = getEnv();
double start = time_now_d();
jstring param = env->NewStringUTF(path.c_str());
jobject retval = env->CallObjectMethod(g_nativeActivity, listContentUriDir, param);
jobjectArray fileList = (jobjectArray)retval;
std::vector<File::FileInfo> items;
int size = env->GetArrayLength(fileList);
for (int i = 0; i < size; i++) {
jstring str = (jstring)env->GetObjectArrayElement(fileList, i);
const char *charArray = env->GetStringUTFChars(str, 0);
if (charArray) { // paranoia
File::FileInfo info;
if (ParseFileInfo(std::string(charArray), &info)) {
items.push_back(info);
}
}
env->ReleaseStringUTFChars(str, charArray);
env->DeleteLocalRef(str);
}
env->DeleteLocalRef(fileList);
double elapsed = time_now_d() - start;
if (elapsed > 0.1) {
INFO_LOG(FILESYS, "Listing directory on content URI took %0.3f s (%d files)", elapsed, (int)size);
}
return items;
}
int64_t Android_GetFreeSpaceByContentUri(const std::string &uri) {
if (!g_nativeActivity) {
return false;
}
auto env = getEnv();
jstring param = env->NewStringUTF(uri.c_str());
return env->CallLongMethod(g_nativeActivity, contentUriGetFreeStorageSpace, param);
}
int64_t Android_GetFreeSpaceByFilePath(const std::string &filePath) {
if (!g_nativeActivity) {
return false;
}
auto env = getEnv();
jstring param = env->NewStringUTF(filePath.c_str());
return env->CallLongMethod(g_nativeActivity, filePathGetFreeStorageSpace, param);
}
#endif
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include <vector>
#include <string>
#include "Common/File/DirListing.h"
// To emphasize that Android storage mode strings are different, let's just use
// an enum.
enum class Android_OpenContentUriMode {
READ = 0, // "r"
READ_WRITE = 1, // "rw"
READ_WRITE_TRUNCATE = 2, // "rwt"
};
#if PPSSPP_PLATFORM(ANDROID) && !defined(__LIBRETRO__)
#include <jni.h>
extern std::string g_extFilesDir;
void Android_StorageSetNativeActivity(jobject nativeActivity);
bool Android_IsContentUri(const std::string &uri);
int Android_OpenContentUriFd(const std::string &uri, const Android_OpenContentUriMode mode);
bool Android_CreateDirectory(const std::string &parentTreeUri, const std::string &dirName);
bool Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName);
bool Android_RemoveFile(const std::string &fileUri);
bool Android_GetFileInfo(const std::string &fileUri, File::FileInfo *info);
int64_t Android_GetFreeSpaceByContentUri(const std::string &uri);
int64_t Android_GetFreeSpaceByFilePath(const std::string &filePath);
std::vector<File::FileInfo> Android_ListContentUri(const std::string &uri);
void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj);
#else
// Stub out the Android Storage wrappers, so that we can avoid ifdefs everywhere.
inline bool Android_IsContentUri(const std::string &uri) { return false; }
inline int Android_OpenContentUriFd(const std::string &uri, const Android_OpenContentUriMode mode) { return -1; }
inline bool Android_CreateDirectory(const std::string &parentTreeUri, const std::string &dirName) { return false; }
inline bool Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName) { return false; }
inline bool Android_RemoveFile(const std::string &fileUri) { return false; }
inline bool Android_GetFileInfo(const std::string &fileUri, File::FileInfo *info) { return false; }
inline int64_t Android_GetFreeSpaceByContentUri(const std::string &uri) { return -1; }
inline int64_t Android_GetFreeSpaceByFilePath(const std::string &filePath) { return -1; }
inline std::vector<File::FileInfo> Android_ListContentUri(const std::string &uri) {
return std::vector<File::FileInfo>();
}
#endif
+58
View File
@@ -20,8 +20,10 @@
#include "Common/Data/Encoding/Utf8.h"
#include "Common/StringUtils.h"
#include "Common/Net/URL.h"
#include "Common/File/DirListing.h"
#include "Common/File/FileUtil.h"
#include "Common/File/AndroidStorage.h"
#if !defined(__linux__) && !defined(_WIN32) && !defined(__QNX__)
#define stat64 stat
@@ -40,6 +42,8 @@ bool GetFileInfo(const Path &path, FileInfo * fileInfo) {
switch (path.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
return Android_GetFileInfo(path.ToString(), fileInfo);
default:
return false;
}
@@ -129,7 +133,61 @@ bool FileInfo::operator <(const FileInfo & other) const {
return false;
}
std::vector<File::FileInfo> ApplyFilter(std::vector<File::FileInfo> files, const char *filter) {
std::set<std::string> filters;
if (filter) {
std::string tmp;
while (*filter) {
if (*filter == ':') {
filters.insert("." + tmp);
tmp.clear();
} else {
tmp.push_back(*filter);
}
filter++;
}
if (!tmp.empty())
filters.insert("." + tmp);
}
auto pred = [&](const File::FileInfo &info) {
if (info.isDirectory || !filter)
return false;
std::string ext = info.fullName.GetFileExtension();
return filters.find(ext) == filters.end();
};
files.erase(std::remove_if(files.begin(), files.end(), pred), files.end());
return files;
}
size_t GetFilesInDir(const Path &directory, std::vector<FileInfo> * files, const char *filter, int flags) {
if (directory.Type() == PathType::CONTENT_URI) {
std::vector<File::FileInfo> fileList = Android_ListContentUri(directory.ToString());
*files = ApplyFilter(fileList, filter);
std::sort(files->begin(), files->end());
return true;
}
#if PPSSPP_PLATFORM(WINDOWS)
if (directory.IsRoot()) {
// Special path that means root of file system.
std::vector<std::string> drives = File::GetWindowsDrives();
for (auto drive = drives.begin(); drive != drives.end(); ++drive) {
if (*drive == "A:/" || *drive == "B:/")
continue;
File::FileInfo fake;
fake.fullName = Path(*drive);
fake.name = *drive;
fake.isDirectory = true;
fake.exists = true;
fake.size = 0;
fake.isWritable = false;
files->push_back(fake);
}
return files->size();
}
#endif
size_t foundEntries = 0;
std::set<std::string> filters;
if (filter) {
+7 -3
View File
@@ -3,7 +3,7 @@
#include <string>
#include <vector>
#include <stdio.h>
#include <cstdio>
#include <inttypes.h>
@@ -24,18 +24,22 @@ struct FileInfo {
uint64_t ctime;
uint32_t access; // st_mode & 0x1ff
// Currently only supported for Android storage files.
// Other places use different methods to get this.
uint64_t lastModified = 0;
bool operator <(const FileInfo &other) const;
};
bool GetFileInfo(const Path &path, FileInfo *fileInfo);
enum {
GETFILES_GETHIDDEN = 1
GETFILES_GETHIDDEN = 1,
};
size_t GetFilesInDir(const Path &directory, std::vector<FileInfo> *files, const char *filter = nullptr, int flags = 0);
int64_t GetDirectoryRecursiveSize(const Path &path, const char *filter = nullptr, int flags = 0);
std::vector<File::FileInfo> ApplyFilter(std::vector<File::FileInfo> files, const char *filter);
#ifdef _WIN32
std::vector<std::string> GetWindowsDrives();
+230 -176
View File
@@ -24,6 +24,9 @@
#include "ppsspp_config.h"
#include "android/jni/app-android.h"
#include "android/jni/AndroidContentURI.h"
#ifdef __MINGW32__
#include <unistd.h>
#ifndef _POSIX_THREAD_SAFE_FUNCTIONS
@@ -35,6 +38,7 @@
#include <memory>
#include "Common/Log.h"
#include "Common/LogReporting.h"
#include "Common/File/FileUtil.h"
#include "Common/StringUtils.h"
#include "Common/SysError.h"
@@ -97,18 +101,89 @@ FILE *OpenCFile(const Path &path, const char *mode) {
switch (path.Type()) {
case PathType::NATIVE:
break;
case PathType::CONTENT_URI:
// We're gonna need some error codes..
if (!strcmp(mode, "r") || !strcmp(mode, "rb")) {
INFO_LOG(COMMON, "Opening content file for read: '%s'", path.c_str());
// Read, let's support this - easy one.
int descriptor = Android_OpenContentUriFd(path.ToString(), Android_OpenContentUriMode::READ);
if (descriptor == -1) {
return nullptr;
}
return fdopen(descriptor, "rb");
} else if (!strcmp(mode, "w") || !strcmp(mode, "wb")) {
// Need to be able to create the file here if it doesn't exist.
// Not exactly sure which abstractions are best, let's start simple.
if (!File::Exists(path)) {
INFO_LOG(COMMON, "Opening content file '%s' for write. Doesn't exist, creating empty and reopening.", path.c_str());
std::string name = path.GetFilename();
if (path.CanNavigateUp()) {
Path parent = path.NavigateUp();
if (!Android_CreateFile(parent.ToString(), name)) {
WARN_LOG(COMMON, "Failed to create file '%s' in '%s'", name.c_str(), parent.c_str());
return nullptr;
}
} else {
INFO_LOG_REPORT_ONCE(openCFileFailedNavigateUp, COMMON, "Failed to navigate up to create file: %s", path.c_str());
return nullptr;
}
} else {
INFO_LOG(COMMON, "Opening file by fd for write");
}
// TODO: Support append modes and stuff... For now let's go with the most common one.
int descriptor = Android_OpenContentUriFd(path.ToString(), Android_OpenContentUriMode::READ_WRITE_TRUNCATE);
if (descriptor == -1) {
INFO_LOG(COMMON, "Opening '%s' for write failed", path.ToString().c_str());
return nullptr;
}
return fdopen(descriptor, "wb");
} else {
ERROR_LOG(COMMON, "OpenCFile(%s): Mode not yet supported: %s", path.c_str(), mode);
return nullptr;
}
break;
default:
ERROR_LOG(COMMON, "OpenCFile(%s): Not yet supported", path.c_str());
ERROR_LOG(COMMON, "OpenCFile(%s): PathType not yet supported", path.c_str());
return nullptr;
}
#if defined(_WIN32) && defined(UNICODE)
return _wfopen(path.ToWString().c_str(), ConvertUTF8ToWString(mode).c_str());
#else
return fopen(path.ToString().c_str(), mode);
return fopen(path.c_str(), mode);
#endif
}
int OpenFD(const Path &path, OpenFlag flags) {
switch (path.Type()) {
case PathType::CONTENT_URI:
break;
default:
// Not yet supported.
return -1;
}
Android_OpenContentUriMode mode;
if (flags == OPEN_READ) {
mode = Android_OpenContentUriMode::READ;
} else if (flags & OPEN_WRITE) {
if (flags & OPEN_TRUNCATE) {
mode = Android_OpenContentUriMode::READ_WRITE;
} else {
mode = Android_OpenContentUriMode::READ_WRITE_TRUNCATE;
}
// TODO: Maybe better checking of additional flags here.
} else {
// TODO: Add support for more modes if possible.
ERROR_LOG_REPORT_ONCE(openFlagNotSupported, COMMON, "OpenFlag 0x%x not yet supported", flags);
return -1;
}
int descriptor = Android_OpenContentUriFd(path.ToString(), mode);
return descriptor;
}
#ifdef _WIN32
static bool ResolvePathVista(const std::wstring &path, wchar_t *buf, DWORD bufSize) {
typedef DWORD(WINAPI *getFinalPathNameByHandleW_f)(HANDLE hFile, LPWSTR lpszFilePath, DWORD cchFilePath, DWORD dwFlags);
@@ -146,7 +221,13 @@ std::string ResolvePath(const std::string &path) {
if (startsWith(path, "http://") || startsWith(path, "https://")) {
return path;
}
#if PPSSPP_PLATFORM(WINDOWS)
if (Android_IsContentUri(path)) {
// Nothing to do?
return path;
}
#ifdef _WIN32
static const int BUF_SIZE = 32768;
wchar_t *buf = new wchar_t[BUF_SIZE] {};
@@ -190,39 +271,28 @@ std::string ResolvePath(const std::string &path) {
#endif
}
// Remove any ending forward slashes from directory paths
// Modifies argument.
static void StripTailDirSlashes(std::string &fname) {
if (fname.length() > 1) {
size_t i = fname.length() - 1;
#if PPSSPP_PLATFORM(WINDOWS)
if (i == 2 && fname[1] == ':' && fname[2] == '\\')
return;
#endif
while (strchr(DIR_SEP_CHRS, fname[i]))
fname[i--] = '\0';
}
return;
}
// Returns true if file filename exists. Will return true on directories.
bool ExistsInDir(const Path &path, const std::string &filename) {
return Exists(path / filename);
}
bool Exists(const std::string &filename) {
std::string fn = filename;
StripTailDirSlashes(fn);
bool Exists(const Path &path) {
if (path.Type() == PathType::CONTENT_URI) {
FileInfo info;
if (!Android_GetFileInfo(path.c_str(), &info)) {
return false;
}
return info.exists;
}
#if defined(_WIN32)
std::wstring copy = ConvertUTF8ToWString(fn);
// Make sure Windows will no longer handle critical errors, which means no annoying "No disk" dialog
#if !PPSSPP_PLATFORM(UWP)
int OldMode = SetErrorMode(SEM_FAILCRITICALERRORS);
#endif
WIN32_FILE_ATTRIBUTE_DATA data{};
if (!GetFileAttributesEx(copy.c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
if (!GetFileAttributesEx(path.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
return false;
}
#if !PPSSPP_PLATFORM(UWP)
@@ -231,43 +301,44 @@ bool Exists(const std::string &filename) {
return true;
#else
struct stat file_info;
return stat(fn.c_str(), &file_info) == 0;
return stat(path.c_str(), &file_info) == 0;
#endif
}
bool Exists(const Path &path) {
// Temporary forward.
return Exists(path.ToString());
}
// Returns true if filename exists and is a directory
bool IsDirectory(const Path &filename) {
switch (filename.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
{
FileInfo info;
if (!Android_GetFileInfo(filename.ToString(), &info)) {
return false;
}
return info.exists && info.isDirectory;
}
default:
return false;
}
std::string fn = filename.ToString();
StripTailDirSlashes(fn);
#if defined(_WIN32)
std::wstring copy = ConvertUTF8ToWString(fn);
WIN32_FILE_ATTRIBUTE_DATA data{};
if (!GetFileAttributesEx(copy.c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
if (!GetFileAttributesEx(filename.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
auto err = GetLastError();
WARN_LOG(COMMON, "GetFileAttributes failed on %s: %08x %s", fn.c_str(), (uint32_t)err, GetStringErrorMsg(err).c_str());
if (err != ERROR_FILE_NOT_FOUND) {
WARN_LOG(COMMON, "GetFileAttributes failed on %s: %08x %s", filename.ToVisualString().c_str(), (uint32_t)err, GetStringErrorMsg(err).c_str());
}
return false;
}
DWORD result = data.dwFileAttributes;
return (result & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY;
#else
std::string copy(fn);
std::string copy = filename.ToString();
struct stat file_info;
int result = stat(copy.c_str(), &file_info);
if (result < 0) {
WARN_LOG(COMMON, "IsDirectory: stat failed on %s: %s", fn.c_str(), GetLastErrorMsg().c_str());
WARN_LOG(COMMON, "IsDirectory: stat failed on %s: %s", copy.c_str(), GetLastErrorMsg().c_str());
return false;
}
return S_ISDIR(file_info.st_mode);
@@ -280,6 +351,8 @@ bool Delete(const Path &filename) {
switch (filename.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
return Android_RemoveFile(filename.ToString());
default:
return false;
}
@@ -289,13 +362,13 @@ bool Delete(const Path &filename) {
// Return true because we care about the file no
// being there, not the actual delete.
if (!Exists(filename)) {
WARN_LOG(COMMON, "Delete: %s does not exists", filename.c_str());
WARN_LOG(COMMON, "Delete: '%s' already does not exist", filename.c_str());
return true;
}
// We can't delete a directory
if (IsDirectory(filename)) {
WARN_LOG(COMMON, "Delete failed: %s is a directory", filename.c_str());
WARN_LOG(COMMON, "Delete failed: '%s' is a directory", filename.c_str());
return false;
}
@@ -320,103 +393,109 @@ bool CreateDir(const Path &path) {
switch (path.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
{
// Convert it to a "CreateDirIn" call, if possible, since that's
// what we can do with the storage API.
AndroidContentURI uri(path.ToString());
std::string newDirName = uri.GetLastPart();
if (uri.NavigateUp()) {
INFO_LOG(COMMON, "Calling Android_CreateDirectory(%s, %s)", uri.ToString().c_str(), newDirName.c_str());
return Android_CreateDirectory(uri.ToString(), newDirName);
} else {
// Bad path - can't create this directory.
WARN_LOG(COMMON, "CreateDir failed: '%s'", path.c_str());
return false;
}
break;
}
default:
return false;
}
std::string fn = path.ToString();
DEBUG_LOG(COMMON, "CreateDir('%s')", fn.c_str());
DEBUG_LOG(COMMON, "CreateDir('%s')", path.c_str());
#ifdef _WIN32
if (::CreateDirectory(ConvertUTF8ToWString(fn).c_str(), NULL))
if (::CreateDirectory(path.ToWString().c_str(), NULL))
return true;
DWORD error = GetLastError();
if (error == ERROR_ALREADY_EXISTS)
{
if (error == ERROR_ALREADY_EXISTS) {
WARN_LOG(COMMON, "CreateDir: CreateDirectory failed on %s: already exists", path.c_str());
return true;
}
ERROR_LOG(COMMON, "CreateDir: CreateDirectory failed on %s: %08x %s", path.c_str(), (uint32_t)error, GetStringErrorMsg(error).c_str());
return false;
#else
if (mkdir(fn.c_str(), 0755) == 0)
return true;
int err = errno;
if (err == EEXIST)
{
WARN_LOG(COMMON, "CreateDir: mkdir failed on %s: already exists", fn.c_str());
if (mkdir(path.ToString().c_str(), 0755) == 0) {
return true;
}
ERROR_LOG(COMMON, "CreateDir: mkdir failed on %s: %s", fn.c_str(), strerror(err));
int err = errno;
if (err == EEXIST) {
WARN_LOG(COMMON, "CreateDir: mkdir failed on %s: already exists", path.c_str());
return true;
}
ERROR_LOG(COMMON, "CreateDir: mkdir failed on %s: %s", path.c_str(), strerror(err));
return false;
#endif
}
// Creates the full path of fullPath returns true on success
bool CreateFullPath(const Path &path) {
switch (path.Type()) {
case PathType::NATIVE:
break; // OK
default:
return false;
}
std::string fullPath = path.ToString();
int panicCounter = 100;
VERBOSE_LOG(COMMON, "CreateFullPath: '%s'", fullPath.c_str());
if (File::Exists(fullPath)) {
DEBUG_LOG(COMMON, "CreateFullPath: path already exists: '%s'", fullPath.c_str());
if (File::Exists(path)) {
DEBUG_LOG(COMMON, "CreateFullPath: path exists %s", path.c_str());
return true;
}
size_t position = 0;
#ifdef _WIN32
// Skip the drive letter when looking for slashes, no need to create C:\.
if (path.IsAbsolute()) {
position = 3;
switch (path.Type()) {
case PathType::NATIVE:
case PathType::CONTENT_URI:
break; // OK
default:
ERROR_LOG(COMMON, "CreateFullPath(%s): Not yet supported", path.c_str());
return false;
}
#endif
while (true) {
// Find next sub path
position = fullPath.find_first_of(DIR_SEP_CHRS, position);
// we're done, yay!
if (position == fullPath.npos) {
if (!File::Exists(fullPath))
return File::CreateDir(Path(fullPath));
return true;
}
std::string subPath = fullPath.substr(0, position);
if (position != 0 && !File::Exists(subPath)) {
File::CreateDir(Path(subPath));
}
// The below code is entirely agnostic of path format.
// A safety check
panicCounter--;
if (panicCounter <= 0) {
ERROR_LOG(COMMON, "CreateFullPath: directory structure too deep");
return false;
}
position++;
Path root = path.GetRootVolume();
std::string diff = root.PathTo(path); // always with forward slashes
std::vector<std::string> parts;
SplitString(diff, '/', parts);
// Probably not necessary sanity check, ported from the old code.
if (parts.size() > 100) {
ERROR_LOG(COMMON, "CreateFullPath: directory structure too deep");
return false;
}
Path curPath = root;
for (auto &part : parts) {
curPath /= part;
if (!File::Exists(curPath)) {
File::CreateDir(curPath);
}
}
return true;
}
// Deletes a directory filename, returns true on success
// Deletes an empty directory, returns true on success
bool DeleteDir(const Path &path) {
switch (path.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
return Android_RemoveFile(path.ToString());
default:
return false;
}
INFO_LOG(COMMON, "DeleteDir: directory %s", path.c_str());
// check if a directory
if (!File::IsDirectory(path))
{
if (!File::IsDirectory(path)) {
ERROR_LOG(COMMON, "DeleteDir: Not a directory %s", path.c_str());
return false;
}
@@ -444,11 +523,15 @@ bool Rename(const Path &srcFilename, const Path &destFilename)
switch (srcFilename.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
ERROR_LOG_REPORT_ONCE(renameUriNotSupported, COMMON, "Moving files by Android URI is not yet supported");
return false;
default:
return false;
}
INFO_LOG(COMMON, "Rename: %s --> %s", srcFilename.c_str(), destFilename.c_str());
#if defined(_WIN32) && defined(UNICODE)
std::wstring srcw = srcFilename.ToWString();
std::wstring destw = destFilename.ToWString();
@@ -470,12 +553,18 @@ bool Copy(const Path &srcFilename, const Path &destFilename)
switch (srcFilename.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
ERROR_LOG_REPORT_ONCE(copyUriNotSupported, COMMON, "Copying files by Android URI is not yet supported");
break;
default:
return false;
}
switch (destFilename.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
ERROR_LOG_REPORT_ONCE(copyUriNotSupported, COMMON, "Copying files by Android URI is not yet supported");
return false;
default:
return false;
}
@@ -563,6 +652,23 @@ bool Move(const Path &srcFilename, const Path &destFilename) {
// Returns the size of file (64bit)
// TODO: Add a way to return an error.
uint64_t GetFileSize(const Path &filename) {
switch (filename.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
{
FileInfo info;
if (Android_GetFileInfo(filename.ToString(), &info)) {
return info.size;
} else {
return 0;
}
}
break;
default:
return false;
}
#if defined(_WIN32) && defined(UNICODE)
WIN32_FILE_ATTRIBUTE_DATA attr;
if (!GetFileAttributesEx(filename.ToWString().c_str(), GetFileExInfoStandard, &attr))
@@ -591,8 +697,7 @@ uint64_t GetFileSize(const Path &filename) {
#endif
}
uint64_t GetFileSize(FILE *f)
{
uint64_t GetFileSize(FILE *f) {
// This will only support 64-bit when large file support is available.
// That won't be the case on some versions of Android, at least.
#if defined(__ANDROID__) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64)
@@ -632,11 +737,10 @@ uint64_t GetFileSize(FILE *f)
// creates an empty file filename, returns true on success
bool CreateEmptyFile(const Path &filename) {
INFO_LOG(COMMON, "CreateEmptyFile: %s", filename.c_str());
FILE *pFile = OpenCFile(filename, "wb");
if (!pFile) {
ERROR_LOG(COMMON, "CreateEmptyFile: failed %s: %s", filename.c_str(), GetLastErrorMsg().c_str());
ERROR_LOG(COMMON, "CreateEmptyFile: failed to create '%s': %s", filename.c_str(), GetLastErrorMsg().c_str());
return false;
}
fclose(pFile);
@@ -644,72 +748,38 @@ bool CreateEmptyFile(const Path &filename) {
}
// Deletes the given directory and anything under it. Returns true on success.
bool DeleteDirRecursively(const Path &directory) {
//Removed check, it prevents the UWP from deleting store downloads
INFO_LOG(COMMON, "DeleteDirRecursively: %s", directory.c_str());
#ifdef _WIN32
// Find the first file in the directory.
WIN32_FIND_DATA ffd;
HANDLE hFind = FindFirstFile((directory.ToWString() + L"\\*").c_str(), &ffd);
if (hFind == INVALID_HANDLE_VALUE) {
bool DeleteDirRecursively(const Path &directory) {
switch (directory.Type()) {
case PathType::CONTENT_URI:
case PathType::NATIVE:
break; // OK
default:
ERROR_LOG(COMMON, "DeleteDirRecursively: Path type not supported");
return false;
}
// windows loop
do {
const std::string virtualName = ConvertWStringToUTF8(ffd.cFileName);
#else
struct dirent *result = NULL;
DIR *dirp = opendir(directory.c_str());
if (!dirp)
return false;
// non windows loop
while ((result = readdir(dirp))) {
const std::string virtualName = result->d_name;
#endif
// check for "." and ".."
if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
((virtualName[0] == '.') && (virtualName[1] == '.') &&
(virtualName[2] == '\0')))
continue;
Path newPath = directory / virtualName;
if (IsDirectory(newPath)) {
if (!DeleteDirRecursively(newPath)) {
#ifndef _WIN32
closedir(dirp);
#else
FindClose(hFind);
#endif
return false;
}
std::vector<FileInfo> files;
GetFilesInDir(directory, &files, nullptr, GETFILES_GETHIDDEN);
for (const auto &file : files) {
if (file.isDirectory) {
DeleteDirRecursively(file.fullName);
} else {
Delete(file.fullName);
}
else {
if (!File::Delete(newPath)) {
#ifndef _WIN32
closedir(dirp);
#else
FindClose(hFind);
#endif
return false;
}
}
#ifdef _WIN32
} while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
#else
}
closedir(dirp);
#endif
return File::DeleteDir(directory);
return DeleteDir(directory);
}
void OpenFileInEditor(const Path &fileName) {
#if defined(_WIN32)
bool OpenFileInEditor(const Path &fileName) {
switch (fileName.Type()) {
case PathType::NATIVE:
break; // OK
default:
ERROR_LOG(COMMON, "OpenFileInEditor(%s): Path type not supported", fileName.c_str());
return false;
}
#if PPSSPP_PLATFORM(WINDOWS)
#if PPSSPP_PLATFORM(UWP)
// Do nothing.
#else
@@ -729,6 +799,7 @@ void OpenFileInEditor(const Path &fileName) {
ERROR_LOG(COMMON, "Failed to launch ini file");
}
#endif
return true;
}
const Path &GetExeDirectory() {
@@ -791,32 +862,15 @@ const Path &GetExeDirectory() {
return ExePath;
}
IOFile::IOFile() {}
IOFile::IOFile(std::FILE* file)
: m_file(file)
{}
IOFile::IOFile(const std::string &filename, const char openmode[]) {
IOFile::IOFile(const Path &filename, const char openmode[]) {
Open(filename, openmode);
}
IOFile::IOFile(const Path &filename, const char openmode[]) {
Open(filename.ToString(), openmode);
}
IOFile::~IOFile() {
Close();
}
bool IOFile::Open(const std::string& filename, const char openmode[])
{
Close();
m_file = File::OpenCFile(Path(filename), openmode);
m_good = IsOpen();
return m_good;
}
bool IOFile::Open(const Path& filename, const char openmode[])
{
Close();
+19 -6
View File
@@ -27,6 +27,8 @@
#include "Common/Common.h"
#include "Common/File/Path.h"
// Some functions here support Android content URIs. These are marked as such.
#ifdef _MSC_VER
inline struct tm* localtime_r(const time_t *clock, struct tm *result) {
if (localtime_s(result, clock) == 0)
@@ -40,6 +42,21 @@ namespace File {
// Mostly to handle UTF-8 filenames better on Windows.
FILE *OpenCFile(const Path &filename, const char *mode);
// Reminiscent of PSP's FileAccess enum, due to its use in emulating it.
enum OpenFlag {
OPEN_NONE = 0,
OPEN_READ = 1,
OPEN_WRITE = 2,
OPEN_APPEND = 4,
OPEN_CREATE = 8,
OPEN_TRUNCATE = 16,
// EXCL?
};
// TODO: This currently only handles Android Content URIs, but might port the rest
// of DirectoryFileSystem::Open here eventually for symmetry.
int OpenFD(const Path &filename, OpenFlag flags);
// Resolves symlinks and similar.
std::string ResolvePath(const std::string &path);
@@ -97,7 +114,7 @@ bool CreateEmptyFile(const Path &filename);
// Opens ini file (cheats, texture replacements etc.)
// TODO: Belongs in System or something.
void OpenFileInEditor(const Path &fileName);
bool OpenFileInEditor(const Path &fileName);
// TODO: Belongs in System or something.
const Path &GetExeDirectory();
@@ -107,8 +124,7 @@ const Path &GetExeDirectory();
// and make forgetting an fclose() harder
class IOFile {
public:
IOFile();
IOFile(FILE* file);
IOFile() {}
IOFile(const Path &filename, const char openmode[]);
~IOFile();
@@ -173,9 +189,6 @@ public:
}
private:
IOFile(const std::string &filename, const char openmode[]);
bool Open(const std::string &filename, const char openmode[]);
std::FILE *m_file = nullptr;
bool m_good = true;
};
+116 -5
View File
@@ -6,11 +6,16 @@
#include "Common/Log.h"
#include "Common/Data/Encoding/Utf8.h"
#include "android/jni/app-android.h"
#include "android/jni/AndroidContentURI.h"
Path::Path(const std::string &str) {
if (str.empty()) {
type_ = PathType::UNDEFINED;
} else if (startsWith(str, "http://") || startsWith(str, "https://")) {
type_ = PathType::HTTP;
} else if (Android_IsContentUri(str)) {
type_ = PathType::CONTENT_URI;
} else {
type_ = PathType::NATIVE;
}
@@ -46,6 +51,13 @@ void Path::Init(const std::string &str) {
// We always use forward slashes internally, we convert to backslash only when
// converted to a wstring.
Path Path::operator /(const std::string &subdir) const {
if (type_ == PathType::CONTENT_URI) {
AndroidContentURI uri(path_);
return Path(uri.WithComponent(subdir).ToString());
}
// Direct string manipulation.
if (subdir.empty()) {
return Path(path_);
}
@@ -66,11 +78,21 @@ void Path::operator /=(const std::string &subdir) {
}
Path Path::WithExtraExtension(const std::string &ext) const {
if (type_ == PathType::CONTENT_URI) {
AndroidContentURI uri(path_);
return Path(uri.WithExtraExtension(ext).ToString());
}
_dbg_assert_(!ext.empty() && ext[0] == '.');
return Path(path_ + ext);
}
Path Path::WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const {
if (type_ == PathType::CONTENT_URI) {
AndroidContentURI uri(path_);
return Path(uri.WithReplacedExtension(oldExtension, newExtension).ToString());
}
_dbg_assert_(!oldExtension.empty() && oldExtension[0] == '.');
_dbg_assert_(!newExtension.empty() && newExtension[0] == '.');
if (endsWithNoCase(path_, oldExtension)) {
@@ -92,6 +114,10 @@ Path Path::WithReplacedExtension(const std::string &newExtension) const {
}
std::string Path::GetFilename() const {
if (type_ == PathType::CONTENT_URI) {
AndroidContentURI uri(path_);
return uri.GetLastPart();
}
size_t pos = path_.rfind('/');
if (pos != std::string::npos) {
return path_.substr(pos + 1);
@@ -99,24 +125,39 @@ std::string Path::GetFilename() const {
return path_;
}
std::string Path::GetFileExtension() const {
size_t pos = path_.rfind(".");
static std::string GetExtFromString(const std::string &str) {
size_t pos = str.rfind(".");
if (pos == std::string::npos) {
return "";
}
size_t slash_pos = path_.rfind("/");
size_t slash_pos = str.rfind("/");
if (slash_pos != std::string::npos && slash_pos > pos) {
// Don't want to detect "df/file" from "/as.df/file"
return "";
}
std::string ext = path_.substr(pos);
std::string ext = str.substr(pos);
for (size_t i = 0; i < ext.size(); i++) {
ext[i] = tolower(ext[i]);
}
return ext;
}
std::string Path::GetFileExtension() const {
if (type_ == PathType::CONTENT_URI) {
AndroidContentURI uri(path_);
return GetExtFromString(uri.FilePath());
}
return GetExtFromString(path_);
}
std::string Path::GetDirectory() const {
if (type_ == PathType::CONTENT_URI) {
// Unclear how meaningful this is.
AndroidContentURI uri(path_);
uri.NavigateUp();
return uri.ToString();
}
size_t pos = path_.rfind('/');
if (type_ == PathType::HTTP) {
// Things are a bit different for HTTP, because we probably ended with /.
@@ -151,7 +192,12 @@ std::string Path::GetDirectory() const {
}
bool Path::FilePathContains(const std::string &needle) const {
const std::string &haystack = path_;
std::string haystack;
if (type_ == PathType::CONTENT_URI) {
haystack = AndroidContentURI(path_).FilePath();
} else {
haystack = path_;
}
return haystack.find(needle) != std::string::npos;
}
@@ -180,10 +226,17 @@ std::wstring Path::ToWString() const {
#endif
std::string Path::ToVisualString() const {
if (type_ == PathType::CONTENT_URI) {
return AndroidContentURI(path_).ToVisualString();
}
return path_;
}
bool Path::CanNavigateUp() const {
if (type_ == PathType::CONTENT_URI) {
return AndroidContentURI(path_).CanNavigateUp();
}
if (path_ == "/" || path_ == "") {
return false;
}
@@ -198,11 +251,44 @@ bool Path::CanNavigateUp() const {
}
Path Path::NavigateUp() const {
if (type_ == PathType::CONTENT_URI) {
AndroidContentURI uri(path_);
uri.NavigateUp();
return Path(uri.ToString());
}
std::string dir = GetDirectory();
return Path(dir);
}
Path Path::GetRootVolume() const {
if (!IsAbsolute()) {
// Can't do anything
return Path(path_);
}
if (type_ == PathType::CONTENT_URI) {
AndroidContentURI uri(path_);
AndroidContentURI rootPath = uri.WithRootFilePath("");
return Path(rootPath.ToString());
}
#if PPSSPP_PLATFORM(WINDOWS)
if (path_[1] == ':') {
// Windows path with drive letter
std::string path = path_.substr(0, 2);
return Path(path);
}
#endif
return Path("/");
}
bool Path::IsAbsolute() const {
if (type_ == PathType::CONTENT_URI) {
// These don't exist in relative form.
return true;
}
if (path_.empty())
return true;
else if (path_.front() == '/')
@@ -214,3 +300,28 @@ bool Path::IsAbsolute() const {
else
return false;
}
std::string Path::PathTo(const Path &other) {
if (!other.StartsWith(*this)) {
// Can't do this. Should return an error.
return std::string();
}
std::string diff;
if (type_ == PathType::CONTENT_URI) {
AndroidContentURI a(path_);
AndroidContentURI b(other.path_);
if (a.RootPath() != b.RootPath()) {
// No common root, can't do anything
return std::string();
}
diff = a.PathTo(b);
} else if (path_ == "/") {
diff = other.path_.substr(1);
} else {
diff = other.path_.substr(path_.size() + 1);
}
return diff;
}
+9 -4
View File
@@ -6,9 +6,8 @@
enum class PathType {
UNDEFINED = 0,
NATIVE = 1,
// RELATIVE, // (do we need this?)
// CONTENT_URI = 2, // Android only
NATIVE = 1, // Can be relative.
CONTENT_URI = 2, // Android only. Can only be absolute!
HTTP = 3, // http://, https://
};
@@ -44,7 +43,7 @@ public:
return path_.size();
}
// WARNING: Unsafe usage.
// WARNING: Potentially unsafe usage, if it's not NATIVE.
const char *c_str() const {
return path_.c_str();
}
@@ -78,6 +77,12 @@ public:
bool CanNavigateUp() const;
Path NavigateUp() const;
// Navigates as far up as possible from this path. If not possible to navigate upwards, returns the same path.
// Not actually always the root of the volume, especially on systems like Mac and Linux where things are often mounted.
Path GetRootVolume() const;
std::string PathTo(const Path &child);
bool operator ==(const Path &other) const {
return path_ == other.path_ && type_ == other.type_;
}
+15 -61
View File
@@ -9,6 +9,7 @@
#include "Common/File/PathBrowser.h"
#include "Common/File/FileUtil.h"
#include "Common/File/DirListing.h"
#include "Common/StringUtils.h"
#include "Common/TimeUtil.h"
#include "Common/Log.h"
@@ -16,6 +17,11 @@
#include "Core/System.h"
#if PPSSPP_PLATFORM(ANDROID)
#include "android/jni/app-android.h"
#include "android/jni/AndroidContentURI.h"
#endif
bool LoadRemoteFileList(const Path &url, bool *cancel, std::vector<File::FileInfo> &files) {
_dbg_assert_(url.Type() == PathType::HTTP);
@@ -97,33 +103,6 @@ bool LoadRemoteFileList(const Path &url, bool *cancel, std::vector<File::FileInf
return !files.empty();
}
std::vector<File::FileInfo> ApplyFilter(std::vector<File::FileInfo> files, const char *filter) {
std::set<std::string> filters;
if (filter) {
std::string tmp;
while (*filter) {
if (*filter == ':') {
filters.insert("." + tmp);
tmp.clear();
} else {
tmp.push_back(*filter);
}
filter++;
}
if (!tmp.empty())
filters.insert("." + tmp);
}
auto pred = [&](const File::FileInfo &info) {
if (info.isDirectory || !filter)
return false;
std::string ext = info.fullName.GetFileExtension();
return filters.find(ext) == filters.end();
};
files.erase(std::remove_if(files.begin(), files.end(), pred), files.end());
return files;
}
PathBrowser::~PathBrowser() {
std::unique_lock<std::mutex> guard(pendingLock_);
pendingCancel_ = true;
@@ -149,13 +128,6 @@ void PathBrowser::HandlePath() {
return;
}
if (path_.Type() != PathType::HTTP) {
if (pendingActive_)
ResetPending();
ready_ = true;
return;
}
std::lock_guard<std::mutex> guard(pendingLock_);
ready_ = false;
pendingActive_ = true;
@@ -184,6 +156,12 @@ void PathBrowser::HandlePath() {
results.clear();
success = LoadRemoteFileList(lastPath, &pendingCancel_, results);
guard.lock();
} else {
guard.unlock();
results.clear();
File::GetFilesInDir(lastPath, &results, nullptr);
success = true;
guard.lock();
}
if (pendingPath_ == lastPath) {
@@ -231,36 +209,12 @@ bool PathBrowser::GetListing(std::vector<File::FileInfo> &fileInfo, const char *
while (!IsListingReady() && (!cancel || !*cancel)) {
// In case cancel changes, just sleep.
guard.unlock();
sleep_ms(100);
sleep_ms(50);
guard.lock();
}
#ifdef _WIN32
if (path_.IsRoot()) {
// Special path that means root of file system.
std::vector<std::string> drives = File::GetWindowsDrives();
for (auto drive = drives.begin(); drive != drives.end(); ++drive) {
if (*drive == "A:/" || *drive == "B:/")
continue;
File::FileInfo fake;
fake.fullName = Path(*drive);
fake.name = *drive;
fake.isDirectory = true;
fake.exists = true;
fake.size = 0;
fake.isWritable = false;
fileInfo.push_back(fake);
}
}
#endif
if (path_.Type() == PathType::HTTP) {
fileInfo = ApplyFilter(pendingFiles_, filter);
return true;
} else {
File::GetFilesInDir(path_, &fileInfo, filter);
return true;
}
fileInfo = ApplyFilter(pendingFiles_, filter);
return true;
}
bool PathBrowser::CanNavigateUp() {
+8 -2
View File
@@ -26,6 +26,8 @@
namespace Draw {
static constexpr int MAX_BOUND_TEXTURES = 8;
// A problem is that we can't get the D3Dcompiler.dll without using a later SDK than 7.1, which was the last that
// supported XP. A possible solution might be here:
// https://tedwvc.wordpress.com/2014/01/01/how-to-target-xp-with-vc2012-or-vc2013-and-continue-to-use-the-windows-8-x-sdk/
@@ -1162,6 +1164,7 @@ void D3D11DrawContext::UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t
}
void D3D11DrawContext::BindVertexBuffers(int start, int count, Buffer **buffers, int *offsets) {
_assert_(start + count <= ARRAY_SIZE(nextVertexBuffers_));
// Lazy application
for (int i = 0; i < count; i++) {
D3D11Buffer *buf = (D3D11Buffer *)buffers[i];
@@ -1328,7 +1331,8 @@ Framebuffer *D3D11DrawContext::CreateFramebuffer(const FramebufferDesc &desc) {
void D3D11DrawContext::BindTextures(int start, int count, Texture **textures) {
// Collect the resource views from the textures.
ID3D11ShaderResourceView *views[8];
ID3D11ShaderResourceView *views[MAX_BOUND_TEXTURES];
_assert_(start + count <= ARRAY_SIZE(views));
for (int i = 0; i < count; i++) {
D3D11Texture *tex = (D3D11Texture *)textures[i];
views[i] = tex ? tex->view : nullptr;
@@ -1337,7 +1341,8 @@ void D3D11DrawContext::BindTextures(int start, int count, Texture **textures) {
}
void D3D11DrawContext::BindSamplerStates(int start, int count, SamplerState **states) {
ID3D11SamplerState *samplers[8];
ID3D11SamplerState *samplers[MAX_BOUND_TEXTURES];
_assert_(start + count <= ARRAY_SIZE(samplers));
for (int i = 0; i < count; i++) {
D3D11SamplerState *samp = (D3D11SamplerState *)states[i];
samplers[i] = samp->ss;
@@ -1613,6 +1618,7 @@ void D3D11DrawContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const Ren
}
void D3D11DrawContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) {
_assert_(binding < MAX_BOUND_TEXTURES);
D3D11Framebuffer *fb = (D3D11Framebuffer *)fbo;
switch (channelBit) {
case FBChannel::FB_COLOR_BIT:
+6
View File
@@ -28,6 +28,8 @@
namespace Draw {
static constexpr int MAX_BOUND_TEXTURES = 8;
// Could be declared as u8
static const D3DCMPFUNC compareToD3D9[] = {
D3DCMP_NEVER,
@@ -525,12 +527,14 @@ public:
void BindTextures(int start, int count, Texture **textures) override;
void BindSamplerStates(int start, int count, SamplerState **states) override {
_assert_(start + count <= MAX_BOUND_TEXTURES);
for (int i = 0; i < count; ++i) {
D3D9SamplerState *s = static_cast<D3D9SamplerState *>(states[i]);
s->Apply(device_, start + i);
}
}
void BindVertexBuffers(int start, int count, Buffer **buffers, int *offsets) override {
_assert_(start + count <= ARRAY_SIZE(curVBuffers_));
for (int i = 0; i < count; i++) {
curVBuffers_[i + start] = (D3D9Buffer *)buffers[i];
curVBufferOffsets_[i + start] = offsets ? offsets[i] : 0;
@@ -785,6 +789,7 @@ Texture *D3D9Context::CreateTexture(const TextureDesc &desc) {
}
void D3D9Context::BindTextures(int start, int count, Texture **textures) {
_assert_(start + count <= MAX_BOUND_TEXTURES);
for (int i = start; i < start + count; i++) {
D3D9Texture *tex = static_cast<D3D9Texture *>(textures[i - start]);
if (tex) {
@@ -1182,6 +1187,7 @@ uintptr_t D3D9Context::GetFramebufferAPITexture(Framebuffer *fbo, int channelBit
}
void D3D9Context::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int color) {
_assert_(binding < MAX_BOUND_TEXTURES);
D3D9Framebuffer *fb = (D3D9Framebuffer *)fbo;
switch (channelBit) {
case FB_DEPTH_BIT:
+16 -2
View File
@@ -17,7 +17,7 @@
#include "GLRenderManager.h"
#include "DataFormatGL.h"
#define TEXCACHE_NAME_CACHE_SIZE 16
static constexpr int TEXCACHE_NAME_CACHE_SIZE = 16;
#if PPSSPP_PLATFORM(IOS)
extern void bindDefaultFBO();
@@ -793,6 +793,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
bool blendEnabled = false;
bool cullEnabled = false;
bool ditherEnabled = false;
bool depthClampEnabled = false;
#ifndef USING_GLES2
int logicOp = -1;
bool logicEnabled = false;
@@ -800,7 +801,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
GLuint blendEqColor = (GLuint)-1;
GLuint blendEqAlpha = (GLuint)-1;
GLRTexture *curTex[8]{};
GLRTexture *curTex[MAX_GL_TEXTURE_SLOTS]{};
CHECK_GL_ERROR_IF_DEBUG();
auto &commands = step.commands;
@@ -1283,6 +1284,17 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
glDisable(GL_DITHER);
ditherEnabled = false;
}
#ifndef USING_GLES2
if (c.raster.depthClampEnable) {
if (!depthClampEnabled) {
glEnable(GL_DEPTH_CLAMP);
depthClampEnabled = true;
}
} else if (!c.raster.depthClampEnable && depthClampEnabled) {
glDisable(GL_DEPTH_CLAMP);
depthClampEnabled = false;
}
#endif
CHECK_GL_ERROR_IF_DEBUG();
break;
default:
@@ -1322,6 +1334,8 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
if (cullEnabled)
glDisable(GL_CULL_FACE);
#ifndef USING_GLES2
if (depthClampEnabled)
glDisable(GL_DEPTH_CLAMP);
if (!gl_extensions.IsGLES && logicEnabled) {
glDisable(GL_COLOR_LOGIC_OP);
}
+1
View File
@@ -196,6 +196,7 @@ struct GLRRenderData {
GLenum frontFace;
GLenum cullFace;
GLboolean ditherEnable;
GLboolean depthClampEnable;
} raster;
};
};
+1
View File
@@ -357,6 +357,7 @@ void GLRenderManager::BindFramebufferAsRenderTarget(GLRFramebuffer *fb, GLRRende
void GLRenderManager::BindFramebufferAsTexture(GLRFramebuffer *fb, int binding, int aspectBit, int attachment) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
_dbg_assert_(binding < MAX_GL_TEXTURE_SLOTS);
GLRRenderData data{ GLRRenderCommand::BIND_FB_TEXTURE };
data.bind_fb_texture.slot = binding;
data.bind_fb_texture.framebuffer = fb;
+7 -2
View File
@@ -21,6 +21,8 @@ namespace Draw {
class DrawContext;
}
constexpr int MAX_GL_TEXTURE_SLOTS = 8;
class GLRTexture {
public:
GLRTexture(int width, int height, int numMips);
@@ -571,8 +573,8 @@ public:
void BindTexture(int slot, GLRTexture *tex) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
_dbg_assert_(slot < MAX_GL_TEXTURE_SLOTS);
GLRRenderData data{ GLRRenderCommand::BINDTEXTURE };
_dbg_assert_(slot < 16);
data.texture.slot = slot;
data.texture.texture = tex;
curRenderStep_->commands.push_back(data);
@@ -811,19 +813,21 @@ public:
curRenderStep_->commands.push_back(data);
}
void SetRaster(GLboolean cullEnable, GLenum frontFace, GLenum cullFace, GLboolean ditherEnable) {
void SetRaster(GLboolean cullEnable, GLenum frontFace, GLenum cullFace, GLboolean ditherEnable, GLboolean depthClamp) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::RASTER };
data.raster.cullEnable = cullEnable;
data.raster.frontFace = frontFace;
data.raster.cullFace = cullFace;
data.raster.ditherEnable = ditherEnable;
data.raster.depthClampEnable = depthClamp;
curRenderStep_->commands.push_back(data);
}
// Modifies the current texture as per GL specs, not global state.
void SetTextureSampler(int slot, GLenum wrapS, GLenum wrapT, GLenum magFilter, GLenum minFilter, float anisotropy) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
_dbg_assert_(slot < MAX_GL_TEXTURE_SLOTS);
GLRRenderData data{ GLRRenderCommand::TEXTURESAMPLER };
data.textureSampler.slot = slot;
data.textureSampler.wrapS = wrapS;
@@ -836,6 +840,7 @@ public:
void SetTextureLod(int slot, float minLod, float maxLod, float lodBias) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
_dbg_assert_(slot < MAX_GL_TEXTURE_SLOTS);
GLRRenderData data{ GLRRenderCommand::TEXTURELOD};
data.textureLod.slot = slot;
data.textureLod.minLod = minLod;
+16 -10
View File
@@ -188,7 +188,7 @@ public:
class OpenGLRasterState : public RasterState {
public:
void Apply(GLRenderManager *render) {
render->SetRaster(cullEnable, frontFace, cullMode, false);
render->SetRaster(cullEnable, frontFace, cullMode, GL_FALSE, GL_FALSE);
}
GLboolean cullEnable;
@@ -299,7 +299,7 @@ public:
// TODO: Optimize by getting the locations first and putting in a custom struct
UniformBufferDesc dynamicUniforms;
GLint samplerLocs_[8]{};
GLint samplerLocs_[MAX_TEXTURE_SLOTS]{};
std::vector<GLint> dynamicUniformLocs_;
GLRProgram *program_ = nullptr;
@@ -368,9 +368,7 @@ public:
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
void BindSamplerStates(int start, int count, SamplerState **states) override {
if (start + count > MAX_TEXTURE_SLOTS) {
return;
}
_assert_(start + count <= MAX_TEXTURE_SLOTS);
for (int i = 0; i < count; i++) {
int index = i + start;
boundSamplers_[index] = static_cast<OpenGLSamplerState *>(states[i]);
@@ -402,6 +400,7 @@ public:
void BindTextures(int start, int count, Texture **textures) override;
void BindPipeline(Pipeline *pipeline) override;
void BindVertexBuffers(int start, int count, Buffer **buffers, int *offsets) override {
_assert_(start + count <= ARRAY_SIZE(curVBuffers_));
for (int i = 0; i < count; i++) {
curVBuffers_[i + start] = (OpenGLBuffer *)buffers[i];
curVBufferOffsets_[i + start] = offsets ? offsets[i] : 0;
@@ -534,6 +533,12 @@ OpenGLContext::OpenGLContext() {
}
caps_.framebufferBlitSupported = gl_extensions.NV_framebuffer_blit || gl_extensions.ARB_framebuffer_object;
caps_.framebufferDepthBlitSupported = caps_.framebufferBlitSupported;
caps_.depthClampSupported = gl_extensions.ARB_depth_clamp;
// Interesting potential hack for emulating GL_DEPTH_CLAMP (use a separate varying, force depth in fragment shader):
// This will induce a performance penalty on many architectures though so a blanket enable of this
// is probably not a good idea.
// https://stackoverflow.com/questions/5960757/how-to-emulate-gl-depth-clamp-nv
switch (gl_extensions.gpuVendor) {
case GPU_VENDOR_AMD: caps_.vendor = GPUVendor::VENDOR_AMD; break;
@@ -1070,9 +1075,7 @@ Pipeline *OpenGLContext::CreateGraphicsPipeline(const PipelineDesc &desc) {
}
void OpenGLContext::BindTextures(int start, int count, Texture **textures) {
if (start + count > MAX_TEXTURE_SLOTS) {
return;
}
_assert_(start + count <= MAX_TEXTURE_SLOTS);
for (int i = start; i < start + count; i++) {
OpenGLTexture *glTex = static_cast<OpenGLTexture *>(textures[i - start]);
if (!glTex) {
@@ -1151,12 +1154,14 @@ bool OpenGLPipeline::LinkShaders() {
std::vector<GLRProgram::UniformLocQuery> queries;
queries.push_back({ &samplerLocs_[0], "sampler0" });
queries.push_back({ &samplerLocs_[1], "sampler1" });
queries.push_back({ &samplerLocs_[2], "sampler2" });
_assert_(queries.size() >= MAX_TEXTURE_SLOTS);
for (size_t i = 0; i < dynamicUniforms.uniforms.size(); ++i) {
queries.push_back({ &dynamicUniformLocs_[i], dynamicUniforms.uniforms[i].name });
}
std::vector<GLRProgram::Initializer> initialize;
initialize.push_back({ &samplerLocs_[0], 0, 0 });
initialize.push_back({ &samplerLocs_[1], 0, 1 });
for (int i = 0; i < MAX_TEXTURE_SLOTS; ++i)
initialize.push_back({ &samplerLocs_[i], 0, i });
program_ = render_->CreateProgram(linkShaders, semantics, queries, initialize, false);
return true;
}
@@ -1354,6 +1359,7 @@ bool OpenGLContext::BlitFramebuffer(Framebuffer *fbsrc, int srcX1, int srcY1, in
void OpenGLContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int color) {
OpenGLFramebuffer *fb = (OpenGLFramebuffer *)fbo;
_assert_(binding < MAX_TEXTURE_SLOTS);
GLuint aspect = 0;
if (channelBit & FB_COLOR_BIT) {
+16 -11
View File
@@ -85,6 +85,7 @@ cbuffer data : register(b0) {
float2 u_texelDelta;
float2 u_pixelDelta;
float4 u_time;
float4 u_timeDelta;
float4 u_setting;
float u_video;
};
@@ -101,6 +102,7 @@ layout (std140, set = 0, binding = 0) uniform Data {
vec2 u_texelDelta;
vec2 u_pixelDelta;
vec4 u_time;
vec4 u_timeDelta;
vec4 u_setting;
float u_video;
};
@@ -111,8 +113,9 @@ float4 gl_HalfPixel : register(c0);
float2 u_texelDelta : register(c1);
float2 u_pixelDelta : register(c2);
float4 u_time : register(c3);
float4 u_setting : register(c4);
float u_video : register(c5);
float4 u_timeDelta : register(c4);
float4 u_setting : register(c5);
float u_video : register(c6);
)";
// SPIRV-Cross' HLSL output has some deficiencies we need to work around.
@@ -135,12 +138,9 @@ std::string Postprocess(std::string code, ShaderLanguage lang, ShaderStage stage
std::string line;
std::stringstream instream(code);
while (std::getline(instream, line)) {
if (line == "uniform sampler2D sampler0;" && lang == HLSL_D3D9) {
out << "sampler2D sampler0 : register(s0);\n";
continue;
}
if (line == "uniform sampler2D sampler1;" && lang == HLSL_D3D9) {
out << "sampler2D sampler1 : register(s1);\n";
int num;
if (lang == HLSL_D3D9 && sscanf(line.c_str(), "uniform sampler2D sampler%d;", &num) == 1) {
out << "sampler2D sampler" << num << " : register(s" << num << ");\n";
continue;
}
if (line.find("uniform float") != std::string::npos) {
@@ -184,7 +184,9 @@ bool ConvertToVulkanGLSL(std::string *dest, TranslatedShaderMetadata *destMetada
if (line.find("uniform bool") != std::string::npos) {
continue;
} else if (line.find("uniform sampler2D") == 0) {
if (line.find("sampler0") != line.npos)
if (sscanf(line.c_str(), "uniform sampler2D sampler%d", &num) == 1)
line = StringFromFormat("layout(set = 0, binding = %d) ", num + 1) + line;
else if (line.find("sampler0") != line.npos)
line = "layout(set = 0, binding = 1) " + line;
else
line = "layout(set = 0, binding = 2) " + line;
@@ -299,8 +301,11 @@ bool TranslateShader(std::string *dest, ShaderLanguage destLang, const ShaderLan
int i = 0;
for (auto &resource : resources.sampled_images) {
// int location = hlsl.get_decoration(resource.id, spv::DecorationLocation);
hlsl.set_decoration(resource.id, spv::DecorationLocation, i);
const std::string &name = hlsl.get_name(resource.id);
int num;
if (sscanf(name.c_str(), "sampler%d", &num) != 1)
num = i;
hlsl.set_decoration(resource.id, spv::DecorationBinding, num);
i++;
}
spirv_cross::CompilerHLSL::Options options{};
+8 -25
View File
@@ -565,33 +565,16 @@ void VulkanContext::ChooseDevice(int physical_device) {
}
deviceFeatures_.enabled = {};
// Enable a few safe ones if they are available.
if (deviceFeatures_.available.dualSrcBlend) {
deviceFeatures_.enabled.dualSrcBlend = true;
}
if (deviceFeatures_.available.largePoints) {
deviceFeatures_.enabled.largePoints = true;
}
if (deviceFeatures_.available.wideLines) {
deviceFeatures_.enabled.wideLines = true;
}
if (deviceFeatures_.available.logicOp) {
deviceFeatures_.enabled.logicOp = true;
}
if (deviceFeatures_.available.depthClamp) {
deviceFeatures_.enabled.depthClamp = true;
}
if (deviceFeatures_.available.depthBounds) {
deviceFeatures_.enabled.depthBounds = true;
}
if (deviceFeatures_.available.samplerAnisotropy) {
deviceFeatures_.enabled.samplerAnisotropy = true;
}
deviceFeatures_.enabled.dualSrcBlend = deviceFeatures_.available.dualSrcBlend;
deviceFeatures_.enabled.largePoints = deviceFeatures_.available.largePoints;
deviceFeatures_.enabled.wideLines = deviceFeatures_.available.wideLines;
deviceFeatures_.enabled.logicOp = deviceFeatures_.available.logicOp;
deviceFeatures_.enabled.depthClamp = deviceFeatures_.available.depthClamp;
deviceFeatures_.enabled.depthBounds = deviceFeatures_.available.depthBounds;
deviceFeatures_.enabled.samplerAnisotropy = deviceFeatures_.available.samplerAnisotropy;
// For easy wireframe mode, someday.
if (deviceFeatures_.available.fillModeNonSolid) {
deviceFeatures_.enabled.fillModeNonSolid = true;
}
deviceFeatures_.enabled.fillModeNonSolid = deviceFeatures_.available.fillModeNonSolid;
GetDeviceLayerExtensionList(nullptr, device_extension_properties_);
+5 -1
View File
@@ -295,7 +295,7 @@ class VKBuffer;
class VKSamplerState;
enum {
MAX_BOUND_TEXTURES = 2
MAX_BOUND_TEXTURES = MAX_TEXTURE_SLOTS,
};
struct DescriptorSetKey {
@@ -416,6 +416,7 @@ public:
// TODO: Make VKBuffers proper buffers, and do a proper binding model. This is just silly.
void BindVertexBuffers(int start, int count, Buffer **buffers, int *offsets) override {
_assert_(start + count <= ARRAY_SIZE(curVBuffers_));
for (int i = 0; i < count; i++) {
curVBuffers_[i + start] = (VKBuffer *)buffers[i];
curVBufferOffsets_[i + start] = offsets ? offsets[i] : 0;
@@ -689,6 +690,7 @@ RasterState *VKContext::CreateRasterState(const RasterStateDesc &desc) {
}
void VKContext::BindSamplerStates(int start, int count, SamplerState **state) {
_assert_(start + count <= MAX_BOUND_TEXTURES);
for (int i = start; i < start + count; i++) {
boundSamplers_[i] = (VKSamplerState *)state[i - start];
}
@@ -1271,6 +1273,7 @@ void VKContext::UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset,
}
void VKContext::BindTextures(int start, int count, Texture **textures) {
_assert_(start + count <= MAX_BOUND_TEXTURES);
for (int i = start; i < start + count; i++) {
boundTextures_[i] = static_cast<VKTexture *>(textures[i - start]);
boundImageView_[i] = boundTextures_[i] ? boundTextures_[i]->GetImageView() : GetNullTexture()->GetImageView();
@@ -1544,6 +1547,7 @@ void VKContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPass
void VKContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) {
VKFramebuffer *fb = (VKFramebuffer *)fbo;
_assert_(binding < MAX_BOUND_TEXTURES);
// TODO: There are cases where this is okay, actually. But requires layout transitions and stuff -
// we're not ready for this.
+1 -1
View File
@@ -278,7 +278,7 @@ enum class Event {
PRESENTED,
};
constexpr uint32_t MAX_TEXTURE_SLOTS = 2;
constexpr uint32_t MAX_TEXTURE_SLOTS = 3;
struct FramebufferDesc {
int width;
+12
View File
@@ -17,6 +17,12 @@
#include "ppsspp_config.h"
#if PPSSPP_PLATFORM(ANDROID)
#include <android/log.h>
#endif
#include <algorithm>
#include <cstring>
@@ -51,8 +57,14 @@ void GenericLog(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char
instance->Log(level, type, file, line, fmt, args);
} else {
// Fall back to printf if we're before the log manager has been initialized.
#if PPSSPP_PLATFORM(ANDROID)
char temp[512];
vsnprintf(temp, sizeof(temp), fmt, args);
__android_log_print(ANDROID_LOG_INFO, "PPSSPP", "EARLY: %s", temp);
#else
vprintf(fmt, args);
printf("\n");
#endif
}
va_end(args);
}
+92
View File
@@ -0,0 +1,92 @@
// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <cstdarg>
#include <cstdio>
#include <mutex>
#include <unordered_map>
#include "Common/LogReporting.h"
namespace Reporting {
// Keeps track of report-only-once identifiers. Since they're always constants, a pointer is okay.
static std::unordered_map<const char *, int> logNTimes;
static std::mutex logNTimesLock;
AllowedCallback allowedCallback = nullptr;
MessageCallback messageCallback = nullptr;
bool ShouldLogNTimes(const char *identifier, int count) {
// True if it wasn't there already -> so yes, log.
std::lock_guard<std::mutex> lock(logNTimesLock);
auto iter = logNTimes.find(identifier);
if (iter == logNTimes.end()) {
logNTimes.insert(std::pair<const char*, int>(identifier, 1));
return true;
} else {
if (iter->second >= count) {
return false;
} else {
iter->second++;
return true;
}
}
}
void ResetCounts() {
std::lock_guard<std::mutex> lock(logNTimesLock);
logNTimes.clear();
}
void SetupCallbacks(AllowedCallback allowed, MessageCallback message) {
allowedCallback = allowed;
messageCallback = message;
}
void ReportMessage(const char *message, ...) {
if (!allowedCallback || !messageCallback) {
ERROR_LOG(SYSTEM, "Reporting not initialized, skipping: %s", message);
return;
}
if (!allowedCallback())
return;
const int MESSAGE_BUFFER_SIZE = 65536;
char temp[MESSAGE_BUFFER_SIZE];
va_list args;
va_start(args, message);
vsnprintf(temp, MESSAGE_BUFFER_SIZE - 1, message, args);
temp[MESSAGE_BUFFER_SIZE - 1] = '\0';
va_end(args);
messageCallback(message, temp);
}
void ReportMessageFormatted(const char *message, const char *formatted) {
if (!allowedCallback || !messageCallback) {
ERROR_LOG(SYSTEM, "Reporting not initialized, skipping: %s", message);
return;
}
if (!allowedCallback())
return;
messageCallback(message, formatted);
}
}
+65
View File
@@ -0,0 +1,65 @@
// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "Common/CommonTypes.h"
#include "Common/Log.h"
#define DEBUG_LOG_REPORT(t,...) do { DEBUG_LOG(t, __VA_ARGS__); Reporting::ReportMessage(__VA_ARGS__); } while (false)
#define ERROR_LOG_REPORT(t,...) do { ERROR_LOG(t, __VA_ARGS__); Reporting::ReportMessage(__VA_ARGS__); } while (false)
#define WARN_LOG_REPORT(t,...) do { WARN_LOG(t, __VA_ARGS__); Reporting::ReportMessage(__VA_ARGS__); } while (false)
#define NOTICE_LOG_REPORT(t,...) do { NOTICE_LOG(t, __VA_ARGS__); Reporting::ReportMessage(__VA_ARGS__); } while (false)
#define INFO_LOG_REPORT(t,...) do { INFO_LOG(t, __VA_ARGS__); Reporting::ReportMessage(__VA_ARGS__); } while (false)
#define DEBUG_LOG_REPORT_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { DEBUG_LOG_REPORT(t, __VA_ARGS__); } } while (false)
#define ERROR_LOG_REPORT_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { ERROR_LOG_REPORT(t, __VA_ARGS__); } } while (false)
#define WARN_LOG_REPORT_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { WARN_LOG_REPORT(t, __VA_ARGS__); } } while (false)
#define NOTICE_LOG_REPORT_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { NOTICE_LOG_REPORT(t, __VA_ARGS__); } } while (false)
#define INFO_LOG_REPORT_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { INFO_LOG_REPORT(t, __VA_ARGS__); } } while (false)
#define ERROR_LOG_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { ERROR_LOG(t, __VA_ARGS__); } } while (false)
#define WARN_LOG_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { WARN_LOG(t, __VA_ARGS__); } } while (false)
#define NOTICE_LOG_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { NOTICE_LOG(t, __VA_ARGS__); } } while (false)
#define INFO_LOG_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { INFO_LOG(t, __VA_ARGS__); } } while (false)
#define ERROR_LOG_N_TIMES(s,n,t,...) do { if (Reporting::ShouldLogNTimes(#s, n)) { ERROR_LOG(t, __VA_ARGS__); } } while (false)
#define WARN_LOG_N_TIMES(s,n,t,...) do { if (Reporting::ShouldLogNTimes(#s, n)) { WARN_LOG(t, __VA_ARGS__); } } while (false)
#define NOTICE_LOG_N_TIMES(s,n,t,...) do { if (Reporting::ShouldLogNTimes(#s, n)) { NOTICE_LOG(t, __VA_ARGS__); } } while (false)
#define INFO_LOG_N_TIMES(s,n,t,...) do { if (Reporting::ShouldLogNTimes(#s, n)) { INFO_LOG(t, __VA_ARGS__); } } while (false)
namespace Reporting {
typedef bool(*AllowedCallback)();
typedef void(*MessageCallback)(const char *message, const char *formatted);
// Resets counts on any count-limited logs (see ShouldLogNTimes).
void ResetCounts();
// Returns true if that identifier has not been logged yet.
bool ShouldLogNTimes(const char *identifier, int n);
// Set callbacks for implementation of message reporting.
void SetupCallbacks(AllowedCallback allowed, MessageCallback message);
// Report a message string, using the format string as a key.
void ReportMessage(const char *message, ...);
// The same, but with a preformatted version (message is still the key.)
void ReportMessageFormatted(const char *message, const char *formatted);
}
+1 -1
View File
@@ -36,7 +36,7 @@ struct MemArenaData;
class MemArena {
public:
size_t roundup(size_t x);
void GrabLowMemSpace(size_t size);
bool GrabMemSpace(size_t size);
void ReleaseSpace();
void *CreateView(s64 offset, size_t size, void *base = 0);
void ReleaseView(void *view, size_t size);
+3 -2
View File
@@ -98,7 +98,7 @@ size_t MemArena::roundup(size_t x) {
return x;
}
void MemArena::GrabLowMemSpace(size_t size) {
bool MemArena::GrabMemSpace(size_t size) {
// Use ashmem so we don't have to allocate a file on disk!
const char* name = "PPSSPP_RAM";
@@ -111,8 +111,9 @@ void MemArena::GrabLowMemSpace(size_t size) {
// Note that it appears that ashmem is pinned by default, so no need to pin.
if (fd < 0) {
ERROR_LOG(MEMMAP, "Failed to grab ashmem space of size: %08x errno: %d", (int)size, (int)(errno));
return;
return false;
}
return true;
}
void MemArena::ReleaseSpace() {
+3 -1
View File
@@ -39,13 +39,15 @@ size_t MemArena::roundup(size_t x) {
return x;
}
void MemArena::GrabLowMemSpace(size_t size) {
bool MemArena::GrabMemSpace(size_t size) {
vm_size = size;
kern_return_t retval = vm_allocate(mach_task_self(), &vm_mem, size, VM_FLAGS_ANYWHERE);
if (retval != KERN_SUCCESS) {
ERROR_LOG(MEMMAP, "Failed to grab a block of virtual memory");
return false;
} else {
INFO_LOG(MEMMAP, "Successfully allocated %d bytes at %p", (int)size, (void *)vm_mem);
return true;
}
}
+12 -6
View File
@@ -50,7 +50,7 @@ bool MemArena::NeedsProbing() {
return false;
}
void MemArena::GrabLowMemSpace(size_t size) {
bool MemArena::GrabMemSpace(size_t size) {
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
// Some platforms (like Raspberry Pi) end up flushing to disk.
@@ -61,15 +61,20 @@ void MemArena::GrabLowMemSpace(size_t size) {
if (fd >= 0) {
// Great, this definitely shouldn't flush to disk.
ram_temp_file = tmpfs_ram_temp_file;
} else {
INFO_LOG(MEMMAP, "Got tmpfs ram file: %s", tmpfs_ram_temp_file.c_str());
}
} else {
INFO_LOG(MEMMAP, "'%s' does not exist. Trying other ram file options.", tmpfs_location.c_str());
}
if (fd < 0) {
INFO_LOG(MEMMAP, "Trying '%s' as ram temp file", ram_temp_file.c_str());
fd = open(ram_temp_file.c_str(), O_RDWR | O_CREAT, mode);
}
if (fd < 0) {
ERROR_LOG(MEMMAP, "Failed to grab memory space as a file: %s of size: %08x errno: %d", ram_temp_file.c_str(), (int)size, (int)(errno));
return;
ERROR_LOG(MEMMAP, "Failed to grab memory space as a file: %s of size: %08x. Error: %s", ram_temp_file.c_str(), (int)size, strerror(errno));
return false;
}
// delete immediately, we keep the fd so it still lives
if (unlink(ram_temp_file.c_str()) != 0) {
@@ -77,8 +82,9 @@ void MemArena::GrabLowMemSpace(size_t size) {
}
if (ftruncate(fd, size) != 0) {
ERROR_LOG(MEMMAP, "Failed to ftruncate %d (%s) to size %08x", (int)fd, ram_temp_file.c_str(), (int)size);
// Should this be a failure?
}
return;
return true;
}
void MemArena::ReleaseSpace() {
@@ -95,7 +101,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base)
((base == 0) ? 0 : MAP_FIXED), fd, offset);
if (retval == MAP_FAILED) {
NOTICE_LOG(MEMMAP, "mmap on %s (fd: %d) failed", ram_temp_file.c_str(), (int)fd);
NOTICE_LOG(MEMMAP, "mmap on %s (fd: %d) failed: %s", ram_temp_file.c_str(), (int)fd, strerror(errno));
return 0;
}
return retval;
@@ -122,7 +128,7 @@ u8* MemArena::Find4GBBase() {
return reinterpret_cast<u8 *>(aligned_base);
} else {
u8 *hardcoded_ptr = reinterpret_cast<u8*>(0x2300000000ULL);
INFO_LOG(MEMMAP, "Failed to anonymously map 8GB. Fall back to the hardcoded pointer %p.", hardcoded_ptr);
INFO_LOG(MEMMAP, "Failed to anonymously map 8GB (%s). Fall back to the hardcoded pointer %p.", strerror(errno), hardcoded_ptr);
// Just grab some random 4GB...
// This has been known to fail lately though, see issue #12249.
return hardcoded_ptr;
+2 -1
View File
@@ -28,13 +28,14 @@ size_t MemArena::roundup(size_t x) {
return (x + gran - 1) & ~(gran - 1);
}
void MemArena::GrabLowMemSpace(size_t size) {
bool MemArena::GrabMemSpace(size_t size) {
#if !PPSSPP_PLATFORM(UWP)
hMemoryMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, (DWORD)(size), NULL);
GetSystemInfo(&sysInfo);
#else
hMemoryMapping = 0;
#endif
return true;
}
void MemArena::ReleaseSpace() {
+1
View File
@@ -180,6 +180,7 @@ void CPUInfo::Detect()
Mode64bit = false;
#endif
vendor = VENDOR_OTHER;
logical_cpu_count = 1;
// Get the information about the CPU
#if !defined(__linux__)
+3 -3
View File
@@ -552,12 +552,12 @@ void Download::Do() {
}
if (resultCode == 200) {
INFO_LOG(IO, "Completed downloading %s to %s", url_.c_str(), outfile_.empty() ? "memory" : outfile_.ToVisualString().c_str());
INFO_LOG(IO, "Completed downloading %s to %s", url_.c_str(), outfile_.empty() ? "memory" : outfile_.c_str());
if (!outfile_.empty() && !buffer_.FlushToFile(outfile_)) {
ERROR_LOG(IO, "Failed writing download to %s", outfile_.ToVisualString().c_str());
ERROR_LOG(IO, "Failed writing download to '%s'", outfile_.c_str());
}
} else {
ERROR_LOG(IO, "Error downloading %s to %s: %i", url_.c_str(), outfile_.ToVisualString().c_str(), resultCode);
ERROR_LOG(IO, "Error downloading '%s' to '%s': %i", url_.c_str(), outfile_.c_str(), resultCode);
}
resultCode_ = resultCode;
}
+6 -2
View File
@@ -33,14 +33,18 @@
#include <cstdio>
#include <cstdlib>
#include <thread>
#include "Common/Net/HTTPServer.h"
#include "Common/Net/NetBuffer.h"
#include "Common/Net/Sinks.h"
#include "Common/File/FileDescriptor.h"
#include "Common/Buffer.h"
#include "Common/Log.h"
void NewThreadExecutor::Run(std::function<void()> &&func) {
void NewThreadExecutor::Run(std::function<void()> func) {
threads_.push_back(std::thread(func));
}
@@ -138,7 +142,7 @@ void Request::Close() {
}
Server::Server(NewThreadExecutor *executor)
: executor_(executor) {
: port_(0), executor_(executor) {
RegisterHandler("/", std::bind(&Server::HandleListing, this, std::placeholders::_1));
SetFallbackHandler(std::bind(&Server::Handle404, this, std::placeholders::_1));
}
+4 -2
View File
@@ -10,16 +10,18 @@
class NewThreadExecutor {
public:
~NewThreadExecutor();
void Run(std::function<void()> &&func);
void Run(std::function<void()> func);
private:
std::vector<std::thread> threads_;
};
namespace net {
class InputSink;
class OutputSink;
};
} // namespace net
namespace http {
+2 -2
View File
@@ -155,7 +155,7 @@ std::string UriDecode(const std::string & sSrc)
return sResult;
}
// Only alphanum is safe.
// Only alphanum and underscore is safe.
const char SAFE[256] =
{
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
@@ -165,7 +165,7 @@ const char SAFE[256] =
/* 3 */ 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
/* 4 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
/* 5 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
/* 5 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,1, // last here is underscore. it's ok.
/* 6 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
/* 7 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
+5
View File
@@ -359,6 +359,11 @@ void TextDrawerUWP::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStrin
size.cx = metrics.width + 1;
size.cy = metrics.height + 1;
if (size.cx > MAX_TEXT_WIDTH)
size.cx = MAX_TEXT_WIDTH;
if (size.cy > MAX_TEXT_HEIGHT)
size.cy = MAX_TEXT_HEIGHT;
// Prevent zero-sized textures, which can occur. Not worth to avoid
// creating the texture altogether in this case. One example is a string
// containing only '\r\n', see issue #10764.
+66
View File
@@ -0,0 +1,66 @@
#pragma once
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <cassert>
// Named Channel.h because I originally intended to support a multi item channel as
// well as a simple blocking mailbox. Let's see if we get there.
// Single item mailbox.
template<class T>
struct Mailbox {
Mailbox() : refcount_(1) {}
~Mailbox() {
assert(refcount_ == 0);
}
std::mutex mutex_;
std::condition_variable condvar_;
T *data_ = nullptr;
T *Wait() {
std::unique_lock<std::mutex> lock(mutex_);
while (!data_) {
condvar_.wait(lock);
}
return data_;
}
bool Poll(T **data) {
std::unique_lock<std::mutex> lock(mutex_);
if (data_) {
*data = data_;
return true;
} else {
return false;
}
}
bool Send(T *data) {
std::unique_lock<std::mutex> lock(mutex_);
if (!data_) {
data_ = data;
condvar_.notify_one();
return true;
} else {
// Already has value.
return false;
}
}
void AddRef() {
refcount_.fetch_add(1);
}
void Release() {
int count = refcount_.fetch_sub(1);
if (count == 1) { // was definitely decreased to 0
delete this;
}
}
private:
std::atomic<int> refcount_;
};
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "Common/Thread/ThreadManager.h"
#include <condition_variable>
#include <mutex>
struct Event : public Waitable {
public:
void Wait() override {
if (triggered_) {
return;
}
std::unique_lock<std::mutex> lock;
cond_.wait(lock, [&] { return !triggered_; });
}
void Notify() {
std::unique_lock<std::mutex> lock;
triggered_ = true;
cond_.notify_one();
}
private:
std::condition_variable cond_;
std::mutex mutex_;
bool triggered_ = false;
};
+134
View File
@@ -0,0 +1,134 @@
#include <algorithm>
#include <cstring>
#include "Common/Thread/ParallelLoop.h"
#include "Common/CPUDetect.h"
class LoopRangeTask : public Task {
public:
LoopRangeTask(WaitableCounter *counter, const std::function<void(int, int)> &loop, int lower, int upper)
: counter_(counter), loop_(loop), lower_(lower), upper_(upper) {}
void Run() override {
loop_(lower_, upper_);
counter_->Count();
}
std::function<void(int, int)> loop_;
WaitableCounter *counter_;
int lower_;
int upper_;
};
WaitableCounter *ParallelRangeLoopWaitable(ThreadManager *threadMan, const std::function<void(int, int)> &loop, int lower, int upper, int minSize) {
if (minSize == -1) {
minSize = 1;
}
int numTasks = threadMan->GetNumLooperThreads();
int range = upper - lower;
if (range <= 0) {
// Nothing to do. A finished counter allocated to keep the API.
return new WaitableCounter(0);
} else if (range <= minSize) {
// Single background task.
WaitableCounter *waitableCounter = new WaitableCounter(1);
threadMan->EnqueueTaskOnThread(0, new LoopRangeTask(waitableCounter, loop, lower, upper), TaskType::CPU_COMPUTE);
return waitableCounter;
} else {
// Split the range between threads. Allow for some fractional bits.
const int fractionalBits = 8;
int64_t totalFrac = (int64_t)range << fractionalBits;
int64_t delta = totalFrac / numTasks;
delta = std::max(delta, (int64_t)minSize << fractionalBits);
// Now we can compute the actual number of tasks.
// Remember that stragglers are done on the current thread
// so we don't round up.
numTasks = (int)(totalFrac / delta);
WaitableCounter *waitableCounter = new WaitableCounter(numTasks);
int64_t counter = (int64_t)lower << fractionalBits;
// Split up tasks as equitable as possible.
for (int i = 0; i < numTasks; i++) {
int start = (int)(counter >> fractionalBits);
int end = (int)((counter + delta) >> fractionalBits);
if (end > upper) {
// Let's do the stragglers on the current thread.
break;
}
threadMan->EnqueueTaskOnThread(i, new LoopRangeTask(waitableCounter, loop, start, end), TaskType::CPU_COMPUTE);
counter += delta;
if ((counter >> fractionalBits) >= upper) {
break;
}
}
// Run stragglers on the calling thread directly.
// We might add a flag later to avoid this for some cases.
int stragglerStart = (int)(counter >> fractionalBits);
int stragglerEnd = upper;
if (stragglerStart < stragglerEnd) {
loop(stragglerStart, stragglerEnd);
}
return waitableCounter;
}
}
void ParallelRangeLoop(ThreadManager *threadMan, const std::function<void(int, int)> &loop, int lower, int upper, int minSize) {
if (cpu_info.num_cores == 1 || (minSize >= (upper - lower) && upper > lower)) {
// "Optimization" for single-core devices, or minSize larger than the range.
// No point in adding threading overhead, let's just do it inline (since this is the blocking variant).
loop(lower, upper);
return;
}
if (minSize < 1) {
// There's no obvious value to default to.
minSize = 1;
}
WaitableCounter *counter = ParallelRangeLoopWaitable(threadMan, loop, lower, upper, minSize);
// TODO: Optimize using minSize. We'll just compute whether there's a remainer, remove it from the call to ParallelRangeLoopWaitable,
// and process the remainder right here. If there's no remainer, we'll steal a whole chunk.
if (counter) {
counter->WaitAndRelease();
}
}
// NOTE: Supports a max of 2GB.
void ParallelMemcpy(ThreadManager *threadMan, void *dst, const void *src, size_t bytes) {
// This threshold can probably be a lot bigger.
if (bytes < 512) {
memcpy(dst, src, bytes);
return;
}
// unknown's testing showed that 128kB is an appropriate minimum size.
char *d = (char *)dst;
const char *s = (const char *)src;
ParallelRangeLoop(threadMan, [&](int l, int h) {
memmove(d + l, s + l, h - l);
}, 0, (int)bytes, 128 * 1024);
}
// NOTE: Supports a max of 2GB.
void ParallelMemset(ThreadManager *threadMan, void *dst, uint8_t value, size_t bytes) {
// This threshold can probably be a lot bigger.
if (bytes < 512) {
memset(dst, 0, bytes);
return;
}
// unknown's testing showed that 128kB is an appropriate minimum size.
char *d = (char *)dst;
ParallelRangeLoop(threadMan, [&](int l, int h) {
memset(d + l, value, h - l);
}, 0, (int)bytes, 128 * 1024);
}
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <functional>
#include <mutex>
#include <condition_variable>
#include "Common/Thread/ThreadManager.h"
// Same as the latch from C++21.
struct WaitableCounter : public Waitable {
public:
WaitableCounter(int count) : count_(count) {}
void Count() {
std::unique_lock<std::mutex> lock(mutex_);
if (count_ == 0) {
return;
}
count_--;
if (count_ == 0) {
// We were the last one to increment
cond_.notify_all();
}
}
void Wait() override {
std::unique_lock<std::mutex> lock(mutex_);
while (count_ != 0) {
cond_.wait(lock);
}
}
int count_;
std::mutex mutex_;
std::condition_variable cond_;
};
// Note that upper bounds are non-inclusive: range is [lower, upper)
WaitableCounter *ParallelRangeLoopWaitable(ThreadManager *threadMan, const std::function<void(int, int)> &loop, int lower, int upper, int minSize);
// Note that upper bounds are non-inclusive: range is [lower, upper)
void ParallelRangeLoop(ThreadManager *threadMan, const std::function<void(int, int)> &loop, int lower, int upper, int minSize);
// Common utilities for large (!) memory copies.
// Will only fall back to threads if it seems to make sense.
// NOTE: These support a max of 2GB.
void ParallelMemcpy(ThreadManager *threadMan, void *dst, const void *src, size_t bytes);
void ParallelMemset(ThreadManager *threadMan, void *dst, uint8_t value, size_t bytes);
-140
View File
@@ -1,140 +0,0 @@
#include <functional>
#include <thread>
#include "Common/TimeUtil.h"
#include "Common/Thread/PrioritizedWorkQueue.h"
#include "Common/Log.h"
PrioritizedWorkQueue::~PrioritizedWorkQueue() {
if (!done_) {
ERROR_LOG(SYSTEM, "PrioritizedWorkQueue destroyed but not done!");
}
}
void PrioritizedWorkQueue::Add(PrioritizedWorkQueueItem *item) {
std::lock_guard<std::mutex> guard(mutex_);
queue_.push_back(item);
notEmpty_.notify_one();
}
void PrioritizedWorkQueue::Stop() {
std::lock_guard<std::mutex> guard(mutex_);
done_ = true;
notEmpty_.notify_one();
}
void PrioritizedWorkQueue::Flush() {
std::lock_guard<std::mutex> guard(mutex_);
int flush_count = 0;
for (auto iter = queue_.begin(); iter != queue_.end(); ++iter) {
delete *iter;
flush_count++;
}
queue_.clear();
if (flush_count > 0) {
INFO_LOG(SYSTEM, "PrioritizedWorkQueue: Flushed %d un-executed tasks", flush_count);
}
}
bool PrioritizedWorkQueue::WaitUntilDone(bool all) {
// We'll lock drain this entire time, so make sure you follow that lock ordering.
std::unique_lock<std::mutex> guard(drainMutex_);
if (AllItemsDone()) {
return true;
}
while (!AllItemsDone()) {
drain_.wait(guard);
if (!all) {
// Return whether empty or not, something just drained.
return AllItemsDone();
}
}
return true;
}
void PrioritizedWorkQueue::NotifyDrain() {
std::lock_guard<std::mutex> guard(drainMutex_);
drain_.notify_one();
}
bool PrioritizedWorkQueue::AllItemsDone() {
std::lock_guard<std::mutex> guard(mutex_);
return queue_.empty() && !working_;
}
// The worker should simply call this in a loop. Will block when appropriate.
PrioritizedWorkQueueItem *PrioritizedWorkQueue::Pop() {
{
std::lock_guard<std::mutex> guard(mutex_);
working_ = false; // The thread only calls Pop if it's done.
}
// Important: make sure mutex_ is not locked while draining.
NotifyDrain();
std::unique_lock<std::mutex> guard(mutex_);
if (done_) {
return 0;
}
while (queue_.empty()) {
notEmpty_.wait(guard);
if (done_) {
return 0;
}
}
// Find the top priority item (lowest value).
float best_prio = std::numeric_limits<float>::infinity();
std::vector<PrioritizedWorkQueueItem *>::iterator best = queue_.end();
for (auto iter = queue_.begin(); iter != queue_.end(); ++iter) {
if ((*iter)->priority() < best_prio) {
best = iter;
best_prio = (*iter)->priority();
}
}
if (best != queue_.end()) {
PrioritizedWorkQueueItem *poppedItem = *best;
queue_.erase(best);
working_ = true; // This will be worked on.
return poppedItem;
} else {
// Not really sure how this can happen, but let's be safe.
return 0;
}
}
// TODO: This feels ugly. Revisit later.
static std::thread *workThread;
static void threadfunc(PrioritizedWorkQueue *wq) {
SetCurrentThreadName("PrioQueue");
while (true) {
PrioritizedWorkQueueItem *item = wq->Pop();
if (!item) {
if (wq->Done())
break;
} else {
item->run();
delete item;
}
}
}
void ProcessWorkQueueOnThreadWhile(PrioritizedWorkQueue *wq) {
workThread = new std::thread([=](){threadfunc(wq);});
}
void StopProcessingWorkQueue(PrioritizedWorkQueue *wq) {
wq->Stop();
if (workThread) {
workThread->join();
delete workThread;
}
workThread = nullptr;
}
-65
View File
@@ -1,65 +0,0 @@
#pragma once
#include <vector>
#include <limits>
#include <mutex>
#include <condition_variable>
#include "Common/Thread/ThreadUtil.h"
#include "Common/Common.h"
// Priorities can change dynamically.
// Try to make priority() fast, it will be called a lot.
class PrioritizedWorkQueueItem {
public:
PrioritizedWorkQueueItem() {}
virtual ~PrioritizedWorkQueueItem() {}
virtual void run() = 0;
virtual float priority() = 0; // Low priority value = high priority!
private:
DISALLOW_COPY_AND_ASSIGN(PrioritizedWorkQueueItem);
};
class PrioritizedWorkQueue {
public:
PrioritizedWorkQueue() : done_(false), working_(false) {}
~PrioritizedWorkQueue();
// Takes ownership.
void Add(PrioritizedWorkQueueItem *item);
// The worker should simply call this in a loop. Will block when appropriate.
PrioritizedWorkQueueItem *Pop();
void Flush();
bool Done() { return done_; }
void Stop();
bool WaitUntilDone(bool all = true);
bool IsWorking() {
return working_;
}
private:
void NotifyDrain();
bool AllItemsDone();
bool done_;
bool working_;
std::mutex mutex_;
std::mutex drainMutex_;
std::condition_variable notEmpty_;
std::condition_variable drain_;
std::vector<PrioritizedWorkQueueItem *> queue_;
DISALLOW_COPY_AND_ASSIGN(PrioritizedWorkQueue);
};
// Starts up a thread that keeps trying to run this workqueue.
// TODO: This feels ugly. Revisit later.
void ProcessWorkQueueOnThreadWhile(PrioritizedWorkQueue *wq);
void StopProcessingWorkQueue(PrioritizedWorkQueue *wq);
+87
View File
@@ -0,0 +1,87 @@
#pragma once
#include <functional>
#include "Common/Thread/Channel.h"
#include "Common/Thread/ThreadManager.h"
#include "Common/Log.h"
template<class T>
class PromiseTask : public Task {
public:
PromiseTask(std::function<T *()> fun, Mailbox<T> *tx) : fun_(fun), tx_(tx) {
tx_->AddRef();
}
~PromiseTask() {
tx_->Release();
}
void Run() override {
T *value = fun_();
tx_->Send(value);
}
std::function<T *()> fun_;
Mailbox<T> *tx_;
};
// Represents pending or actual data.
// Has ownership over the data. Single use.
// TODO: Split Mailbox (rx_ and tx_) up into separate proxy objects.
// NOTE: Poll/BlockUntilReady should only be used from one thread.
template<class T>
class Promise {
public:
static Promise<T> *Spawn(ThreadManager *threadman, std::function<T *()> fun, TaskType taskType) {
Mailbox<T> *mailbox = new Mailbox<T>();
Promise<T> *promise = new Promise<T>();
promise->rx_ = mailbox;
PromiseTask<T> *task = new PromiseTask<T>(fun, mailbox);
threadman->EnqueueTask(task, taskType);
return promise;
}
~Promise() {
// A promise should have been fulfilled before it's destroyed.
_assert_(ready_);
_assert_(!rx_);
delete data_;
}
// Returns *T if the data is ready, nullptr if it's not.
T *Poll() {
if (ready_) {
return data_;
} else {
if (rx_->Poll(&data_)) {
rx_->Release();
rx_ = nullptr;
ready_ = true;
return data_;
} else {
return nullptr;
}
}
}
T *BlockUntilReady() {
if (ready_) {
return data_;
} else {
data_ = rx_->Wait();
rx_->Release();
rx_ = nullptr;
ready_ = true;
return data_;
}
}
private:
Promise() {}
T *data_ = nullptr;
bool ready_ = false;
Mailbox<T> *rx_;
};
+176
View File
@@ -0,0 +1,176 @@
#include <cstdio>
#include <algorithm>
#include <thread>
#include <deque>
#include <condition_variable>
#include <mutex>
#include <vector>
#include <atomic>
#include "Common/Log.h"
#include "Common/Thread/ThreadUtil.h"
#include "Common/Thread/ThreadManager.h"
// Threads and task scheduling
//
// * The threadpool should contain a number of threads that's the the number of cores,
// plus a fixed number more for I/O-limited background tasks.
// * Parallel compute-limited loops should use as many threads as there are cores.
// They should always be scheduled to the first N threads.
// * For some tasks, splitting the input values up linearly between the threads
// is not fair. However, we ignore that for now.
const int MAX_CORES_TO_USE = 16;
const int EXTRA_THREADS = 4; // For I/O limited tasks
struct GlobalThreadContext {
std::mutex mutex; // associated with each respective condition variable
std::deque<Task *> queue;
std::vector<ThreadContext *> threads_;
int roundRobin = 0;
};
struct ThreadContext {
std::thread thread; // the worker thread
std::condition_variable cond; // used to signal new work
std::mutex mutex; // protects the local queue.
std::atomic<int> queueSize;
int index;
std::atomic<bool> cancelled;
std::deque<Task *> private_queue;
};
ThreadManager::ThreadManager() : global_(new GlobalThreadContext()) {
}
ThreadManager::~ThreadManager() {
delete global_;
}
void ThreadManager::Teardown() {
for (size_t i = 0; i < global_->threads_.size(); i++) {
global_->threads_[i]->cancelled = true;
global_->threads_[i]->cond.notify_one();
}
for (size_t i = 0; i < global_->threads_.size(); i++) {
global_->threads_[i]->thread.join();
delete global_->threads_[i];
}
global_->threads_.clear();
}
static void WorkerThreadFunc(GlobalThreadContext *global, ThreadContext *thread) {
char threadName[16];
snprintf(threadName, sizeof(threadName), "PoolWorker %d", thread->index);
SetCurrentThreadName(threadName);
while (!thread->cancelled) {
Task *task = nullptr;
// Check the global queue first, then check the private queue and wait if there's nothing to do.
{
// Grab one from the global queue if there is any.
std::unique_lock<std::mutex> lock(global->mutex);
if (!global->queue.empty()) {
task = global->queue.front();
global->queue.pop_front();
}
}
if (!task) {
std::unique_lock<std::mutex> lock(thread->mutex);
if (!thread->private_queue.empty()) {
task = thread->private_queue.front();
thread->private_queue.pop_front();
thread->queueSize.store((int)thread->private_queue.size());
} else {
thread->cond.wait(lock);
}
}
// The task itself takes care of notifying anyone waiting on it. Not the
// responsibility of the ThreadManager (although it could be!).
if (task) {
task->Run();
delete task;
}
}
}
void ThreadManager::Init(int numRealCores, int numLogicalCoresPerCpu) {
if (!global_->threads_.empty()) {
Teardown();
}
numComputeThreads_ = std::min(numRealCores * numLogicalCoresPerCpu, MAX_CORES_TO_USE);
int numThreads = numComputeThreads_ + EXTRA_THREADS;
numThreads_ = numThreads;
INFO_LOG(SYSTEM, "ThreadManager::Init(compute threads: %d, all: %d)", numComputeThreads_, numThreads_);
for (int i = 0; i < numThreads; i++) {
ThreadContext *thread = new ThreadContext();
thread->cancelled.store(false);
thread->thread = std::thread(&WorkerThreadFunc, global_, thread);
thread->index = i;
global_->threads_.push_back(thread);
}
}
void ThreadManager::EnqueueTask(Task *task, TaskType taskType) {
int maxThread;
int threadOffset = 0;
if (taskType == TaskType::CPU_COMPUTE) {
// only the threads reserved for heavy compute.
maxThread = numComputeThreads_;
threadOffset = 0;
} else {
// any free thread
maxThread = numThreads_;
threadOffset = numComputeThreads_;
}
// Find a thread with no outstanding work.
int threadNum = threadOffset;
for (int i = 0; i < maxThread; i++, threadNum++) {
if (threadNum >= global_->threads_.size()) {
threadNum = 0;
}
ThreadContext *thread = global_->threads_[threadNum];
if (thread->queueSize.load() == 0) {
std::unique_lock<std::mutex> lock(thread->mutex);
thread->private_queue.push_back(task);
thread->queueSize.store((int)thread->private_queue.size());
thread->cond.notify_one();
// Found it - done.
return;
}
}
// Still not scheduled? Put it on the global queue and notify a thread chosen by round-robin.
// Not particularly scientific, but hopefully we should not run into this too much.
{
std::unique_lock<std::mutex> lock(global_->mutex);
global_->queue.push_back(task);
global_->threads_[global_->roundRobin % maxThread]->cond.notify_one();
global_->roundRobin++;
}
}
void ThreadManager::EnqueueTaskOnThread(int threadNum, Task *task, TaskType taskType) {
_assert_(threadNum >= 0 && threadNum < (int)global_->threads_.size());
ThreadContext *thread = global_->threads_[threadNum];
{
std::unique_lock<std::mutex> lock(thread->mutex);
thread->private_queue.push_back(task);
thread->cond.notify_one();
}
}
int ThreadManager::GetNumLooperThreads() const {
return numComputeThreads_;
}
void ThreadManager::TryCancelTask(uint64_t taskID) {
// Do nothing
}
+69
View File
@@ -0,0 +1,69 @@
#pragma once
#include <cstdint>
// The new threadpool.
// To help future smart scheduling.
enum class TaskType {
CPU_COMPUTE,
IO_BLOCKING,
};
// Implement this to make something that you can run on the thread manager.
class Task {
public:
virtual ~Task() {}
virtual void Run() = 0;
virtual bool Cancellable() { return false; }
virtual void Cancel() {}
virtual uint64_t id() { return 0; }
};
class Waitable {
public:
virtual ~Waitable() {}
virtual void Wait() = 0;
void WaitAndRelease() {
Wait();
delete this;
}
};
struct ThreadContext;
struct GlobalThreadContext;
class ThreadManager {
public:
ThreadManager();
~ThreadManager();
// The distinction here is to be able to take hyper-threading into account.
// It gets even trickier when you think about mobile chips with BIG/LITTLE, but we'll
// just ignore it and let the OS handle it.
void Init(int numCores, int numLogicalCoresPerCpu);
void EnqueueTask(Task *task, TaskType taskType);
void EnqueueTaskOnThread(int threadNum, Task *task, TaskType taskType);
void Teardown();
// Currently does nothing. It will always be best-effort - maybe it cancels,
// maybe it doesn't. Note that the id is the id() returned by the task. You need to make that
// something meaningful yourself.
void TryCancelTask(uint64_t id);
// Parallel loops (assumed compute-limited) get one thread per logical core. We have a few extra threads too
// for I/O bounds tasks, that can be run concurrently with those.
int GetNumLooperThreads() const;
private:
GlobalThreadContext *global_ = nullptr;
int numThreads_ = 0;
int numComputeThreads_ = 0;
friend struct ThreadContext;
};
extern ThreadManager g_threadManager;
-142
View File
@@ -1,142 +0,0 @@
#include <algorithm>
#include <cstring>
#include "Common/Thread/ThreadPool.h"
#include "Common/Thread/ThreadUtil.h"
#include "Common/Log.h"
#include "Common/MakeUnique.h"
///////////////////////////// WorkerThread
WorkerThread::~WorkerThread() {
{
std::lock_guard<std::mutex> guard(mutex);
active = false;
signal.notify_one();
}
if (thread.joinable()) {
thread.join();
}
}
void WorkerThread::StartUp() {
thread = std::thread(std::bind(&WorkerThread::WorkFunc, this));
}
void WorkerThread::Process(std::function<void()> work) {
std::lock_guard<std::mutex> guard(mutex);
work_ = std::move(work);
jobsTarget = jobsDone + 1;
signal.notify_one();
}
void WorkerThread::WaitForCompletion() {
std::unique_lock<std::mutex> guard(doneMutex);
while (jobsDone < jobsTarget) {
done.wait(guard);
}
}
void WorkerThread::WorkFunc() {
SetCurrentThreadName("Worker");
std::unique_lock<std::mutex> guard(mutex);
while (active) {
// 'active == false' is one of the conditions for signaling,
// do not "optimize" it
while (active && jobsTarget <= jobsDone) {
signal.wait(guard);
}
if (active) {
work_();
std::lock_guard<std::mutex> doneGuard(doneMutex);
jobsDone++;
done.notify_one();
}
}
}
void LoopWorkerThread::ProcessLoop(std::function<void(int, int)> work, int start, int end) {
std::lock_guard<std::mutex> guard(mutex);
loopWork_ = std::move(work);
work_ = [this]() {
loopWork_(start_, end_);
};
start_ = start;
end_ = end;
jobsTarget = jobsDone + 1;
signal.notify_one();
}
///////////////////////////// ThreadPool
ThreadPool::ThreadPool(int numThreads) {
if (numThreads <= 0) {
numThreads_ = 1;
INFO_LOG(JIT, "ThreadPool: Bad number of threads %d", numThreads);
} else if (numThreads > 16) {
INFO_LOG(JIT, "ThreadPool: Capping number of threads to 16 (was %d)", numThreads);
numThreads_ = 16;
} else {
numThreads_ = numThreads;
}
}
void ThreadPool::StartWorkers() {
if (!workersStarted) {
workers.reserve(numThreads_ - 1);
for(int i = 0; i < numThreads_ - 1; ++i) { // create one less worker thread as the thread calling ParallelLoop will also do work
auto workerPtr = make_unique<LoopWorkerThread>();
workerPtr->StartUp();
workers.push_back(std::move(workerPtr));
}
workersStarted = true;
}
}
void ThreadPool::ParallelLoop(const std::function<void(int,int)> &loop, int lower, int upper, int minSize) {
// Don't parallelize tiny loops.
if (minSize == -1)
minSize = 4;
int range = upper - lower;
if (range >= minSize) {
std::lock_guard<std::mutex> guard(mutex);
StartWorkers();
// could do slightly better load balancing for the generic case,
// but doesn't matter since all our loops are power of 2
int chunk = std::max(minSize, range / numThreads_);
int s = lower;
for (auto &worker : workers) {
// We'll do the last chunk on the current thread.
if (s + chunk >= upper) {
break;
}
worker->ProcessLoop(loop, s, s + chunk);
s += chunk;
}
// This is the final chunk.
if (s < upper)
loop(s, upper);
for (auto &worker : workers) {
worker->WaitForCompletion();
}
} else {
loop(lower, upper);
}
}
void ThreadPool::ParallelMemcpy(void *dest, const void *src, int size) {
static const int MIN_SIZE = 128 * 1024;
ParallelLoop([&](int l, int h) {
memmove((uint8_t *)dest + l, (const uint8_t *)src + l, h - l);
}, 0, size, MIN_SIZE);
}
void ThreadPool::ParallelMemset(void *dest, uint8_t val, int size) {
static const int MIN_SIZE = 128 * 1024;
ParallelLoop([&](int l, int h) {
memset((uint8_t *)dest + l, val, h - l);
}, 0, size, MIN_SIZE);
}
-78
View File
@@ -1,78 +0,0 @@
#pragma once
#include <functional>
#include <memory>
#include <vector>
#include <thread>
#include <mutex>
#include <condition_variable>
// This is the simplest possible worker implementation I can think of
// but entirely sufficient for the given purpose.
// Only handles a single item of work at a time.
class WorkerThread {
public:
WorkerThread() = default;
virtual ~WorkerThread();
void StartUp();
// submit a new work item
void Process(std::function<void()> work);
// wait for a submitted work item to be completed
void WaitForCompletion();
protected:
virtual void WorkFunc();
std::thread thread; // the worker thread
std::condition_variable signal; // used to signal new work
std::condition_variable done; // used to signal work completion
std::mutex mutex, doneMutex; // associated with each respective condition variable
bool active = true;
int jobsDone = 0;
int jobsTarget = 0;
std::function<void()> work_; // the work to be done by this thread
private:
WorkerThread(const WorkerThread& other) = delete; // prevent copies
void operator =(const WorkerThread &other) = delete;
};
class LoopWorkerThread final : public WorkerThread {
public:
LoopWorkerThread() = default;
void ProcessLoop(std::function<void(int, int)> work, int start, int end);
private:
int start_;
int end_;
std::function<void(int, int)> loopWork_; // the work to be done by this thread
};
// A thread pool manages a set of worker threads, and allows the execution of parallel loops on them
// individual parallel loops are fully sequentialized to simplify synchronization, which should not
// be a problem as they should each use the entire system
class ThreadPool {
public:
ThreadPool(int numThreads);
// don't need a destructor, "workers" is cleared on delete,
// leading to the stopping and joining of all worker threads (RAII and all that)
void ParallelLoop(const std::function<void(int,int)> &loop, int lower, int upper, int minSize);
void ParallelMemcpy(void *dest, const void *src, int sz);
void ParallelMemset(void *dest, uint8_t val, int sz);
private:
int numThreads_;
std::vector<std::unique_ptr<LoopWorkerThread>> workers;
std::mutex mutex; // used to sequentialize loop execution
bool workersStarted = false;
void StartWorkers();
ThreadPool(const ThreadPool& other) = delete; // prevent copies
void operator =(const ThreadPool &other) = delete;
};
+43 -8
View File
@@ -1,11 +1,19 @@
#ifdef _WIN32
#include "ppsspp_config.h"
#if PPSSPP_PLATFORM(WINDOWS)
#include <windows.h>
#ifdef __MINGW32__
#include <excpt.h>
#endif
#define TLS_SUPPORTED
#elif defined(__ANDROID__)
#define TLS_SUPPORTED
#endif
#include <cstring>
@@ -14,8 +22,15 @@
#include "Common/Log.h"
#include "Common/Thread/ThreadUtil.h"
#if defined(__ANDROID__) || defined(__APPLE__) || (defined(__GLIBC__) && defined(_GNU_SOURCE))
#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(LINUX)
#define _GNU_SOURCE
#endif
#if !PPSSPP_PLATFORM(WINDOWS)
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/syscall.h>
#endif
#ifdef TLS_SUPPORTED
@@ -41,12 +56,14 @@ static EXCEPTION_DISPOSITION NTAPI ignore_handler(EXCEPTION_RECORD *rec,
#endif
void SetCurrentThreadName(const char* threadName) {
#ifdef _WIN32
#if PPSSPP_PLATFORM(WINDOWS)
// Set the debugger-visible threadname through an unholy magic hack
static const DWORD MS_VC_EXCEPTION = 0x406D1388;
#endif
#if defined(_WIN32) && defined(__MINGW32__)
// TODO: Use the new function SetThreadDescription available since Windows 10, version 1607.
#if PPSSPP_PLATFORM(WINDOWS) && defined(__MINGW32__)
// Thread information for VS compatible debugger. -1 sets current thread.
THREADNAME_INFO ti;
ti.dwType = 0x1000;
@@ -67,7 +84,7 @@ void SetCurrentThreadName(const char* threadName) {
// Pop exception handler
tib->ExceptionList = tib->ExceptionList->Next;
#elif defined(_WIN32)
#elif PPSSPP_PLATFORM(WINDOWS)
#pragma pack(push,8)
struct THREADNAME_INFO {
DWORD dwType; // must be 0x1000
@@ -98,12 +115,10 @@ void SetCurrentThreadName(const char* threadName) {
{}
#else
#if defined(__ANDROID__) || (defined(__GLIBC__) && defined(_GNU_SOURCE))
#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(LINUX)
pthread_setname_np(pthread_self(), threadName);
#elif defined(__APPLE__)
pthread_setname_np(threadName);
// #else
// pthread_setname_np(threadName);
#endif
// Do nothing
@@ -121,3 +136,23 @@ void AssertCurrentThreadName(const char *threadName) {
}
#endif
}
int GetCurrentThreadIdForDebug() {
#if __LIBRETRO__
// Not sure why gettid() would not be available, but it isn't.
// The return value of this function is only used in unit tests anyway...
return 1;
#elif PPSSPP_PLATFORM(WINDOWS)
return (int)GetCurrentThreadId();
#elif PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS) || defined(__OpenBSD__) || defined(__FreeBSD__)
uint64_t tid = 0;
pthread_threadid_np(NULL, &tid);
return (int)tid;
#elif PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(LINUX)
// See issue 14545
return (int)syscall(__NR_gettid);
// return (int)gettid();
#else
return 1;
#endif
}
+6
View File
@@ -1,6 +1,12 @@
#pragma once
#include <mutex>
// Note that name must be a global string that lives until the end of the process,
// for AssertCurrentThreadName to work.
void SetCurrentThreadName(const char *threadName);
void AssertCurrentThreadName(const char *threadName);
// Just gets a cheap thread identifier so that you can see different threads in debug output,
// exactly what it is is badly specified and not useful for anything.
int GetCurrentThreadIdForDebug();
+2 -1
View File
@@ -112,8 +112,9 @@ public:
: LayoutParams(w, h, LP_ANCHOR), left(l), top(t), right(r), bottom(b), center(c) {
}
// There's a small hack here to make this behave more intuitively - AnchorLayout ordinarily ignores FILL_PARENT.
AnchorLayoutParams(Size w, Size h, bool c = false)
: LayoutParams(w, h, LP_ANCHOR), left(0), top(0), right(NONE), bottom(NONE), center(c) {
: LayoutParams(w, h, LP_ANCHOR), left(0), top(0), right(w == FILL_PARENT ? 0 : NONE), bottom(h == FILL_PARENT ? 0 : NONE), center(c) {
}
AnchorLayoutParams(float l, float t, float r, float b, bool c = false)
: LayoutParams(WRAP_CONTENT, WRAP_CONTENT, LP_ANCHOR), left(l), top(t), right(r), bottom(b), center(c) {}
+88 -45
View File
@@ -68,46 +68,56 @@ struct ConfigSetting {
TYPE_BOOL,
TYPE_INT,
TYPE_UINT32,
TYPE_UINT64,
TYPE_FLOAT,
TYPE_STRING,
TYPE_TOUCH_POS,
TYPE_PATH,
TYPE_CUSTOM_BUTTON
};
union DefaultValue {
bool b;
int i;
uint32_t u;
uint64_t lu;
float f;
const char *s;
const char *p; // not sure how much point..
ConfigTouchPos touchPos;
ConfigCustomButton customButton;
};
union SettingPtr {
bool *b;
int *i;
uint32_t *u;
uint64_t *lu;
float *f;
std::string *s;
Path *p;
ConfigTouchPos *touchPos;
ConfigCustomButton *customButton;
};
typedef bool (*BoolDefaultCallback)();
typedef int (*IntDefaultCallback)();
typedef uint32_t (*Uint32DefaultCallback)();
typedef uint64_t (*Uint64DefaultCallback)();
typedef float (*FloatDefaultCallback)();
typedef const char *(*StringDefaultCallback)();
typedef ConfigTouchPos(*TouchPosDefaultCallback)();
typedef const char *(*PathDefaultCallback)();
typedef ConfigCustomButton (*CustomButtonDefaultCallback)();
union Callback {
BoolDefaultCallback b;
IntDefaultCallback i;
Uint32DefaultCallback u;
Uint64DefaultCallback lu;
FloatDefaultCallback f;
StringDefaultCallback s;
PathDefaultCallback p;
TouchPosDefaultCallback touchPos;
CustomButtonDefaultCallback customButton;
};
ConfigSetting(bool v)
@@ -144,6 +154,13 @@ struct ConfigSetting {
default_.u = def;
}
ConfigSetting(const char *ini, uint64_t *v, uint64_t def, bool save = true, bool perGame = false)
: iniKey_(ini), type_(TYPE_UINT64), report_(false), save_(save), perGame_(perGame) {
ptr_.lu = v;
cb_.lu = nullptr;
default_.lu = def;
}
ConfigSetting(const char *ini, float *v, float def, bool save = true, bool perGame = false)
: iniKey_(ini), type_(TYPE_FLOAT), report_(false), save_(save), perGame_(perGame) {
ptr_.f = v;
@@ -172,6 +189,13 @@ struct ConfigSetting {
default_.touchPos = def;
}
ConfigSetting(const char *iniKey, const char *iniImage, const char *iniShape, const char *iniToggle, ConfigCustomButton *v, ConfigCustomButton def, bool save = true, bool perGame = false)
: iniKey_(iniKey), ini2_(iniImage), ini3_(iniShape), ini4_(iniToggle), type_(TYPE_CUSTOM_BUTTON), report_(false), save_(save), perGame_(perGame) {
ptr_.customButton = v;
cb_.customButton = nullptr;
default_.customButton = def;
}
ConfigSetting(const char *ini, bool *v, BoolDefaultCallback def, bool save = true, bool perGame = false)
: iniKey_(ini), type_(TYPE_BOOL), report_(false), save_(save), perGame_(perGame) {
ptr_.b = v;
@@ -242,6 +266,11 @@ struct ConfigSetting {
default_.u = cb_.u();
}
return section->Get(iniKey_, ptr_.u, default_.u);
case TYPE_UINT64:
if (cb_.lu) {
default_.lu = cb_.lu();
}
return section->Get(iniKey_, ptr_.lu, default_.lu);
case TYPE_FLOAT:
if (cb_.f) {
default_.f = cb_.f();
@@ -277,6 +306,15 @@ struct ConfigSetting {
}
return result;
}
case TYPE_CUSTOM_BUTTON:
if (cb_.customButton) {
default_.customButton = cb_.customButton();
}
section->Get(iniKey_, &ptr_.customButton->key, default_.customButton.key);
section->Get(ini2_, &ptr_.customButton->image, default_.customButton.image);
section->Get(ini3_, &ptr_.customButton->shape, default_.customButton.shape);
section->Get(ini4_, &ptr_.customButton->toggle, default_.customButton.toggle);
return true;
default:
_dbg_assert_msg_(false, "Unexpected ini setting type");
return false;
@@ -298,6 +336,8 @@ struct ConfigSetting {
return section->Set(iniKey_, *ptr_.i);
case TYPE_UINT32:
return section->Set(iniKey_, *ptr_.u);
case TYPE_UINT64:
return section->Set(iniKey_, *ptr_.lu);
case TYPE_FLOAT:
return section->Set(iniKey_, *ptr_.f);
case TYPE_STRING:
@@ -312,6 +352,12 @@ struct ConfigSetting {
section->Set(ini4_, ptr_.touchPos->show);
}
return;
case TYPE_CUSTOM_BUTTON:
section->Set(iniKey_, ptr_.customButton->key);
section->Set(ini2_, ptr_.customButton->image);
section->Set(ini3_, ptr_.customButton->shape);
section->Set(ini4_, ptr_.customButton->toggle);
return;
default:
_dbg_assert_msg_(false, "Unexpected ini setting type");
return;
@@ -329,6 +375,8 @@ struct ConfigSetting {
return data.Add(prefix + iniKey_, *ptr_.i);
case TYPE_UINT32:
return data.Add(prefix + iniKey_, *ptr_.u);
case TYPE_UINT64:
return data.Add(prefix + iniKey_, *ptr_.lu);
case TYPE_FLOAT:
return data.Add(prefix + iniKey_, *ptr_.f);
case TYPE_STRING:
@@ -338,6 +386,9 @@ struct ConfigSetting {
case TYPE_TOUCH_POS:
// Doesn't report.
return;
case TYPE_CUSTOM_BUTTON:
// Doesn't report.
return;
default:
_dbg_assert_msg_(false, "Unexpected ini setting type");
return;
@@ -423,12 +474,6 @@ std::string CreateRandMAC() {
return randStream.str();
}
static int DefaultNumWorkers() {
// Let's cap the global thread pool at 16 threads. Nothing we do really should have much
// use for more...
return std::min(16, cpu_info.num_cores);
}
static int DefaultCpuCore() {
#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
return (int)CPUCore::JIT;
@@ -477,7 +522,6 @@ static ConfigSetting generalSettings[] = {
ConfigSetting("DiscordPresence", &g_Config.bDiscordPresence, false, true, false), // Or maybe it makes sense to have it per-game? Race conditions abound...
ConfigSetting("UISound", &g_Config.bUISound, false, true, false),
ReportedConfigSetting("NumWorkerThreads", &g_Config.iNumWorkerThreads, &DefaultNumWorkers, true, true),
ConfigSetting("AutoLoadSaveState", &g_Config.iAutoLoadSaveState, 0, true, true),
ReportedConfigSetting("EnableCheats", &g_Config.bEnableCheats, true, true, true),
ConfigSetting("CwCheatRefreshRate", &g_Config.iCwCheatRefreshRate, 77, true, true),
@@ -503,7 +547,6 @@ static ConfigSetting generalSettings[] = {
ConfigSetting("GridView1", &g_Config.bGridView1, true),
ConfigSetting("GridView2", &g_Config.bGridView2, true),
ConfigSetting("GridView3", &g_Config.bGridView3, false),
ConfigSetting("ComboMode", &g_Config.iComboMode, 0),
ConfigSetting("RightAnalogUp", &g_Config.iRightAnalogUp, 0, true, true),
ConfigSetting("RightAnalogDown", &g_Config.iRightAnalogDown, 0, true, true),
ConfigSetting("RightAnalogLeft", &g_Config.iRightAnalogLeft, 0, true, true),
@@ -905,17 +948,16 @@ static ConfigSetting controlSettings[] = {
ConfigSetting("ShowTouchSquare", &g_Config.bShowTouchSquare, true, true, true),
ConfigSetting("ShowTouchTriangle", &g_Config.bShowTouchTriangle, true, true, true),
ConfigSetting("ComboKey0Mapping", &g_Config.iCombokey0, 0, true, true),
ConfigSetting("ComboKey1Mapping", &g_Config.iCombokey1, 0, true, true),
ConfigSetting("ComboKey2Mapping", &g_Config.iCombokey2, 0, true, true),
ConfigSetting("ComboKey3Mapping", &g_Config.iCombokey3, 0, true, true),
ConfigSetting("ComboKey4Mapping", &g_Config.iCombokey4, 0, true, true),
ConfigSetting("ComboKey0Toggle", &g_Config.bComboToggle0, false, true, true),
ConfigSetting("ComboKey1Toggle", &g_Config.bComboToggle1, false, true, true),
ConfigSetting("ComboKey2Toggle", &g_Config.bComboToggle2, false, true, true),
ConfigSetting("ComboKey3Toggle", &g_Config.bComboToggle3, false, true, true),
ConfigSetting("ComboKey4Toggle", &g_Config.bComboToggle4, false, true, true),
ConfigSetting("Custom0Mapping", "Custom0Image", "Custom0Shape", "Custom0Toggle", &g_Config.CustomKey0, {0, 0, 0, false}, true, true),
ConfigSetting("Custom1Mapping", "Custom1Image", "Custom1Shape", "Custom1Toggle", &g_Config.CustomKey1, {0, 1, 0, false}, true, true),
ConfigSetting("Custom2Mapping", "Custom2Image", "Custom2Shape", "Custom2Toggle", &g_Config.CustomKey2, {0, 2, 0, false}, true, true),
ConfigSetting("Custom3Mapping", "Custom3Image", "Custom3Shape", "Custom3Toggle", &g_Config.CustomKey3, {0, 3, 0, false}, true, true),
ConfigSetting("Custom4Mapping", "Custom4Image", "Custom4Shape", "Custom4Toggle", &g_Config.CustomKey4, {0, 4, 0, false}, true, true),
ConfigSetting("Custom5Mapping", "Custom5Image", "Custom5Shape", "Custom5Toggle", &g_Config.CustomKey5, {0, 0, 1, false}, true, true),
ConfigSetting("Custom6Mapping", "Custom6Image", "Custom6Shape", "Custom6Toggle", &g_Config.CustomKey6, {0, 1, 1, false}, true, true),
ConfigSetting("Custom7Mapping", "Custom7Image", "Custom7Shape", "Custom7Toggle", &g_Config.CustomKey7, {0, 2, 1, false}, true, true),
ConfigSetting("Custom8Mapping", "Custom8Image", "Custom8Shape", "Custom8Toggle", &g_Config.CustomKey8, {0, 3, 1, false}, true, true),
ConfigSetting("Custom9Mapping", "Custom9Image", "Custom9Shape", "Custom9Toggle", &g_Config.CustomKey9, {0, 4, 1, false}, true, true),
#if defined(_WIN32)
// A win32 user seeing touch controls is likely using PPSSPP on a tablet. There it makes
@@ -948,7 +990,7 @@ static ConfigSetting controlSettings[] = {
ConfigSetting("TouchButtonOpacity", &g_Config.iTouchButtonOpacity, 65, true, true),
ConfigSetting("TouchButtonHideSeconds", &g_Config.iTouchButtonHideSeconds, 20, true, true),
ConfigSetting("AutoCenterTouchAnalog", &g_Config.bAutoCenterTouchAnalog, false, true, true),
ConfigSetting("AnalogAutoRotSpeed", &g_Config.fAnalogAutoRotSpeed, 15.0f, true, true),
ConfigSetting("AnalogAutoRotSpeed", &g_Config.fAnalogAutoRotSpeed, 8.0f, true, true),
// Snap touch control position
ConfigSetting("TouchSnapToGrid", &g_Config.bTouchSnapToGrid, false, true, true),
@@ -974,25 +1016,18 @@ static ConfigSetting controlSettings[] = {
ConfigSetting("fcombo2X", "fcombo2Y", "comboKeyScale2", "ShowComboKey2", &g_Config.touchCombo2, defaultTouchPosHide, true, true),
ConfigSetting("fcombo3X", "fcombo3Y", "comboKeyScale3", "ShowComboKey3", &g_Config.touchCombo3, defaultTouchPosHide, true, true),
ConfigSetting("fcombo4X", "fcombo4Y", "comboKeyScale4", "ShowComboKey4", &g_Config.touchCombo4, defaultTouchPosHide, true, true),
ConfigSetting("Speed1KeyX", "Speed1KeyY", "Speed1KeyScale", "ShowSpeed1Key", &g_Config.touchSpeed1Key, defaultTouchPosHide, true, true),
ConfigSetting("Speed2KeyX", "Speed2KeyY", "Speed2KeyScale", "ShowSpeed2Key", &g_Config.touchSpeed2Key, defaultTouchPosHide, true, true),
ConfigSetting("RapidFireKeyX", "RapidFireKeyY", "RapidFireKeyScale", "ShowRapidFireKey", &g_Config.touchRapidFireKey, defaultTouchPosHide, true, true),
ConfigSetting("AnalogRotationCWKeyX", "AnalogRotationKeyCWY", "AnalogRotationKeyCWScale", "ShowAnalogRotationCWKey", &g_Config.touchAnalogRotationCWKey, defaultTouchPosHide, true, true),
ConfigSetting("AnalogRotationCCWKeyX", "AnalogRotationKeyCCWY", "AnalogRotationKeyCCWScale", "ShowAnalogRotationCCWKey", &g_Config.touchAnalogRotationCCWKey, defaultTouchPosHide, true, true),
ConfigSetting("fcombo5X", "fcombo5Y", "comboKeyScale5", "ShowComboKey5", &g_Config.touchCombo5, defaultTouchPosHide, true, true),
ConfigSetting("fcombo6X", "fcombo6Y", "comboKeyScale6", "ShowComboKey6", &g_Config.touchCombo6, defaultTouchPosHide, true, true),
ConfigSetting("fcombo7X", "fcombo7Y", "comboKeyScale7", "ShowComboKey7", &g_Config.touchCombo7, defaultTouchPosHide, true, true),
ConfigSetting("fcombo8X", "fcombo8Y", "comboKeyScale8", "ShowComboKey8", &g_Config.touchCombo8, defaultTouchPosHide, true, true),
ConfigSetting("fcombo9X", "fcombo9Y", "comboKeyScale9", "ShowComboKey9", &g_Config.touchCombo9, defaultTouchPosHide, true, true),
#ifdef _WIN32
ConfigSetting("DInputAnalogDeadzone", &g_Config.fDInputAnalogDeadzone, 0.1f, true, true),
ConfigSetting("DInputAnalogInverseMode", &g_Config.iDInputAnalogInverseMode, 0, true, true),
ConfigSetting("DInputAnalogInverseDeadzone", &g_Config.fDInputAnalogInverseDeadzone, 0.0f, true, true),
ConfigSetting("DInputAnalogSensitivity", &g_Config.fDInputAnalogSensitivity, 1.0f, true, true),
ConfigSetting("AnalogDeadzone", &g_Config.fAnalogDeadzone, 0.15f, true, true),
ConfigSetting("AnalogInverseDeadzone", &g_Config.fAnalogInverseDeadzone, 0.0f, true, true),
ConfigSetting("AnalogSensitivity", &g_Config.fAnalogSensitivity, 1.1f, true, true),
ConfigSetting("AnalogIsCircular", &g_Config.bAnalogIsCircular, false , true, true),
ConfigSetting("EnableDInputWithXInput", &g_Config.bEnableDInputWithXInput, false, true, true),
ConfigSetting("XInputAnalogDeadzone", &g_Config.fXInputAnalogDeadzone, 0.24f, true, true),
ConfigSetting("XInputAnalogInverseMode", &g_Config.iXInputAnalogInverseMode, 0, true, true),
ConfigSetting("XInputAnalogInverseDeadzone", &g_Config.fXInputAnalogInverseDeadzone, 0.0f, true, true),
#endif
// Also reused as generic analog sensitivity
ConfigSetting("XInputAnalogSensitivity", &g_Config.fXInputAnalogSensitivity, 1.0f, true, true),
ConfigSetting("AnalogLimiterDeadzone", &g_Config.fAnalogLimiterDeadzone, 0.6f, true, true),
ConfigSetting("AxisBindThreshold", &g_Config.fAxisBindThreshold, 0.25f, true, true),
@@ -1229,17 +1264,22 @@ void Config::Reload() {
reload_ = false;
}
void Config::Load(const char *iniFileName, const char *controllerIniFilename) {
// Call this if you change the search path (such as when changing memstick directory. can't
// really think of any other legit uses).
void Config::UpdateIniLocation(const char *iniFileName, const char *controllerIniFilename) {
const bool useIniFilename = iniFileName != nullptr && strlen(iniFileName) > 0;
iniFilename_ = FindConfigFile(useIniFilename ? iniFileName : "ppsspp.ini");
const bool useControllerIniFilename = controllerIniFilename != nullptr && strlen(controllerIniFilename) > 0;
controllerIniFilename_ = FindConfigFile(useControllerIniFilename ? controllerIniFilename : "controls.ini");
}
void Config::Load(const char *iniFileName, const char *controllerIniFilename) {
if (!bUpdatedInstanceCounter) {
InitInstanceCounter();
bUpdatedInstanceCounter = true;
}
const bool useControllerIniFilename = controllerIniFilename != nullptr && strlen(controllerIniFilename) > 0;
controllerIniFilename_ = FindConfigFile(useControllerIniFilename ? controllerIniFilename : "controls.ini");
UpdateIniLocation(iniFileName, controllerIniFilename);
INFO_LOG(LOADER, "Loading config: %s", iniFilename_.c_str());
bSaveSettings = true;
@@ -1255,7 +1295,10 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) {
});
iRunCount++;
if (!File::Exists(Path(currentDirectory)))
// This check is probably not really necessary here anyway, you can always
// press Home or Browse if you're in a bad directory.
if (!File::Exists(currentDirectory))
currentDirectory = defaultCurrentDirectory;
Section *log = iniFile.GetOrCreateSection(logSectionName);
@@ -1809,11 +1852,11 @@ void Config::ResetControlLayout() {
reset(g_Config.touchCombo2);
reset(g_Config.touchCombo3);
reset(g_Config.touchCombo4);
reset(g_Config.touchSpeed1Key);
reset(g_Config.touchSpeed2Key);
reset(g_Config.touchRapidFireKey);
reset(g_Config.touchAnalogRotationCWKey);
reset(g_Config.touchAnalogRotationCCWKey);
reset(g_Config.touchCombo5);
reset(g_Config.touchCombo6);
reset(g_Config.touchCombo7);
reset(g_Config.touchCombo8);
reset(g_Config.touchCombo9);
}
void Config::GetReportingInfo(UrlEncoder &data) {
+30 -29
View File
@@ -56,6 +56,13 @@ struct ConfigTouchPos {
bool show;
};
struct ConfigCustomButton {
uint64_t key;
int image;
int shape;
bool toggle;
};
struct Config {
public:
Config();
@@ -73,7 +80,6 @@ public:
bool bBrowse; // when opening the emulator, immediately show a file browser
// General
int iNumWorkerThreads;
bool bScreenshotsAsPNG;
bool bScreenshotsAtRenderRes;
bool bSavestateScreenshotResLimit;
@@ -309,8 +315,6 @@ public:
bool bGridView1;
bool bGridView2;
bool bGridView3;
//Combo key screen flag
int iComboMode;
// Right analog binding
int iRightAnalogUp;
@@ -359,11 +363,11 @@ public:
ConfigTouchPos touchCombo2;
ConfigTouchPos touchCombo3;
ConfigTouchPos touchCombo4;
ConfigTouchPos touchSpeed1Key;
ConfigTouchPos touchSpeed2Key;
ConfigTouchPos touchRapidFireKey;
ConfigTouchPos touchAnalogRotationCWKey;
ConfigTouchPos touchAnalogRotationCCWKey;
ConfigTouchPos touchCombo5;
ConfigTouchPos touchCombo6;
ConfigTouchPos touchCombo7;
ConfigTouchPos touchCombo8;
ConfigTouchPos touchCombo9;
// Controls Visibility
bool bShowTouchControls;
@@ -373,35 +377,30 @@ public:
bool bShowTouchTriangle;
bool bShowTouchSquare;
// Combo_key mapping. These are bitfields.
int iCombokey0;
int iCombokey1;
int iCombokey2;
int iCombokey3;
int iCombokey4;
bool bComboToggle0;
bool bComboToggle1;
bool bComboToggle2;
bool bComboToggle3;
bool bComboToggle4;
ConfigCustomButton CustomKey0;
ConfigCustomButton CustomKey1;
ConfigCustomButton CustomKey2;
ConfigCustomButton CustomKey3;
ConfigCustomButton CustomKey4;
ConfigCustomButton CustomKey5;
ConfigCustomButton CustomKey6;
ConfigCustomButton CustomKey7;
ConfigCustomButton CustomKey8;
ConfigCustomButton CustomKey9;
// Ignored on iOS and other platforms that lack pause.
bool bShowTouchPause;
bool bHapticFeedback;
float fDInputAnalogDeadzone;
int iDInputAnalogInverseMode;
float fDInputAnalogInverseDeadzone;
float fDInputAnalogSensitivity;
bool bEnableDInputWithXInput;
// We also use the XInput settings as analog settings on other platforms like Android.
float fXInputAnalogDeadzone;
int iXInputAnalogInverseMode;
float fXInputAnalogInverseDeadzone;
float fXInputAnalogSensitivity;
float fAnalogDeadzone;
float fAnalogInverseDeadzone;
float fAnalogSensitivity;
// convert analog stick circle to square
bool bAnalogIsCircular;
float fAnalogLimiterDeadzone;
float fAxisBindThreshold;
@@ -524,6 +523,8 @@ public:
void SetSearchPath(const Path &path);
const Path FindConfigFile(const std::string &baseFilename);
void UpdateIniLocation(const char *iniFileName = nullptr, const char *controllerIniFilename = nullptr);
// Utility functions for "recent" management
void AddRecent(const std::string &file);
void RemoveRecent(const std::string &file);
+374
View File
@@ -0,0 +1,374 @@
#include <algorithm>
#include "Common/Math/math_util.h"
#include "Common/TimeUtil.h"
#include "Core/KeyMap.h"
#include "Core/ControlMapper.h"
#include "Core/Config.h"
static float MapAxisValue(float v) {
const float deadzone = g_Config.fAnalogDeadzone;
const float invDeadzone = g_Config.fAnalogInverseDeadzone;
const float sensitivity = g_Config.fAnalogSensitivity;
const float sign = v >= 0.0f ? 1.0f : -1.0f;
return sign * Clamp(invDeadzone + (abs(v) - deadzone) / (1.0f - deadzone) * (sensitivity - invDeadzone), 0.0f, 1.0f);
}
void ConvertAnalogStick(float &x, float &y) {
const bool isCircular = g_Config.bAnalogIsCircular;
float norm = std::max(fabsf(x), fabsf(y));
if (norm == 0.0f)
return;
if (isCircular) {
float newNorm = sqrtf(x * x + y * y);
float factor = newNorm / norm;
x *= factor;
y *= factor;
norm = newNorm;
}
float mappedNorm = MapAxisValue(norm);
x = Clamp(x / norm * mappedNorm, -1.0f, 1.0f);
y = Clamp(y / norm * mappedNorm, -1.0f, 1.0f);
}
void ControlMapper::SetCallbacks(std::function<void(int)> onVKeyDown, std::function<void(int)> onVKeyUp, std::function<void(int, float, float)> setPSPAnalog) {
onVKeyDown_ = onVKeyDown;
onVKeyUp_ = onVKeyUp;
setPSPAnalog_ = setPSPAnalog;
}
void ControlMapper::SetRawCallback(std::function<void(int, float, float)> setRawAnalog) {
setRawAnalog_ = setRawAnalog;
}
void ControlMapper::SetPSPAxis(char axis, float value, int stick) {
static float history[2][2] = {};
int axisId = axis == 'X' ? 0 : 1;
history[stick][axisId] = value;
float x = history[stick][0];
float y = history[stick][1];
if (setRawAnalog_) {
setRawAnalog_(stick, x, y);
}
ConvertAnalogStick(x, y);
setPSPAnalog_(stick, x, y);
}
bool ControlMapper::Key(const KeyInput &key, bool *pauseTrigger) {
std::vector<int> pspKeys;
KeyMap::KeyToPspButton(key.deviceId, key.keyCode, &pspKeys);
if (pspKeys.size() && (key.flags & KEY_IS_REPEAT)) {
// Claim that we handled this. Prevents volume key repeats from popping up the volume control on Android.
return true;
}
for (size_t i = 0; i < pspKeys.size(); i++) {
pspKey(pspKeys[i], key.flags);
}
if (!pspKeys.size() || key.deviceId == DEVICE_ID_DEFAULT) {
if ((key.flags & KEY_DOWN) && key.keyCode == NKCODE_BACK) {
*pauseTrigger = true;
return true;
}
}
return pspKeys.size() > 0;
}
bool ControlMapper::Axis(const AxisInput &axis) {
if (axis.value > 0) {
processAxis(axis, 1);
return true;
} else if (axis.value < 0) {
processAxis(axis, -1);
return true;
} else if (axis.value == 0) {
// Both directions! Prevents sticking for digital input devices that are axises (like HAT)
processAxis(axis, 1);
processAxis(axis, -1);
return true;
}
return false;
}
void ControlMapper::Update() {
if (autoRotatingAnalogCW_) {
const double now = time_now_d();
// Clamp to a square
float x = std::min(1.0f, std::max(-1.0f, 1.42f * (float)cos(now * -g_Config.fAnalogAutoRotSpeed)));
float y = std::min(1.0f, std::max(-1.0f, 1.42f * (float)sin(now * -g_Config.fAnalogAutoRotSpeed)));
setPSPAnalog_(0, x, y);
} else if (autoRotatingAnalogCCW_) {
const double now = time_now_d();
float x = std::min(1.0f, std::max(-1.0f, 1.42f * (float)cos(now * g_Config.fAnalogAutoRotSpeed)));
float y = std::min(1.0f, std::max(-1.0f, 1.42f * (float)sin(now * g_Config.fAnalogAutoRotSpeed)));
setPSPAnalog_(0, x, y);
}
}
inline bool IsAnalogStickKey(int key) {
switch (key) {
case VIRTKEY_AXIS_X_MIN:
case VIRTKEY_AXIS_X_MAX:
case VIRTKEY_AXIS_Y_MIN:
case VIRTKEY_AXIS_Y_MAX:
case VIRTKEY_AXIS_RIGHT_X_MIN:
case VIRTKEY_AXIS_RIGHT_X_MAX:
case VIRTKEY_AXIS_RIGHT_Y_MIN:
case VIRTKEY_AXIS_RIGHT_Y_MAX:
return true;
default:
return false;
}
}
static int RotatePSPKeyCode(int x) {
switch (x) {
case CTRL_UP: return CTRL_RIGHT;
case CTRL_RIGHT: return CTRL_DOWN;
case CTRL_DOWN: return CTRL_LEFT;
case CTRL_LEFT: return CTRL_UP;
default:
return x;
}
}
void ControlMapper::setVKeyAnalog(char axis, int stick, int virtualKeyMin, int virtualKeyMax, bool setZero) {
// The down events can repeat, so just trust the virtKeys array.
bool minDown = virtKeys[virtualKeyMin - VIRTKEY_FIRST];
bool maxDown = virtKeys[virtualKeyMax - VIRTKEY_FIRST];
const float scale = virtKeys[VIRTKEY_ANALOG_LIGHTLY - VIRTKEY_FIRST] ? g_Config.fAnalogLimiterDeadzone : 1.0f;
float value = 0.0f;
if (minDown)
value -= scale;
if (maxDown)
value += scale;
if (setZero || minDown || maxDown) {
SetPSPAxis(axis, value, stick);
}
}
void ControlMapper::pspKey(int pspKeyCode, int flags) {
int rotations = 0;
switch (g_Config.iInternalScreenRotation) {
case ROTATION_LOCKED_HORIZONTAL180:
rotations = 2;
break;
case ROTATION_LOCKED_VERTICAL:
rotations = 1;
break;
case ROTATION_LOCKED_VERTICAL180:
rotations = 3;
break;
}
for (int i = 0; i < rotations; i++) {
pspKeyCode = RotatePSPKeyCode(pspKeyCode);
}
if (pspKeyCode >= VIRTKEY_FIRST) {
int vk = pspKeyCode - VIRTKEY_FIRST;
if (flags & KEY_DOWN) {
virtKeys[vk] = true;
onVKeyDown(pspKeyCode);
}
if (flags & KEY_UP) {
virtKeys[vk] = false;
onVKeyUp(pspKeyCode);
}
} else {
// INFO_LOG(SYSTEM, "pspKey %i %i", pspKeyCode, flags);
if (flags & KEY_DOWN)
__CtrlButtonDown(pspKeyCode);
if (flags & KEY_UP)
__CtrlButtonUp(pspKeyCode);
}
}
void ControlMapper::onVKeyDown(int vkey) {
switch (vkey) {
case VIRTKEY_AXIS_X_MIN:
case VIRTKEY_AXIS_X_MAX:
setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX);
break;
case VIRTKEY_AXIS_Y_MIN:
case VIRTKEY_AXIS_Y_MAX:
setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX);
break;
case VIRTKEY_AXIS_RIGHT_X_MIN:
case VIRTKEY_AXIS_RIGHT_X_MAX:
setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX);
break;
case VIRTKEY_AXIS_RIGHT_Y_MIN:
case VIRTKEY_AXIS_RIGHT_Y_MAX:
setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX);
break;
case VIRTKEY_ANALOG_LIGHTLY:
setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX, false);
setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX, false);
setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX, false);
setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX, false);
break;
case VIRTKEY_ANALOG_ROTATE_CW:
autoRotatingAnalogCW_ = true;
autoRotatingAnalogCCW_ = false;
break;
case VIRTKEY_ANALOG_ROTATE_CCW:
autoRotatingAnalogCW_ = false;
autoRotatingAnalogCCW_ = true;
break;
default:
if (onVKeyDown_)
onVKeyDown_(vkey);
break;
}
}
void ControlMapper::onVKeyUp(int vkey) {
switch (vkey) {
case VIRTKEY_AXIS_X_MIN:
case VIRTKEY_AXIS_X_MAX:
setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX);
break;
case VIRTKEY_AXIS_Y_MIN:
case VIRTKEY_AXIS_Y_MAX:
setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX);
break;
case VIRTKEY_AXIS_RIGHT_X_MIN:
case VIRTKEY_AXIS_RIGHT_X_MAX:
setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX);
break;
case VIRTKEY_AXIS_RIGHT_Y_MIN:
case VIRTKEY_AXIS_RIGHT_Y_MAX:
setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX);
break;
case VIRTKEY_ANALOG_LIGHTLY:
setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX, false);
setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX, false);
setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX, false);
setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX, false);
break;
case VIRTKEY_ANALOG_ROTATE_CW:
autoRotatingAnalogCW_ = false;
__CtrlSetAnalogXY(0, 0.0f, 0.0f);
break;
case VIRTKEY_ANALOG_ROTATE_CCW:
autoRotatingAnalogCCW_ = false;
__CtrlSetAnalogXY(0, 0.0f, 0.0f);
break;
default:
if (onVKeyUp_)
onVKeyUp_(vkey);
break;
}
}
void ControlMapper::processAxis(const AxisInput &axis, int direction) {
// Sanity check
if (axis.axisId < 0 || axis.axisId >= JOYSTICK_AXIS_MAX) {
return;
}
const float scale = virtKeys[VIRTKEY_ANALOG_LIGHTLY - VIRTKEY_FIRST] ? g_Config.fAnalogLimiterDeadzone : 1.0f;
std::vector<int> results;
KeyMap::AxisToPspButton(axis.deviceId, axis.axisId, direction, &results);
for (int result : results) {
float value = fabs(axis.value) * scale;
switch (result) {
case VIRTKEY_AXIS_X_MIN:
SetPSPAxis('X', -value, CTRL_STICK_LEFT);
break;
case VIRTKEY_AXIS_X_MAX:
SetPSPAxis('X', value, CTRL_STICK_LEFT);
break;
case VIRTKEY_AXIS_Y_MIN:
SetPSPAxis('Y', -value, CTRL_STICK_LEFT);
break;
case VIRTKEY_AXIS_Y_MAX:
SetPSPAxis('Y', value, CTRL_STICK_LEFT);
break;
case VIRTKEY_AXIS_RIGHT_X_MIN:
SetPSPAxis('X', -value, CTRL_STICK_RIGHT);
break;
case VIRTKEY_AXIS_RIGHT_X_MAX:
SetPSPAxis('X', value, CTRL_STICK_RIGHT);
break;
case VIRTKEY_AXIS_RIGHT_Y_MIN:
SetPSPAxis('Y', -value, CTRL_STICK_RIGHT);
break;
case VIRTKEY_AXIS_RIGHT_Y_MAX:
SetPSPAxis('Y', value, CTRL_STICK_RIGHT);
break;
}
}
std::vector<int> resultsOpposite;
KeyMap::AxisToPspButton(axis.deviceId, axis.axisId, -direction, &resultsOpposite);
int axisState = 0;
float threshold = axis.deviceId == DEVICE_ID_MOUSE ? AXIS_BIND_THRESHOLD_MOUSE : AXIS_BIND_THRESHOLD;
if (direction == 1 && axis.value >= threshold) {
axisState = 1;
} else if (direction == -1 && axis.value <= -threshold) {
axisState = -1;
} else {
axisState = 0;
}
if (axisState != axisState_[axis.axisId]) {
axisState_[axis.axisId] = axisState;
if (axisState != 0) {
for (size_t i = 0; i < results.size(); i++) {
if (!IsAnalogStickKey(results[i]))
pspKey(results[i], KEY_DOWN);
}
// Also unpress the other direction (unless both directions press the same key.)
for (size_t i = 0; i < resultsOpposite.size(); i++) {
if (!IsAnalogStickKey(resultsOpposite[i]) && std::find(results.begin(), results.end(), resultsOpposite[i]) == results.end())
pspKey(resultsOpposite[i], KEY_UP);
}
} else if (axisState == 0) {
// Release both directions, trying to deal with some erratic controllers that can cause it to stick.
for (size_t i = 0; i < results.size(); i++) {
if (!IsAnalogStickKey(results[i]))
pspKey(results[i], KEY_UP);
}
for (size_t i = 0; i < resultsOpposite.size(); i++) {
if (!IsAnalogStickKey(resultsOpposite[i]))
pspKey(resultsOpposite[i], KEY_UP);
}
}
}
}
+57
View File
@@ -0,0 +1,57 @@
#pragma once
#include "Common/Input/InputState.h"
#include "Core/KeyMap.h"
#include <functional>
// Utilities for mapping input events to PSP inputs and virtual keys.
// Main use is of course from EmuScreen.cpp, but also useful from control settings etc.
// At some point I want to refactor this from using callbacks to simply providing lists of events.
// Still it won't be able to be completely stateless due to the 2-D processing of analog sticks.
class ControlMapper {
public:
void Update();
bool Key(const KeyInput &key, bool *pauseTrigger);
void pspKey(int pspKeyCode, int flags);
bool Axis(const AxisInput &axis);
// Required callbacks
void SetCallbacks(
std::function<void(int)> onVKeyDown,
std::function<void(int)> onVKeyUp,
std::function<void(int, float, float)> setPSPAnalog);
// Optional callback, only used in config
void SetRawCallback(std::function<void(int, float, float)> setRawAnalog);
private:
void processAxis(const AxisInput &axis, int direction);
void setVKeyAnalog(char axis, int stick, int virtualKeyMin, int virtualKeyMax, bool setZero = true);
void SetPSPAxis(char axis, float value, int stick);
void onVKeyDown(int vkey);
void onVKeyUp(int vkey);
// To track mappable virtual keys. We can have as many as we want.
bool virtKeys[VIRTKEY_COUNT]{};
// De-noise mapped axis updates
int axisState_[JOYSTICK_AXIS_MAX]{};
// Mappable auto-rotation. Useful for keyboard/dpad->analog in a few games.
bool autoRotatingAnalogCW_ = false;
bool autoRotatingAnalogCCW_ = false;
// Callbacks
std::function<void(int)> onVKeyDown_;
std::function<void(int)> onVKeyUp_;
std::function<void(int, float, float)> setPSPAnalog_;
std::function<void(int, float, float)> setRawAnalog_;
};
void ConvertAnalogStick(float &x, float &y);
+2
View File
@@ -509,6 +509,7 @@
<ClCompile Include="..\ext\udis86\syn-intel.c" />
<ClCompile Include="..\ext\udis86\syn.c" />
<ClCompile Include="..\ext\udis86\udis86.c" />
<ClCompile Include="ControlMapper.cpp" />
<ClCompile Include="AVIDump.cpp" />
<ClCompile Include="Debugger\MemBlockInfo.cpp" />
<ClCompile Include="Debugger\WebSocket.cpp" />
@@ -1064,6 +1065,7 @@
<ClInclude Include="..\ext\udis86\types.h" />
<ClInclude Include="..\ext\udis86\udint.h" />
<ClInclude Include="..\ext\udis86\udis86.h" />
<ClInclude Include="ControlMapper.h" />
<ClInclude Include="AVIDump.h" />
<ClInclude Include="ConfigValues.h" />
<ClInclude Include="Debugger\MemBlockInfo.h" />
+6
View File
@@ -1175,6 +1175,9 @@
<ClCompile Include="Debugger\WebSocket\ReplaySubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="ControlMapper.cpp">
<Filter>Core</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ELF\ElfReader.h">
@@ -1892,6 +1895,9 @@
<ClInclude Include="Debugger\WebSocket\ReplaySubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="ControlMapper.h">
<Filter>Core</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="CMakeLists.txt" />
+5 -1
View File
@@ -338,10 +338,14 @@ void hleCheat(u64 userdata, int cyclesLate) {
} else if (gameTitle == "ULES00033" || gameTitle == "ULES00034" || gameTitle == "ULES00035") { // euro, also 34 and 35
cheatEngine->InvalidateICache(0x088655D8, 72);
cheatEngine->InvalidateICache(0x08865668, 4);
} else if (gameTitle == "ULUS10138") { // MTX MotoTrax. No hack for ULES00581 known.
} else if (gameTitle == "ULUS10138") { // MTX MotoTrax US
cheatEngine->InvalidateICache(0x0886DCC0, 72);
cheatEngine->InvalidateICache(0x0886DC20, 4);
cheatEngine->InvalidateICache(0x0886DD40, 4);
} else if (gameTitle == "ULES00581") { // MTX MotoTrax EU (ported from US cwcheat codes)
cheatEngine->InvalidateICache(0x0886E1D8, 72);
cheatEngine->InvalidateICache(0x0886E138, 4);
cheatEngine->InvalidateICache(0x0886E258, 4);
}
}
+1
View File
@@ -1013,6 +1013,7 @@ void SymbolMap::GetLabels(std::vector<LabelDefinition> &dest) {
LabelDefinition entry;
entry.value = it->first;
entry.name = ConvertUTF8ToWString(it->second.name);
std::transform(entry.name.begin(), entry.name.end(), entry.name.begin(), ::towlower);
dest.push_back(entry);
}
}
+1 -2
View File
@@ -254,8 +254,7 @@ void WebSocketInputState::AnalogSend(DebuggerRequest &req) {
if (!AnalogValue(req, &x, "x") || !AnalogValue(req, &y, "y"))
return;
__CtrlSetAnalogX(x, stick == "left" ? CTRL_STICK_LEFT : CTRL_STICK_RIGHT);
__CtrlSetAnalogY(y, stick == "left" ? CTRL_STICK_LEFT : CTRL_STICK_RIGHT);
__CtrlSetAnalogXY(stick == "left" ? CTRL_STICK_LEFT : CTRL_STICK_RIGHT, x, y);
req.Respond();
}
+4 -2
View File
@@ -47,7 +47,7 @@ const static int OSK_SHUTDOWN_DELAY_US = 40000;
static std::map<std::string, std::pair<std::string, int>> languageMapping;
const int numKeyCols[OSK_KEYBOARD_COUNT] = {12, 12, 13, 13, 12, 12, 12, 12, 12};
const int numKeyRows[OSK_KEYBOARD_COUNT] = {4, 4, 5, 5, 5, 4, 4, 4, 4};
const int numKeyRows[OSK_KEYBOARD_COUNT] = {4, 4, 6, 6, 5, 4, 4, 4, 4};
// Japanese (Kana) diacritics
static const wchar_t diacritics[2][103] =
@@ -74,7 +74,7 @@ const int kor_lconsCom[] = {18,0,2,21,3,4,26,3,5,0,7,8,15,7,9,16,7,10,18,7,11,24
// Korean (Hangul) last consonant Separation key
const int kor_lconsSpr[] = {2,1,9,4,4,12,5,4,18,8,8,0,9,8,6,10,8,7,11,8,9,12,8,16,13,8,17,14,8,18,17,17,9};
static const char16_t oskKeys[OSK_KEYBOARD_COUNT][5][14] =
static const char16_t oskKeys[OSK_KEYBOARD_COUNT][6][14] =
{
{
// Latin Lowercase
@@ -97,6 +97,7 @@ static const char16_t oskKeys[OSK_KEYBOARD_COUNT][5][14] =
{u"うくすつぬふむゆるをぅゅ゛"},
{u"えけせてねへめ れ ぇ ゜"},
{u"おこそとのほもよろんぉょー"},
{u"・。、「」『』〜 "},
},
{
// Katakana
@@ -105,6 +106,7 @@ static const char16_t oskKeys[OSK_KEYBOARD_COUNT][5][14] =
{u"ウクスツヌフムユルヲゥュ゛"},
{u"エケセテネヘメ レ ェ ゜"},
{u"オコソトノホモヨロンォョー"},
{u"・。、「」『』〜 "},
},
{
// Korean(Hangul)
+9 -4
View File
@@ -15,7 +15,11 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <atomic>
#include "Common/StringUtils.h"
#include "Common/Thread/ParallelLoop.h"
#include "Core/MemMap.h"
#include "Core/Reporting.h"
#include "Core/ThreadPools.h"
@@ -60,9 +64,10 @@ bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
relocOps.resize(numRelocs);
DEBUG_LOG(LOADER, "Loading %i relocations...", numRelocs);
std::atomic<int> numErrors;
numErrors.store(0);
int numErrors = 0;
GlobalThreadPool::Loop([&](int l, int h) {
ParallelRangeLoop(&g_threadManager, [&](int l, int h) {
for (int r = l; r < h; r++) {
u32 info = rels[r].r_info;
u32 addr = rels[r].r_offset;
@@ -96,7 +101,7 @@ bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
}
}, 0, numRelocs, 128);
GlobalThreadPool::Loop([&](int l, int h) {
ParallelRangeLoop(&g_threadManager, [&](int l, int h) {
for (int r = l; r < h; r++) {
VERBOSE_LOG(LOADER, "Loading reloc %i (%p)...", r, rels + r);
u32 info = rels[r].r_info;
@@ -210,7 +215,7 @@ bool ElfReader::LoadRelocations(const Elf32_Rel *rels, int numRelocs) {
}, 0, numRelocs, 128);
if (numErrors) {
WARN_LOG(LOADER, "%i bad relocations found!!!", numErrors);
WARN_LOG(LOADER, "%i bad relocations found!!!", numErrors.load());
}
return numErrors == 0;
}
+33 -9
View File
@@ -25,6 +25,10 @@
#include "Common/File/DirListing.h"
#include "Core/FileLoaders/LocalFileLoader.h"
#if PPSSPP_PLATFORM(ANDROID)
#include "android/jni/app-android.h"
#endif
#ifdef _WIN32
#include "Common/CommonWindows.h"
#else
@@ -32,11 +36,6 @@
#endif
#ifndef _WIN32
LocalFileLoader::LocalFileLoader(int fd, const Path &filename) : fd_(fd), filename_(filename), isOpenedByFd_(fd != -1) {
if (fd != -1) {
DetectSizeFd();
}
}
void LocalFileLoader::DetectSizeFd() {
#if PPSSPP_PLATFORM(ANDROID) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64)
@@ -52,11 +51,27 @@ void LocalFileLoader::DetectSizeFd() {
#endif
LocalFileLoader::LocalFileLoader(const Path &filename)
: filesize_(0), filename_(filename), isOpenedByFd_(false) {
: filesize_(0), filename_(filename) {
if (filename.empty()) {
ERROR_LOG(FILESYS, "LocalFileLoader can't load empty filenames");
return;
}
#if PPSSPP_PLATFORM(ANDROID)
if (filename.Type() == PathType::CONTENT_URI) {
int fd = Android_OpenContentUriFd(filename.ToString(), Android_OpenContentUriMode::READ);
INFO_LOG(SYSTEM, "Fd %d for content URI: '%s'", fd, filename.c_str());
if (fd == -1) {
ERROR_LOG(FILESYS, "LoadFileLoader failed to open content URI: '%s'", filename.c_str());
return;
}
fd_ = fd;
isOpenedByFd_ = true;
DetectSizeFd();
return;
}
#endif
#ifndef _WIN32
fd_ = open(filename.c_str(), O_RDONLY | O_CLOEXEC);
@@ -106,14 +121,18 @@ bool LocalFileLoader::Exists() {
// If we couldn't open it for reading, we say it does not exist.
#ifndef _WIN32
if (isOpenedByFd_) {
return true;
return fd_ != -1;
}
if (fd_ != -1 || IsDirectory()) {
#else
if (handle_ != INVALID_HANDLE_VALUE || IsDirectory()) {
#endif
File::FileInfo info;
return File::GetFileInfo(filename_, &info);
if (File::GetFileInfo(filename_, &info)) {
return info.exists;
} else {
return false;
}
}
return false;
}
@@ -121,7 +140,7 @@ bool LocalFileLoader::Exists() {
bool LocalFileLoader::IsDirectory() {
File::FileInfo info;
if (File::GetFileInfo(filename_, &info)) {
return info.isDirectory;
return info.exists && info.isDirectory;
}
return false;
}
@@ -134,6 +153,11 @@ size_t LocalFileLoader::ReadAt(s64 absolutePos, size_t bytes, size_t count, void
if (bytes == 0)
return 0;
if (filesize_ == 0) {
ERROR_LOG(FILESYS, "ReadAt from 0-sized file: %s", filename_.c_str());
return 0;
}
#if PPSSPP_PLATFORM(SWITCH)
// Toolchain has no fancy IO API. We must lock.
std::lock_guard<std::mutex> guard(readLock_);
+4 -5
View File
@@ -30,7 +30,6 @@ typedef void *HANDLE;
class LocalFileLoader : public FileLoader {
public:
LocalFileLoader(const Path &filename);
LocalFileLoader(const int fd, const Path &filename);
virtual ~LocalFileLoader();
virtual bool Exists() override;
@@ -44,12 +43,12 @@ public:
private:
#ifndef _WIN32
void DetectSizeFd();
int fd_;
int fd_ = -1;
#else
HANDLE handle_;
HANDLE handle_ = 0;
#endif
u64 filesize_;
u64 filesize_ = 0;
Path filename_;
std::mutex readLock_;
bool isOpenedByFd_;
bool isOpenedByFd_ = false;
};
+42 -7
View File
@@ -187,7 +187,7 @@ bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, File
error = 0;
#if HOST_IS_CASE_SENSITIVE
if (access & (FILEACCESS_APPEND|FILEACCESS_CREATE|FILEACCESS_WRITE)) {
if (access & (FILEACCESS_APPEND | FILEACCESS_CREATE | FILEACCESS_WRITE)) {
DEBUG_LOG(FILESYS, "Checking case for path %s", fileName.c_str());
if (!FixPathCase(basePath.ToString(), fileName, FPC_PATH_MUST_EXIST)) {
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
@@ -208,7 +208,7 @@ bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, File
}
//TODO: tests, should append seek to end of file? seeking in a file opened for append?
#ifdef _WIN32
#if PPSSPP_PLATFORM(WINDOWS)
// Convert parameters to Windows permissions and access
DWORD desired = 0;
DWORD sharemode = 0;
@@ -265,6 +265,37 @@ bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, File
}
}
#else
if (fullName.Type() == PathType::CONTENT_URI) {
// Convert flags. Don't want to share this type, bad dependency.
u32 flags = File::OPEN_NONE;
if (access & FILEACCESS_READ)
flags |= File::OPEN_READ;
if (access & FILEACCESS_WRITE)
flags |= File::OPEN_WRITE;
if (access & FILEACCESS_APPEND)
flags |= File::OPEN_APPEND;
if (access & FILEACCESS_CREATE)
flags |= File::OPEN_CREATE;
if (access & FILEACCESS_TRUNCATE)
flags |= File::OPEN_TRUNCATE;
int fd = File::OpenFD(fullName, (File::OpenFlag)flags);
// Try to detect reads/writes to PSP/GAME to avoid them in replays.
if (fullName.FilePathContains("PSP/GAME/")) {
inGameDir_ = true;
}
hFile = fd;
if (fd != -1) {
// Success
return true;
} else {
// TODO: Need better error codes from OpenFD so we can distinguish
// disk full. Just set not found for now.
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
return false;
}
}
int flags = 0;
if (access & FILEACCESS_APPEND) {
flags |= O_APPEND;
@@ -623,18 +654,22 @@ int DirectoryFileSystem::OpenFile(std::string filename, FileAccess access, const
err = ReplayApplyDisk(ReplayAction::FILE_OPEN, err, CoreTiming::GetGlobalTimeUs());
if (err != 0) {
std::string errorString;
int logError;
#ifdef _WIN32
auto win32err = GetLastError();
ERROR_LOG(FILESYS, "DirectoryFileSystem::OpenFile: FAILED, %i - access = %i, %s", (int)win32err, (int)access, GetStringErrorMsg(win32err).c_str());
logError = (int)win32err;
errorString = GetStringErrorMsg(win32err);
#else
ERROR_LOG(FILESYS, "DirectoryFileSystem::OpenFile: FAILED, %i - access = %i", errno, (int)access);
logError = (int)errno;
#endif
//wwwwaaaaahh!!
ERROR_LOG(FILESYS, "DirectoryFileSystem::OpenFile(%s): FAILED, %i - access = %d '%s'", filename.c_str(), logError, (int)access, errorString.c_str());
return err;
} else {
#ifdef _WIN32
if (access & FILEACCESS_APPEND)
entry.hFile.Seek(0,FILEMOVE_END);
if (access & FILEACCESS_APPEND) {
entry.hFile.Seek(0, FILEMOVE_END);
}
#endif
u32 newHandle = hAlloc->GetNewHandle();
+1 -2
View File
@@ -285,8 +285,7 @@ int VirtualDiscFileSystem::getFileListIndex(std::string &fileName)
#endif
}
FileType type = File::IsDirectory(fullName) ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;
if (type == FILETYPE_DIRECTORY)
if (File::IsDirectory(fullName))
return -1;
FileListEntry entry = {""};
+8 -8
View File
@@ -230,14 +230,14 @@ bool PGF::ReadPtr(const u8 *ptr, size_t dataSize) {
const u8 *uptr = (const u8 *)wptr;
if (uptr >= startPtr + dataSize) {
return false;
}
int shadowCharMapSize = ((header.shadowMapLength * header.shadowMapBpe + 31) & ~31) / 8;
const u8 *shadowCharMap = uptr;
uptr += shadowCharMapSize;
if (uptr < startPtr || uptr >= startPtr + dataSize) {
return false;
}
const u16_le *sptr = (const u16_le *)uptr;
if (header.revision == 3) {
charmapCompressionTable1[0].resize(rev3extra.compCharMapLength1);
@@ -257,10 +257,6 @@ bool PGF::ReadPtr(const u8 *ptr, size_t dataSize) {
uptr = (const u8 *)sptr;
if (uptr >= startPtr + dataSize) {
return false;
}
int charMapSize = ((header.charMapLength * header.charMapBpe + 31) & ~31) / 8;
const u8 *charMap = uptr;
uptr += charMapSize;
@@ -269,6 +265,10 @@ bool PGF::ReadPtr(const u8 *ptr, size_t dataSize) {
const u8 *charPointerTable = uptr;
uptr += charPointerSize;
if (uptr < startPtr || uptr >= startPtr + dataSize) {
return false;
}
// PGF Fontdata.
u32 fontDataOffset = (u32)(uptr - startPtr);
+1
View File
@@ -220,6 +220,7 @@ T hleDoLog(LogTypes::LOG_TYPE t, LogTypes::LOG_LEVELS level, T res, const char *
#define hleLogError(t, res, ...) hleLogHelper(t, LERROR, res, 'x', ##__VA_ARGS__)
#define hleLogWarning(t, res, ...) hleLogHelper(t, LWARNING, res, 'x', ##__VA_ARGS__)
#define hleLogDebug(t, res, ...) hleLogHelper(t, HLE_LOG_LDEBUG, res, 'x', ##__VA_ARGS__)
#define hleLogVerbose(t, res, ...) hleLogHelper(t, HLE_LOG_LVERBOSE, res, 'x', ##__VA_ARGS__)
#define hleLogSuccessX(t, res, ...) hleLogHelper(t, HLE_LOG_LDEBUG, res, 'x', ##__VA_ARGS__)
#define hleLogSuccessI(t, res, ...) hleLogHelper(t, HLE_LOG_LDEBUG, res, 'i', ##__VA_ARGS__)
#define hleLogSuccessInfoX(t, res, ...) hleLogHelper(t, LINFO, res, 'x', ##__VA_ARGS__)
+6 -10
View File
@@ -188,18 +188,14 @@ void __CtrlButtonUp(u32 buttonBit)
ctrlCurrent.buttons &= ~buttonBit;
}
void __CtrlSetAnalogX(float x, int stick)
void __CtrlSetAnalogXY(int stick, float x, float y)
{
u8 scaled = clamp_u8((int)ceilf(x * 127.5f + 127.5f));
u8 scaledX = clamp_u8((int)ceilf(x * 127.5f + 127.5f));
// TODO: We might have too many negations of Y...
u8 scaledY = clamp_u8((int)ceilf(-y * 127.5f + 127.5f));
std::lock_guard<std::mutex> guard(ctrlMutex);
ctrlCurrent.analog[stick][CTRL_ANALOG_X] = scaled;
}
void __CtrlSetAnalogY(float y, int stick)
{
u8 scaled = clamp_u8((int)ceilf(-y * 127.5f + 127.5f));
std::lock_guard<std::mutex> guard(ctrlMutex);
ctrlCurrent.analog[stick][CTRL_ANALOG_Y] = scaled;
ctrlCurrent.analog[stick][CTRL_ANALOG_X] = scaledX;
ctrlCurrent.analog[stick][CTRL_ANALOG_Y] = scaledY;
}
void __CtrlSetRapidFire(bool state)
+2 -3
View File
@@ -72,10 +72,9 @@ void __CtrlButtonDown(u32 buttonBit);
void __CtrlButtonUp(u32 buttonBit);
// Call this to set the position of an analog stick, ideally when it changes.
// Position value should be from -1 to 1, inclusive, in a square (no need to force to a circle.)
// X and Y values should be from -1 to 1, inclusive, in a square (no need to force to a circle.)
// No deadzone filtering is done (but note that this applies to the actual PSP as well.)
void __CtrlSetAnalogX(float value, int stick = CTRL_STICK_LEFT);
void __CtrlSetAnalogY(float value, int stick = CTRL_STICK_LEFT);
void __CtrlSetAnalogXY(int stick, float x, float y);
// Call this to enable rapid-fire. This will cause buttons other than arrows to alternate.
void __CtrlSetRapidFire(bool state);
+5 -2
View File
@@ -443,7 +443,7 @@ static bool IsRunningSlow() {
best = std::max(fpsHistory[index], best);
}
return best < System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE) * 0.999;
return best < System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE) * 0.97;
}
return false;
@@ -770,7 +770,10 @@ void __DisplayFlip(int cyclesLate) {
// Let the user know if we're running slow, so they know to adjust settings.
// Sometimes users just think the sound emulation is broken.
static bool hasNotifiedSlow = false;
if (!g_Config.bHideSlowWarnings && !hasNotifiedSlow && PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL && IsRunningSlow()) {
if (!g_Config.bHideSlowWarnings &&
!hasNotifiedSlow &&
PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL &&
IsRunningSlow()) {
#ifndef _DEBUG
auto err = GetI18NCategory("Error");
if (g_Config.bSoftwareRendering) {
+53 -23
View File
@@ -208,9 +208,10 @@ static int asyncDefaultPriority = -1;
class FileNode : public KernelObject {
public:
FileNode() : callbackID(0), callbackArg(0), asyncResult(0), hasAsyncResult(false), pendingAsyncResult(false), sectorBlockMode(false), closePending(false), npdrm(0), pgdInfo(NULL) {}
FileNode() {}
~FileNode() {
pspFileSystem.CloseFile(handle);
if (handle != -1)
pspFileSystem.CloseFile(handle);
pgd_close(pgdInfo);
}
const char *GetName() override { return fullpath.c_str(); }
@@ -228,7 +229,7 @@ public:
}
void DoState(PointerWrap &p) override {
auto s = p.Section("FileNode", 1, 2);
auto s = p.Section("FileNode", 1, 3);
if (!s)
return;
@@ -262,35 +263,40 @@ public:
if (s >= 2) {
Do(p, waitingSyncThreads);
}
if (s >= 3) {
Do(p, isTTY);
}
Do(p, pausedWaits);
}
std::string fullpath;
u32 handle;
u32 callbackID;
u32 callbackArg;
u32 callbackID = 0;
u32 callbackArg = 0;
s64 asyncResult;
bool hasAsyncResult;
bool pendingAsyncResult;
s64 asyncResult = 0;
bool hasAsyncResult = false;
bool pendingAsyncResult = false;
bool sectorBlockMode;
bool sectorBlockMode = false;
// TODO: Use an enum instead?
bool closePending;
bool closePending = false;
PSPFileInfo info;
u32 openMode;
u32 openMode = 0;
u32 npdrm;
u32 pgd_offset;
PGD_DESC *pgdInfo;
u32 npdrm = 0;
u32 pgd_offset = 0;
PGD_DESC *pgdInfo = nullptr;
std::vector<SceUID> waitingThreads;
std::vector<SceUID> waitingSyncThreads;
// Key is the callback id it was for, or if no callback, the thread id.
// Value is actually meaningless but kept for consistency with other wait types.
std::map<SceUID, u64> pausedWaits;
bool isTTY = false;
};
/******************************************************************************/
@@ -1180,6 +1186,14 @@ static bool __IoWrite(int &result, int id, u32 data_addr, int size, int &us) {
const std::string tag = "IoWrite/" + IODetermineFilename(f);
NotifyMemInfo(MemBlockFlags::READ, data_addr, size, tag.c_str(), tag.size());
if (f->isTTY) {
const char *str = (const char *)data_ptr;
const int str_size = size <= 0 ? 0 : (str[validSize - 1] == '\n' ? validSize - 1 : validSize);
INFO_LOG(SCEIO, "%s: %.*s", "tty", str_size, str);
result = validSize;
return true;
}
bool useThread = __KernelIsDispatchEnabled() && ioManagerThreadEnabled && size > IO_THREAD_MIN_DATA_SIZE;
if (useThread) {
// If there's a pending operation on this file, wait for it to finish and don't overwrite it.
@@ -1284,6 +1298,8 @@ static u32 sceIoGetDevType(int id) {
if (f) {
// TODO: When would this return PSP_DEV_TYPE_ALIAS?
WARN_LOG(SCEIO, "sceIoGetDevType(%d - %s)", id, f->fullpath.c_str());
if (f->isTTY)
result = (u32)PSPDevType::FILE;
result = (u32)pspFileSystem.DevType(f->handle) & (u32)PSPDevType::EMU_MASK;
} else {
ERROR_LOG(SCEIO, "sceIoGetDevType: unknown id %d", id);
@@ -1468,22 +1484,35 @@ static FileNode *__IoOpen(int &error, const char *filename, int flags, int mode)
if (flags & PSP_O_EXCL)
access |= FILEACCESS_EXCL;
PSPFileInfo info = pspFileSystem.GetFileInfo(filename);
PSPFileInfo info;
int h = -1;
bool isTTY = false;
// TODO: Technically, tty1, etc. too and space doesn't matter.
if (startsWithNoCase(filename, "tty0:")) {
info.name = filename;
info.access = 0777;
info.exists = true;
int h = pspFileSystem.OpenFile(filename, (FileAccess)access);
if (h < 0) {
error = h;
return nullptr;
isTTY = true;
} else {
info = pspFileSystem.GetFileInfo(filename);
h = pspFileSystem.OpenFile(filename, (FileAccess)access);
if (h < 0) {
error = h;
return nullptr;
}
}
error = 0;
FileNode *f = new FileNode();
SceUID id = kernelObjects.Create(f);
kernelObjects.Create(f);
f->handle = h;
f->fullpath = filename;
f->asyncResult = id;
f->asyncResult = h;
f->info = info;
f->openMode = access;
f->isTTY = isTTY;
f->npdrm = (flags & PSP_O_NPDRM)? true: false;
f->pgd_offset = 0;
@@ -1527,7 +1556,7 @@ static u32 sceIoOpen(const char *filename, int flags, int mode) {
} else {
asyncParams[id].priority = asyncDefaultPriority;
IFileSystem *sys = pspFileSystem.GetSystemFromFilename(filename);
if (sys && (sys->DevType(f->handle) & (PSPDevType::BLOCK | PSPDevType::EMU_LBN))) {
if (sys && !f->isTTY && (sys->DevType(f->handle) & (PSPDevType::BLOCK | PSPDevType::EMU_LBN))) {
// These are fast to open, no delay or even rescheduling happens.
return hleLogSuccessI(SCEIO, id);
}
@@ -2089,7 +2118,8 @@ static u32 sceIoOpenAsync(const char *filename, int flags, int mode) {
return hleLogError(SCEIO, error, "device not found");
f = new FileNode();
f->handle = kernelObjects.Create(f);
kernelObjects.Create(f);
f->handle = -1;
f->fullpath = filename;
f->closePending = true;
}
+2 -1
View File
@@ -20,6 +20,7 @@
#include <vector>
#include <map>
#include "Common/Thread/ParallelLoop.h"
#include "Core/CoreTiming.h"
#include "Core/Debugger/MemBlockInfo.h"
#include "Core/HLE/HLE.h"
@@ -431,7 +432,7 @@ void __KernelMemoryInit()
MemBlockInfoInit();
kernelMemory.Init(PSP_GetKernelMemoryBase(), PSP_GetKernelMemoryEnd() - PSP_GetKernelMemoryBase(), false);
userMemory.Init(PSP_GetUserMemoryBase(), PSP_GetUserMemoryEnd() - PSP_GetUserMemoryBase(), false);
GlobalThreadPool::Memset(Memory::GetPointer(PSP_GetKernelMemoryBase()), 0, PSP_GetUserMemoryEnd() - PSP_GetKernelMemoryBase());
ParallelMemset(&g_threadManager, Memory::GetPointer(PSP_GetKernelMemoryBase()), 0, PSP_GetUserMemoryEnd() - PSP_GetKernelMemoryBase());
NotifyMemInfo(MemBlockFlags::WRITE, PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd() - PSP_GetKernelMemoryBase(), "MemInit");
INFO_LOG(SCEKERNEL, "Kernel and user memory pools initialized");
+1 -1
View File
@@ -1345,7 +1345,7 @@ int __KernelGetThreadExitStatus(SceUID threadID) {
if (t->nt.status == THREADSTATUS_DORMANT) {
return hleLogSuccessI(SCEKERNEL, t->nt.exitStatus);
}
return hleLogDebug(SCEKERNEL, SCE_KERNEL_ERROR_NOT_DORMANT, "not dormant");
return hleLogVerbose(SCEKERNEL, SCE_KERNEL_ERROR_NOT_DORMANT, "not dormant");
}
int sceKernelGetThreadExitStatus(SceUID threadID) {
+75 -50
View File
@@ -1303,10 +1303,16 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 f
if (isLocalServer) {
getLocalIp(&addr);
}
addr.sin_port = htons(static_cast<int>(port + static_cast<int>(portOffset)));
// The port might be under 1024 (ie. GTA:VCS use port 1, Ford Street Racing use port 0 (UNUSED_PORT), etc) and already used by other application/host OS, should we add 1024 to the port whenever it tried to use an already used port?
uint16_t requestedport = static_cast<int>(port + static_cast<int>(portOffset));
// Avoid getting random port due to port offset when original port wasn't 0 (ie. original_port + port_offset = 65536 = 0)
if (requestedport == 0 && port > 0)
requestedport = 65535; // Hopefully it will be safe to default it to 65535 since there can't be more than one port that can bumped into 65536
// Show a warning about privileged ports
if (requestedport != 0 && requestedport < 1024) {
WARN_LOG(SCENET, "sceNetAdhocPdpCreate - Ports below 1024(ie. %hu) may require Admin Privileges", requestedport);
}
addr.sin_port = htons(requestedport);
// Bound Socket to local Port
int iResult = bind(usocket, (struct sockaddr*)&addr, sizeof(addr));
@@ -1318,9 +1324,10 @@ static int sceNetAdhocPdpCreate(const char *mac, int port, int bufferSize, u32 f
// Update sport with the port assigned internal->lport = ntohs(local.sin_port)
socklen_t len = sizeof(addr);
if (getsockname(usocket, (struct sockaddr*)&addr, &len) == 0) {
if (port + static_cast<int>(portOffset) > 65535 || static_cast<int>(ntohs(addr.sin_port)) - static_cast<int>(portOffset) <= 0)
WARN_LOG(SCENET, "sceNetAdhocPdpCreate - Shifting to Negative Port: %d -> %d -> %d", port, port + portOffset, ntohs(addr.sin_port) - portOffset);
port = ntohs(addr.sin_port) - portOffset;
uint16_t boundport = ntohs(addr.sin_port);
if (port + static_cast<int>(portOffset) >= 65536 || static_cast<int>(boundport) - static_cast<int>(portOffset) <= 0)
WARN_LOG(SCENET, "sceNetAdhocPdpCreate - Wrapped Port Detected: Original(%d) -> Requested(%d), Bound(%d) -> BoundOriginal(%d)", port, requestedport, boundport, boundport - portOffset);
port = boundport - portOffset;
}
// Allocate Memory for Internal Data
@@ -1526,7 +1533,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int
// Free Network Lock
//_freeNetworkLock();
hleEatMicro(1000); // Can be longer than 1ms tho
hleEatMicro(50); // Can be longer than 1ms tho
// Sent Data
if (sent >= 0) {
DEBUG_LOG(SCENET, "sceNetAdhocPdpSend[%i:%u]: Sent %u bytes to %s:%u\n", id, getLocalPort(pdpsocket.id), sent, ip2str(target.sin_addr).c_str(), ntohs(target.sin_port));
@@ -1537,7 +1544,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int
// Non-Blocking
if (flag)
return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block");
return hleLogSuccessVerboseX(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block");
// Does PDP can Timeout? There is no concept of Timeout when sending UDP due to no ACK, but might happen if the socket buffer is full, not sure about PDP since some games did use the timeout arg
return hleLogDebug(SCENET, ERROR_NET_ADHOC_TIMEOUT, "timeout?"); // ERROR_NET_ADHOC_INVALID_ADDR;
@@ -1618,7 +1625,7 @@ static int sceNetAdhocPdpSend(int id, const char *mac, u32 port, void *data, int
// Free Network Lock
//_freeNetworkLock();
hleEatMicro(1000);
hleEatMicro(50);
// Success, Broadcast never fails!
return 0; // len;
}
@@ -1761,7 +1768,7 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void *
// Free Network Lock
//_freeNetworkLock();
return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_NOT_ENOUGH_SPACE, "not enough space"); //received;
return hleLogSuccessVerboseX(SCENET, ERROR_NET_ADHOC_NOT_ENOUGH_SPACE, "not enough space"); //received;
}
sinlen = sizeof(sin);
memset(&sin, 0, sinlen);
@@ -1776,10 +1783,10 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void *
return WaitBlockingAdhocSocket(threadSocketId, PDP_RECV, id, buf, len, timeout, saddr, sport, "pdp recv");
}
return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block");
return hleLogSuccessVerboseX(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block");
}
hleEatMicro(1000);
hleEatMicro(50);
// Received Data. UDP can also receives 0 data, while on TCP 0 data = connection gracefully closed, but not sure about PDP tho
if (received >= 0) {
DEBUG_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Received %u bytes from %s:%u\n", id, getLocalPort(pdpsocket.id), received, ip2str(sin.sin_addr).c_str(), ntohs(sin.sin_port));
@@ -2024,7 +2031,7 @@ int sceNetAdhocPollSocket(u32 socketStructAddr, int count, int timeout, int nonb
}
}
// Workaround to get 30 FPS instead of the too fast 60 FPS on Fate Unlimited Codes, it's abit absurd for a non-blocking call to have this much delay tho, and hleDelayResult doesn't works as good as hleEatMicro for this workaround.
hleEatMicro(1000); // hleEatMicro(7500); // normally 1ms, but using 7.5ms here seems to show better result for Bleach Heat the Soul 7 and other games with too high FPS, but may have a risk of slowing down games that already runs at normal FPS? (need more games to test this)
hleEatMicro(50); // hleEatMicro(7500); // normally 1ms, but using 7.5ms here seems to show better result for Bleach Heat the Soul 7 and other games with too high FPS, but may have a risk of slowing down games that already runs at normal FPS? (need more games to test this)
return hleLogDebug(SCENET, affectedsockets, "success");
}
//else if (nonblock && affectedsockets < 0)
@@ -2268,7 +2275,7 @@ int sceNetAdhocctlGetScanInfo(u32 sizeAddr, u32 bufAddr) {
// Multithreading Unlock
peerlock.unlock();
hleEatMicro(2000);
hleEatMicro(200);
// Return Success
return 0;
}
@@ -2715,7 +2722,7 @@ int sceNetAdhocctlCreate(const char *groupName) {
adhocctlCurrentMode = ADHOCCTL_MODE_NORMAL;
adhocConnectionType = ADHOC_CREATE;
return NetAdhocctl_Create(groupName);
return hleLogDebug(SCENET, NetAdhocctl_Create(groupName), "");
}
int sceNetAdhocctlConnect(const char* groupName) {
@@ -2729,7 +2736,7 @@ int sceNetAdhocctlConnect(const char* groupName) {
adhocctlCurrentMode = ADHOCCTL_MODE_NORMAL;
adhocConnectionType = ADHOC_CONNECT;
return NetAdhocctl_Create(groupName);
return hleLogDebug(SCENET, NetAdhocctl_Create(groupName), "");
}
int sceNetAdhocctlJoin(u32 scanInfoAddr) {
@@ -2753,7 +2760,7 @@ int sceNetAdhocctlJoin(u32 scanInfoAddr) {
// TODO: Adhoc Server may need to be changed to differentiate between Host/Create and Join, otherwise it can't support multiple Host using the same Group name, thus causing one of the Host to be confused being treated as Join.
adhocctlCurrentMode = ADHOCCTL_MODE_NORMAL;
adhocConnectionType = ADHOC_JOIN;
return NetAdhocctl_Create(grpName);
return hleLogDebug(SCENET, NetAdhocctl_Create(grpName), "");
}
// Invalid Argument
@@ -3090,7 +3097,7 @@ static int sceNetAdhocGetPtpStat(u32 structSize, u32 structAddr) {
// Update Buffer Length
*buflen = i * sizeof(SceNetAdhocPtpStat);
hleEatMicro(1000); // Not sure how long it takes, since GetPtpStat didn't get logged when using prx files on JPCSP
hleEatMicro(50); // Not sure how long it takes, since GetPtpStat didn't get logged when using prx files on JPCSP
// Success
return 0;
}
@@ -3182,16 +3189,25 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac,
if (isLocalServer) {
getLocalIp(&addr);
}
addr.sin_port = htons(static_cast<int>(sport + static_cast<int>(portOffset)));
uint16_t requestedport = static_cast<int>(sport + static_cast<int>(portOffset));
// Avoid getting random port due to port offset when original port wasn't 0 (ie. original_port + port_offset = 65536 = 0)
if (requestedport == 0 && sport > 0)
requestedport = 65535; // Hopefully it will be safe to default it to 65535 since there can't be more than one port that can bumped into 65536
// Show a warning about privileged ports
if (requestedport != 0 && requestedport < 1024) {
WARN_LOG(SCENET, "sceNetAdhocPtpOpen - Ports below 1024(ie. %hu) may require Admin Privileges", requestedport);
}
addr.sin_port = htons(requestedport);
// Bound Socket to local Port
if (bind(tcpsocket, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
// Update sport with the port assigned internal->lport = ntohs(local.sin_port)
socklen_t len = sizeof(addr);
if (getsockname(tcpsocket, (struct sockaddr*)&addr, &len) == 0) {
if (sport + static_cast<int>(portOffset) > 65535 || static_cast<int>(ntohs(addr.sin_port)) - static_cast<int>(portOffset) <= 0)
WARN_LOG(SCENET, "sceNetAdhocPtpOpen - Shifting to Negative Port: %d -> %d -> %d", sport, sport + portOffset, ntohs(addr.sin_port) - portOffset);
sport = ntohs(addr.sin_port) - portOffset;
uint16_t boundport = ntohs(addr.sin_port);
if (sport + static_cast<int>(portOffset) >= 65536 || static_cast<int>(boundport) - static_cast<int>(portOffset) <= 0)
WARN_LOG(SCENET, "sceNetAdhocPtpOpen - Wrapped Port Detected: Original(%d) -> Requested(%d), Bound(%d) -> BoundOriginal(%d)", sport, requestedport, boundport, boundport - portOffset);
sport = boundport - portOffset;
}
// Allocate Memory
@@ -3431,7 +3447,7 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int
// Listener Socket
if (ptpsocket.state == ADHOC_PTP_STATE_LISTEN) {
hleEatMicro(500);
hleEatMicro(50);
// Address Information
struct sockaddr_in peeraddr;
memset(&peeraddr, 0, sizeof(peeraddr));
@@ -3467,26 +3483,26 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int
// Action would block
if (flag)
return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block");
return hleLogSuccessVerboseX(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block");
// Timeout
return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_TIMEOUT, "timeout");
return hleLogSuccessVerboseX(SCENET, ERROR_NET_ADHOC_TIMEOUT, "timeout");
}
// Client Socket
return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_NOT_LISTENED, "not listened");
return hleLogSuccessVerboseX(SCENET, ERROR_NET_ADHOC_NOT_LISTENED, "not listened");
}
// Invalid Socket
return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_INVALID_SOCKET_ID, "invalid socket id");
return hleLogSuccessVerboseX(SCENET, ERROR_NET_ADHOC_INVALID_SOCKET_ID, "invalid socket id");
}
// Invalid Arguments
return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_INVALID_ARG, "invalid arg");
return hleLogSuccessVerboseX(SCENET, ERROR_NET_ADHOC_INVALID_ARG, "invalid arg");
}
// Library is uninitialized
return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_NOT_INITIALIZED, "not initialized");
return hleLogSuccessVerboseX(SCENET, ERROR_NET_ADHOC_NOT_INITIALIZED, "not initialized");
}
int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect) {
@@ -3512,7 +3528,7 @@ int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect)
// Valid Client Socket
if (ptpsocket.state == ADHOC_PTP_STATE_CLOSED || ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) {
hleEatMicro(500);
hleEatMicro(50);
// Target Address
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
@@ -3744,7 +3760,15 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int
if (isLocalServer) {
getLocalIp(&addr);
}
addr.sin_port = htons(static_cast<int>(sport + static_cast<int>(portOffset)));
uint16_t requestedport = static_cast<int>(sport + static_cast<int>(portOffset));
// Avoid getting random port due to port offset when original port wasn't 0 (ie. original_port + port_offset = 65536 = 0)
if (requestedport == 0 && sport > 0)
requestedport = 65535; // Hopefully it will be safe to default it to 65535 since there can't be more than one port that can bumped into 65536
// Show a warning about privileged ports
if (requestedport != 0 && requestedport < 1024) {
WARN_LOG(SCENET, "sceNetAdhocPtpListen - Ports below 1024(ie. %hu) may require Admin Privileges", requestedport);
}
addr.sin_port = htons(requestedport);
int iResult = 0;
// Bound Socket to local Port
@@ -3752,9 +3776,10 @@ static int sceNetAdhocPtpListen(const char *srcmac, int sport, int bufsize, int
// Update sport with the port assigned internal->lport = ntohs(local.sin_port)
socklen_t len = sizeof(addr);
if (getsockname(tcpsocket, (struct sockaddr*)&addr, &len) == 0) {
if (sport + static_cast<int>(portOffset) > 65535 || static_cast<int>(ntohs(addr.sin_port)) - static_cast<int>(portOffset) <= 0)
WARN_LOG(SCENET, "sceNetAdhocPtpListen - Shifting to Negative Port: %d -> %d -> %d", sport, sport + portOffset, ntohs(addr.sin_port) - portOffset);
sport = ntohs(addr.sin_port) - portOffset;
uint16_t boundport = ntohs(addr.sin_port);
if (sport + static_cast<int>(portOffset) >= 65536 || static_cast<int>(boundport) - static_cast<int>(portOffset) <= 0)
WARN_LOG(SCENET, "sceNetAdhocPtpListen - Wrapped Port Detected: Original(%d) -> Requested(%d), Bound(%d) -> BoundOriginal(%d)", sport, requestedport, boundport, boundport - portOffset);
sport = boundport - portOffset;
}
// Switch into Listening Mode
if ((iResult = listen(tcpsocket, backlog)) == 0) {
@@ -3894,7 +3919,7 @@ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeou
// Success
if (sent > 0) {
hleEatMicro(1000); // mostly 1ms, sometimes 1~10ms ? doesn't seems to be switching to a different thread during this duration
hleEatMicro(50); // mostly 1ms, sometimes 1~10ms ? doesn't seems to be switching to a different thread during this duration
// Save Length
*len = sent;
@@ -3912,7 +3937,7 @@ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeou
else if (sent == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && (error == ENOTCONN || connectInProgress(error))))) {
// Non-Blocking
if (flag)
return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block");
return hleLogSuccessVerboseX(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block");
// Simulate blocking behaviour with non-blocking socket
u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id;
@@ -4001,13 +4026,13 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou
return WaitBlockingAdhocSocket(threadSocketId, PTP_RECV, id, buf, len, timeout, nullptr, nullptr, "ptp recv");
}
return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block");
return hleLogSuccessVerboseX(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block");
}
// Free Network Lock
// _freeNetworkLock();
hleEatMicro(1000);
hleEatMicro(50);
// Received Data
if (received > 0) {
@@ -4103,7 +4128,7 @@ static int sceNetAdhocPtpFlush(int id, int timeout, int nonblock) {
// Connected Socket
if (ptpsocket.state == ADHOC_PTP_STATE_ESTABLISHED) {
hleEatMicro(1000);
hleEatMicro(50);
// There are two ways to flush, you can either set TCP_NODELAY to 1 or TCP_CORK to 0.
// Apply Send Timeout Settings to Socket
setSockTimeout(ptpsocket.id, SO_SNDTIMEO, timeout);
@@ -4113,7 +4138,7 @@ static int sceNetAdhocPtpFlush(int id, int timeout, int nonblock) {
if (error == EAGAIN || error == EWOULDBLOCK) {
// Non-Blocking
if (nonblock)
return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block");
return hleLogSuccessVerboseX(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block");
// Simulate blocking behaviour with non-blocking socket
u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id;
@@ -4264,7 +4289,7 @@ static int sceNetAdhocGameModeUpdateMaster() {
gma.dataSent = 0;
}
hleEatMicro(1000);
hleEatMicro(100);
return 0;
}
@@ -4329,7 +4354,7 @@ static int sceNetAdhocGameModeUpdateReplica(int id, u32 infoAddr) {
}
}
hleEatMicro(1000);
hleEatMicro(100);
return 0;
}
@@ -5297,7 +5322,7 @@ static int sceNetAdhocMatchingGetMembers(int matchingId, u32 sizeAddr, u32 buf)
}
// Return Success
return hleDelayResult(0, "delay 1 ~ 10ms", 1000); // seems to have different thread running within the delay duration
return hleDelayResult(0, "delay 100 ~ 1000us", 100); // seems to have different thread running within the delay duration
}
// Gran Turismo may replace the 1st bit of the 1st byte of MAC address's OUI with 0 (unicast bit), or replace the whole 6-bytes of MAC address with all 00 (invalid mac) for unknown reason
@@ -5684,7 +5709,7 @@ static int sceNetAdhocctlGetGameModeInfo(u32 infoAddr) {
break;
}
hleEatMicro(1000);
hleEatMicro(100);
return 0;
}
@@ -5771,7 +5796,7 @@ static int sceNetAdhocctlGetPeerList(u32 sizeAddr, u32 bufAddr) {
peerlock.unlock();
// Return Success
return hleDelayResult(0, "delay 1 ~ 10ms", 1000); // seems to have different thread running within the delay duration
return hleDelayResult(0, "delay 100 ~ 1000us", 100); // seems to have different thread running within the delay duration
}
// Invalid Arguments
@@ -5888,7 +5913,7 @@ static int sceNetAdhocctlGetAddrByName(const char *nickName, u32 sizeAddr, u32 b
peerlock.unlock();
// Return Success
return hleLogDebug(SCENET, hleDelayResult(0, "delay 1 ~ 10ms", 1000), "success"); // FIXME: Might have similar delay with GetPeerList? need to know which games using this tho
return hleLogDebug(SCENET, hleDelayResult(0, "delay 100 ~ 1000us", 100), "success"); // FIXME: Might have similar delay with GetPeerList? need to know which games using this tho
}
// Invalid Arguments
@@ -5925,7 +5950,7 @@ const HLEFunction sceNetAdhocctl[] = {
};
int sceNetAdhocDiscoverInitStart() {
ERROR_LOG(SCENET, "UNIMPL sceNetAdhocDiscoverInitStart()");
ERROR_LOG_REPORT_ONCE(sceNetAdhocDiscoverInitStart, SCENET, "UNIMPL sceNetAdhocDiscoverInitStart() at %08x", currentMIPS->pc);
return 0;
}
@@ -5940,13 +5965,13 @@ int sceNetAdhocDiscoverTerm() {
}
int sceNetAdhocDiscoverUpdate() {
ERROR_LOG(SCENET, "UNIMPL sceNetAdhocDiscoverUpdate()");
ERROR_LOG(SCENET, "UNIMPL sceNetAdhocDiscoverUpdate() at %08x", currentMIPS->pc);
return 0;
}
int sceNetAdhocDiscoverGetStatus() {
ERROR_LOG(SCENET, "UNIMPL sceNetAdhocDiscoverGetStatus()");
return 0;
return 0; // returning 2 will trigger Legend Of The Dragon to call sceNetAdhocctlGetPeerList
}
int sceNetAdhocDiscoverRequestSuspend()
+1 -1
View File
@@ -337,7 +337,7 @@ bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string) {
case IdentifiedFileType::UNKNOWN_ELF:
case IdentifiedFileType::UNKNOWN:
default:
ERROR_LOG(LOADER, "Failed to identify file");
ERROR_LOG(LOADER, "Failed to identify file: %s", fileLoader->GetPath().c_str());
*error_string = "Failed to identify file";
break;
}
+9 -6
View File
@@ -43,7 +43,7 @@
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/JitCommon/JitBlockCache.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/ThreadPools.h"
#include "Common/Thread/ParallelLoop.h"
#include "UI/OnScreenDisplay.h"
namespace Memory {
@@ -213,7 +213,10 @@ bool MemoryMap_Setup(u32 flags) {
}
// Grab some pagefile backed memory out of the void ...
g_arena.GrabLowMemSpace(total_mem);
if (!g_arena.GrabMemSpace(total_mem)) {
// It'll already have logged.
return false;
}
#endif
#if !PPSSPP_PLATFORM(ANDROID)
@@ -318,19 +321,19 @@ static void DoMemoryVoid(PointerWrap &p, uint32_t start, uint32_t size) {
switch (p.mode) {
case PointerWrap::MODE_READ:
GlobalThreadPool::Memcpy(d, storage, size);
ParallelMemcpy(&g_threadManager, d, storage, size);
break;
case PointerWrap::MODE_WRITE:
GlobalThreadPool::Memcpy(storage, d, size);
ParallelMemcpy(&g_threadManager, storage, d, size);
break;
case PointerWrap::MODE_MEASURE:
// Nothing to do here.
break;
case PointerWrap::MODE_VERIFY:
GlobalThreadPool::Loop([&](int l, int h) {
ParallelRangeLoop(&g_threadManager, [&](int l, int h) {
for (int i = l; i < h; i++)
_dbg_assert_msg_(d[i] == storage[i], "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", d[i], d[i], &d[i], storage[i], storage[i], &storage[i]);
}, 0, size);
}, 0, size, 128);
break;
}
storage += size;
+9 -59
View File
@@ -60,9 +60,6 @@ namespace Reporting
static u32 spamProtectionCount = 0;
// Temporarily stores a reference to the hostname.
static std::string lastHostname;
// Keeps track of report-only-once identifiers. Since they're always constants, a pointer is okay.
static std::map<const char *, int> logNTimes;
static std::mutex logNTimesLock;
// Keeps track of whether a harmful setting was ever used.
static bool everUnsupported = false;
@@ -331,14 +328,18 @@ namespace Reporting
#endif
}
bool MessageAllowed();
void SendReportMessage(const char *message, const char *formatted);
void Init()
{
// New game, clean slate.
spamProtectionCount = 0;
logNTimes.clear();
ResetCounts();
everUnsupported = false;
currentSupported = IsSupported();
pendingMessagesDone = false;
Reporting::SetupCallbacks(&MessageAllowed, &SendReportMessage);
}
void Shutdown()
@@ -377,29 +378,6 @@ namespace Reporting
everUnsupported = true;
}
bool ShouldLogNTimes(const char *identifier, int count)
{
// True if it wasn't there already -> so yes, log.
std::lock_guard<std::mutex> lock(logNTimesLock);
auto iter = logNTimes.find(identifier);
if (iter == logNTimes.end()) {
logNTimes.insert(std::pair<const char*, int>(identifier, 1));
return true;
} else {
if (iter->second >= count) {
return false;
} else {
iter->second++;
return true;
}
}
}
void ResetCounts() {
std::lock_guard<std::mutex> lock(logNTimesLock);
logNTimes.clear();
}
std::string CurrentGameID()
{
// TODO: Maybe ParamSFOData shouldn't include nulls in std::strings? Don't work to break savedata, though...
@@ -639,41 +617,13 @@ namespace Reporting
return 0;
}
void ReportMessage(const char *message, ...)
{
bool MessageAllowed() {
if (!IsEnabled() || CheckSpamLimited())
return;
int pos = NextFreePos();
if (pos == -1)
return;
const int MESSAGE_BUFFER_SIZE = 65536;
char temp[MESSAGE_BUFFER_SIZE];
va_list args;
va_start(args, message);
vsnprintf(temp, MESSAGE_BUFFER_SIZE - 1, message, args);
temp[MESSAGE_BUFFER_SIZE - 1] = '\0';
va_end(args);
Payload &payload = payloadBuffer[pos];
payload.type = RequestType::MESSAGE;
payload.string1 = message;
payload.string2 = temp;
std::lock_guard<std::mutex> guard(pendingMessageLock);
pendingMessages.push_back(pos);
pendingMessageCond.notify_one();
if (!messageThread.joinable()) {
messageThread = std::thread(ProcessPending);
}
return false;
return true;
}
void ReportMessageFormatted(const char *message, const char *formatted)
{
if (!IsEnabled() || CheckSpamLimited())
return;
void SendReportMessage(const char *message, const char *formatted) {
int pos = NextFreePos();
if (pos == -1)
return;
+1 -35
View File
@@ -22,29 +22,7 @@
#include "Common/CommonTypes.h"
#include "Common/File/Path.h"
#include "Common/Log.h"
#define DEBUG_LOG_REPORT(t,...) do { DEBUG_LOG(t, __VA_ARGS__); Reporting::ReportMessage(__VA_ARGS__); } while (false)
#define ERROR_LOG_REPORT(t,...) do { ERROR_LOG(t, __VA_ARGS__); Reporting::ReportMessage(__VA_ARGS__); } while (false)
#define WARN_LOG_REPORT(t,...) do { WARN_LOG(t, __VA_ARGS__); Reporting::ReportMessage(__VA_ARGS__); } while (false)
#define NOTICE_LOG_REPORT(t,...) do { NOTICE_LOG(t, __VA_ARGS__); Reporting::ReportMessage(__VA_ARGS__); } while (false)
#define INFO_LOG_REPORT(t,...) do { INFO_LOG(t, __VA_ARGS__); Reporting::ReportMessage(__VA_ARGS__); } while (false)
#define DEBUG_LOG_REPORT_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { DEBUG_LOG_REPORT(t, __VA_ARGS__); } } while (false)
#define ERROR_LOG_REPORT_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { ERROR_LOG_REPORT(t, __VA_ARGS__); } } while (false)
#define WARN_LOG_REPORT_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { WARN_LOG_REPORT(t, __VA_ARGS__); } } while (false)
#define NOTICE_LOG_REPORT_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { NOTICE_LOG_REPORT(t, __VA_ARGS__); } } while (false)
#define INFO_LOG_REPORT_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { INFO_LOG_REPORT(t, __VA_ARGS__); } } while (false)
#define ERROR_LOG_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { ERROR_LOG(t, __VA_ARGS__); } } while (false)
#define WARN_LOG_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { WARN_LOG(t, __VA_ARGS__); } } while (false)
#define NOTICE_LOG_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { NOTICE_LOG(t, __VA_ARGS__); } } while (false)
#define INFO_LOG_ONCE(n,t,...) do { if (Reporting::ShouldLogNTimes(#n, 1)) { INFO_LOG(t, __VA_ARGS__); } } while (false)
#define ERROR_LOG_N_TIMES(s,n,t,...) do { if (Reporting::ShouldLogNTimes(#s, n)) { ERROR_LOG(t, __VA_ARGS__); } } while (false)
#define WARN_LOG_N_TIMES(s,n,t,...) do { if (Reporting::ShouldLogNTimes(#s, n)) { WARN_LOG(t, __VA_ARGS__); } } while (false)
#define NOTICE_LOG_N_TIMES(s,n,t,...) do { if (Reporting::ShouldLogNTimes(#s, n)) { NOTICE_LOG(t, __VA_ARGS__); } } while (false)
#define INFO_LOG_N_TIMES(s,n,t,...) do { if (Reporting::ShouldLogNTimes(#s, n)) { INFO_LOG(t, __VA_ARGS__); } } while (false)
#include "Common/LogReporting.h"
class PointerWrap;
@@ -54,9 +32,6 @@ namespace Reporting
void Init();
void Shutdown();
// Resets counts on any count-limited logs (see ShouldLogNTimes).
void ResetCounts();
// Check savestate compatibility, mostly needed on load.
void DoState(PointerWrap &p);
@@ -76,12 +51,6 @@ namespace Reporting
// Use the default reporting setting (per compiled settings) of host and enabled state.
void EnableDefault();
// Report a message string, using the format string as a key.
void ReportMessage(const char *message, ...);
// The same, but with a preformatted version (message is still the key.)
void ReportMessageFormatted(const char *message, const char *formatted);
// Report the compatibility of the current game / configuration.
void ReportCompatibility(const char *compat, int graphics, int speed, int gameplay, const std::string &screenshotFilename);
@@ -98,9 +67,6 @@ namespace Reporting
// To avoid stalling, call HasCRC() in update() or similar and call this if it returns true.
uint32_t RetrieveCRC(const Path &gamePath);
// Returns true if that identifier has not been logged yet.
bool ShouldLogNTimes(const char *identifier, int n);
enum class ReportStatus {
WORKING,
BUSY,
+23 -14
View File
@@ -581,38 +581,47 @@ CoreParameter &PSP_CoreParameter() {
}
Path GetSysDirectory(PSPDirectories directoryType) {
Path memStickDirectory = g_Config.memStickDirectory;
Path pspDirectory;
if (memStickDirectory.GetFilename() == "PSP") {
// Let's strip this off, to easily allow choosing a root directory named "PSP" on Android.
pspDirectory = memStickDirectory;
} else {
pspDirectory = memStickDirectory / "PSP";
}
switch (directoryType) {
case DIRECTORY_CHEATS:
return g_Config.memStickDirectory / "PSP/Cheats";
return pspDirectory / "Cheats";
case DIRECTORY_GAME:
return g_Config.memStickDirectory / "PSP/GAME";
return pspDirectory / "GAME";
case DIRECTORY_SAVEDATA:
return g_Config.memStickDirectory / "PSP/SAVEDATA";
return pspDirectory / "SAVEDATA";
case DIRECTORY_SCREENSHOT:
return g_Config.memStickDirectory / "PSP/SCREENSHOT";
return pspDirectory / "SCREENSHOT";
case DIRECTORY_SYSTEM:
return g_Config.memStickDirectory / "PSP/SYSTEM";
return pspDirectory / "SYSTEM";
case DIRECTORY_PAUTH:
return g_Config.memStickDirectory / "PAUTH";
return memStickDirectory / "PAUTH"; // This one's at the root...
case DIRECTORY_DUMP:
return g_Config.memStickDirectory / "PSP/SYSTEM/DUMP";
return pspDirectory / "SYSTEM/DUMP";
case DIRECTORY_SAVESTATE:
return g_Config.memStickDirectory / "PSP/PPSSPP_STATE";
return pspDirectory / "PPSSPP_STATE";
case DIRECTORY_CACHE:
return g_Config.memStickDirectory / "PSP/SYSTEM/CACHE";
return pspDirectory / "SYSTEM/CACHE";
case DIRECTORY_TEXTURES:
return g_Config.memStickDirectory / "PSP/TEXTURES";
return pspDirectory / "TEXTURES";
case DIRECTORY_PLUGINS:
return g_Config.memStickDirectory / "PSP/PLUGINS";
return pspDirectory / "PLUGINS";
case DIRECTORY_APP_CACHE:
if (!g_Config.appCacheDirectory.empty()) {
return g_Config.appCacheDirectory;
}
return g_Config.memStickDirectory / "PSP/SYSTEM/CACHE";
return pspDirectory / "SYSTEM/CACHE";
case DIRECTORY_VIDEO:
return g_Config.memStickDirectory / "PSP/VIDEO";
return pspDirectory / "VIDEO";
case DIRECTORY_AUDIO:
return g_Config.memStickDirectory / "PSP/AUDIO";
return pspDirectory / "AUDIO";
case DIRECTORY_MEMSTICK_ROOT:
return g_Config.memStickDirectory;
// Just return the memory stick root if we run into some sort of problem.
+5 -3
View File
@@ -30,6 +30,7 @@
#include "Common/Data/Text/Parsers.h"
#include "Common/File/FileUtil.h"
#include "Common/StringUtils.h"
#include "Common/Thread/ParallelLoop.h"
#include "Core/Config.h"
#include "Core/Host.h"
#include "Core/System.h"
@@ -747,12 +748,13 @@ void ReplacedTexture::Load(int level, void *out, int rowPitch) {
int w, h, f;
uint8_t *image;
const int MIN_LINES_PER_THREAD = 4;
if (LoadZIMPtr(&zim[0], zimSize, &w, &h, &f, &image)) {
GlobalThreadPool::Loop([&](int low, int high) {
for (int y = low; y < high; ++y) {
ParallelRangeLoop(&g_threadManager, [&](int l, int h) {
for (int y = l; y < h; ++y) {
memcpy((uint8_t *)out + rowPitch * y, image + w * 4 * y, w * 4);
}
}, 0, h);
}, 0, h, MIN_LINES_PER_THREAD);
free(image);
}
+1 -24
View File
@@ -1,26 +1,3 @@
#include "ThreadPools.h"
#include "../Core/Config.h"
#include "Common/MakeUnique.h"
std::unique_ptr<ThreadPool> GlobalThreadPool::pool;
std::once_flag GlobalThreadPool::init_flag;
void GlobalThreadPool::Loop(const std::function<void(int,int)>& loop, int lower, int upper, int minSize) {
std::call_once(init_flag, Inititialize);
pool->ParallelLoop(loop, lower, upper, minSize);
}
void GlobalThreadPool::Memcpy(void *dest, const void *src, int size) {
std::call_once(init_flag, Inititialize);
pool->ParallelMemcpy(dest, src, size);
}
void GlobalThreadPool::Memset(void *dest, uint8_t val, int size) {
std::call_once(init_flag, Inititialize);
pool->ParallelMemset(dest, val, size);
}
void GlobalThreadPool::Inititialize() {
pool = make_unique<ThreadPool>(g_Config.iNumWorkerThreads);
}
ThreadManager g_threadManager;
+2 -14
View File
@@ -1,17 +1,5 @@
#pragma once
#include "Common/Thread/ThreadPool.h"
#include "Common/Thread/ThreadManager.h"
class GlobalThreadPool {
public:
// will execute slices of "loop" from "lower" to "upper"
// in parallel on the global thread pool
static void Loop(const std::function<void(int,int)>& loop, int lower, int upper, int minSize = -1);
static void Memcpy(void *dest, const void *src, int size);
static void Memset(void *dest, uint8_t val, int size);
private:
static std::unique_ptr<ThreadPool> pool;
static std::once_flag init_flag;
static void Inititialize();
};
extern ThreadManager g_threadManager;
+24 -6
View File
@@ -49,6 +49,26 @@
GameManager g_GameManager;
static struct zip *ZipOpenPath(Path fileName) {
int error = 0;
// Need to special case for content URI here, similar to OpenCFile.
struct zip *z;
#if PPSSPP_PLATFORM(ANDROID)
if (fileName.Type() == PathType::CONTENT_URI) {
int fd = File::OpenFD(fileName, File::OPEN_READ);
z = zip_fdopen(fd, 0, &error);
} else
#endif
{ // continuation of above else in the ifdef
z = zip_open(fileName.c_str(), 0, &error);
}
if (!z) {
ERROR_LOG(HLE, "Failed to open ZIP file '%s', error code=%i", fileName.c_str(), error);
}
return z;
}
GameManager::GameManager() {
}
@@ -131,7 +151,7 @@ bool GameManager::Uninstall(std::string name) {
void GameManager::Update() {
if (curDownload_.get() && curDownload_->Done()) {
INFO_LOG(HLE, "Download completed! Status = %d", curDownload_->ResultCode());
Path fileName = Path(curDownload_->outfile());
Path fileName = curDownload_->outfile();
if (curDownload_->ResultCode() == 200) {
if (!File::Exists(fileName)) {
ERROR_LOG(HLE, "Downloaded file '%s' does not exist :(", fileName.c_str());
@@ -172,8 +192,7 @@ static void countSlashes(const std::string &fileName, int *slashLocation, int *s
}
ZipFileContents DetectZipFileContents(const Path &fileName, ZipFileInfo *info) {
int error = 0;
struct zip *z = zip_open(fileName.c_str(), 0, &error);
struct zip *z = ZipOpenPath(fileName);
if (!z) {
return ZipFileContents::UNKNOWN;
}
@@ -283,10 +302,9 @@ bool GameManager::InstallGame(Path url, Path fileName, bool deleteAfter) {
Path dest = pspGame;
int error = 0;
// TODO(scoped): zip_open ain't gonna work. zip_fdopen though..
struct zip *z = zip_open(fileName.c_str(), 0, &error);
struct zip *z = ZipOpenPath(fileName);
if (!z) {
ERROR_LOG(HLE, "Failed to open ZIP file '%s', error code=%i", fileName.c_str(), error);
installInProgress_ = false;
return false;
}
+7
View File
@@ -25,6 +25,7 @@
#include "Common/Log.h"
#include "Core/Reporting.h"
#include "GPU/GPUState.h"
#include "GPU/Common/GPUStateUtils.h"
#include "GPU/Common/DepalettizeShaderCommon.h"
#define WRITE p+=sprintf
@@ -71,6 +72,12 @@ void GenerateDepalShader300(char *buffer, GEBufferFormat pixelFormat, ShaderLang
WRITE(p, "out vec4 fragColor0;\n");
WRITE(p, "uniform sampler2D tex;\n");
WRITE(p, "uniform sampler2D pal;\n");
if (pixelFormat == GE_FORMAT_DEPTH16) {
DepthScaleFactors factors = GetDepthScaleFactors();
WRITE(p, "const float z_scale = %f;\n", factors.scale);
WRITE(p, "const float z_offset = %f;\n", factors.offset);
}
}
if (language == HLSL_D3D11) {
+14 -8
View File
@@ -575,9 +575,6 @@ void ConvertViewportAndScissor(bool useBufferedRendering, float renderWidth, flo
renderHeightFactor = renderHeight / 272.0f;
}
_assert_(renderWidthFactor > 0.0);
_assert_(renderHeightFactor > 0.0);
renderX = gstate_c.curRTOffsetX;
// Scissor
@@ -667,6 +664,12 @@ void ConvertViewportAndScissor(bool useBufferedRendering, float renderWidth, flo
left += overageLeft;
right -= overageRight;
// Protect against the viewport being entirely outside the scissor.
// Emit a tiny but valid viewport. Really, we should probably emit a flag to ignore draws.
if (right <= left) {
right = left + 1.0f;
}
wScale = vpWidth / (right - left);
xOffset = drift / (right - left);
}
@@ -690,6 +693,12 @@ void ConvertViewportAndScissor(bool useBufferedRendering, float renderWidth, flo
top += overageTop;
bottom -= overageBottom;
// Protect against the viewport being entirely outside the scissor.
// Emit a tiny but valid viewport. Really, we should probably emit a flag to ignore draws.
if (bottom <= top) {
bottom = top + 1.0f;
}
hScale = vpHeight / (bottom - top);
yOffset = drift / (bottom - top);
}
@@ -697,11 +706,8 @@ void ConvertViewportAndScissor(bool useBufferedRendering, float renderWidth, flo
out.viewportX = left * renderWidthFactor + displayOffsetX;
out.viewportY = top * renderHeightFactor + displayOffsetY;
// The calculations should end up with zero or positive values, but let's protect against any
// precision issues. See #13921.
out.viewportW = std::max(0.0f, (right - left) * renderWidthFactor);
out.viewportH = std::max(0.0f, (bottom - top) * renderHeightFactor);
out.viewportW = (right - left) * renderWidthFactor;
out.viewportH = (bottom - top) * renderHeightFactor;
// The depth viewport parameters are the same, but we handle it a bit differently.
// When clipping is enabled, depth is clamped to [0, 65535]. And minz/maxz discard.
+1
View File
@@ -127,6 +127,7 @@ void LoadPostShaderInfo(const std::vector<Path> &directories) {
section.Get("Upscaling", &info.isUpscalingFilter, false);
section.Get("SSAA", &info.SSAAFilterLevel, 0);
section.Get("60fps", &info.requires60fps, false);
section.Get("UsePreviousFrame", &info.usePreviousFrame, false);
if (info.parent == "Off")
info.parent = "";
+2
View File
@@ -43,6 +43,8 @@ struct ShaderInfo {
int SSAAFilterLevel;
// Force constant/max refresh for animated filters
bool requires60fps;
// Takes previous frame as input (for blending effects.)
bool usePreviousFrame;
struct Setting {
std::string name;
+103 -40
View File
@@ -185,6 +185,10 @@ void PresentationCommon::CalculatePostShaderUniforms(int bufferWidth, int buffer
uniforms->pixelDelta[0] = u_pixel_delta;
uniforms->pixelDelta[1] = v_pixel_delta;
memcpy(uniforms->time, time, 4 * sizeof(float));
uniforms->timeDelta[0] = time[0] - previousUniforms_.time[0];
uniforms->timeDelta[1] = (time[2] - previousUniforms_.time[2]) * (1.0f / 60.0f);
uniforms->timeDelta[2] = time[2] - previousUniforms_.time[2];
uniforms->timeDelta[3] = time[3] != previousUniforms_.time[3] ? 1.0f : 0.0f;
uniforms->video = hasVideo_ ? 1.0f : 0.0f;
// The shader translator tacks this onto our shaders, if we don't set it they render garbage.
@@ -222,12 +226,35 @@ bool PresentationCommon::UpdatePostShader() {
if (shaderInfo.empty())
return false;
bool usePreviousFrame = false;
bool usePreviousAtOutputResolution = false;
for (int i = 0; i < shaderInfo.size(); ++i) {
const ShaderInfo *next = i + 1 < shaderInfo.size() ? shaderInfo[i + 1] : nullptr;
if (!BuildPostShader(shaderInfo[i], next)) {
DestroyPostShader();
return false;
}
if (shaderInfo[i]->usePreviousFrame) {
usePreviousFrame = true;
usePreviousAtOutputResolution = shaderInfo[i]->outputResolution;
}
}
if (usePreviousFrame) {
int w = usePreviousAtOutputResolution ? pixelWidth_ : renderWidth_;
int h = usePreviousAtOutputResolution ? pixelHeight_ : renderHeight_;
static constexpr int FRAMES = 2;
previousFramebuffers_.resize(FRAMES);
previousIndex_ = 0;
for (int i = 0; i < FRAMES; ++i) {
previousFramebuffers_[i] = draw_->CreateFramebuffer({ w, h, 1, 1, false, "inter_presentation" });
if (!previousFramebuffers_[i]) {
DestroyPostShader();
return false;
}
}
}
usePostShader_ = true;
@@ -263,8 +290,9 @@ bool PresentationCommon::BuildPostShader(const ShaderInfo *shaderInfo, const Sha
{ "u_texelDelta", 1, 1, UniformType::FLOAT2, offsetof(PostShaderUniforms, texelDelta) },
{ "u_pixelDelta", 2, 2, UniformType::FLOAT2, offsetof(PostShaderUniforms, pixelDelta) },
{ "u_time", 3, 3, UniformType::FLOAT4, offsetof(PostShaderUniforms, time) },
{ "u_setting", 4, 4, UniformType::FLOAT4, offsetof(PostShaderUniforms, setting) },
{ "u_video", 5, 5, UniformType::FLOAT1, offsetof(PostShaderUniforms, video) },
{ "u_timeDelta", 4, 4, UniformType::FLOAT4, offsetof(PostShaderUniforms, timeDelta) },
{ "u_setting", 5, 5, UniformType::FLOAT4, offsetof(PostShaderUniforms, setting) },
{ "u_video", 6, 6, UniformType::FLOAT1, offsetof(PostShaderUniforms, video) },
} };
Draw::Pipeline *pipeline = CreatePipeline({ vs, fs }, true, &postShaderDesc);
@@ -467,6 +495,7 @@ void PresentationCommon::DestroyPostShader() {
DoReleaseVector(postShaderModules_);
DoReleaseVector(postShaderPipelines_);
DoReleaseVector(postShaderFramebuffers_);
DoReleaseVector(previousFramebuffers_);
postShaderInfo_.clear();
postShaderFBOUsage_.clear();
}
@@ -531,7 +560,7 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u
bool useNearest = flags & OutputFlags::NEAREST;
const bool usePostShader = usePostShader_ && !(flags & OutputFlags::RB_SWIZZLE);
const bool isFinalAtOutputResolution = usePostShader && postShaderFramebuffers_.size() < postShaderPipelines_.size();
bool usePostShaderOutput = false;
Draw::Framebuffer *postShaderOutput = nullptr;
int lastWidth = srcWidth_;
int lastHeight = srcHeight_;
@@ -615,6 +644,47 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u
}
}
// Grab the previous framebuffer early so we can change previousIndex_ when we want.
Draw::Framebuffer *previousFramebuffer = previousFramebuffers_.empty() ? nullptr : previousFramebuffers_[previousIndex_];
PostShaderUniforms uniforms;
const auto performShaderPass = [&](const ShaderInfo *shaderInfo, Draw::Framebuffer *postShaderFramebuffer, Draw::Pipeline *postShaderPipeline) {
if (postShaderOutput) {
draw_->BindFramebufferAsTexture(postShaderOutput, 0, Draw::FB_COLOR_BIT, 0);
} else {
BindSource(0);
}
BindSource(1);
if (shaderInfo->usePreviousFrame)
draw_->BindFramebufferAsTexture(previousFramebuffer, 2, Draw::FB_COLOR_BIT, 0);
int nextWidth, nextHeight;
draw_->GetFramebufferDimensions(postShaderFramebuffer, &nextWidth, &nextHeight);
Draw::Viewport viewport{ 0, 0, (float)nextWidth, (float)nextHeight, 0.0f, 1.0f };
draw_->SetViewports(1, &viewport);
draw_->SetScissorRect(0, 0, nextWidth, nextHeight);
CalculatePostShaderUniforms(lastWidth, lastHeight, nextWidth, nextHeight, shaderInfo, &uniforms);
draw_->BindPipeline(postShaderPipeline);
draw_->UpdateDynamicUniformBuffer(&uniforms, sizeof(uniforms));
Draw::SamplerState *sampler = useNearest || shaderInfo->isUpscalingFilter ? samplerNearest_ : samplerLinear_;
draw_->BindSamplerStates(0, 1, &sampler);
draw_->BindSamplerStates(1, 1, &sampler);
if (shaderInfo->usePreviousFrame)
draw_->BindSamplerStates(2, 1, &sampler);
draw_->BindVertexBuffers(0, 1, &vdata_, &postVertsOffset);
draw_->BindIndexBuffer(idata_, 0);
draw_->DrawIndexed(6, 0);
draw_->BindIndexBuffer(nullptr, 0);
postShaderOutput = postShaderFramebuffer;
lastWidth = nextWidth;
lastHeight = nextHeight;
};
if (usePostShader) {
bool flipped = flags & OutputFlags::POSITION_FLIPPED;
float post_v0 = !flipped ? 1.0f : 0.0f;
@@ -629,40 +699,17 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u
Draw::Pipeline *postShaderPipeline = postShaderPipelines_[i];
const ShaderInfo *shaderInfo = &postShaderInfo_[i];
Draw::Framebuffer *postShaderFramebuffer = postShaderFramebuffers_[i];
if (!isFinalAtOutputResolution && i == postShaderFramebuffers_.size() - 1 && !previousFramebuffers_.empty()) {
// This is the last pass and we're going direct to the backbuffer after this.
// Redirect output to a separate framebuffer to keep the previous frame.
previousIndex_++;
if (previousIndex_ >= previousFramebuffers_.size())
previousIndex_ = 0;
postShaderFramebuffer = previousFramebuffers_[previousIndex_];
}
draw_->BindFramebufferAsRenderTarget(postShaderFramebuffer, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "PostShader");
if (usePostShaderOutput) {
draw_->BindFramebufferAsTexture(postShaderFramebuffers_[i - 1], 0, Draw::FB_COLOR_BIT, 0);
} else {
BindSource(0);
}
BindSource(1);
int nextWidth, nextHeight;
draw_->GetFramebufferDimensions(postShaderFramebuffer, &nextWidth, &nextHeight);
Draw::Viewport viewport{ 0, 0, (float)nextWidth, (float)nextHeight, 0.0f, 1.0f };
draw_->SetViewports(1, &viewport);
draw_->SetScissorRect(0, 0, nextWidth, nextHeight);
PostShaderUniforms uniforms;
CalculatePostShaderUniforms(lastWidth, lastHeight, nextWidth, nextHeight, shaderInfo, &uniforms);
draw_->BindPipeline(postShaderPipeline);
draw_->UpdateDynamicUniformBuffer(&uniforms, sizeof(uniforms));
Draw::SamplerState *sampler = useNearest || shaderInfo->isUpscalingFilter ? samplerNearest_ : samplerLinear_;
draw_->BindSamplerStates(0, 1, &sampler);
draw_->BindSamplerStates(1, 1, &sampler);
draw_->BindVertexBuffers(0, 1, &vdata_, &postVertsOffset);
draw_->BindIndexBuffer(idata_, 0);
draw_->DrawIndexed(6, 0);
draw_->BindIndexBuffer(nullptr, 0);
usePostShaderOutput = true;
lastWidth = nextWidth;
lastHeight = nextHeight;
performShaderPass(shaderInfo, postShaderFramebuffer, postShaderPipeline);
}
if (isFinalAtOutputResolution && postShaderInfo_.back().isUpscalingFilter)
@@ -671,8 +718,23 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u
draw_->UpdateBuffer(vdata_, (const uint8_t *)verts, 0, postVertsOffset, Draw::UPDATE_DISCARD);
}
// If we need to save the previous frame, we have to save any final pass in a framebuffer.
if (isFinalAtOutputResolution && !previousFramebuffers_.empty()) {
Draw::Pipeline *postShaderPipeline = postShaderPipelines_.back();
const ShaderInfo *shaderInfo = &postShaderInfo_.back();
// Pick the next to render to.
previousIndex_++;
if (previousIndex_ >= previousFramebuffers_.size())
previousIndex_ = 0;
Draw::Framebuffer *postShaderFramebuffer = previousFramebuffers_[previousIndex_];
draw_->BindFramebufferAsRenderTarget(postShaderFramebuffer, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "InterFrameBlit");
performShaderPass(shaderInfo, postShaderFramebuffer, postShaderPipeline);
}
Draw::Pipeline *pipeline = flags & OutputFlags::RB_SWIZZLE ? texColorRBSwizzle_ : texColor_;
if (isFinalAtOutputResolution) {
if (isFinalAtOutputResolution && previousFramebuffers_.empty()) {
pipeline = postShaderPipelines_.back();
}
@@ -681,15 +743,14 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u
draw_->BindPipeline(pipeline);
if (usePostShaderOutput) {
draw_->BindFramebufferAsTexture(postShaderFramebuffers_.back(), 0, Draw::FB_COLOR_BIT, 0);
if (postShaderOutput) {
draw_->BindFramebufferAsTexture(postShaderOutput, 0, Draw::FB_COLOR_BIT, 0);
} else {
BindSource(0);
}
BindSource(1);
if (isFinalAtOutputResolution) {
PostShaderUniforms uniforms;
if (isFinalAtOutputResolution && previousFramebuffers_.empty()) {
CalculatePostShaderUniforms(lastWidth, lastHeight, (int)rc.w, (int)rc.h, &postShaderInfo_.back(), &uniforms);
draw_->UpdateDynamicUniformBuffer(&uniforms, sizeof(uniforms));
} else {
@@ -732,6 +793,8 @@ void PresentationCommon::CopyToOutput(OutputFlags flags, int uvRotation, float u
// Unbinds all textures and samplers too, needed since sometimes a MakePixelTexture is deleted etc.
draw_->BindPipeline(nullptr);
previousUniforms_ = uniforms;
}
void PresentationCommon::CalculateRenderResolution(int *width, int *height, int *scaleFactor, bool *upscaling, bool *ssaa) {
+4
View File
@@ -31,6 +31,7 @@ struct CardboardSettings {
struct PostShaderUniforms {
float texelDelta[2]; float pixelDelta[2];
float time[4];
float timeDelta[4];
float setting[4];
float video; float pad[3];
// Used on Direct3D9.
@@ -131,6 +132,9 @@ protected:
std::vector<Draw::Pipeline *> postShaderPipelines_;
std::vector<Draw::Framebuffer *> postShaderFramebuffers_;
std::vector<ShaderInfo> postShaderInfo_;
std::vector<Draw::Framebuffer *> previousFramebuffers_;
int previousIndex_ = 0;
PostShaderUniforms previousUniforms_{};
Draw::Texture *srcTexture_ = nullptr;
Draw::Framebuffer *srcFramebuffer_ = nullptr;
+40 -61
View File
@@ -367,7 +367,7 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
int bufw = GetTextureBufw(0, texaddr, format);
u8 maxLevel = gstate.getTextureMaxLevel();
u32 texhash = MiniHash((const u32 *)Memory::GetPointerUnchecked(texaddr));
u32 minihash = MiniHash((const u32 *)Memory::GetPointerUnchecked(texaddr));
TexCache::iterator entryIter = cache_.find(cachekey);
TexCacheEntry *entry = nullptr;
@@ -437,7 +437,7 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
rehash = true;
}
if (texhash != entry->hash) {
if (minihash != entry->minihash) {
match = false;
reason = "minihash";
} else if (entry->GetHashStatus() == TexCacheEntry::STATUS_RELIABLE) {
@@ -548,7 +548,7 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
// We have to decode it, let's setup the cache entry first.
entry->addr = texaddr;
entry->hash = texhash;
entry->minihash = minihash;
entry->dim = dim;
entry->format = format;
entry->maxLevel = maxLevel;
@@ -1306,6 +1306,39 @@ static inline void ConvertFormatToRGBA8888(GEPaletteFormat format, u32 *dst, con
ConvertFormatToRGBA8888(GETextureFormat(format), dst, src, numPixels);
}
template <typename DXTBlock, int n>
static void DecodeDXTBlock(uint8_t *out, int outPitch, uint32_t texaddr, const uint8_t *texptr, int w, int h, int bufw, bool reverseColors, bool useBGRA) {
int minw = std::min(bufw, w);
uint32_t *dst = (uint32_t *)out;
int outPitch32 = outPitch / sizeof(uint32_t);
const DXTBlock *src = (const DXTBlock *)texptr;
if (!Memory::IsValidRange(texaddr, (h / 4) * (bufw / 4) * sizeof(DXTBlock))) {
ERROR_LOG_REPORT(G3D, "DXT%d texture extends beyond valid RAM: %08x + %d x %d", n, texaddr, bufw, h);
uint32_t limited = Memory::ValidSize(texaddr, (h / 4) * (bufw / 4) * sizeof(DXTBlock));
// This might possibly be 0, but try to decode what we can (might even be how the PSP behaves.)
h = (((int)limited / sizeof(DXTBlock)) / (bufw / 4)) * 4;
}
for (int y = 0; y < h; y += 4) {
u32 blockIndex = (y / 4) * (bufw / 4);
int blockHeight = std::min(h - y, 4);
for (int x = 0; x < minw; x += 4) {
if (n == 1)
DecodeDXT1Block(dst + outPitch32 * y + x, (const DXT1Block *)src + blockIndex, outPitch32, blockHeight, false);
if (n == 3)
DecodeDXT3Block(dst + outPitch32 * y + x, (const DXT3Block *)src + blockIndex, outPitch32, blockHeight);
if (n == 5)
DecodeDXT5Block(dst + outPitch32 * y + x, (const DXT5Block *)src + blockIndex, outPitch32, blockHeight);
blockIndex++;
}
}
w = (w + 3) & ~3;
if (reverseColors) {
ReverseColors(out, out, GE_TFMT_8888, outPitch32 * h, useBGRA);
}
}
void TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, GETextureFormat format, GEPaletteFormat clutformat, uint32_t texaddr, int level, int bufw, bool reverseColors, bool useBGRA, bool expandTo32bit) {
bool swizzled = gstate.isTextureSwizzled();
if ((texaddr & 0x00600000) != 0 && Memory::IsVRAMAddress(texaddr)) {
@@ -1481,70 +1514,16 @@ void TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, GETextureForm
break;
case GE_TFMT_DXT1:
{
int minw = std::min(bufw, w);
u32 *dst = (u32 *)out;
int outPitch32 = outPitch / sizeof(u32);
DXT1Block *src = (DXT1Block*)texptr;
for (int y = 0; y < h; y += 4) {
u32 blockIndex = (y / 4) * (bufw / 4);
int blockHeight = std::min(h - y, 4);
for (int x = 0; x < minw; x += 4) {
DecodeDXT1Block(dst + outPitch32 * y + x, src + blockIndex, outPitch32, blockHeight, false);
blockIndex++;
}
}
w = (w + 3) & ~3;
if (reverseColors) {
ReverseColors(out, out, GE_TFMT_8888, outPitch32 * h, useBGRA);
}
DecodeDXTBlock<DXT1Block, 1>(out, outPitch, texaddr, texptr, w, h, bufw, reverseColors, useBGRA);
break;
}
case GE_TFMT_DXT3:
{
int minw = std::min(bufw, w);
u32 *dst = (u32 *)out;
int outPitch32 = outPitch / sizeof(u32);
DXT3Block *src = (DXT3Block*)texptr;
for (int y = 0; y < h; y += 4) {
u32 blockIndex = (y / 4) * (bufw / 4);
int blockHeight = std::min(h - y, 4);
for (int x = 0; x < minw; x += 4) {
DecodeDXT3Block(dst + outPitch32 * y + x, src + blockIndex, outPitch32, blockHeight);
blockIndex++;
}
}
w = (w + 3) & ~3;
if (reverseColors) {
ReverseColors(out, out, GE_TFMT_8888, outPitch32 * h, useBGRA);
}
DecodeDXTBlock<DXT3Block, 3>(out, outPitch, texaddr, texptr, w, h, bufw, reverseColors, useBGRA);
break;
}
case GE_TFMT_DXT5:
{
int minw = std::min(bufw, w);
u32 *dst = (u32 *)out;
int outPitch32 = outPitch / sizeof(u32);
DXT5Block *src = (DXT5Block*)texptr;
for (int y = 0; y < h; y += 4) {
u32 blockIndex = (y / 4) * (bufw / 4);
int blockHeight = std::min(h - y, 4);
for (int x = 0; x < minw; x += 4) {
DecodeDXT5Block(dst + outPitch32 * y + x, src + blockIndex, outPitch32, blockHeight);
blockIndex++;
}
}
w = (w + 3) & ~3;
if (reverseColors) {
ReverseColors(out, out, GE_TFMT_8888, outPitch32 * h, useBGRA);
}
DecodeDXTBlock<DXT5Block, 5>(out, outPitch, texaddr, texptr, w, h, bufw, reverseColors, useBGRA);
break;
}
default:
ERROR_LOG_REPORT(G3D, "Unknown Texture Format %d!!!", format);
@@ -1843,7 +1822,7 @@ void TextureCacheCommon::Invalidate(u32 addr, int size, GPUInvalidationType type
if (type == GPU_INVALIDATE_FORCE) {
// Just random values to force the hash not to match.
entry->fullhash = (entry->fullhash ^ 0x12345678) + 13;
entry->hash = (entry->hash ^ 0x89ABCDEF) + 89;
entry->minihash = (entry->minihash ^ 0x89ABCDEF) + 89;
}
if (type != GPU_INVALIDATE_ALL) {
gpuStats.numTextureInvalidations++;
+1 -1
View File
@@ -139,7 +139,7 @@ struct TexCacheEntry {
int status;
u32 addr;
u32 hash;
u32 minihash;
u32 sizeInRAM; // Could be computed
u8 format; // GeTextureFormat
u8 maxLevel;
Loaded 100 of 198 files, more files were not shown because too many files have changed in this diff. Show more