First Commit
This commit is contained in:
50
common/AlignedMalloc.cpp
Normal file
50
common/AlignedMalloc.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
// This module contains implementations of _aligned_malloc for platforms that don't have
|
||||
// it built into their CRT/libc.
|
||||
|
||||
#if !defined(_WIN32)
|
||||
|
||||
#include "common/AlignedMalloc.h"
|
||||
#include "common/Assertions.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
void* _aligned_malloc(size_t size, size_t align)
|
||||
{
|
||||
pxAssert(align < 0x10000);
|
||||
#if defined(__USE_ISOC11) && !defined(ASAN_WORKAROUND) // not supported yet on gcc 4.9
|
||||
return aligned_alloc(align, size);
|
||||
#else
|
||||
#ifdef __APPLE__
|
||||
// MacOS has a bug where posix_memalign is ridiculously slow on unaligned sizes
|
||||
// This especially bad on M1s for some reason
|
||||
size = (size + align - 1) & ~(align - 1);
|
||||
#endif
|
||||
void* result = 0;
|
||||
posix_memalign(&result, align, size);
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
void* pcsx2_aligned_realloc(void* handle, size_t new_size, size_t align, size_t old_size)
|
||||
{
|
||||
pxAssert(align < 0x10000);
|
||||
|
||||
void* newbuf = _aligned_malloc(new_size, align);
|
||||
|
||||
if (newbuf != NULL && handle != NULL)
|
||||
{
|
||||
memcpy(newbuf, handle, std::min(old_size, new_size));
|
||||
_aligned_free(handle);
|
||||
}
|
||||
return newbuf;
|
||||
}
|
||||
|
||||
__fi void _aligned_free(void* pmem)
|
||||
{
|
||||
free(pmem);
|
||||
}
|
||||
#endif
|
||||
32
common/AlignedMalloc.h
Normal file
32
common/AlignedMalloc.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Pcsx2Defs.h"
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <new> // std::bad_alloc
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
// Implementation note: all known implementations of _aligned_free check the pointer for
|
||||
// NULL status (our implementation under GCC, and microsoft's under MSVC), so no need to
|
||||
// do it here.
|
||||
#define safe_aligned_free(ptr) \
|
||||
((void)(_aligned_free(ptr), (ptr) = NULL))
|
||||
|
||||
// aligned_malloc: Implement/declare linux equivalents here!
|
||||
#if !defined(_MSC_VER)
|
||||
extern void* _aligned_malloc(size_t size, size_t align);
|
||||
extern void* pcsx2_aligned_realloc(void* handle, size_t new_size, size_t align, size_t old_size);
|
||||
extern void _aligned_free(void* pmem);
|
||||
#else
|
||||
#define pcsx2_aligned_realloc(handle, new_size, align, old_size) \
|
||||
_aligned_realloc(handle, new_size, align)
|
||||
#endif
|
||||
120
common/Assertions.cpp
Normal file
120
common/Assertions.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "Assertions.h"
|
||||
#include "CrashHandler.h"
|
||||
#include "HostSys.h"
|
||||
#include "Threading.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "RedtapeWindows.h"
|
||||
#include <intrin.h>
|
||||
#include <tlhelp32.h>
|
||||
#endif
|
||||
|
||||
#ifdef __UNIX__
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
static std::mutex s_assertion_failed_mutex;
|
||||
|
||||
static inline void FreezeThreads(void** handle)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
||||
if (snapshot != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
THREADENTRY32 threadEntry;
|
||||
if (Thread32First(snapshot, &threadEntry))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (threadEntry.th32ThreadID == GetCurrentThreadId())
|
||||
continue;
|
||||
|
||||
HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, threadEntry.th32ThreadID);
|
||||
if (hThread != nullptr)
|
||||
{
|
||||
SuspendThread(hThread);
|
||||
CloseHandle(hThread);
|
||||
}
|
||||
} while (Thread32Next(snapshot, &threadEntry));
|
||||
}
|
||||
}
|
||||
|
||||
*handle = static_cast<void*>(snapshot);
|
||||
#else
|
||||
* handle = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void ResumeThreads(void* handle)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
if (handle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
THREADENTRY32 threadEntry;
|
||||
if (Thread32First(reinterpret_cast<HANDLE>(handle), &threadEntry))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (threadEntry.th32ThreadID == GetCurrentThreadId())
|
||||
continue;
|
||||
|
||||
HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, threadEntry.th32ThreadID);
|
||||
if (hThread != nullptr)
|
||||
{
|
||||
ResumeThread(hThread);
|
||||
CloseHandle(hThread);
|
||||
}
|
||||
} while (Thread32Next(reinterpret_cast<HANDLE>(handle), &threadEntry));
|
||||
}
|
||||
CloseHandle(reinterpret_cast<HANDLE>(handle));
|
||||
}
|
||||
#else
|
||||
#endif
|
||||
}
|
||||
|
||||
void pxOnAssertFail(const char* file, int line, const char* func, const char* msg)
|
||||
{
|
||||
std::unique_lock guard(s_assertion_failed_mutex);
|
||||
|
||||
void* handle;
|
||||
FreezeThreads(&handle);
|
||||
|
||||
char full_msg[512];
|
||||
std::snprintf(full_msg, sizeof(full_msg), "%s:%d: assertion failed in function %s: %s\n", file, line, func, msg);
|
||||
|
||||
#if defined(_WIN32)
|
||||
HANDLE error_handle = GetStdHandle(STD_ERROR_HANDLE);
|
||||
if (error_handle != INVALID_HANDLE_VALUE)
|
||||
WriteConsoleA(GetStdHandle(STD_ERROR_HANDLE), full_msg, static_cast<DWORD>(std::strlen(full_msg)), NULL, NULL);
|
||||
OutputDebugStringA(full_msg);
|
||||
|
||||
std::snprintf(
|
||||
full_msg, sizeof(full_msg),
|
||||
"Assertion failed in function %s (%s:%d):\n\n%s\n\nPress Abort to exit, Retry to break to debugger, or Ignore to attempt to continue.",
|
||||
func, file, line, msg);
|
||||
|
||||
int result = MessageBoxA(NULL, full_msg, NULL, MB_ABORTRETRYIGNORE | MB_ICONERROR);
|
||||
if (result == IDRETRY)
|
||||
{
|
||||
__debugbreak();
|
||||
}
|
||||
else if (result != IDIGNORE)
|
||||
{
|
||||
// try to save a crash dump before exiting
|
||||
CrashHandler::WriteDumpForCaller();
|
||||
TerminateProcess(GetCurrentProcess(), 0xBAADC0DE);
|
||||
}
|
||||
#else
|
||||
fputs(full_msg, stderr);
|
||||
fputs("\nAborting application.\n", stderr);
|
||||
fflush(stderr);
|
||||
AbortWithMessage(full_msg);
|
||||
#endif
|
||||
|
||||
ResumeThreads(handle);
|
||||
}
|
||||
51
common/Assertions.h
Normal file
51
common/Assertions.h
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
|
||||
#ifndef __pxFUNCTION__
|
||||
#if defined(__GNUG__)
|
||||
#define __pxFUNCTION__ __PRETTY_FUNCTION__
|
||||
#else
|
||||
#define __pxFUNCTION__ __FUNCTION__
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// pxAssertRel - assertion check even in Release builds.
|
||||
// pxFailRel - aborts program even in Release builds.
|
||||
//
|
||||
// pxAssert[Msg] - assertion check only in Debug/Devel builds, noop in Release.
|
||||
// pxAssume[Msg] - assertion check in Debug/Devel builds, optimization hint in Release builds.
|
||||
// pxFail - aborts program only in Debug/Devel builds, noop in Release.
|
||||
|
||||
extern void pxOnAssertFail(const char* file, int line, const char* func, const char* msg);
|
||||
|
||||
#define pxAssertRel(cond, msg) do { if (!(cond)) [[unlikely]] { pxOnAssertFail(__FILE__, __LINE__, __pxFUNCTION__, msg); } } while(0)
|
||||
#define pxFailRel(msg) pxOnAssertFail(__FILE__, __LINE__, __pxFUNCTION__, msg)
|
||||
|
||||
#if defined(PCSX2_DEBUG) || defined(PCSX2_DEVBUILD)
|
||||
|
||||
#define pxAssertMsg(cond, msg) pxAssertRel(cond, msg)
|
||||
#define pxAssumeMsg(cond, msg) pxAssertRel(cond, msg)
|
||||
#define pxFail(msg) pxFailRel(msg)
|
||||
|
||||
#else
|
||||
|
||||
#define pxAssertMsg(cond, msg) ((void)0)
|
||||
#define pxAssumeMsg(cond, msg) ASSUME(cond)
|
||||
#define pxFail(msg) ((void)0)
|
||||
|
||||
#endif
|
||||
|
||||
#define pxAssert(cond) pxAssertMsg(cond, #cond)
|
||||
#define pxAssume(cond) pxAssumeMsg(cond, #cond)
|
||||
|
||||
// jNO_DEFAULT -- disables the default case in a switch, which improves switch optimization.
|
||||
#define jNO_DEFAULT \
|
||||
default: \
|
||||
{ \
|
||||
pxAssumeMsg(false, "Incorrect usage of jNO_DEFAULT detected (default case is not unreachable!)"); \
|
||||
break; \
|
||||
}
|
||||
100
common/BitUtils.h
Normal file
100
common/BitUtils.h
Normal file
@@ -0,0 +1,100 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#include <intrin.h>
|
||||
|
||||
#else
|
||||
|
||||
static inline int _BitScanReverse(unsigned long* const Index, const unsigned long Mask)
|
||||
{
|
||||
if (Mask == 0)
|
||||
return 0;
|
||||
|
||||
// For some reason, clang won't emit bsr if we use std::countl_zeros()...
|
||||
*Index = 31 - __builtin_clz(Mask);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace Common
|
||||
{
|
||||
template <typename T>
|
||||
static constexpr __fi bool IsAligned(T value, unsigned int alignment)
|
||||
{
|
||||
return (value % static_cast<T>(alignment)) == 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static constexpr __fi T AlignUp(T value, unsigned int alignment)
|
||||
{
|
||||
return (value + static_cast<T>(alignment - 1)) / static_cast<T>(alignment) * static_cast<T>(alignment);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static constexpr __fi T AlignDown(T value, unsigned int alignment)
|
||||
{
|
||||
return value / static_cast<T>(alignment) * static_cast<T>(alignment);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static constexpr __fi bool IsAlignedPow2(T value, unsigned int alignment)
|
||||
{
|
||||
return (value & static_cast<T>(alignment - 1)) == 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static constexpr __fi T AlignUpPow2(T value, unsigned int alignment)
|
||||
{
|
||||
return (value + static_cast<T>(alignment - 1)) & static_cast<T>(~static_cast<T>(alignment - 1));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static constexpr __fi T AlignDownPow2(T value, unsigned int alignment)
|
||||
{
|
||||
return value & static_cast<T>(~static_cast<T>(alignment - 1));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static constexpr T PageAlign(T size)
|
||||
{
|
||||
static_assert(std::has_single_bit(__pagesize), "Page size is a power of 2");
|
||||
return Common::AlignUpPow2(size, __pagesize);
|
||||
}
|
||||
|
||||
__fi static u32 CountLeadingSignBits(s32 n)
|
||||
{
|
||||
// If the sign bit is 1, we invert the bits to 0 for count-leading-zero.
|
||||
if (n < 0)
|
||||
n = ~n;
|
||||
|
||||
// If BSR is used directly, it would have an undefined value for 0.
|
||||
if (n == 0)
|
||||
return 32;
|
||||
|
||||
// Perform our count leading zero.
|
||||
return std::countl_zero(static_cast<u32>(n));
|
||||
}
|
||||
} // namespace Common
|
||||
|
||||
template <typename T>
|
||||
[[maybe_unused]] __fi static T GetBufferT(const u8* buffer, u32 offset)
|
||||
{
|
||||
T value;
|
||||
std::memcpy(&value, buffer + offset, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static u8 GetBufferU8(const u8* buffer, u32 offset) { return GetBufferT<u8>(buffer, offset); }
|
||||
[[maybe_unused]] __fi static u16 GetBufferU16(const u8* buffer, u32 offset) { return GetBufferT<u16>(buffer, offset); }
|
||||
[[maybe_unused]] __fi static u32 GetBufferU32(const u8* buffer, u32 offset) { return GetBufferT<u32>(buffer, offset); }
|
||||
[[maybe_unused]] __fi static u64 GetBufferU64(const u8* buffer, u32 offset) { return GetBufferT<u64>(buffer, offset); }
|
||||
44
common/ByteSwap.h
Normal file
44
common/ByteSwap.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
T ByteSwap(T value)
|
||||
{
|
||||
if constexpr (std::is_signed_v<T>)
|
||||
{
|
||||
return static_cast<T>(ByteSwap(std::make_unsigned_t<T>(value)));
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::uint16_t>)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return _byteswap_ushort(value);
|
||||
#else
|
||||
return __builtin_bswap16(value);
|
||||
#endif
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::uint32_t>)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return _byteswap_ulong(value);
|
||||
#else
|
||||
return __builtin_bswap32(value);
|
||||
#endif
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, std::uint64_t>)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return _byteswap_uint64(value);
|
||||
#else
|
||||
return __builtin_bswap64(value);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
224
common/CMakeLists.txt
Normal file
224
common/CMakeLists.txt
Normal file
@@ -0,0 +1,224 @@
|
||||
# Check that people use the good file
|
||||
if(NOT TOP_CMAKE_WAS_SOURCED)
|
||||
message(FATAL_ERROR "
|
||||
You did not 'cmake' the good CMakeLists.txt file. Use the one in the top dir.
|
||||
It is advice to delete all wrongly generated cmake stuff => CMakeFiles & CMakeCache.txt")
|
||||
endif(NOT TOP_CMAKE_WAS_SOURCED)
|
||||
|
||||
add_library(common)
|
||||
|
||||
# x86emitter sources
|
||||
target_sources(common PRIVATE
|
||||
AlignedMalloc.cpp
|
||||
Assertions.cpp
|
||||
Console.cpp
|
||||
CrashHandler.cpp
|
||||
DynamicLibrary.cpp
|
||||
Error.cpp
|
||||
FastJmp.cpp
|
||||
FileSystem.cpp
|
||||
HostSys.cpp
|
||||
Image.cpp
|
||||
HTTPDownloader.cpp
|
||||
MemorySettingsInterface.cpp
|
||||
MD5Digest.cpp
|
||||
PrecompiledHeader.cpp
|
||||
Perf.cpp
|
||||
ProgressCallback.cpp
|
||||
ReadbackSpinManager.cpp
|
||||
Semaphore.cpp
|
||||
SettingsWrapper.cpp
|
||||
SmallString.cpp
|
||||
StringUtil.cpp
|
||||
TextureDecompress.cpp
|
||||
Timer.cpp
|
||||
WAVWriter.cpp
|
||||
WindowInfo.cpp
|
||||
)
|
||||
|
||||
# x86emitter headers
|
||||
target_sources(common PRIVATE
|
||||
AlignedMalloc.h
|
||||
Assertions.h
|
||||
boost_spsc_queue.hpp
|
||||
BitUtils.h
|
||||
ByteSwap.h
|
||||
Console.h
|
||||
CrashHandler.h
|
||||
DynamicLibrary.h
|
||||
Easing.h
|
||||
EnumOps.h
|
||||
Error.h
|
||||
FPControl.h
|
||||
FastJmp.h
|
||||
FileSystem.h
|
||||
HashCombine.h
|
||||
HostSys.h
|
||||
HeterogeneousContainers.h
|
||||
Image.h
|
||||
LRUCache.h
|
||||
HeapArray.h
|
||||
HTTPDownloader.h
|
||||
MemorySettingsInterface.h
|
||||
MD5Digest.h
|
||||
MRCHelpers.h
|
||||
Path.h
|
||||
PrecompiledHeader.h
|
||||
ProgressCallback.h
|
||||
ReadbackSpinManager.h
|
||||
RedtapeWilCom.h
|
||||
RedtapeWindows.h
|
||||
ScopedGuard.h
|
||||
SettingsInterface.h
|
||||
SettingsWrapper.h
|
||||
SingleRegisterTypes.h
|
||||
SmallString.h
|
||||
StringUtil.h
|
||||
Timer.h
|
||||
TextureDecompress.h
|
||||
Threading.h
|
||||
VectorIntrin.h
|
||||
WAVWriter.h
|
||||
WindowInfo.h
|
||||
WrappedMemCopy.h
|
||||
)
|
||||
|
||||
if(_M_X86)
|
||||
target_sources(common PRIVATE
|
||||
emitter/avx.cpp
|
||||
emitter/bmi.cpp
|
||||
emitter/fpu.cpp
|
||||
emitter/groups.cpp
|
||||
emitter/jmp.cpp
|
||||
emitter/legacy.cpp
|
||||
emitter/legacy_sse.cpp
|
||||
emitter/movs.cpp
|
||||
emitter/simd.cpp
|
||||
emitter/x86emitter.cpp
|
||||
)
|
||||
|
||||
target_sources(common PRIVATE
|
||||
emitter/implement/dwshift.h
|
||||
emitter/implement/group1.h
|
||||
emitter/implement/group2.h
|
||||
emitter/implement/group3.h
|
||||
emitter/implement/helpers.h
|
||||
emitter/implement/incdec.h
|
||||
emitter/implement/jmpcall.h
|
||||
emitter/implement/movs.h
|
||||
emitter/implement/simd_arithmetic.h
|
||||
emitter/implement/simd_comparisons.h
|
||||
emitter/implement/simd_helpers.h
|
||||
emitter/implement/simd_moremovs.h
|
||||
emitter/implement/simd_shufflepack.h
|
||||
emitter/implement/simd_templated_helpers.h
|
||||
emitter/implement/test.h
|
||||
emitter/implement/xchg.h
|
||||
emitter/instructions.h
|
||||
emitter/internal.h
|
||||
emitter/legacy_instructions.h
|
||||
emitter/legacy_internal.h
|
||||
emitter/legacy_types.h
|
||||
emitter/x86emitter.h
|
||||
emitter/x86types.h
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
enable_language(ASM_MASM)
|
||||
target_sources(common PRIVATE
|
||||
FastJmp.asm
|
||||
HTTPDownloaderWinHTTP.cpp
|
||||
HTTPDownloaderWinHTTP.h
|
||||
StackWalker.cpp
|
||||
StackWalker.h
|
||||
Windows/WinThreads.cpp
|
||||
Windows/WinHostSys.cpp
|
||||
Windows/WinMisc.cpp
|
||||
)
|
||||
target_link_libraries(common PUBLIC
|
||||
WIL::WIL
|
||||
winmm
|
||||
pathcch
|
||||
)
|
||||
elseif(APPLE)
|
||||
target_sources(common PRIVATE
|
||||
CocoaTools.mm
|
||||
CocoaTools.h
|
||||
Darwin/DarwinThreads.cpp
|
||||
Darwin/DarwinMisc.cpp
|
||||
Darwin/DarwinMisc.h
|
||||
)
|
||||
target_compile_options(common PRIVATE -fobjc-arc)
|
||||
target_link_options(common PRIVATE -fobjc-link-runtime)
|
||||
target_link_libraries(common PRIVATE
|
||||
"-framework Foundation"
|
||||
"-framework IOKit"
|
||||
)
|
||||
else()
|
||||
target_sources(common PRIVATE
|
||||
Linux/LnxHostSys.cpp
|
||||
Linux/LnxThreads.cpp
|
||||
Linux/LnxMisc.cpp
|
||||
)
|
||||
target_include_directories(common PRIVATE
|
||||
${DBUS_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(common PRIVATE
|
||||
${DBUS_LINK_LIBRARIES}
|
||||
X11::X11
|
||||
X11::Xrandr
|
||||
X11::Xi
|
||||
)
|
||||
if(USE_BACKTRACE)
|
||||
target_compile_definitions(common PRIVATE "HAS_LIBBACKTRACE=1")
|
||||
target_link_libraries(common PRIVATE libbacktrace::libbacktrace)
|
||||
endif()
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
|
||||
target_link_libraries(common PRIVATE cpuinfo)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set_source_files_properties(PrecompiledHeader.cpp PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
|
||||
if(USE_VTUNE)
|
||||
target_link_libraries(common PUBLIC Vtune::Vtune)
|
||||
endif()
|
||||
|
||||
if (USE_GCC AND CMAKE_INTERPROCEDURAL_OPTIMIZATION)
|
||||
# GCC LTO doesn't work with asm statements
|
||||
set_source_files_properties(FastJmp.cpp PROPERTIES COMPILE_FLAGS -fno-lto)
|
||||
endif()
|
||||
|
||||
if(NOT WIN32)
|
||||
# libcurl-based HTTPDownloader
|
||||
target_sources(common PRIVATE
|
||||
HTTPDownloaderCurl.cpp
|
||||
HTTPDownloaderCurl.h
|
||||
)
|
||||
target_link_libraries(common PRIVATE
|
||||
CURL::libcurl
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(common PRIVATE
|
||||
${LIBC_LIBRARIES}
|
||||
JPEG::JPEG
|
||||
PNG::PNG
|
||||
WebP::libwebp
|
||||
)
|
||||
|
||||
target_link_libraries(common PUBLIC
|
||||
fmt::fmt
|
||||
fast_float
|
||||
)
|
||||
|
||||
fixup_file_properties(common)
|
||||
target_compile_features(common PUBLIC cxx_std_17)
|
||||
target_include_directories(common PUBLIC ../3rdparty/include ../)
|
||||
target_compile_definitions(common PUBLIC "${PCSX2_DEFS}")
|
||||
target_compile_options(common PRIVATE "${PCSX2_WARNINGS}")
|
||||
|
||||
if(COMMAND target_precompile_headers)
|
||||
target_precompile_headers(common PRIVATE PrecompiledHeader.h)
|
||||
endif()
|
||||
49
common/CocoaTools.h
Normal file
49
common/CocoaTools.h
Normal file
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
struct WindowInfo;
|
||||
|
||||
/// Helper functions for things that need Objective-C
|
||||
namespace CocoaTools
|
||||
{
|
||||
bool CreateMetalLayer(WindowInfo* wi);
|
||||
void DestroyMetalLayer(WindowInfo* wi);
|
||||
std::optional<float> GetViewRefreshRate(const WindowInfo& wi);
|
||||
|
||||
/// Add a handler to be run when macOS changes between dark and light themes
|
||||
void AddThemeChangeHandler(void* ctx, void(handler)(void* ctx));
|
||||
/// Remove a handler previously added using AddThemeChangeHandler with the given context
|
||||
void RemoveThemeChangeHandler(void* ctx);
|
||||
/// Mark an NSMenu as the help menu
|
||||
void MarkHelpMenu(void* menu);
|
||||
/// Returns the bundle path.
|
||||
std::optional<std::string> GetBundlePath();
|
||||
/// Get the bundle path to the actual application without any translocation fun
|
||||
std::optional<std::string> GetNonTranslocatedBundlePath();
|
||||
/// Move the given file to the trash, and return the path to its new location
|
||||
std::optional<std::string> MoveToTrash(std::string_view file);
|
||||
/// Launch the given application once this one quits
|
||||
bool DelayedLaunch(std::string_view file);
|
||||
/// Open a Finder window to the given URL
|
||||
bool ShowInFinder(std::string_view file);
|
||||
/// Get the path to the resources directory of the current application
|
||||
std::optional<std::string> GetResourcePath();
|
||||
|
||||
/// Create a window
|
||||
void* CreateWindow(std::string_view title, uint32_t width, uint32_t height);
|
||||
/// Destroy a window
|
||||
void DestroyWindow(void* window);
|
||||
/// Make a WindowInfo from the given window
|
||||
void GetWindowInfoFromWindow(WindowInfo* wi, void* window);
|
||||
/// Run cocoa event loop
|
||||
void RunCocoaEventLoop(bool wait_forever = false);
|
||||
/// Posts an event to the main telling `RunCocoaEventLoop(true)` to exit
|
||||
void StopMainThreadEventLoop();
|
||||
}
|
||||
|
||||
#endif // __APPLE__
|
||||
326
common/CocoaTools.mm
Normal file
326
common/CocoaTools.mm
Normal file
@@ -0,0 +1,326 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#error "Compile this with -fobjc-arc"
|
||||
#endif
|
||||
|
||||
#include "CocoaTools.h"
|
||||
#include "Console.h"
|
||||
#include "HostSys.h"
|
||||
#include "WindowInfo.h"
|
||||
#include <dlfcn.h>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
|
||||
// MARK: - Metal Layers
|
||||
|
||||
static NSString*_Nonnull NSStringFromStringView(std::string_view sv)
|
||||
{
|
||||
return [[NSString alloc] initWithBytes:sv.data() length:sv.size() encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
bool CocoaTools::CreateMetalLayer(WindowInfo* wi)
|
||||
{
|
||||
if (![NSThread isMainThread])
|
||||
{
|
||||
bool ret;
|
||||
dispatch_sync(dispatch_get_main_queue(), [&ret, wi]{ ret = CreateMetalLayer(wi); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
CAMetalLayer* layer = [CAMetalLayer layer];
|
||||
if (!layer)
|
||||
{
|
||||
Console.Error("Failed to create Metal layer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
NSView* view = (__bridge NSView*)wi->window_handle;
|
||||
[view setWantsLayer:YES];
|
||||
[view setLayer:layer];
|
||||
[layer setContentsScale:[[[view window] screen] backingScaleFactor]];
|
||||
// Store the layer pointer, that way MoltenVK doesn't call [NSView layer] outside the main thread.
|
||||
wi->surface_handle = (__bridge_retained void*)layer;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CocoaTools::DestroyMetalLayer(WindowInfo* wi)
|
||||
{
|
||||
if (![NSThread isMainThread])
|
||||
{
|
||||
dispatch_sync_f(dispatch_get_main_queue(), wi, [](void* ctx){ DestroyMetalLayer(static_cast<WindowInfo*>(ctx)); });
|
||||
return;
|
||||
}
|
||||
|
||||
NSView* view = (__bridge NSView*)wi->window_handle;
|
||||
CAMetalLayer* layer = (__bridge_transfer CAMetalLayer*)wi->surface_handle;
|
||||
if (!layer)
|
||||
return;
|
||||
wi->surface_handle = nullptr;
|
||||
[view setLayer:nil];
|
||||
[view setWantsLayer:NO];
|
||||
}
|
||||
|
||||
std::optional<float> CocoaTools::GetViewRefreshRate(const WindowInfo& wi)
|
||||
{
|
||||
if (![NSThread isMainThread])
|
||||
{
|
||||
std::optional<float> ret;
|
||||
dispatch_sync(dispatch_get_main_queue(), [&ret, wi]{ ret = GetViewRefreshRate(wi); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<float> ret;
|
||||
NSView* const view = (__bridge NSView*)wi.window_handle;
|
||||
const u32 did = [[[[[view window] screen] deviceDescription] valueForKey:@"NSScreenNumber"] unsignedIntValue];
|
||||
if (CGDisplayModeRef mode = CGDisplayCopyDisplayMode(did))
|
||||
{
|
||||
ret = CGDisplayModeGetRefreshRate(mode);
|
||||
CGDisplayModeRelease(mode);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// MARK: - Theme Change Handlers
|
||||
|
||||
@interface PCSX2KVOHelper : NSObject
|
||||
|
||||
- (void)addCallback:(void*)ctx run:(void(*)(void*))callback;
|
||||
- (void)removeCallback:(void*)ctx;
|
||||
|
||||
@end
|
||||
|
||||
@implementation PCSX2KVOHelper
|
||||
{
|
||||
std::vector<std::pair<void*, void(*)(void*)>> _callbacks;
|
||||
}
|
||||
|
||||
- (void)addCallback:(void*)ctx run:(void(*)(void*))callback
|
||||
{
|
||||
_callbacks.push_back(std::make_pair(ctx, callback));
|
||||
}
|
||||
|
||||
- (void)removeCallback:(void*)ctx
|
||||
{
|
||||
auto new_end = std::remove_if(_callbacks.begin(), _callbacks.end(), [ctx](const auto& entry){
|
||||
return ctx == entry.first;
|
||||
});
|
||||
_callbacks.erase(new_end, _callbacks.end());
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
|
||||
{
|
||||
for (const auto& callback : _callbacks)
|
||||
callback.second(callback.first);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static PCSX2KVOHelper* s_themeChangeHandler;
|
||||
|
||||
void CocoaTools::AddThemeChangeHandler(void* ctx, void(handler)(void* ctx))
|
||||
{
|
||||
assert([NSThread isMainThread]);
|
||||
if (!s_themeChangeHandler)
|
||||
{
|
||||
s_themeChangeHandler = [[PCSX2KVOHelper alloc] init];
|
||||
NSApplication* app = [NSApplication sharedApplication];
|
||||
[app addObserver:s_themeChangeHandler
|
||||
forKeyPath:@"effectiveAppearance"
|
||||
options:0
|
||||
context:nil];
|
||||
}
|
||||
[s_themeChangeHandler addCallback:ctx run:handler];
|
||||
}
|
||||
|
||||
void CocoaTools::RemoveThemeChangeHandler(void* ctx)
|
||||
{
|
||||
assert([NSThread isMainThread]);
|
||||
[s_themeChangeHandler removeCallback:ctx];
|
||||
}
|
||||
|
||||
void CocoaTools::MarkHelpMenu(void* menu)
|
||||
{
|
||||
[NSApp setHelpMenu:(__bridge NSMenu*)menu];
|
||||
}
|
||||
|
||||
// MARK: - Sound playback
|
||||
|
||||
bool Common::PlaySoundAsync(const char* path)
|
||||
{
|
||||
NSString* nspath = [[NSString alloc] initWithUTF8String:path];
|
||||
NSSound* sound = [[NSSound alloc] initWithContentsOfFile:nspath byReference:YES];
|
||||
return [sound play];
|
||||
}
|
||||
|
||||
// MARK: - Updater
|
||||
|
||||
std::optional<std::string> CocoaTools::GetBundlePath()
|
||||
{
|
||||
std::optional<std::string> ret;
|
||||
@autoreleasepool {
|
||||
NSURL* url = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
|
||||
if (url)
|
||||
ret = std::string([url fileSystemRepresentation]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<std::string> CocoaTools::GetNonTranslocatedBundlePath()
|
||||
{
|
||||
// See https://objective-see.com/blog/blog_0x15.html
|
||||
|
||||
NSURL* url = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
|
||||
if (!url)
|
||||
return std::nullopt;
|
||||
|
||||
if (void* handle = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY))
|
||||
{
|
||||
auto IsTranslocatedURL = reinterpret_cast<Boolean(*)(CFURLRef path, bool* isTranslocated, CFErrorRef*__nullable error)>(dlsym(handle, "SecTranslocateIsTranslocatedURL"));
|
||||
auto CreateOriginalPathForURL = reinterpret_cast<CFURLRef __nullable(*)(CFURLRef translocatedPath, CFErrorRef*__nullable error)>(dlsym(handle, "SecTranslocateCreateOriginalPathForURL"));
|
||||
bool is_translocated = false;
|
||||
if (IsTranslocatedURL)
|
||||
IsTranslocatedURL((__bridge CFURLRef)url, &is_translocated, nullptr);
|
||||
if (is_translocated)
|
||||
{
|
||||
if (CFURLRef actual = CreateOriginalPathForURL((__bridge CFURLRef)url, nullptr))
|
||||
url = (__bridge_transfer NSURL*)actual;
|
||||
}
|
||||
dlclose(handle);
|
||||
}
|
||||
|
||||
return std::string([url fileSystemRepresentation]);
|
||||
}
|
||||
|
||||
std::optional<std::string> CocoaTools::MoveToTrash(std::string_view file)
|
||||
{
|
||||
NSURL* url = [NSURL fileURLWithPath:NSStringFromStringView(file)];
|
||||
NSURL* new_url;
|
||||
if (![[NSFileManager defaultManager] trashItemAtURL:url resultingItemURL:&new_url error:nil])
|
||||
return std::nullopt;
|
||||
return std::string([new_url fileSystemRepresentation]);
|
||||
}
|
||||
|
||||
bool CocoaTools::DelayedLaunch(std::string_view file)
|
||||
{
|
||||
@autoreleasepool {
|
||||
NSTask* task = [NSTask new];
|
||||
[task setExecutableURL:[NSURL fileURLWithPath:@"/bin/sh"]];
|
||||
[task setEnvironment:@{
|
||||
@"WAITPID": [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]],
|
||||
@"LAUNCHAPP": NSStringFromStringView(file),
|
||||
}];
|
||||
[task setArguments:@[@"-c", @"while /bin/ps -p $WAITPID > /dev/null; do /bin/sleep 0.1; done; exec /usr/bin/open \"$LAUNCHAPP\";"]];
|
||||
return [task launchAndReturnError:nil];
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Directory Services
|
||||
|
||||
bool CocoaTools::ShowInFinder(std::string_view file)
|
||||
{
|
||||
return [[NSWorkspace sharedWorkspace] selectFile:NSStringFromStringView(file)
|
||||
inFileViewerRootedAtPath:@""];
|
||||
}
|
||||
|
||||
std::optional<std::string> CocoaTools::GetResourcePath()
|
||||
{ @autoreleasepool {
|
||||
if (NSBundle* bundle = [NSBundle mainBundle])
|
||||
{
|
||||
NSString* rsrc = [bundle resourcePath];
|
||||
NSString* root = [bundle bundlePath];
|
||||
if ([rsrc isEqualToString:root])
|
||||
rsrc = [rsrc stringByAppendingString:@"/resources"];
|
||||
return [rsrc UTF8String];
|
||||
}
|
||||
return std::nullopt;
|
||||
}}
|
||||
|
||||
// MARK: - GSRunner
|
||||
|
||||
void* CocoaTools::CreateWindow(std::string_view title, u32 width, u32 height)
|
||||
{
|
||||
if (!NSApp)
|
||||
{
|
||||
[NSApplication sharedApplication];
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
[NSApp finishLaunching];
|
||||
}
|
||||
constexpr NSWindowStyleMask style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
|
||||
NSScreen* mainScreen = [NSScreen mainScreen];
|
||||
// Center the window on the screen, because why not
|
||||
NSRect screenFrame = [mainScreen frame];
|
||||
NSRect viewFrame = screenFrame;
|
||||
viewFrame.size = NSMakeSize(width, height);
|
||||
viewFrame.origin.x += (screenFrame.size.width - viewFrame.size.width) / 2;
|
||||
viewFrame.origin.y += (screenFrame.size.height - viewFrame.size.height) / 2;
|
||||
NSWindow* window = [[NSWindow alloc]
|
||||
initWithContentRect:viewFrame
|
||||
styleMask:style
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
[window setTitle:NSStringFromStringView(title)];
|
||||
[window makeKeyAndOrderFront:window];
|
||||
return (__bridge_retained void*)window;
|
||||
}
|
||||
|
||||
void CocoaTools::DestroyWindow(void* window)
|
||||
{
|
||||
(void)(__bridge_transfer NSWindow*)window;
|
||||
}
|
||||
|
||||
void CocoaTools::GetWindowInfoFromWindow(WindowInfo* wi, void* cf_window)
|
||||
{
|
||||
if (cf_window)
|
||||
{
|
||||
NSWindow* window = (__bridge NSWindow*)cf_window;
|
||||
float scale = [window backingScaleFactor];
|
||||
NSView* view = [window contentView];
|
||||
NSRect dims = [view frame];
|
||||
wi->type = WindowInfo::Type::MacOS;
|
||||
wi->window_handle = (__bridge void*)view;
|
||||
wi->surface_width = dims.size.width * scale;
|
||||
wi->surface_height = dims.size.height * scale;
|
||||
wi->surface_scale = scale;
|
||||
}
|
||||
else
|
||||
{
|
||||
wi->type = WindowInfo::Type::Surfaceless;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr short STOP_EVENT_LOOP = 0x100;
|
||||
|
||||
void CocoaTools::RunCocoaEventLoop(bool forever)
|
||||
{
|
||||
NSDate* end = forever ? [NSDate distantFuture] : [NSDate distantPast];
|
||||
[NSApplication sharedApplication]; // Ensure NSApp is initialized
|
||||
while (true)
|
||||
{ @autoreleasepool {
|
||||
NSEvent* ev = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
untilDate:end
|
||||
inMode:NSDefaultRunLoopMode
|
||||
dequeue:YES];
|
||||
if (!ev || ([ev type] == NSEventTypeApplicationDefined && [ev subtype] == STOP_EVENT_LOOP))
|
||||
break;
|
||||
[NSApp sendEvent:ev];
|
||||
}}
|
||||
}
|
||||
|
||||
void CocoaTools::StopMainThreadEventLoop()
|
||||
{ @autoreleasepool {
|
||||
NSEvent* ev = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
|
||||
location:{}
|
||||
modifierFlags:0
|
||||
timestamp:0
|
||||
windowNumber:0
|
||||
context:nil
|
||||
subtype:STOP_EVENT_LOOP
|
||||
data1:0
|
||||
data2:0];
|
||||
[NSApp postEvent:ev atStart:NO];
|
||||
}}
|
||||
510
common/Console.cpp
Normal file
510
common/Console.cpp
Normal file
@@ -0,0 +1,510 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/Console.h"
|
||||
#include "common/Assertions.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/SmallString.h"
|
||||
#include "common/Timer.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "common/RedtapeWindows.h"
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
// Dummy objects, need to get rid of them...
|
||||
ConsoleLogWriter<LOGLEVEL_INFO> Console;
|
||||
ConsoleLogWriter<LOGLEVEL_DEV> DevCon;
|
||||
|
||||
#ifdef _DEBUG
|
||||
ConsoleLogWriter<LOGLEVEL_DEBUG> DbgConWriter;
|
||||
#else
|
||||
NullLogWriter DbgConWriter;
|
||||
#endif
|
||||
|
||||
#define TIMESTAMP_FORMAT_STRING "[{:10.4f}] "
|
||||
#define TIMESTAMP_PRINTF_STRING "[%10.4f] "
|
||||
|
||||
namespace Log
|
||||
{
|
||||
static void WriteToConsole(LOGLEVEL level, ConsoleColors color, std::string_view message);
|
||||
static void WriteToDebug(LOGLEVEL level, ConsoleColors color, std::string_view message);
|
||||
static void WriteToFile(LOGLEVEL level, ConsoleColors color, std::string_view message);
|
||||
|
||||
static void UpdateMaxLevel();
|
||||
|
||||
static void ExecuteCallbacks(LOGLEVEL level, ConsoleColors color, std::string_view message);
|
||||
|
||||
static Common::Timer::Value s_start_timestamp = Common::Timer::GetCurrentValue();
|
||||
|
||||
static LOGLEVEL s_max_level = LOGLEVEL_NONE;
|
||||
static LOGLEVEL s_console_level = LOGLEVEL_NONE;
|
||||
static LOGLEVEL s_debug_level = LOGLEVEL_NONE;
|
||||
static LOGLEVEL s_file_level = LOGLEVEL_NONE;
|
||||
static LOGLEVEL s_host_level = LOGLEVEL_NONE;
|
||||
static bool s_log_timestamps = true;
|
||||
|
||||
static FileSystem::ManagedCFilePtr s_file_handle;
|
||||
static std::string s_file_path;
|
||||
static std::mutex s_file_mutex;
|
||||
|
||||
static HostCallbackType s_host_callback;
|
||||
|
||||
#ifdef _WIN32
|
||||
static HANDLE s_hConsoleStdIn = NULL;
|
||||
static HANDLE s_hConsoleStdOut = NULL;
|
||||
static HANDLE s_hConsoleStdErr = NULL;
|
||||
#endif
|
||||
} // namespace Log
|
||||
|
||||
float Log::GetCurrentMessageTime()
|
||||
{
|
||||
return static_cast<float>(Common::Timer::ConvertValueToSeconds(Common::Timer::GetCurrentValue() - s_start_timestamp));
|
||||
}
|
||||
|
||||
__ri void Log::WriteToConsole(LOGLEVEL level, ConsoleColors color, std::string_view message)
|
||||
{
|
||||
static constexpr std::string_view s_ansi_color_codes[ConsoleColors_Count] = {
|
||||
"\033[0m"sv, // default
|
||||
"\033[30m\033[1m"sv, // black
|
||||
"\033[32m"sv, // green
|
||||
"\033[31m"sv, // red
|
||||
"\033[34m"sv, // blue
|
||||
"\033[35m"sv, // magenta
|
||||
"\033[35m"sv, // orange (FIXME)
|
||||
"\033[37m"sv, // gray
|
||||
"\033[36m"sv, // cyan
|
||||
"\033[33m"sv, // yellow
|
||||
"\033[37m"sv, // white
|
||||
"\033[30m\033[1m"sv, // strong black
|
||||
"\033[31m\033[1m"sv, // strong red
|
||||
"\033[32m\033[1m"sv, // strong green
|
||||
"\033[34m\033[1m"sv, // strong blue
|
||||
"\033[35m\033[1m"sv, // strong magenta
|
||||
"\033[35m\033[1m"sv, // strong orange (FIXME)
|
||||
"\033[37m\033[1m"sv, // strong gray
|
||||
"\033[36m\033[1m"sv, // strong cyan
|
||||
"\033[33m\033[1m"sv, // strong yellow
|
||||
"\033[37m\033[1m"sv, // strong white
|
||||
};
|
||||
|
||||
static constexpr size_t BUFFER_SIZE = 512;
|
||||
|
||||
SmallStackString<BUFFER_SIZE> buffer;
|
||||
buffer.reserve(32 + message.length());
|
||||
buffer.append(s_ansi_color_codes[color]);
|
||||
|
||||
if (s_log_timestamps)
|
||||
buffer.append_format(TIMESTAMP_FORMAT_STRING, Log::GetCurrentMessageTime());
|
||||
|
||||
buffer.append(message);
|
||||
buffer.append('\n');
|
||||
|
||||
#ifdef _WIN32
|
||||
const HANDLE hOutput = (level <= LOGLEVEL_WARNING) ? s_hConsoleStdErr : s_hConsoleStdOut;
|
||||
|
||||
// Convert to UTF-16 first so Unicode characters display correctly. NT is going to do it anyway...
|
||||
wchar_t wbuf[BUFFER_SIZE];
|
||||
wchar_t* wmessage_buf = wbuf;
|
||||
int wmessage_buflen = static_cast<int>(std::size(wbuf) - 1);
|
||||
if (buffer.length() >= std::size(wbuf))
|
||||
{
|
||||
wmessage_buflen = static_cast<int>(buffer.length());
|
||||
wmessage_buf = static_cast<wchar_t*>(std::malloc(static_cast<size_t>(wmessage_buflen) * sizeof(wchar_t)));
|
||||
if (!wmessage_buf)
|
||||
return;
|
||||
}
|
||||
|
||||
const int wmessage_size = MultiByteToWideChar(CP_UTF8, 0, buffer.data(), static_cast<int>(buffer.length()), wmessage_buf, wmessage_buflen);
|
||||
if (wmessage_size <= 0)
|
||||
goto cleanup;
|
||||
|
||||
DWORD chars_written;
|
||||
WriteConsoleW(hOutput, wmessage_buf, static_cast<DWORD>(wmessage_size), &chars_written, nullptr);
|
||||
|
||||
cleanup:
|
||||
if (wmessage_buf != wbuf)
|
||||
std::free(wmessage_buf);
|
||||
#else
|
||||
const int fd = (level <= LOGLEVEL_WARNING) ? STDERR_FILENO : STDOUT_FILENO;
|
||||
write(fd, buffer.data(), buffer.length());
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Log::IsConsoleOutputEnabled()
|
||||
{
|
||||
return (s_console_level > LOGLEVEL_NONE);
|
||||
}
|
||||
|
||||
void Log::SetConsoleOutputLevel(LOGLEVEL level)
|
||||
{
|
||||
if (s_console_level == level)
|
||||
return;
|
||||
|
||||
const bool was_enabled = (s_console_level > LOGLEVEL_NONE);
|
||||
const bool now_enabled = (level > LOGLEVEL_NONE);
|
||||
s_console_level = level;
|
||||
UpdateMaxLevel();
|
||||
|
||||
if (was_enabled == now_enabled)
|
||||
return;
|
||||
|
||||
// Worst that happens here is we write to a bad handle..
|
||||
|
||||
#if defined(_WIN32)
|
||||
static constexpr auto enable_virtual_terminal_processing = [](HANDLE hConsole) {
|
||||
DWORD old_mode;
|
||||
if (!GetConsoleMode(hConsole, &old_mode))
|
||||
return;
|
||||
|
||||
// already enabled?
|
||||
if (old_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
return;
|
||||
|
||||
SetConsoleMode(hConsole, old_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
||||
};
|
||||
|
||||
// On windows, no console is allocated by default on a windows based application
|
||||
static bool console_was_allocated = false;
|
||||
static HANDLE old_stdin = NULL;
|
||||
static HANDLE old_stdout = NULL;
|
||||
static HANDLE old_stderr = NULL;
|
||||
|
||||
if (now_enabled)
|
||||
{
|
||||
old_stdin = GetStdHandle(STD_INPUT_HANDLE);
|
||||
old_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
old_stderr = GetStdHandle(STD_ERROR_HANDLE);
|
||||
|
||||
if (!old_stdout)
|
||||
{
|
||||
// Attach to the parent console if we're running from a command window
|
||||
if (!AttachConsole(ATTACH_PARENT_PROCESS) && !AllocConsole())
|
||||
return;
|
||||
|
||||
s_hConsoleStdIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||
s_hConsoleStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
s_hConsoleStdErr = GetStdHandle(STD_ERROR_HANDLE);
|
||||
|
||||
enable_virtual_terminal_processing(s_hConsoleStdOut);
|
||||
enable_virtual_terminal_processing(s_hConsoleStdErr);
|
||||
|
||||
std::FILE* fp;
|
||||
freopen_s(&fp, "CONIN$", "r", stdin);
|
||||
freopen_s(&fp, "CONOUT$", "w", stdout);
|
||||
freopen_s(&fp, "CONOUT$", "w", stderr);
|
||||
|
||||
console_was_allocated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
s_hConsoleStdIn = old_stdin;
|
||||
s_hConsoleStdOut = old_stdout;
|
||||
s_hConsoleStdErr = old_stderr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (console_was_allocated)
|
||||
{
|
||||
console_was_allocated = false;
|
||||
|
||||
std::FILE* fp;
|
||||
freopen_s(&fp, "NUL:", "w", stderr);
|
||||
freopen_s(&fp, "NUL:", "w", stdout);
|
||||
freopen_s(&fp, "NUL:", "w", stdin);
|
||||
|
||||
SetStdHandle(STD_ERROR_HANDLE, old_stderr);
|
||||
SetStdHandle(STD_OUTPUT_HANDLE, old_stdout);
|
||||
SetStdHandle(STD_INPUT_HANDLE, old_stdin);
|
||||
|
||||
s_hConsoleStdIn = NULL;
|
||||
s_hConsoleStdOut = NULL;
|
||||
s_hConsoleStdErr = NULL;
|
||||
|
||||
FreeConsole();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
__ri void Log::WriteToDebug(LOGLEVEL level, ConsoleColors color, std::string_view message)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
static constexpr size_t BUFFER_SIZE = 512;
|
||||
|
||||
// Convert to UTF-16 first so Unicode characters display correctly. NT is going to do it anyway...
|
||||
wchar_t wbuf[BUFFER_SIZE];
|
||||
wchar_t* wmessage_buf = wbuf;
|
||||
int wmessage_buflen = static_cast<int>(std::size(wbuf) - 1);
|
||||
if (message.length() >= std::size(wbuf))
|
||||
{
|
||||
wmessage_buflen = static_cast<int>(message.length());
|
||||
wmessage_buf = static_cast<wchar_t*>(std::malloc((static_cast<size_t>(wmessage_buflen) + 2) * sizeof(wchar_t)));
|
||||
if (!wmessage_buf)
|
||||
return;
|
||||
}
|
||||
|
||||
int wmessage_size = 0;
|
||||
if (!message.empty()) [[likely]]
|
||||
{
|
||||
wmessage_size = MultiByteToWideChar(CP_UTF8, 0, message.data(), static_cast<int>(message.length()), wmessage_buf, wmessage_buflen);
|
||||
if (wmessage_size <= 0)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
wmessage_buf[wmessage_size++] = L'\n';
|
||||
wmessage_buf[wmessage_size++] = 0;
|
||||
OutputDebugStringW(wmessage_buf);
|
||||
|
||||
cleanup:
|
||||
if (wmessage_buf != wbuf)
|
||||
std::free(wmessage_buf);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Log::IsDebugOutputAvailable()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return IsDebuggerPresent();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Log::IsDebugOutputEnabled()
|
||||
{
|
||||
return (s_console_level > LOGLEVEL_NONE);
|
||||
}
|
||||
|
||||
void Log::SetDebugOutputLevel(LOGLEVEL level)
|
||||
{
|
||||
s_debug_level = level;
|
||||
UpdateMaxLevel();
|
||||
}
|
||||
|
||||
__ri void Log::WriteToFile(LOGLEVEL level, ConsoleColors color, std::string_view message)
|
||||
{
|
||||
std::unique_lock lock(s_file_mutex);
|
||||
if (!s_file_handle) [[unlikely]]
|
||||
return;
|
||||
|
||||
if (!message.empty()) [[likely]]
|
||||
{
|
||||
if (s_log_timestamps)
|
||||
{
|
||||
std::fprintf(s_file_handle.get(), TIMESTAMP_PRINTF_STRING "%.*s\n", GetCurrentMessageTime(),
|
||||
static_cast<int>(message.size()), message.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fprintf(s_file_handle.get(), "%.*s\n", static_cast<int>(message.size()), message.data());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s_log_timestamps)
|
||||
{
|
||||
std::fprintf(s_file_handle.get(), TIMESTAMP_PRINTF_STRING "\n", GetCurrentMessageTime());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fputc('\n', s_file_handle.get());
|
||||
}
|
||||
}
|
||||
|
||||
std::fflush(s_file_handle.get());
|
||||
}
|
||||
|
||||
bool Log::IsFileOutputEnabled()
|
||||
{
|
||||
return (s_file_level > LOGLEVEL_NONE);
|
||||
}
|
||||
|
||||
bool Log::SetFileOutputLevel(LOGLEVEL level, std::string path)
|
||||
{
|
||||
std::unique_lock lock(s_file_mutex);
|
||||
|
||||
const bool was_enabled = (s_file_level > LOGLEVEL_NONE);
|
||||
const bool new_enabled = (level > LOGLEVEL_NONE && !path.empty());
|
||||
if (was_enabled != new_enabled || (new_enabled && path == s_file_path))
|
||||
{
|
||||
if (new_enabled)
|
||||
{
|
||||
if (!s_file_handle || s_file_path != path)
|
||||
{
|
||||
s_file_handle.reset();
|
||||
s_file_handle = FileSystem::OpenManagedSharedCFile(path.c_str(), "wb", FileSystem::FileShareMode::DenyWrite);
|
||||
if (s_file_handle)
|
||||
{
|
||||
s_file_path = std::move(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
s_file_path = {};
|
||||
|
||||
if (IsConsoleOutputEnabled())
|
||||
WriteToConsole(LOGLEVEL_ERROR, Color_StrongRed, TinyString::from_format("Failed to open log file '{}'", path));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
s_file_handle.reset();
|
||||
s_file_path = {};
|
||||
}
|
||||
}
|
||||
|
||||
s_file_level = s_file_handle ? level : LOGLEVEL_NONE;
|
||||
UpdateMaxLevel();
|
||||
return IsFileOutputEnabled();
|
||||
}
|
||||
|
||||
std::FILE* Log::GetFileLogHandle()
|
||||
{
|
||||
std::unique_lock lock(s_file_mutex);
|
||||
return s_file_handle.get();
|
||||
}
|
||||
|
||||
bool Log::IsHostOutputEnabled()
|
||||
{
|
||||
return (s_host_level > LOGLEVEL_NONE);
|
||||
}
|
||||
|
||||
void Log::SetHostOutputLevel(LOGLEVEL level, HostCallbackType callback)
|
||||
{
|
||||
s_host_callback = callback;
|
||||
s_host_level = callback ? level : LOGLEVEL_NONE;
|
||||
UpdateMaxLevel();
|
||||
}
|
||||
|
||||
bool Log::AreTimestampsEnabled()
|
||||
{
|
||||
return s_log_timestamps;
|
||||
}
|
||||
|
||||
void Log::SetTimestampsEnabled(bool enabled)
|
||||
{
|
||||
s_log_timestamps = enabled;
|
||||
}
|
||||
|
||||
LOGLEVEL Log::GetMaxLevel()
|
||||
{
|
||||
return s_max_level;
|
||||
}
|
||||
|
||||
__ri void Log::UpdateMaxLevel()
|
||||
{
|
||||
s_max_level = std::max(s_console_level, std::max(s_debug_level, std::max(s_file_level, s_host_level)));
|
||||
}
|
||||
|
||||
void Log::ExecuteCallbacks(LOGLEVEL level, ConsoleColors color, std::string_view message)
|
||||
{
|
||||
// TODO: Cache the message time.
|
||||
|
||||
// Split newlines into separate messages.
|
||||
std::string_view::size_type start_pos = 0;
|
||||
if (std::string_view::size_type end_pos = message.find('\n'); end_pos != std::string::npos) [[unlikely]]
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
std::string_view message_line;
|
||||
if (start_pos != end_pos)
|
||||
message_line = message.substr(start_pos, (end_pos == std::string_view::npos) ? end_pos : end_pos - start_pos);
|
||||
|
||||
ExecuteCallbacks(level, color, message_line);
|
||||
|
||||
if (end_pos == std::string_view::npos)
|
||||
return;
|
||||
|
||||
start_pos = end_pos + 1;
|
||||
end_pos = message.find('\n', start_pos);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
pxAssert(level > LOGLEVEL_NONE);
|
||||
if (level <= s_console_level)
|
||||
WriteToConsole(level, color, message);
|
||||
|
||||
if (level <= s_debug_level)
|
||||
WriteToDebug(level, color, message);
|
||||
|
||||
if (level <= s_file_level)
|
||||
WriteToFile(level, color, message);
|
||||
|
||||
if (level <= s_host_level)
|
||||
{
|
||||
// double check in case of race here
|
||||
const HostCallbackType callback = s_host_callback;
|
||||
if (callback)
|
||||
s_host_callback(level, color, message);
|
||||
}
|
||||
}
|
||||
|
||||
void Log::Write(LOGLEVEL level, ConsoleColors color, std::string_view message)
|
||||
{
|
||||
if (level > s_max_level)
|
||||
return;
|
||||
|
||||
ExecuteCallbacks(level, color, message);
|
||||
}
|
||||
|
||||
void Log::Writef(LOGLEVEL level, ConsoleColors color, const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, format);
|
||||
Writev(level, color, format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void Log::Writev(LOGLEVEL level, ConsoleColors color, const char* format, va_list ap)
|
||||
{
|
||||
if (level > s_max_level)
|
||||
return;
|
||||
|
||||
std::va_list ap_copy;
|
||||
va_copy(ap_copy, ap);
|
||||
|
||||
#ifdef _WIN32
|
||||
const u32 required_size = static_cast<u32>(_vscprintf(format, ap_copy));
|
||||
#else
|
||||
const u32 required_size = std::vsnprintf(nullptr, 0, format, ap_copy);
|
||||
#endif
|
||||
va_end(ap_copy);
|
||||
|
||||
if (required_size < 512)
|
||||
{
|
||||
char buffer[512];
|
||||
const int len = std::vsnprintf(buffer, std::size(buffer), format, ap);
|
||||
if (len > 0)
|
||||
ExecuteCallbacks(level, color, std::string_view(buffer, static_cast<size_t>(len)));
|
||||
}
|
||||
else
|
||||
{
|
||||
char* buffer = new char[required_size + 1];
|
||||
const int len = std::vsnprintf(buffer, required_size + 1, format, ap);
|
||||
if (len > 0)
|
||||
ExecuteCallbacks(level, color, std::string_view(buffer, static_cast<size_t>(len)));
|
||||
delete[] buffer;
|
||||
}
|
||||
}
|
||||
|
||||
void Log::WriteFmtArgs(LOGLEVEL level, ConsoleColors color, fmt::string_view fmt, fmt::format_args args)
|
||||
{
|
||||
if (level > s_max_level)
|
||||
return;
|
||||
|
||||
fmt::memory_buffer buffer;
|
||||
fmt::vformat_to(std::back_inserter(buffer), fmt, args);
|
||||
|
||||
ExecuteCallbacks(level, color, std::string_view(buffer.data(), buffer.size()));
|
||||
}
|
||||
199
common/Console.h
Normal file
199
common/Console.h
Normal file
@@ -0,0 +1,199 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Pcsx2Defs.h"
|
||||
|
||||
#include "fmt/base.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <string>
|
||||
|
||||
// TODO: This whole thing needs to get ripped out.
|
||||
|
||||
enum ConsoleColors
|
||||
{
|
||||
Color_Default = 0,
|
||||
|
||||
Color_Black,
|
||||
Color_Green,
|
||||
Color_Red,
|
||||
Color_Blue,
|
||||
Color_Magenta,
|
||||
Color_Orange,
|
||||
Color_Gray,
|
||||
|
||||
Color_Cyan, // faint visibility, intended for logging PS2/IOP output
|
||||
Color_Yellow, // faint visibility, intended for logging PS2/IOP output
|
||||
Color_White, // faint visibility, intended for logging PS2/IOP output
|
||||
|
||||
// Strong text *may* result in mis-aligned text in the console, depending on the
|
||||
// font and the platform, so use these with caution.
|
||||
Color_StrongBlack,
|
||||
Color_StrongRed, // intended for errors
|
||||
Color_StrongGreen, // intended for infrequent state information
|
||||
Color_StrongBlue, // intended for block headings
|
||||
Color_StrongMagenta,
|
||||
Color_StrongOrange, // intended for warnings
|
||||
Color_StrongGray,
|
||||
|
||||
Color_StrongCyan,
|
||||
Color_StrongYellow,
|
||||
Color_StrongWhite,
|
||||
|
||||
ConsoleColors_Count
|
||||
};
|
||||
|
||||
enum LOGLEVEL
|
||||
{
|
||||
LOGLEVEL_NONE, // Silences all log traffic
|
||||
LOGLEVEL_ERROR,
|
||||
LOGLEVEL_WARNING,
|
||||
LOGLEVEL_INFO,
|
||||
LOGLEVEL_DEV,
|
||||
LOGLEVEL_DEBUG,
|
||||
LOGLEVEL_TRACE,
|
||||
|
||||
LOGLEVEL_COUNT,
|
||||
};
|
||||
|
||||
// TODO: Move this elsewhere, add channels.
|
||||
|
||||
namespace Log
|
||||
{
|
||||
// log message callback type
|
||||
using HostCallbackType = void (*)(LOGLEVEL level, ConsoleColors color, std::string_view message);
|
||||
|
||||
// returns the time in seconds since the start of the process
|
||||
float GetCurrentMessageTime();
|
||||
|
||||
// adds a standard console output
|
||||
bool IsConsoleOutputEnabled();
|
||||
void SetConsoleOutputLevel(LOGLEVEL level);
|
||||
|
||||
// adds a debug console output
|
||||
bool IsDebugOutputAvailable();
|
||||
bool IsDebugOutputEnabled();
|
||||
void SetDebugOutputLevel(LOGLEVEL level);
|
||||
|
||||
// adds a file output
|
||||
bool IsFileOutputEnabled();
|
||||
bool SetFileOutputLevel(LOGLEVEL level, std::string path);
|
||||
|
||||
// returns the log file, this is really dangerous to use if it changes...
|
||||
std::FILE* GetFileLogHandle();
|
||||
|
||||
// adds host output
|
||||
bool IsHostOutputEnabled();
|
||||
void SetHostOutputLevel(LOGLEVEL level, HostCallbackType callback);
|
||||
|
||||
// sets logging timestamps
|
||||
bool AreTimestampsEnabled();
|
||||
void SetTimestampsEnabled(bool enabled);
|
||||
|
||||
// Returns the current global filtering level.
|
||||
LOGLEVEL GetMaxLevel();
|
||||
|
||||
// writes a message to the log
|
||||
void Write(LOGLEVEL level, ConsoleColors color, std::string_view message);
|
||||
void Writef(LOGLEVEL level, ConsoleColors color, const char* format, ...);
|
||||
void Writev(LOGLEVEL level, ConsoleColors color, const char* format, va_list ap);
|
||||
void WriteFmtArgs(LOGLEVEL level, ConsoleColors color, fmt::string_view fmt, fmt::format_args args);
|
||||
|
||||
template <typename... T>
|
||||
__fi static void Write(LOGLEVEL level, ConsoleColors color, fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
// Avoid arg packing if filtered.
|
||||
if (level <= GetMaxLevel())
|
||||
return WriteFmtArgs(level, color, fmt, fmt::make_format_args(args...));
|
||||
}
|
||||
} // namespace Log
|
||||
|
||||
// Adapter classes to handle old code.
|
||||
template <LOGLEVEL level>
|
||||
struct ConsoleLogWriter
|
||||
{
|
||||
__fi static void Error(std::string_view str) { Log::Write(level, Color_StrongRed, str); }
|
||||
__fi static void Warning(std::string_view str) { Log::Write(level, Color_StrongOrange, str); }
|
||||
__fi static void WriteLn(std::string_view str) { Log::Write(level, Color_Default, str); }
|
||||
__fi static void WriteLn(ConsoleColors color, std::string_view str) { Log::Write(level, color, str); }
|
||||
__fi static void WriteLn() { Log::Write(level, Color_Default, std::string_view()); }
|
||||
__fi static void FormatV(const char* format, va_list ap) { Log::Writev(level, Color_Default, format, ap); }
|
||||
__fi static void FormatV(ConsoleColors color, const char* format, va_list ap) { Log::Writev(level, color, format, ap); }
|
||||
|
||||
#define MAKE_PRINTF_CONSOLE_WRITER(color) \
|
||||
do \
|
||||
{ \
|
||||
std::va_list ap; \
|
||||
va_start(ap, format); \
|
||||
Log::Writev(level, color, format, ap); \
|
||||
va_end(ap); \
|
||||
} while (0)
|
||||
|
||||
// clang-format off
|
||||
static void Error(const char* format, ...) { MAKE_PRINTF_CONSOLE_WRITER(Color_StrongRed); }
|
||||
static void Warning(const char* format, ...) { MAKE_PRINTF_CONSOLE_WRITER(Color_StrongOrange); }
|
||||
static void WriteLn(const char* format, ...) { MAKE_PRINTF_CONSOLE_WRITER(Color_Default); }
|
||||
static void WriteLn(ConsoleColors color, const char* format, ...) { MAKE_PRINTF_CONSOLE_WRITER(color); }
|
||||
// clang-format on
|
||||
|
||||
#undef MAKE_PRINTF_CONSOLE_WRITER
|
||||
|
||||
#define MAKE_FMT_CONSOLE_WRITER(color) do \
|
||||
{ \
|
||||
if (level <= Log::GetMaxLevel()) \
|
||||
Log::WriteFmtArgs(level, color, fmt, fmt::make_format_args(args...)); \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
// clang-format off
|
||||
template<typename... T> __fi static void ErrorFmt(fmt::format_string<T...> fmt, T&&... args) { MAKE_FMT_CONSOLE_WRITER(Color_StrongRed); }
|
||||
template<typename... T> __fi static void WarningFmt(fmt::format_string<T...> fmt, T&&... args) { MAKE_FMT_CONSOLE_WRITER(Color_StrongOrange); }
|
||||
template<typename... T> __fi static void WriteLnFmt(fmt::format_string<T...> fmt, T&&... args) { MAKE_FMT_CONSOLE_WRITER(Color_Default); }
|
||||
template<typename... T> __fi static void WriteLnFmt(ConsoleColors color, fmt::format_string<T...> fmt, T&&... args) { MAKE_FMT_CONSOLE_WRITER(color); }
|
||||
// clang-format on
|
||||
|
||||
#undef MAKE_FMT_CONSOLE_WRITER
|
||||
};
|
||||
|
||||
struct NullLogWriter
|
||||
{
|
||||
// clang-format off
|
||||
__fi static bool Error(std::string_view str) { return false; }
|
||||
__fi static bool Warning(std::string_view str) { return false; }
|
||||
__fi static bool WriteLn(std::string_view str) { return false; }
|
||||
__fi static bool WriteLn(ConsoleColors color, std::string_view str) { return false; }
|
||||
__fi static bool WriteLn() { return false; }
|
||||
|
||||
__fi static bool Error(const char* format, ...) { return false; }
|
||||
__fi static bool Warning(const char* format, ...) { return false; }
|
||||
__fi static bool WriteLn(const char* format, ...) { return false; }
|
||||
__fi static bool WriteLn(ConsoleColors color, const char* format, ...) { return false; }
|
||||
|
||||
template<typename... T> __fi static bool ErrorFmt(fmt::format_string<T...> fmt, T&&... args) { return false; }
|
||||
template<typename... T> __fi static bool WarningFmt(fmt::format_string<T...> fmt, T&&... args) { return false; }
|
||||
template<typename... T> __fi static bool WriteLnFmt(fmt::format_string<T...> fmt, T&&... args) { return false; }
|
||||
template<typename... T> __fi static bool WriteLnFmt(ConsoleColors color, fmt::format_string<T...> fmt, T&&... args) { return false; }
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
extern ConsoleLogWriter<LOGLEVEL_INFO> Console;
|
||||
extern ConsoleLogWriter<LOGLEVEL_DEV> DevCon;
|
||||
|
||||
#define ERROR_LOG(...) Log::Write(LOGLEVEL_ERROR, Color_StrongRed, __VA_ARGS__)
|
||||
#define WARNING_LOG(...) Log::Write(LOGLEVEL_WARNING, Color_StrongOrange, __VA_ARGS__)
|
||||
#define INFO_LOG(...) Log::Write(LOGLEVEL_INFO, Color_White, __VA_ARGS__)
|
||||
#define DEV_LOG(...) Log::Write(LOGLEVEL_DEV, Color_StrongGray, __VA_ARGS__)
|
||||
|
||||
#ifdef _DEBUG
|
||||
extern ConsoleLogWriter<LOGLEVEL_DEBUG> DbgConWriter;
|
||||
#define DbgCon DbgConWriter
|
||||
#define DEBUG_LOG(...) Log::Write(LOGLEVEL_TRACE, Color_Gray, __VA_ARGS__)
|
||||
#define TRACE_LOG(...) Log::Write(LOGLEVEL_TRACE, Color_Blue, __VA_ARGS__)
|
||||
#else
|
||||
extern NullLogWriter DbgConWriter;
|
||||
#define DbgCon 0 && DbgConWriter
|
||||
#define DEBUG_LOG(...) (void)0
|
||||
#define TRACE_LOG(...) (void)0
|
||||
#endif
|
||||
380
common/CrashHandler.cpp
Normal file
380
common/CrashHandler.cpp
Normal file
@@ -0,0 +1,380 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "Pcsx2Defs.h"
|
||||
#include "CrashHandler.h"
|
||||
#include "DynamicLibrary.h"
|
||||
#include "FileSystem.h"
|
||||
#include "StringUtil.h"
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include "RedtapeWindows.h"
|
||||
|
||||
#include "StackWalker.h"
|
||||
#include <DbgHelp.h>
|
||||
|
||||
class CrashHandlerStackWalker : public StackWalker
|
||||
{
|
||||
public:
|
||||
explicit CrashHandlerStackWalker(HANDLE out_file);
|
||||
~CrashHandlerStackWalker();
|
||||
|
||||
protected:
|
||||
void OnOutput(LPCSTR szText) override;
|
||||
|
||||
private:
|
||||
HANDLE m_out_file;
|
||||
};
|
||||
|
||||
CrashHandlerStackWalker::CrashHandlerStackWalker(HANDLE out_file)
|
||||
: StackWalker(RetrieveVerbose, nullptr, GetCurrentProcessId(), GetCurrentProcess())
|
||||
, m_out_file(out_file)
|
||||
{
|
||||
}
|
||||
|
||||
CrashHandlerStackWalker::~CrashHandlerStackWalker() = default;
|
||||
|
||||
void CrashHandlerStackWalker::OnOutput(LPCSTR szText)
|
||||
{
|
||||
if (m_out_file)
|
||||
{
|
||||
DWORD written;
|
||||
WriteFile(m_out_file, szText, static_cast<DWORD>(std::strlen(szText)), &written, nullptr);
|
||||
}
|
||||
|
||||
OutputDebugStringA(szText);
|
||||
}
|
||||
|
||||
static bool WriteMinidump(HMODULE hDbgHelp, HANDLE hFile, HANDLE hProcess, DWORD process_id, DWORD thread_id,
|
||||
PEXCEPTION_POINTERS exception, MINIDUMP_TYPE type)
|
||||
{
|
||||
using PFNMINIDUMPWRITEDUMP =
|
||||
BOOL(WINAPI*)(HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType,
|
||||
PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
|
||||
PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
|
||||
|
||||
PFNMINIDUMPWRITEDUMP minidump_write_dump =
|
||||
hDbgHelp ? reinterpret_cast<PFNMINIDUMPWRITEDUMP>(GetProcAddress(hDbgHelp, "MiniDumpWriteDump")) : nullptr;
|
||||
if (!minidump_write_dump)
|
||||
return false;
|
||||
|
||||
MINIDUMP_EXCEPTION_INFORMATION mei = {};
|
||||
if (exception)
|
||||
{
|
||||
mei.ThreadId = thread_id;
|
||||
mei.ExceptionPointers = exception;
|
||||
mei.ClientPointers = FALSE;
|
||||
return minidump_write_dump(hProcess, process_id, hFile, type, &mei, nullptr, nullptr);
|
||||
}
|
||||
|
||||
__try
|
||||
{
|
||||
RaiseException(EXCEPTION_INVALID_HANDLE, 0, 0, nullptr);
|
||||
}
|
||||
__except (WriteMinidump(hDbgHelp, hFile, GetCurrentProcess(), GetCurrentProcessId(), GetCurrentThreadId(),
|
||||
GetExceptionInformation(), type),
|
||||
EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::wstring s_write_directory;
|
||||
static DynamicLibrary s_dbghelp_module;
|
||||
static bool s_in_crash_handler = false;
|
||||
|
||||
static void GenerateCrashFilename(wchar_t* buf, size_t len, const wchar_t* prefix, const wchar_t* extension)
|
||||
{
|
||||
SYSTEMTIME st = {};
|
||||
GetLocalTime(&st);
|
||||
|
||||
_snwprintf_s(buf, len, _TRUNCATE, L"%s%scrash-%04u-%02u-%02u-%02u-%02u-%02u-%03u.%s",
|
||||
prefix ? prefix : L"", prefix ? L"\\" : L"",
|
||||
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, extension);
|
||||
}
|
||||
|
||||
static void WriteMinidumpAndCallstack(PEXCEPTION_POINTERS exi)
|
||||
{
|
||||
s_in_crash_handler = true;
|
||||
|
||||
wchar_t filename[1024] = {};
|
||||
GenerateCrashFilename(filename, std::size(filename), s_write_directory.empty() ? nullptr : s_write_directory.c_str(), L"txt");
|
||||
|
||||
// might fail
|
||||
HANDLE hFile = CreateFileW(filename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
|
||||
if (exi && hFile != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
char line[1024];
|
||||
DWORD written;
|
||||
std::snprintf(line, std::size(line), "Exception 0x%08X at 0x%p\n", static_cast<unsigned>(exi->ExceptionRecord->ExceptionCode),
|
||||
exi->ExceptionRecord->ExceptionAddress);
|
||||
WriteFile(hFile, line, static_cast<DWORD>(std::strlen(line)), &written, nullptr);
|
||||
}
|
||||
|
||||
GenerateCrashFilename(filename, std::size(filename), s_write_directory.empty() ? nullptr : s_write_directory.c_str(), L"dmp");
|
||||
|
||||
const MINIDUMP_TYPE minidump_type =
|
||||
static_cast<MINIDUMP_TYPE>(MiniDumpNormal | MiniDumpWithHandleData | MiniDumpWithProcessThreadData |
|
||||
MiniDumpWithThreadInfo | MiniDumpWithIndirectlyReferencedMemory);
|
||||
const HANDLE hMinidumpFile = CreateFileW(filename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
|
||||
if (hMinidumpFile == INVALID_HANDLE_VALUE ||
|
||||
!WriteMinidump(static_cast<HMODULE>(s_dbghelp_module.GetHandle()), hMinidumpFile, GetCurrentProcess(), GetCurrentProcessId(),
|
||||
GetCurrentThreadId(), exi, minidump_type))
|
||||
{
|
||||
static const char error_message[] = "Failed to write minidump file.\n";
|
||||
if (hFile != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DWORD written;
|
||||
WriteFile(hFile, error_message, sizeof(error_message) - 1, &written, nullptr);
|
||||
}
|
||||
}
|
||||
if (hMinidumpFile != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(hMinidumpFile);
|
||||
|
||||
CrashHandlerStackWalker sw(hFile);
|
||||
sw.ShowCallstack(GetCurrentThread(), exi ? exi->ContextRecord : nullptr);
|
||||
|
||||
if (hFile != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
|
||||
static LONG NTAPI ExceptionHandler(PEXCEPTION_POINTERS exi)
|
||||
{
|
||||
// if the debugger is attached, or we're recursively crashing, let it take care of it.
|
||||
if (!s_in_crash_handler)
|
||||
WriteMinidumpAndCallstack(exi);
|
||||
|
||||
// returning EXCEPTION_CONTINUE_SEARCH makes sense, except for the fact that it seems to leave zombie processes
|
||||
// around. instead, force ourselves to terminate.
|
||||
TerminateProcess(GetCurrentProcess(), 0xFEFEFEFEu);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
bool CrashHandler::Install()
|
||||
{
|
||||
// load dbghelp at install/startup, that way we're not LoadLibrary()'ing after a crash
|
||||
// .. because that probably wouldn't go down well.
|
||||
HMODULE mod = StackWalker::LoadDbgHelpLibrary();
|
||||
if (mod)
|
||||
s_dbghelp_module.Adopt(mod);
|
||||
|
||||
SetUnhandledExceptionFilter(ExceptionHandler);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CrashHandler::SetWriteDirectory(std::string_view dump_directory)
|
||||
{
|
||||
s_write_directory = FileSystem::GetWin32Path(dump_directory);
|
||||
}
|
||||
|
||||
void CrashHandler::WriteDumpForCaller()
|
||||
{
|
||||
WriteMinidumpAndCallstack(nullptr);
|
||||
}
|
||||
|
||||
#elif !defined(__APPLE__) && defined(HAS_LIBBACKTRACE)
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <backtrace.h>
|
||||
#include <signal.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <mutex>
|
||||
|
||||
namespace CrashHandler
|
||||
{
|
||||
struct BacktraceBuffer
|
||||
{
|
||||
char* buffer;
|
||||
size_t used;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static const char* GetSignalName(int signal_no);
|
||||
static void AllocateBuffer(BacktraceBuffer* buf);
|
||||
static void FreeBuffer(BacktraceBuffer* buf);
|
||||
static void AppendToBuffer(BacktraceBuffer* buf, const char* format, ...);
|
||||
static int BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int lineno, const char* function);
|
||||
static void LogCallstack(int signal, const void* exception_pc);
|
||||
|
||||
static std::recursive_mutex s_crash_mutex;
|
||||
static bool s_in_signal_handler = false;
|
||||
|
||||
static backtrace_state* s_backtrace_state = nullptr;
|
||||
} // namespace CrashHandler
|
||||
|
||||
const char* CrashHandler::GetSignalName(int signal_no)
|
||||
{
|
||||
switch (signal_no)
|
||||
{
|
||||
// Don't need to list all of them, there's only a couple we register.
|
||||
// clang-format off
|
||||
case SIGSEGV: return "SIGSEGV";
|
||||
case SIGBUS: return "SIGBUS";
|
||||
default: return "UNKNOWN";
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
|
||||
void CrashHandler::AllocateBuffer(BacktraceBuffer* buf)
|
||||
{
|
||||
buf->used = 0;
|
||||
buf->size = __pagesize;
|
||||
buf->buffer = static_cast<char*>(mmap(nullptr, buf->size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
|
||||
if (buf->buffer == static_cast<char*>(MAP_FAILED))
|
||||
{
|
||||
buf->buffer = nullptr;
|
||||
buf->size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CrashHandler::FreeBuffer(BacktraceBuffer* buf)
|
||||
{
|
||||
if (buf->buffer)
|
||||
munmap(buf->buffer, buf->size);
|
||||
}
|
||||
|
||||
void CrashHandler::AppendToBuffer(BacktraceBuffer* buf, const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, format);
|
||||
|
||||
// Hope this doesn't allocate memory... it *can*, but hopefully unlikely since
|
||||
// it won't be the first call, and we're providing the buffer.
|
||||
if (buf->size > 0 && buf->used < (buf->size - 1))
|
||||
{
|
||||
const int written = std::vsnprintf(buf->buffer + buf->used, buf->size - buf->used, format, ap);
|
||||
if (written > 0)
|
||||
buf->used += static_cast<size_t>(written);
|
||||
}
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
int CrashHandler::BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int lineno,
|
||||
const char* function)
|
||||
{
|
||||
BacktraceBuffer* buf = static_cast<BacktraceBuffer*>(data);
|
||||
AppendToBuffer(buf, " %016p", pc);
|
||||
if (function)
|
||||
AppendToBuffer(buf, " %s", function);
|
||||
if (filename)
|
||||
AppendToBuffer(buf, " [%s:%d]", filename, lineno);
|
||||
|
||||
AppendToBuffer(buf, "\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CrashHandler::LogCallstack(int signal, const void* exception_pc)
|
||||
{
|
||||
BacktraceBuffer buf;
|
||||
AllocateBuffer(&buf);
|
||||
if (signal != 0 || exception_pc)
|
||||
AppendToBuffer(&buf, "*************** Unhandled %s at %p ***************\n", GetSignalName(signal), exception_pc);
|
||||
else
|
||||
AppendToBuffer(&buf, "*******************************************************************\n");
|
||||
|
||||
const int rc = backtrace_full(s_backtrace_state, 0, BacktraceFullCallback, nullptr, &buf);
|
||||
if (rc != 0)
|
||||
AppendToBuffer(&buf, " backtrace_full() failed: %d\n");
|
||||
|
||||
AppendToBuffer(&buf, "*******************************************************************\n");
|
||||
|
||||
if (buf.used > 0)
|
||||
write(STDERR_FILENO, buf.buffer, buf.used);
|
||||
|
||||
FreeBuffer(&buf);
|
||||
}
|
||||
|
||||
void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
|
||||
{
|
||||
std::unique_lock lock(s_crash_mutex);
|
||||
|
||||
// If we crash somewhere in libbacktrace, don't bother trying again.
|
||||
if (!s_in_signal_handler)
|
||||
{
|
||||
s_in_signal_handler = true;
|
||||
|
||||
#if defined(__APPLE__) && defined(__x86_64__)
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__rip);
|
||||
#elif defined(__FreeBSD__) && defined(__x86_64__)
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_rip);
|
||||
#elif defined(__x86_64__)
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.gregs[REG_RIP]);
|
||||
#else
|
||||
void* const exception_pc = nullptr;
|
||||
#endif
|
||||
|
||||
LogCallstack(signal, exception_pc);
|
||||
|
||||
s_in_signal_handler = false;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
|
||||
// We can't continue from here. Just bail out and dump core.
|
||||
std::fputs("Aborting application.\n", stderr);
|
||||
std::fflush(stderr);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
bool CrashHandler::Install()
|
||||
{
|
||||
const std::string progpath = FileSystem::GetProgramPath();
|
||||
s_backtrace_state = backtrace_create_state(progpath.empty() ? nullptr : progpath.c_str(), 0, nullptr, nullptr);
|
||||
if (!s_backtrace_state)
|
||||
return false;
|
||||
|
||||
struct sigaction sa;
|
||||
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_SIGINFO | SA_NODEFER;
|
||||
sa.sa_sigaction = CrashSignalHandler;
|
||||
if (sigaction(SIGBUS, &sa, nullptr) != 0)
|
||||
return false;
|
||||
if (sigaction(SIGSEGV, &sa, nullptr) != 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CrashHandler::SetWriteDirectory(std::string_view dump_directory)
|
||||
{
|
||||
}
|
||||
|
||||
void CrashHandler::WriteDumpForCaller()
|
||||
{
|
||||
LogCallstack(0, nullptr);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool CrashHandler::Install()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void CrashHandler::SetWriteDirectory(std::string_view dump_directory)
|
||||
{
|
||||
}
|
||||
|
||||
void CrashHandler::WriteDumpForCaller()
|
||||
{
|
||||
}
|
||||
|
||||
void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
|
||||
{
|
||||
// We can't continue from here. Just bail out and dump core.
|
||||
std::fputs("Aborting application.\n", stderr);
|
||||
std::fflush(stderr);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
#endif
|
||||
20
common/CrashHandler.h
Normal file
20
common/CrashHandler.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <csignal>
|
||||
#endif
|
||||
|
||||
namespace CrashHandler
|
||||
{
|
||||
bool Install();
|
||||
void SetWriteDirectory(std::string_view dump_directory);
|
||||
void WriteDumpForCaller();
|
||||
|
||||
#ifndef _WIN32
|
||||
// Allow crash handler to be invoked from a signal.
|
||||
void CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx);
|
||||
#endif
|
||||
} // namespace CrashHandler
|
||||
617
common/Darwin/DarwinMisc.cpp
Normal file
617
common/Darwin/DarwinMisc.cpp
Normal file
@@ -0,0 +1,617 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "common/BitUtils.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/CrashHandler.h"
|
||||
#include "common/Darwin/DarwinMisc.h"
|
||||
#include "common/Error.h"
|
||||
#include "common/Pcsx2Types.h"
|
||||
#include "common/Threading.h"
|
||||
#include "common/WindowInfo.h"
|
||||
#include "common/HostSys.h"
|
||||
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <time.h>
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach/mach_port.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#include <mach/task.h>
|
||||
#include <mach/vm_map.h>
|
||||
#include <mutex>
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
#include <IOKit/pwr_mgt/IOPMLib.h>
|
||||
|
||||
// Darwin (OSX) is a bit different from Linux when requesting properties of
|
||||
// the OS because of its BSD/Mach heritage. Helpfully, most of this code
|
||||
// should translate pretty well to other *BSD systems. (e.g.: the sysctl(3)
|
||||
// interface).
|
||||
//
|
||||
// For an overview of all of Darwin's sysctls, check:
|
||||
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/sysctl.3.html
|
||||
|
||||
// Return the total physical memory on the machine, in bytes. Returns 0 on
|
||||
// failure (not supported by the operating system).
|
||||
u64 GetPhysicalMemory()
|
||||
{
|
||||
u64 getmem = 0;
|
||||
size_t len = sizeof(getmem);
|
||||
int mib[] = {CTL_HW, HW_MEMSIZE};
|
||||
if (sysctl(mib, std::size(mib), &getmem, &len, NULL, 0) < 0)
|
||||
perror("sysctl:");
|
||||
return getmem;
|
||||
}
|
||||
|
||||
u64 GetAvailablePhysicalMemory()
|
||||
{
|
||||
const mach_port_t host_port = mach_host_self();
|
||||
vm_size_t page_size;
|
||||
|
||||
// Get the system's page size.
|
||||
if (host_page_size(host_port, &page_size) != KERN_SUCCESS)
|
||||
return 0;
|
||||
|
||||
vm_statistics64_data_t vm_stat;
|
||||
mach_msg_type_number_t host_size = sizeof(vm_statistics64_data_t) / sizeof(integer_t);
|
||||
|
||||
// Get system memory statistics.
|
||||
if (host_statistics64(host_port, HOST_VM_INFO, reinterpret_cast<host_info64_t>(&vm_stat), &host_size) != KERN_SUCCESS)
|
||||
return 0;
|
||||
|
||||
// Get the number of free and inactive pages.
|
||||
const u64 free_pages = static_cast<u64>(vm_stat.free_count);
|
||||
const u64 inactive_pages = static_cast<u64>(vm_stat.inactive_count);
|
||||
|
||||
// Calculate available memory.
|
||||
const u64 get_available_mem = (free_pages + inactive_pages) * page_size;
|
||||
|
||||
return get_available_mem;
|
||||
}
|
||||
|
||||
static mach_timebase_info_data_t s_timebase_info;
|
||||
static const u64 tickfreq = []() {
|
||||
if (mach_timebase_info(&s_timebase_info) != KERN_SUCCESS)
|
||||
abort();
|
||||
return (u64)1e9 * (u64)s_timebase_info.denom / (u64)s_timebase_info.numer;
|
||||
}();
|
||||
|
||||
// returns the performance-counter frequency: ticks per second (Hz)
|
||||
//
|
||||
// usage:
|
||||
// u64 seconds_passed = GetCPUTicks() / GetTickFrequency();
|
||||
// u64 millis_passed = (GetCPUTicks() * 1000) / GetTickFrequency();
|
||||
//
|
||||
// NOTE: multiply, subtract, ... your ticks before dividing by
|
||||
// GetTickFrequency() to maintain good precision.
|
||||
u64 GetTickFrequency()
|
||||
{
|
||||
return tickfreq;
|
||||
}
|
||||
|
||||
// return the number of "ticks" since some arbitrary, fixed time in the
|
||||
// past. On OSX x86(-64), this is actually the number of nanoseconds passed,
|
||||
// because mach_timebase_info.numer == denom == 1. So "ticks" ==
|
||||
// nanoseconds.
|
||||
u64 GetCPUTicks()
|
||||
{
|
||||
return mach_absolute_time();
|
||||
}
|
||||
|
||||
static std::string sysctl_str(int category, int name)
|
||||
{
|
||||
char buf[32];
|
||||
size_t len = sizeof(buf);
|
||||
int mib[] = {category, name};
|
||||
sysctl(mib, std::size(mib), buf, &len, nullptr, 0);
|
||||
return std::string(buf, len > 0 ? len - 1 : 0);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::optional<T> sysctlbyname_T(const char* name)
|
||||
{
|
||||
T output = 0;
|
||||
size_t output_size = sizeof(output);
|
||||
if (sysctlbyname(name, &output, &output_size, nullptr, 0) != 0)
|
||||
return std::nullopt;
|
||||
if (output_size != sizeof(output))
|
||||
{
|
||||
ERROR_LOG("(DarwinMisc) sysctl {} gave unexpected size {}", name, output_size);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string GetOSVersionString()
|
||||
{
|
||||
std::string type = sysctl_str(CTL_KERN, KERN_OSTYPE);
|
||||
std::string release = sysctl_str(CTL_KERN, KERN_OSRELEASE);
|
||||
std::string arch = sysctl_str(CTL_HW, HW_MACHINE);
|
||||
return type + " " + release + " " + arch;
|
||||
}
|
||||
|
||||
static IOPMAssertionID s_pm_assertion;
|
||||
|
||||
bool Common::InhibitScreensaver(bool inhibit)
|
||||
{
|
||||
if (s_pm_assertion)
|
||||
{
|
||||
IOPMAssertionRelease(s_pm_assertion);
|
||||
s_pm_assertion = 0;
|
||||
}
|
||||
|
||||
if (inhibit)
|
||||
IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep, kIOPMAssertionLevelOn, CFSTR("Playing a game"), &s_pm_assertion);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Common::SetMousePosition(int x, int y)
|
||||
{
|
||||
// Little bit ugly but;
|
||||
// Creating mouse move events and posting them wasn't very reliable.
|
||||
// Calling CGWarpMouseCursorPosition without CGAssociateMouseAndMouseCursorPosition(false)
|
||||
// ends up with the cursor feeling "sticky".
|
||||
CGAssociateMouseAndMouseCursorPosition(false);
|
||||
CGWarpMouseCursorPosition(CGPointMake(x, y));
|
||||
CGAssociateMouseAndMouseCursorPosition(true); // The default state
|
||||
return;
|
||||
}
|
||||
|
||||
CFMachPortRef mouseEventTap = nullptr;
|
||||
CFRunLoopSourceRef mouseRunLoopSource = nullptr;
|
||||
|
||||
static std::function<void(int, int)> fnMouseMoveCb;
|
||||
CGEventRef mouseMoveCallback(CGEventTapProxy, CGEventType type, CGEventRef event, void* arg)
|
||||
{
|
||||
if (type == kCGEventMouseMoved)
|
||||
{
|
||||
const CGPoint location = CGEventGetLocation(event);
|
||||
fnMouseMoveCb(location.x, location.y);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
bool Common::AttachMousePositionCb(std::function<void(int, int)> cb)
|
||||
{
|
||||
if (!AXIsProcessTrusted())
|
||||
{
|
||||
Console.Warning("Process isn't trusted with accessibility permissions. Mouse tracking will not work!");
|
||||
}
|
||||
|
||||
fnMouseMoveCb = cb;
|
||||
mouseEventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
|
||||
CGEventMaskBit(kCGEventMouseMoved), mouseMoveCallback, nullptr);
|
||||
if (!mouseEventTap)
|
||||
{
|
||||
Console.Warning("Unable to create mouse moved event tap. Mouse tracking will not work!");
|
||||
return false;
|
||||
}
|
||||
|
||||
mouseRunLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, mouseEventTap, 0);
|
||||
CFRunLoopAddSource(CFRunLoopGetCurrent(), mouseRunLoopSource, kCFRunLoopCommonModes);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Common::DetachMousePositionCb()
|
||||
{
|
||||
if (mouseRunLoopSource)
|
||||
{
|
||||
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mouseRunLoopSource, kCFRunLoopCommonModes);
|
||||
CFRelease(mouseRunLoopSource);
|
||||
}
|
||||
if (mouseEventTap)
|
||||
{
|
||||
CFRelease(mouseEventTap);
|
||||
}
|
||||
mouseRunLoopSource = nullptr;
|
||||
mouseEventTap = nullptr;
|
||||
}
|
||||
|
||||
void Threading::Sleep(int ms)
|
||||
{
|
||||
usleep(1000 * ms);
|
||||
}
|
||||
|
||||
void Threading::SleepUntil(u64 ticks)
|
||||
{
|
||||
// This is definitely sub-optimal, but apparently clock_nanosleep() doesn't exist.
|
||||
const s64 diff = static_cast<s64>(ticks - GetCPUTicks());
|
||||
if (diff <= 0)
|
||||
return;
|
||||
|
||||
const u64 nanos = (static_cast<u64>(diff) * static_cast<u64>(s_timebase_info.denom)) / static_cast<u64>(s_timebase_info.numer);
|
||||
if (nanos == 0)
|
||||
return;
|
||||
|
||||
struct timespec ts;
|
||||
ts.tv_sec = nanos / 1000000000ULL;
|
||||
ts.tv_nsec = nanos % 1000000000ULL;
|
||||
nanosleep(&ts, nullptr);
|
||||
}
|
||||
|
||||
std::vector<DarwinMisc::CPUClass> DarwinMisc::GetCPUClasses()
|
||||
{
|
||||
std::vector<CPUClass> out;
|
||||
|
||||
if (std::optional<u32> nperflevels = sysctlbyname_T<u32>("hw.nperflevels"))
|
||||
{
|
||||
char name[64];
|
||||
for (u32 i = 0; i < *nperflevels; i++)
|
||||
{
|
||||
snprintf(name, sizeof(name), "hw.perflevel%u.physicalcpu", i);
|
||||
std::optional<u32> physicalcpu = sysctlbyname_T<u32>(name);
|
||||
snprintf(name, sizeof(name), "hw.perflevel%u.logicalcpu", i);
|
||||
std::optional<u32> logicalcpu = sysctlbyname_T<u32>(name);
|
||||
|
||||
char levelname[64];
|
||||
size_t levelname_size = sizeof(levelname);
|
||||
snprintf(name, sizeof(name), "hw.perflevel%u.name", i);
|
||||
if (0 != sysctlbyname(name, levelname, &levelname_size, nullptr, 0))
|
||||
strcpy(levelname, "???");
|
||||
|
||||
if (!physicalcpu.has_value() || !logicalcpu.has_value())
|
||||
{
|
||||
Console.Warning("(DarwinMisc) Perf level %u is missing data on %s cpus!",
|
||||
i, !physicalcpu.has_value() ? "physical" : "logical");
|
||||
continue;
|
||||
}
|
||||
|
||||
out.push_back({levelname, *physicalcpu, *logicalcpu});
|
||||
}
|
||||
}
|
||||
else if (std::optional<u32> physcpu = sysctlbyname_T<u32>("hw.physicalcpu"))
|
||||
{
|
||||
out.push_back({"Default", *physcpu, sysctlbyname_T<u32>("hw.logicalcpu").value_or(0)});
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Warning("(DarwinMisc) Couldn't get cpu core count!");
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
size_t HostSys::GetRuntimePageSize()
|
||||
{
|
||||
return sysctlbyname_T<u32>("hw.pagesize").value_or(0);
|
||||
}
|
||||
|
||||
size_t HostSys::GetRuntimeCacheLineSize()
|
||||
{
|
||||
return static_cast<size_t>(std::max<s64>(sysctlbyname_T<s64>("hw.cachelinesize").value_or(0), 0));
|
||||
}
|
||||
|
||||
static __ri vm_prot_t MachProt(const PageProtectionMode& mode)
|
||||
{
|
||||
vm_prot_t machmode = (mode.CanWrite()) ? VM_PROT_WRITE : 0;
|
||||
machmode |= (mode.CanRead()) ? VM_PROT_READ : 0;
|
||||
machmode |= (mode.CanExecute()) ? (VM_PROT_EXECUTE | VM_PROT_READ) : 0;
|
||||
return machmode;
|
||||
}
|
||||
|
||||
void* HostSys::Mmap(void* base, size_t size, const PageProtectionMode& mode)
|
||||
{
|
||||
pxAssertMsg((size & (__pagesize - 1)) == 0, "Size is page aligned");
|
||||
if (mode.IsNone())
|
||||
return nullptr;
|
||||
|
||||
#ifdef __aarch64__
|
||||
// We can't allocate executable memory with mach_vm_allocate() on Apple Silicon.
|
||||
// Instead, we need to use MAP_JIT with mmap(), which does not support fixed mappings.
|
||||
if (mode.CanExecute())
|
||||
{
|
||||
if (base)
|
||||
return nullptr;
|
||||
|
||||
const u32 mmap_prot = mode.CanWrite() ? (PROT_READ | PROT_WRITE | PROT_EXEC) : (PROT_READ | PROT_EXEC);
|
||||
const u32 flags = MAP_PRIVATE | MAP_ANON | MAP_JIT;
|
||||
void* const res = mmap(nullptr, size, mmap_prot, flags, -1, 0);
|
||||
return (res == MAP_FAILED) ? nullptr : res;
|
||||
}
|
||||
#endif
|
||||
|
||||
kern_return_t ret = mach_vm_allocate(mach_task_self(), reinterpret_cast<mach_vm_address_t*>(&base), size,
|
||||
base ? VM_FLAGS_FIXED : VM_FLAGS_ANYWHERE);
|
||||
if (ret != KERN_SUCCESS)
|
||||
{
|
||||
DEV_LOG("mach_vm_allocate() returned {}", ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ret = mach_vm_protect(mach_task_self(), reinterpret_cast<mach_vm_address_t>(base), size, false, MachProt(mode));
|
||||
if (ret != KERN_SUCCESS)
|
||||
{
|
||||
DEV_LOG("mach_vm_protect() returned {}", ret);
|
||||
mach_vm_deallocate(mach_task_self(), reinterpret_cast<mach_vm_address_t>(base), size);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
void HostSys::Munmap(void* base, size_t size)
|
||||
{
|
||||
if (!base)
|
||||
return;
|
||||
|
||||
mach_vm_deallocate(mach_task_self(), reinterpret_cast<mach_vm_address_t>(base), size);
|
||||
}
|
||||
|
||||
void HostSys::MemProtect(void* baseaddr, size_t size, const PageProtectionMode& mode)
|
||||
{
|
||||
pxAssertMsg((size & (__pagesize - 1)) == 0, "Size is page aligned");
|
||||
|
||||
kern_return_t res = mach_vm_protect(mach_task_self(), reinterpret_cast<mach_vm_address_t>(baseaddr), size, false,
|
||||
MachProt(mode));
|
||||
if (res != KERN_SUCCESS) [[unlikely]]
|
||||
{
|
||||
ERROR_LOG("mach_vm_protect() failed: {}", res);
|
||||
pxFailRel("mach_vm_protect() failed");
|
||||
}
|
||||
}
|
||||
|
||||
std::string HostSys::GetFileMappingName(const char* prefix)
|
||||
{
|
||||
// name actually is not used.
|
||||
return {};
|
||||
}
|
||||
|
||||
void* HostSys::CreateSharedMemory(const char* name, size_t size)
|
||||
{
|
||||
mach_vm_size_t vm_size = size;
|
||||
mach_port_t port;
|
||||
const kern_return_t res = mach_make_memory_entry_64(
|
||||
mach_task_self(), &vm_size, 0, MAP_MEM_NAMED_CREATE | VM_PROT_READ | VM_PROT_WRITE, &port, MACH_PORT_NULL);
|
||||
if (res != KERN_SUCCESS)
|
||||
{
|
||||
ERROR_LOG("mach_make_memory_entry_64() failed: {}", res);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return reinterpret_cast<void*>(static_cast<uintptr_t>(port));
|
||||
}
|
||||
|
||||
void HostSys::DestroySharedMemory(void* ptr)
|
||||
{
|
||||
mach_port_deallocate(mach_task_self(), static_cast<mach_port_t>(reinterpret_cast<uintptr_t>(ptr)));
|
||||
}
|
||||
|
||||
void* HostSys::MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, const PageProtectionMode& mode)
|
||||
{
|
||||
mach_vm_address_t ptr = reinterpret_cast<mach_vm_address_t>(baseaddr);
|
||||
const kern_return_t res = mach_vm_map(mach_task_self(), &ptr, size, 0, baseaddr ? VM_FLAGS_FIXED : VM_FLAGS_ANYWHERE,
|
||||
static_cast<mach_port_t>(reinterpret_cast<uintptr_t>(handle)), offset, FALSE,
|
||||
MachProt(mode), VM_PROT_READ | VM_PROT_WRITE, VM_INHERIT_NONE);
|
||||
if (res != KERN_SUCCESS)
|
||||
{
|
||||
ERROR_LOG("mach_vm_map() failed: {}", res);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return reinterpret_cast<void*>(ptr);
|
||||
}
|
||||
|
||||
void HostSys::UnmapSharedMemory(void* baseaddr, size_t size)
|
||||
{
|
||||
const kern_return_t res = mach_vm_deallocate(mach_task_self(), reinterpret_cast<mach_vm_address_t>(baseaddr), size);
|
||||
if (res != KERN_SUCCESS)
|
||||
pxFailRel("Failed to unmap shared memory");
|
||||
}
|
||||
|
||||
#ifdef _M_ARM64
|
||||
|
||||
void HostSys::FlushInstructionCache(void* address, u32 size)
|
||||
{
|
||||
__builtin___clear_cache(reinterpret_cast<char*>(address), reinterpret_cast<char*>(address) + size);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
SharedMemoryMappingArea::SharedMemoryMappingArea(u8* base_ptr, size_t size, size_t num_pages)
|
||||
: m_base_ptr(base_ptr)
|
||||
, m_size(size)
|
||||
, m_num_pages(num_pages)
|
||||
{
|
||||
}
|
||||
|
||||
SharedMemoryMappingArea::~SharedMemoryMappingArea()
|
||||
{
|
||||
pxAssertRel(m_num_mappings == 0, "No mappings left");
|
||||
|
||||
if (mach_vm_deallocate(mach_task_self(), reinterpret_cast<mach_vm_address_t>(m_base_ptr), m_size) != KERN_SUCCESS)
|
||||
pxFailRel("Failed to release shared memory area");
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<SharedMemoryMappingArea> SharedMemoryMappingArea::Create(size_t size)
|
||||
{
|
||||
pxAssertRel(Common::IsAlignedPow2(size, __pagesize), "Size is page aligned");
|
||||
|
||||
mach_vm_address_t alloc = 0;
|
||||
const kern_return_t res =
|
||||
mach_vm_map(mach_task_self(), &alloc, size, 0, VM_FLAGS_ANYWHERE,
|
||||
MEMORY_OBJECT_NULL, 0, false, VM_PROT_NONE, VM_PROT_NONE, VM_INHERIT_NONE);
|
||||
if (res != KERN_SUCCESS)
|
||||
{
|
||||
ERROR_LOG("mach_vm_map() failed: {}", res);
|
||||
return {};
|
||||
}
|
||||
|
||||
return std::unique_ptr<SharedMemoryMappingArea>(new SharedMemoryMappingArea(reinterpret_cast<u8*>(alloc), size, size / __pagesize));
|
||||
}
|
||||
|
||||
u8* SharedMemoryMappingArea::Map(void* file_handle, size_t file_offset, void* map_base, size_t map_size, const PageProtectionMode& mode)
|
||||
{
|
||||
pxAssert(static_cast<u8*>(map_base) >= m_base_ptr && static_cast<u8*>(map_base) < (m_base_ptr + m_size));
|
||||
|
||||
const kern_return_t res =
|
||||
mach_vm_map(mach_task_self(), reinterpret_cast<mach_vm_address_t*>(&map_base), map_size, 0, VM_FLAGS_OVERWRITE,
|
||||
static_cast<mach_port_t>(reinterpret_cast<uintptr_t>(file_handle)), file_offset, false,
|
||||
MachProt(mode), VM_PROT_READ | VM_PROT_WRITE, VM_INHERIT_NONE);
|
||||
if (res != KERN_SUCCESS) [[unlikely]]
|
||||
{
|
||||
ERROR_LOG("mach_vm_map() failed: {}", res);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_num_mappings++;
|
||||
return static_cast<u8*>(map_base);
|
||||
}
|
||||
|
||||
bool SharedMemoryMappingArea::Unmap(void* map_base, size_t map_size)
|
||||
{
|
||||
pxAssert(static_cast<u8*>(map_base) >= m_base_ptr && static_cast<u8*>(map_base) < (m_base_ptr + m_size));
|
||||
|
||||
const kern_return_t res =
|
||||
mach_vm_map(mach_task_self(), reinterpret_cast<mach_vm_address_t*>(&map_base), map_size, 0, VM_FLAGS_OVERWRITE,
|
||||
MEMORY_OBJECT_NULL, 0, false, VM_PROT_NONE, VM_PROT_NONE, VM_INHERIT_NONE);
|
||||
if (res != KERN_SUCCESS) [[unlikely]]
|
||||
{
|
||||
ERROR_LOG("mach_vm_map() failed: {}", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_num_mappings--;
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _M_ARM64
|
||||
|
||||
static thread_local int s_code_write_depth = 0;
|
||||
|
||||
void HostSys::BeginCodeWrite()
|
||||
{
|
||||
if ((s_code_write_depth++) == 0)
|
||||
pthread_jit_write_protect_np(0);
|
||||
}
|
||||
|
||||
void HostSys::EndCodeWrite()
|
||||
{
|
||||
pxAssert(s_code_write_depth > 0);
|
||||
if ((--s_code_write_depth) == 0)
|
||||
pthread_jit_write_protect_np(1);
|
||||
}
|
||||
|
||||
[[maybe_unused]] static bool IsStoreInstruction(const void* ptr)
|
||||
{
|
||||
u32 bits;
|
||||
std::memcpy(&bits, ptr, sizeof(bits));
|
||||
|
||||
// Based on vixl's disassembler Instruction::IsStore().
|
||||
// if (Mask(LoadStoreAnyFMask) != LoadStoreAnyFixed)
|
||||
if ((bits & 0x0a000000) != 0x08000000)
|
||||
return false;
|
||||
|
||||
// if (Mask(LoadStorePairAnyFMask) == LoadStorePairAnyFixed)
|
||||
if ((bits & 0x3a000000) == 0x28000000)
|
||||
{
|
||||
// return Mask(LoadStorePairLBit) == 0
|
||||
return (bits & (1 << 22)) == 0;
|
||||
}
|
||||
|
||||
switch (bits & 0xC4C00000)
|
||||
{
|
||||
case 0x00000000: // STRB_w
|
||||
case 0x40000000: // STRH_w
|
||||
case 0x80000000: // STR_w
|
||||
case 0xC0000000: // STR_x
|
||||
case 0x04000000: // STR_b
|
||||
case 0x44000000: // STR_h
|
||||
case 0x84000000: // STR_s
|
||||
case 0xC4000000: // STR_d
|
||||
case 0x04800000: // STR_q
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _M_ARM64
|
||||
|
||||
namespace PageFaultHandler
|
||||
{
|
||||
static void SignalHandler(int sig, siginfo_t* info, void* ctx);
|
||||
|
||||
static std::recursive_mutex s_exception_handler_mutex;
|
||||
static bool s_in_exception_handler = false;
|
||||
static bool s_installed = false;
|
||||
} // namespace PageFaultHandler
|
||||
|
||||
void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx)
|
||||
{
|
||||
#if defined(_M_X86)
|
||||
void* const exception_address =
|
||||
reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__faultvaddr);
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__rip);
|
||||
const bool is_write = (static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__err & 2) != 0;
|
||||
#elif defined(_M_ARM64)
|
||||
void* const exception_address = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__far);
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__pc);
|
||||
const bool is_write = IsStoreInstruction(exception_pc);
|
||||
#endif
|
||||
|
||||
// Executing the handler concurrently from multiple threads wouldn't go down well.
|
||||
s_exception_handler_mutex.lock();
|
||||
|
||||
// Prevent recursive exception filtering.
|
||||
HandlerResult result = HandlerResult::ExecuteNextHandler;
|
||||
if (!s_in_exception_handler)
|
||||
{
|
||||
s_in_exception_handler = true;
|
||||
result = HandlePageFault(exception_pc, exception_address, is_write);
|
||||
s_in_exception_handler = false;
|
||||
}
|
||||
|
||||
s_exception_handler_mutex.unlock();
|
||||
|
||||
// Resumes execution right where we left off (re-executes instruction that caused the SIGSEGV).
|
||||
if (result == HandlerResult::ContinueExecution)
|
||||
return;
|
||||
|
||||
// We couldn't handle it. Pass it off to the crash dumper.
|
||||
CrashHandler::CrashSignalHandler(sig, info, ctx);
|
||||
}
|
||||
|
||||
bool PageFaultHandler::Install(Error* error)
|
||||
{
|
||||
std::unique_lock lock(s_exception_handler_mutex);
|
||||
pxAssertRel(!s_installed, "Page fault handler has already been installed.");
|
||||
|
||||
struct sigaction sa;
|
||||
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
sa.sa_sigaction = SignalHandler;
|
||||
|
||||
// MacOS uses SIGBUS for memory permission violations, as well as SIGSEGV on ARM64.
|
||||
if (sigaction(SIGBUS, &sa, nullptr) != 0)
|
||||
{
|
||||
Error::SetErrno(error, "sigaction() for SIGBUS failed: ", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _M_ARM64
|
||||
if (sigaction(SIGSEGV, &sa, nullptr) != 0)
|
||||
{
|
||||
Error::SetErrno(error, "sigaction() for SIGSEGV failed: ", errno);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Allow us to ignore faults when running under lldb.
|
||||
task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, MACH_PORT_NULL, EXCEPTION_DEFAULT, 0);
|
||||
|
||||
s_installed = true;
|
||||
return true;
|
||||
}
|
||||
24
common/Darwin/DarwinMisc.h
Normal file
24
common/Darwin/DarwinMisc.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/Pcsx2Types.h"
|
||||
|
||||
namespace DarwinMisc {
|
||||
|
||||
struct CPUClass {
|
||||
std::string name;
|
||||
u32 num_physical;
|
||||
u32 num_logical;
|
||||
};
|
||||
|
||||
std::vector<CPUClass> GetCPUClasses();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
279
common/Darwin/DarwinThreads.cpp
Normal file
279
common/Darwin/DarwinThreads.cpp
Normal file
@@ -0,0 +1,279 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/Threading.h"
|
||||
#include "common/Assertions.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cassert> // assert
|
||||
#include <sched.h>
|
||||
#include <sys/time.h> // gettimeofday()
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_error.h> // mach_error_string()
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach/mach_port.h>
|
||||
#include <mach/mach_time.h> // mach_absolute_time()
|
||||
#include <mach/semaphore.h> // semaphore_*()
|
||||
#include <mach/task.h> // semaphore_create() and semaphore_destroy()
|
||||
#include <mach/thread_act.h>
|
||||
|
||||
// Note: assuming multicore is safer because it forces the interlocked routines to use
|
||||
// the LOCK prefix. The prefix works on single core CPUs fine (but is slow), but not
|
||||
// having the LOCK prefix is very bad indeed.
|
||||
|
||||
__forceinline void Threading::Timeslice()
|
||||
{
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
// For use in spin/wait loops, acts as a hint to Intel CPUs and should, in theory
|
||||
// improve performance and reduce cpu power consumption.
|
||||
__forceinline void Threading::SpinWait()
|
||||
{
|
||||
// If this doesn't compile you can just comment it out (it only serves as a
|
||||
// performance hint and isn't required).
|
||||
#if defined(_M_X86)
|
||||
__asm__("pause");
|
||||
#elif defined(_M_ARM64)
|
||||
__asm__ __volatile__("isb");
|
||||
#endif
|
||||
}
|
||||
|
||||
__forceinline void Threading::EnableHiresScheduler()
|
||||
{
|
||||
// Darwin has customizable schedulers, see xnu/osfmk/man. Not
|
||||
// implemented yet though (and not sure if useful for pcsx2).
|
||||
}
|
||||
|
||||
__forceinline void Threading::DisableHiresScheduler()
|
||||
{
|
||||
// see EnableHiresScheduler()
|
||||
}
|
||||
|
||||
// Just like on Windows, this is not really the number of ticks per second,
|
||||
// but just a factor by which one has to divide GetThreadCpuTime() or
|
||||
// pxThread::GetCpuTime() if one wants to receive a value in seconds. NOTE:
|
||||
// doing this will of course yield precision loss.
|
||||
u64 Threading::GetThreadTicksPerSecond()
|
||||
{
|
||||
return 1000000; // the *CpuTime() functions return values in microseconds
|
||||
}
|
||||
|
||||
// gets the CPU time used by the current thread (both system and user), in
|
||||
// microseconds, returns 0 on failure
|
||||
static u64 getthreadtime(thread_port_t thread)
|
||||
{
|
||||
mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
|
||||
thread_basic_info_data_t info;
|
||||
|
||||
kern_return_t kr = thread_info(thread, THREAD_BASIC_INFO,
|
||||
(thread_info_t)&info, &count);
|
||||
if (kr != KERN_SUCCESS)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// add system and user time
|
||||
return (u64)info.user_time.seconds * (u64)1e6 +
|
||||
(u64)info.user_time.microseconds +
|
||||
(u64)info.system_time.seconds * (u64)1e6 +
|
||||
(u64)info.system_time.microseconds;
|
||||
}
|
||||
|
||||
// Returns the current timestamp (not relative to a real world clock) in microseconds
|
||||
u64 Threading::GetThreadCpuTime()
|
||||
{
|
||||
// we could also use mach_thread_self() and mach_port_deallocate(), but
|
||||
// that calls upon mach traps (kinda like system calls). Unless I missed
|
||||
// something in the COMMPAGE (like Linux vDSO) which makes overrides it
|
||||
// to be user-space instead. In contract,
|
||||
// pthread_mach_thread_np(pthread_self()) is entirely in user-space.
|
||||
u64 us = getthreadtime(pthread_mach_thread_np(pthread_self()));
|
||||
return us;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Semaphore Implementation for Darwin/OSX
|
||||
//
|
||||
// Sadly, Darwin/OSX needs its own implementation of Semaphores instead of
|
||||
// relying on phtreads, because OSX unnamed semaphore (the best kind)
|
||||
// support is very poor.
|
||||
//
|
||||
// This implementation makes use of Mach primitives instead. These are also
|
||||
// what Grand Central Dispatch (GCD) is based on, as far as I understand:
|
||||
// http://newosxbook.com/articles/GCD.html.
|
||||
//
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
static void MACH_CHECK(kern_return_t mach_retval)
|
||||
{
|
||||
if (mach_retval != KERN_SUCCESS)
|
||||
{
|
||||
fprintf(stderr, "mach error: %s", mach_error_string(mach_retval));
|
||||
assert(mach_retval == KERN_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
Threading::KernelSemaphore::KernelSemaphore()
|
||||
{
|
||||
MACH_CHECK(semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, 0));
|
||||
}
|
||||
|
||||
Threading::KernelSemaphore::~KernelSemaphore()
|
||||
{
|
||||
MACH_CHECK(semaphore_destroy(mach_task_self(), m_sema));
|
||||
}
|
||||
|
||||
void Threading::KernelSemaphore::Post()
|
||||
{
|
||||
MACH_CHECK(semaphore_signal(m_sema));
|
||||
}
|
||||
|
||||
void Threading::KernelSemaphore::Wait()
|
||||
{
|
||||
MACH_CHECK(semaphore_wait(m_sema));
|
||||
}
|
||||
|
||||
bool Threading::KernelSemaphore::TryWait()
|
||||
{
|
||||
mach_timespec_t time = {};
|
||||
kern_return_t res = semaphore_timedwait(m_sema, time);
|
||||
if (res == KERN_OPERATION_TIMED_OUT)
|
||||
return false;
|
||||
MACH_CHECK(res);
|
||||
return true;
|
||||
}
|
||||
|
||||
Threading::ThreadHandle::ThreadHandle() = default;
|
||||
|
||||
Threading::ThreadHandle::ThreadHandle(const ThreadHandle& handle)
|
||||
: m_native_handle(handle.m_native_handle)
|
||||
{
|
||||
}
|
||||
|
||||
Threading::ThreadHandle::ThreadHandle(ThreadHandle&& handle)
|
||||
: m_native_handle(handle.m_native_handle)
|
||||
{
|
||||
handle.m_native_handle = nullptr;
|
||||
}
|
||||
|
||||
Threading::ThreadHandle::~ThreadHandle() = default;
|
||||
|
||||
Threading::ThreadHandle Threading::ThreadHandle::GetForCallingThread()
|
||||
{
|
||||
ThreadHandle ret;
|
||||
ret.m_native_handle = pthread_self();
|
||||
return ret;
|
||||
}
|
||||
|
||||
Threading::ThreadHandle& Threading::ThreadHandle::operator=(ThreadHandle&& handle)
|
||||
{
|
||||
m_native_handle = handle.m_native_handle;
|
||||
handle.m_native_handle = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Threading::ThreadHandle& Threading::ThreadHandle::operator=(const ThreadHandle& handle)
|
||||
{
|
||||
m_native_handle = handle.m_native_handle;
|
||||
return *this;
|
||||
}
|
||||
|
||||
u64 Threading::ThreadHandle::GetCPUTime() const
|
||||
{
|
||||
return getthreadtime(pthread_mach_thread_np((pthread_t)m_native_handle));
|
||||
}
|
||||
|
||||
bool Threading::ThreadHandle::SetAffinity(u64 processor_mask) const
|
||||
{
|
||||
// Doesn't appear to be possible to set affinity.
|
||||
return false;
|
||||
}
|
||||
|
||||
Threading::Thread::Thread() = default;
|
||||
|
||||
Threading::Thread::Thread(Thread&& thread)
|
||||
: ThreadHandle(thread)
|
||||
, m_stack_size(thread.m_stack_size)
|
||||
{
|
||||
thread.m_stack_size = 0;
|
||||
}
|
||||
|
||||
Threading::Thread::Thread(EntryPoint func)
|
||||
: ThreadHandle()
|
||||
{
|
||||
if (!Start(std::move(func)))
|
||||
pxFailRel("Failed to start implicitly started thread.");
|
||||
}
|
||||
|
||||
Threading::Thread::~Thread()
|
||||
{
|
||||
pxAssertRel(!m_native_handle, "Thread should be detached or joined at destruction");
|
||||
}
|
||||
|
||||
void Threading::Thread::SetStackSize(u32 size)
|
||||
{
|
||||
pxAssertRel(!m_native_handle, "Can't change the stack size on a started thread");
|
||||
m_stack_size = size;
|
||||
}
|
||||
|
||||
void* Threading::Thread::ThreadProc(void* param)
|
||||
{
|
||||
std::unique_ptr<EntryPoint> entry(static_cast<EntryPoint*>(param));
|
||||
(*entry.get())();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Threading::Thread::Start(EntryPoint func)
|
||||
{
|
||||
pxAssertRel(!m_native_handle, "Can't start an already-started thread");
|
||||
|
||||
std::unique_ptr<EntryPoint> func_clone(std::make_unique<EntryPoint>(std::move(func)));
|
||||
|
||||
pthread_attr_t attrs;
|
||||
bool has_attributes = false;
|
||||
|
||||
if (m_stack_size != 0)
|
||||
{
|
||||
has_attributes = true;
|
||||
pthread_attr_init(&attrs);
|
||||
}
|
||||
if (m_stack_size != 0)
|
||||
pthread_attr_setstacksize(&attrs, m_stack_size);
|
||||
|
||||
pthread_t handle;
|
||||
const int res = pthread_create(&handle, has_attributes ? &attrs : nullptr, ThreadProc, func_clone.get());
|
||||
if (res != 0)
|
||||
return false;
|
||||
|
||||
// thread started, it'll release the memory
|
||||
m_native_handle = (void*)handle;
|
||||
func_clone.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Threading::Thread::Detach()
|
||||
{
|
||||
pxAssertRel(m_native_handle, "Can't detach without a thread");
|
||||
pthread_detach((pthread_t)m_native_handle);
|
||||
m_native_handle = nullptr;
|
||||
}
|
||||
|
||||
void Threading::Thread::Join()
|
||||
{
|
||||
pxAssertRel(m_native_handle, "Can't join without a thread");
|
||||
void* retval;
|
||||
const int res = pthread_join((pthread_t)m_native_handle, &retval);
|
||||
if (res != 0)
|
||||
pxFailRel("pthread_join() for thread join failed");
|
||||
|
||||
m_native_handle = nullptr;
|
||||
}
|
||||
|
||||
// name can be up to 16 bytes
|
||||
void Threading::SetNameOfCurrentThread(const char* name)
|
||||
{
|
||||
pthread_setname_np(name);
|
||||
}
|
||||
166
common/DynamicLibrary.cpp
Normal file
166
common/DynamicLibrary.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/DynamicLibrary.h"
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/Error.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/SmallString.h"
|
||||
#include "common/Path.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include <cstring>
|
||||
#include "fmt/format.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "common/RedtapeWindows.h"
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#ifdef __APPLE__
|
||||
#include "common/CocoaTools.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
DynamicLibrary::DynamicLibrary() = default;
|
||||
|
||||
DynamicLibrary::DynamicLibrary(const char* filename)
|
||||
{
|
||||
Error error;
|
||||
if (!Open(filename, &error))
|
||||
Console.ErrorFmt("DynamicLibrary open failed: {}", error.GetDescription());
|
||||
}
|
||||
|
||||
DynamicLibrary::DynamicLibrary(DynamicLibrary&& move)
|
||||
: m_handle(move.m_handle)
|
||||
{
|
||||
move.m_handle = nullptr;
|
||||
}
|
||||
|
||||
DynamicLibrary::~DynamicLibrary()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
std::string DynamicLibrary::GetUnprefixedFilename(const char* filename)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return std::string(filename) + ".dll";
|
||||
#elif defined(__APPLE__)
|
||||
return std::string(filename) + ".dylib";
|
||||
#else
|
||||
return std::string(filename) + ".so";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string DynamicLibrary::GetVersionedFilename(const char* libname, int major, int minor)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
if (major >= 0 && minor >= 0)
|
||||
return fmt::format("{}-{}-{}.dll", libname, major, minor);
|
||||
else if (major >= 0)
|
||||
return fmt::format("{}-{}.dll", libname, major);
|
||||
else
|
||||
return fmt::format("{}.dll", libname);
|
||||
#elif defined(__APPLE__)
|
||||
const char* prefix = std::strncmp(libname, "lib", 3) ? "lib" : "";
|
||||
if (major >= 0 && minor >= 0)
|
||||
return fmt::format("{}{}.{}.{}.dylib", prefix, libname, major, minor);
|
||||
else if (major >= 0)
|
||||
return fmt::format("{}{}.{}.dylib", prefix, libname, major);
|
||||
else
|
||||
return fmt::format("{}{}.dylib", prefix, libname);
|
||||
#else
|
||||
const char* prefix = std::strncmp(libname, "lib", 3) ? "lib" : "";
|
||||
if (major >= 0 && minor >= 0)
|
||||
return fmt::format("{}{}.so.{}.{}", prefix, libname, major, minor);
|
||||
else if (major >= 0)
|
||||
return fmt::format("{}{}.so.{}", prefix, libname, major);
|
||||
else
|
||||
return fmt::format("{}{}.so", prefix, libname);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DynamicLibrary::Open(const char* filename, Error* error)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
m_handle = reinterpret_cast<void*>(LoadLibraryW(StringUtil::UTF8StringToWideString(filename).c_str()));
|
||||
if (!m_handle)
|
||||
{
|
||||
Error::SetWin32(error, TinyString::from_format("Loading {} failed: ", filename), GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
m_handle = dlopen(filename, RTLD_NOW);
|
||||
if (!m_handle)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
// On MacOS, try searching in Frameworks.
|
||||
if (!Path::IsAbsolute(filename))
|
||||
{
|
||||
std::optional<std::string> bundle_path = CocoaTools::GetBundlePath();
|
||||
if (bundle_path.has_value())
|
||||
{
|
||||
std::string frameworks_path = fmt::format("{}/Contents/Frameworks/{}", bundle_path.value(), filename);
|
||||
if (FileSystem::FileExists(frameworks_path.c_str()))
|
||||
{
|
||||
m_handle = dlopen(frameworks_path.c_str(), RTLD_NOW);
|
||||
if (m_handle)
|
||||
{
|
||||
Error::Clear(error);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* err = dlerror();
|
||||
Error::SetStringFmt(error, "Loading {} failed: {}", filename, err ? err : "<UNKNOWN>");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DynamicLibrary::Adopt(void* handle)
|
||||
{
|
||||
pxAssertRel(handle, "Handle is valid");
|
||||
|
||||
Close();
|
||||
|
||||
m_handle = handle;
|
||||
}
|
||||
|
||||
void DynamicLibrary::Close()
|
||||
{
|
||||
if (!IsOpen())
|
||||
return;
|
||||
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(reinterpret_cast<HMODULE>(m_handle));
|
||||
#else
|
||||
dlclose(m_handle);
|
||||
#endif
|
||||
m_handle = nullptr;
|
||||
}
|
||||
|
||||
void* DynamicLibrary::GetSymbolAddress(const char* name) const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return reinterpret_cast<void*>(GetProcAddress(reinterpret_cast<HMODULE>(m_handle), name));
|
||||
#else
|
||||
return reinterpret_cast<void*>(dlsym(m_handle, name));
|
||||
#endif
|
||||
}
|
||||
|
||||
DynamicLibrary& DynamicLibrary::operator=(DynamicLibrary&& move)
|
||||
{
|
||||
Close();
|
||||
m_handle = move.m_handle;
|
||||
move.m_handle = nullptr;
|
||||
return *this;
|
||||
}
|
||||
79
common/DynamicLibrary.h
Normal file
79
common/DynamicLibrary.h
Normal file
@@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
class Error;
|
||||
|
||||
/**
|
||||
* Provides a platform-independent interface for loading a dynamic library and retrieving symbols.
|
||||
* The interface maintains an internal reference count to allow one handle to be shared between
|
||||
* multiple users.
|
||||
*/
|
||||
class DynamicLibrary final
|
||||
{
|
||||
public:
|
||||
/// Default constructor, does not load a library.
|
||||
DynamicLibrary();
|
||||
|
||||
/// Automatically loads the specified library. Call IsOpen() to check validity before use.
|
||||
DynamicLibrary(const char* filename);
|
||||
|
||||
/// Move constructor, transfers ownership.
|
||||
DynamicLibrary(DynamicLibrary&& move);
|
||||
|
||||
/// Closes the library.
|
||||
~DynamicLibrary();
|
||||
|
||||
/// Returns the specified library name with the platform-specific suffix added.
|
||||
static std::string GetUnprefixedFilename(const char* filename);
|
||||
|
||||
/// Returns the specified library name in platform-specific format.
|
||||
/// Major/minor versions will not be included if set to -1.
|
||||
/// If libname already contains the "lib" prefix, it will not be added again.
|
||||
/// Windows: LIBNAME-MAJOR-MINOR.dll
|
||||
/// Linux: libLIBNAME.so.MAJOR.MINOR
|
||||
/// Mac: libLIBNAME.MAJOR.MINOR.dylib
|
||||
static std::string GetVersionedFilename(const char* libname, int major = -1, int minor = -1);
|
||||
|
||||
/// Returns true if a module is loaded, otherwise false.
|
||||
bool IsOpen() const { return m_handle != nullptr; }
|
||||
|
||||
/// Loads (or replaces) the handle with the specified library file name.
|
||||
/// Returns true if the library was loaded and can be used.
|
||||
bool Open(const char* filename, Error* error);
|
||||
|
||||
/// Adopts, or takes ownership of an existing opened library.
|
||||
void Adopt(void* handle);
|
||||
|
||||
/// Unloads the library, any function pointers from this library are no longer valid.
|
||||
void Close();
|
||||
|
||||
/// Returns the address of the specified symbol (function or variable) as an untyped pointer.
|
||||
/// If the specified symbol does not exist in this library, nullptr is returned.
|
||||
void* GetSymbolAddress(const char* name) const;
|
||||
|
||||
/// Obtains the address of the specified symbol, automatically casting to the correct type.
|
||||
/// Returns true if the symbol was found and assigned, otherwise false.
|
||||
template <typename T>
|
||||
bool GetSymbol(const char* name, T* ptr) const
|
||||
{
|
||||
*ptr = reinterpret_cast<T>(GetSymbolAddress(name));
|
||||
return *ptr != nullptr;
|
||||
}
|
||||
|
||||
/// Returns the opaque OS-specific handle.
|
||||
void* GetHandle() const { return m_handle; }
|
||||
|
||||
/// Move assignment, transfer ownership.
|
||||
DynamicLibrary& operator=(DynamicLibrary&& move);
|
||||
|
||||
private:
|
||||
DynamicLibrary(const DynamicLibrary&) = delete;
|
||||
DynamicLibrary& operator=(const DynamicLibrary&) = delete;
|
||||
|
||||
/// Platform-dependent data type representing a dynamic library handle.
|
||||
void* m_handle = nullptr;
|
||||
};
|
||||
261
common/Easing.h
Normal file
261
common/Easing.h
Normal file
@@ -0,0 +1,261 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include "Pcsx2Defs.h"
|
||||
#include <cmath>
|
||||
|
||||
// From https://github.com/nicolausYes/easing-functions/blob/master/src/easing.cpp
|
||||
|
||||
namespace Easing {
|
||||
static constexpr float pi = 3.1415926545f;
|
||||
|
||||
template<typename T>
|
||||
__ri static T InSine(T t)
|
||||
{
|
||||
return std::sin(1.5707963f * t);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T OutSine(T t)
|
||||
{
|
||||
return 1 + std::sin(1.5707963f * (--t));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InOutSine(T t)
|
||||
{
|
||||
return 0.5f * (1 + std::sin(3.1415926f * (t - 0.5f)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InQuad(T t)
|
||||
{
|
||||
return t * t;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T OutQuad(T t)
|
||||
{
|
||||
return t * (2 - t);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InOutQuad(T t)
|
||||
{
|
||||
return t < 0.5f ? 2 * t * t : t * (4 - 2 * t) - 1;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InCubic(T t)
|
||||
{
|
||||
return t * t * t;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T OutCubic(T t)
|
||||
{
|
||||
return 1 + (--t) * t * t;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InOutCubic(T t)
|
||||
{
|
||||
return t < 0.5f ? 4 * t * t * t : 1 + (--t) * (2 * (--t)) * (2 * t);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InQuart(T t)
|
||||
{
|
||||
t *= t;
|
||||
return t * t;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T OutQuart(T t)
|
||||
{
|
||||
t = (--t) * t;
|
||||
return 1 - t * t;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InOutQuart(T t)
|
||||
{
|
||||
if (t < 0.5)
|
||||
{
|
||||
t *= t;
|
||||
return 8 * t * t;
|
||||
}
|
||||
else
|
||||
{
|
||||
t = (--t) * t;
|
||||
return 1 - 8 * t * t;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InQuint(T t)
|
||||
{
|
||||
T t2 = t * t;
|
||||
return t * t2 * t2;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T OutQuint(T t)
|
||||
{
|
||||
T t2 = (--t) * t;
|
||||
return 1 + t * t2 * t2;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InOutQuint(T t)
|
||||
{
|
||||
T t2;
|
||||
if (t < 0.5)
|
||||
{
|
||||
t2 = t * t;
|
||||
return 16 * t * t2 * t2;
|
||||
}
|
||||
else
|
||||
{
|
||||
t2 = (--t) * t;
|
||||
return 1 + 16 * t * t2 * t2;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InExpo(T t)
|
||||
{
|
||||
return (std::pow(2, 8 * t) - 1) / 255;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T OutExpo(T t)
|
||||
{
|
||||
return 1 - std::pow(2, -8 * t);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InOutExpo(T t)
|
||||
{
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return (std::pow(2, 16 * t) - 1) / 510;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1 - 0.5f * std::pow(2, -16 * (t - 0.5f));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InCirc(T t)
|
||||
{
|
||||
return 1 - std::sqrt(1 - t);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T OutCirc(T t)
|
||||
{
|
||||
return std::sqrt(t);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InOutCirc(T t)
|
||||
{
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return (1 - std::sqrt(1 - 2 * t)) * 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (1 + std::sqrt(2 * t - 1)) * 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InBack(T t)
|
||||
{
|
||||
return t * t * (2.70158f * t - 1.70158f);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static T OutBack(T t)
|
||||
{
|
||||
t -= 1;
|
||||
return 1 + t * t * (2.70158f * t + 1.70158f);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InOutBack(T t)
|
||||
{
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return t * t * (7 * t - 2.5f) * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1 + (--t) * t * 2 * (7 * t + 2.5f);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InElastic(T t)
|
||||
{
|
||||
T t2 = t * t;
|
||||
return t2 * t2 * std::sin(t * pi * 4.5f);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T OutElastic(T t)
|
||||
{
|
||||
T t2 = (t - 1) * (t - 1);
|
||||
return 1 - t2 * t2 * std::cos(t * pi * 4.5f);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InOutElastic(T t)
|
||||
{
|
||||
T t2;
|
||||
if (t < 0.45f)
|
||||
{
|
||||
t2 = t * t;
|
||||
return 8 * t2 * t2 * std::sin(t * pi * 9);
|
||||
}
|
||||
else if (t < 0.55f)
|
||||
{
|
||||
return 0.5f + 0.75f * std::sin(t * pi * 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
t2 = (t - 1) * (t - 1);
|
||||
return 1 - 8 * t2 * t2 * std::sin(t * pi * 9);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InBounce(T t)
|
||||
{
|
||||
return std::pow(2, 6 * (t - 1)) * std::abs(sin(t * pi * 3.5f));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T OutBounce(T t)
|
||||
{
|
||||
return 1 - std::pow(2, -6 * t) * std::abs(std::cos(t * pi * 3.5f));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__ri static T InOutBounce(T t)
|
||||
{
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return 8 * std::pow(2, 8 * (t - 1)) * std::abs(std::sin(t * pi * 7));
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1 - 8 * std::pow(2, -8 * t) * std::abs(std::sin(t * pi * 7));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Easing
|
||||
91
common/EnumOps.h
Normal file
91
common/EnumOps.h
Normal file
@@ -0,0 +1,91 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
// Template function for casting enumerations to their underlying type
|
||||
template <typename Enumeration>
|
||||
typename std::underlying_type<Enumeration>::type enum_cast(Enumeration E)
|
||||
{
|
||||
return static_cast<typename std::underlying_type<Enumeration>::type>(E);
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
/// Marks an enum as supporting boolean operators
|
||||
template <typename T>
|
||||
struct enum_is_flags : public std::false_type {};
|
||||
|
||||
/// For return types that should be convertible to bool
|
||||
template <typename Enum>
|
||||
struct enum_bool_helper
|
||||
{
|
||||
Enum value;
|
||||
constexpr enum_bool_helper(Enum value): value(value) {}
|
||||
constexpr operator Enum() const { return value; }
|
||||
constexpr operator bool() const { return static_cast<bool>(static_cast<typename std::underlying_type<Enum>::type>(value)); }
|
||||
};
|
||||
};
|
||||
|
||||
#define MARK_ENUM_AS_FLAGS(T) template<> struct detail::enum_is_flags<T> : public std::true_type {}
|
||||
|
||||
template <typename Enum>
|
||||
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, Enum>::type
|
||||
operator|(Enum lhs, Enum rhs) noexcept
|
||||
{
|
||||
using underlying = typename std::underlying_type<Enum>::type;
|
||||
return static_cast<Enum>(static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
|
||||
}
|
||||
|
||||
template <typename Enum>
|
||||
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, detail::enum_bool_helper<Enum>>::type
|
||||
operator&(Enum lhs, Enum rhs) noexcept
|
||||
{
|
||||
using underlying = typename std::underlying_type<Enum>::type;
|
||||
return static_cast<Enum>(static_cast<underlying>(lhs) & static_cast<underlying>(rhs));
|
||||
}
|
||||
|
||||
template <typename Enum>
|
||||
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, Enum>::type
|
||||
operator^(Enum lhs, Enum rhs) noexcept
|
||||
{
|
||||
using underlying = typename std::underlying_type<Enum>::type;
|
||||
return static_cast<Enum>(static_cast<underlying>(lhs) ^ static_cast<underlying>(rhs));
|
||||
}
|
||||
|
||||
template <typename Enum>
|
||||
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, Enum&>::type
|
||||
operator|=(Enum& lhs, Enum rhs) noexcept
|
||||
{
|
||||
return lhs = lhs | rhs;
|
||||
}
|
||||
|
||||
template <typename Enum>
|
||||
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, Enum&>::type
|
||||
operator&=(Enum& lhs, Enum rhs) noexcept
|
||||
{
|
||||
return lhs = lhs & rhs;
|
||||
}
|
||||
|
||||
template <typename Enum>
|
||||
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, Enum&>::type
|
||||
operator^=(Enum& lhs, Enum rhs) noexcept
|
||||
{
|
||||
return lhs = lhs ^ rhs;
|
||||
}
|
||||
|
||||
template<typename Enum>
|
||||
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, bool>::type
|
||||
operator!(Enum e) noexcept
|
||||
{
|
||||
return !static_cast<typename std::underlying_type<Enum>::type>(e);
|
||||
}
|
||||
|
||||
template<typename Enum>
|
||||
constexpr typename std::enable_if<detail::enum_is_flags<Enum>::value, Enum>::type
|
||||
operator~(Enum e) noexcept
|
||||
{
|
||||
return static_cast<Enum>(~static_cast<typename std::underlying_type<Enum>::type>(e));
|
||||
}
|
||||
286
common/Error.cpp
Normal file
286
common/Error.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "Error.h"
|
||||
#include "StringUtil.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cwctype>
|
||||
#include <type_traits>
|
||||
|
||||
// Platform-specific includes
|
||||
#if defined(_WIN32)
|
||||
#include "RedtapeWindows.h"
|
||||
static_assert(std::is_same<DWORD, unsigned long>::value, "DWORD is unsigned long");
|
||||
static_assert(std::is_same<HRESULT, long>::value, "HRESULT is long");
|
||||
#endif
|
||||
|
||||
Error::Error() = default;
|
||||
|
||||
Error::Error(const Error& c) = default;
|
||||
|
||||
Error::Error(Error&& e) = default;
|
||||
|
||||
Error::~Error() = default;
|
||||
|
||||
void Error::Clear()
|
||||
{
|
||||
m_description = {};
|
||||
}
|
||||
|
||||
void Error::Clear(Error* errptr)
|
||||
{
|
||||
if (errptr)
|
||||
errptr->Clear();
|
||||
}
|
||||
|
||||
void Error::SetErrno(int err)
|
||||
{
|
||||
SetErrno(std::string_view(), err);
|
||||
}
|
||||
|
||||
void Error::SetErrno(std::string_view prefix, int err)
|
||||
{
|
||||
m_type = Type::Errno;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
char buf[128];
|
||||
if (strerror_s(buf, sizeof(buf), err) == 0)
|
||||
m_description = fmt::format("{}errno {}: {}", prefix, err, buf);
|
||||
else
|
||||
m_description = fmt::format("{}errno {}: <Could not get error message>", prefix, err);
|
||||
#else
|
||||
const char* buf = std::strerror(err);
|
||||
if (buf)
|
||||
m_description = fmt::format("{}errno {}: {}", prefix, err, buf);
|
||||
else
|
||||
m_description = fmt::format("{}errno {}: <Could not get error message>", prefix, err);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Error::SetErrno(Error* errptr, int err)
|
||||
{
|
||||
if (errptr)
|
||||
errptr->SetErrno(err);
|
||||
}
|
||||
|
||||
void Error::SetErrno(Error* errptr, std::string_view prefix, int err)
|
||||
{
|
||||
if (errptr)
|
||||
errptr->SetErrno(prefix, err);
|
||||
}
|
||||
|
||||
void Error::SetString(std::string description)
|
||||
{
|
||||
m_type = Type::User;
|
||||
m_description = std::move(description);
|
||||
}
|
||||
|
||||
void Error::SetStringView(std::string_view description)
|
||||
{
|
||||
m_type = Type::User;
|
||||
m_description = std::string(description);
|
||||
}
|
||||
|
||||
void Error::SetString(Error* errptr, std::string description)
|
||||
{
|
||||
if (errptr)
|
||||
errptr->SetString(std::move(description));
|
||||
}
|
||||
|
||||
void Error::SetStringView(Error* errptr, std::string_view description)
|
||||
{
|
||||
if (errptr)
|
||||
errptr->SetStringView(std::move(description));
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
void Error::SetWin32(unsigned long err)
|
||||
{
|
||||
SetWin32(std::string_view(), err);
|
||||
}
|
||||
|
||||
void Error::SetWin32(std::string_view prefix, unsigned long err)
|
||||
{
|
||||
m_type = Type::Win32;
|
||||
|
||||
WCHAR buf[128];
|
||||
DWORD r = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, err, LANG_USER_DEFAULT, buf,
|
||||
static_cast<DWORD>(std::size(buf)), nullptr);
|
||||
while (r > 0 && std::iswspace(buf[r - 1]))
|
||||
r--;
|
||||
|
||||
if (r > 0)
|
||||
{
|
||||
m_description =
|
||||
fmt::format("{}Win32 Error {}: {}", prefix, err, StringUtil::WideStringToUTF8String(std::wstring_view(buf, r)));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_description = fmt::format("{}Win32 Error {}: <Could not resolve system error ID>", prefix, err);
|
||||
}
|
||||
}
|
||||
|
||||
void Error::SetWin32(Error* errptr, unsigned long err)
|
||||
{
|
||||
if (errptr)
|
||||
errptr->SetWin32(err);
|
||||
}
|
||||
|
||||
void Error::SetWin32(Error* errptr, std::string_view prefix, unsigned long err)
|
||||
{
|
||||
if (errptr)
|
||||
errptr->SetWin32(prefix, err);
|
||||
}
|
||||
|
||||
void Error::SetHResult(long err)
|
||||
{
|
||||
SetHResult(std::string_view(), err);
|
||||
}
|
||||
|
||||
void Error::SetHResult(std::string_view prefix, long err)
|
||||
{
|
||||
m_type = Type::HResult;
|
||||
|
||||
WCHAR buf[128];
|
||||
DWORD r = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, err, LANG_USER_DEFAULT, buf,
|
||||
static_cast<DWORD>(std::size(buf)), nullptr);
|
||||
while (r > 0 && std::iswspace(buf[r - 1]))
|
||||
r--;
|
||||
|
||||
if (r > 0)
|
||||
{
|
||||
m_description = fmt::format("{}HRESULT {:08X}: {}", prefix, static_cast<unsigned>(err),
|
||||
StringUtil::WideStringToUTF8String(std::wstring_view(buf, r)));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_description = fmt::format("{}HRESULT {:08X}: <Could not resolve system error ID>", prefix,
|
||||
static_cast<unsigned>(err));
|
||||
}
|
||||
}
|
||||
|
||||
void Error::SetHResult(Error* errptr, long err)
|
||||
{
|
||||
if (errptr)
|
||||
errptr->SetHResult(err);
|
||||
}
|
||||
|
||||
void Error::SetHResult(Error* errptr, std::string_view prefix, long err)
|
||||
{
|
||||
if (errptr)
|
||||
errptr->SetHResult(prefix, err);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Error::SetSocket(int err)
|
||||
{
|
||||
SetSocket(std::string_view(), err);
|
||||
}
|
||||
|
||||
void Error::SetSocket(std::string_view prefix, int err)
|
||||
{
|
||||
// Socket errors are win32 errors on windows
|
||||
#ifdef _WIN32
|
||||
SetWin32(prefix, err);
|
||||
#else
|
||||
SetErrno(prefix, err);
|
||||
#endif
|
||||
m_type = Type::Socket;
|
||||
}
|
||||
|
||||
void Error::SetSocket(Error* errptr, int err)
|
||||
{
|
||||
if (errptr)
|
||||
errptr->SetSocket(err);
|
||||
}
|
||||
|
||||
void Error::SetSocket(Error* errptr, std::string_view prefix, int err)
|
||||
{
|
||||
if (errptr)
|
||||
errptr->SetSocket(prefix, err);
|
||||
}
|
||||
|
||||
Error Error::CreateNone()
|
||||
{
|
||||
return Error();
|
||||
}
|
||||
|
||||
Error Error::CreateErrno(int err)
|
||||
{
|
||||
Error ret;
|
||||
ret.SetErrno(err);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Error Error::CreateSocket(int err)
|
||||
{
|
||||
Error ret;
|
||||
ret.SetSocket(err);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Error Error::CreateString(std::string description)
|
||||
{
|
||||
Error ret;
|
||||
ret.SetString(std::move(description));
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
Error Error::CreateWin32(unsigned long err)
|
||||
{
|
||||
Error ret;
|
||||
ret.SetWin32(err);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Error Error::CreateHResult(long err)
|
||||
{
|
||||
Error ret;
|
||||
ret.SetHResult(err);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Error::AddPrefix(std::string_view prefix)
|
||||
{
|
||||
m_description.insert(0, prefix);
|
||||
}
|
||||
|
||||
void Error::AddSuffix(std::string_view suffix)
|
||||
{
|
||||
m_description.append(suffix);
|
||||
}
|
||||
|
||||
void Error::AddPrefix(Error* errptr, std::string_view prefix)
|
||||
{
|
||||
if (errptr)
|
||||
errptr->AddPrefix(prefix);
|
||||
}
|
||||
|
||||
void Error::AddSuffix(Error* errptr, std::string_view prefix)
|
||||
{
|
||||
if (errptr)
|
||||
errptr->AddSuffix(prefix);
|
||||
}
|
||||
|
||||
Error& Error::operator=(const Error& e) = default;
|
||||
|
||||
Error& Error::operator=(Error&& e) = default;
|
||||
|
||||
bool Error::operator==(const Error& e) const
|
||||
{
|
||||
return (m_type == e.m_type && m_description == e.m_description);
|
||||
}
|
||||
|
||||
bool Error::operator!=(const Error& e) const
|
||||
{
|
||||
return (m_type != e.m_type || m_description != e.m_description);
|
||||
}
|
||||
105
common/Error.h
Normal file
105
common/Error.h
Normal file
@@ -0,0 +1,105 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Pcsx2Defs.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class Error
|
||||
{
|
||||
public:
|
||||
Error();
|
||||
Error(const Error& e);
|
||||
Error(Error&& e);
|
||||
~Error();
|
||||
|
||||
enum class Type
|
||||
{
|
||||
None = 0,
|
||||
Errno = 1,
|
||||
Socket = 2,
|
||||
User = 3,
|
||||
Win32 = 4,
|
||||
HResult = 5,
|
||||
};
|
||||
|
||||
__fi Type GetType() const { return m_type; }
|
||||
__fi bool IsValid() const { return (m_type != Type::None); }
|
||||
__fi const std::string& GetDescription() const { return m_description; }
|
||||
|
||||
void Clear();
|
||||
|
||||
/// Error that is set by system functions, such as open().
|
||||
void SetErrno(int err);
|
||||
void SetErrno(std::string_view prefix, int err);
|
||||
|
||||
/// Error that is set by socket functions, such as socket(). On Unix this is the same as errno.
|
||||
void SetSocket(int err);
|
||||
void SetSocket(std::string_view prefix, int err);
|
||||
|
||||
/// Set both description and message.
|
||||
void SetString(std::string description);
|
||||
void SetStringView(std::string_view description);
|
||||
|
||||
#ifdef _WIN32
|
||||
/// Error that is returned by some Win32 functions, such as RegOpenKeyEx. Also used by other APIs through
|
||||
/// GetLastError().
|
||||
void SetWin32(unsigned long err);
|
||||
void SetWin32(std::string_view prefix, unsigned long err);
|
||||
|
||||
/// Error that is returned by Win32 COM methods, e.g. S_OK.
|
||||
void SetHResult(long err);
|
||||
void SetHResult(std::string_view prefix, long err);
|
||||
#endif
|
||||
|
||||
static Error CreateNone();
|
||||
static Error CreateErrno(int err);
|
||||
static Error CreateSocket(int err);
|
||||
static Error CreateString(std::string description);
|
||||
#ifdef _WIN32
|
||||
static Error CreateWin32(unsigned long err);
|
||||
static Error CreateHResult(long err);
|
||||
#endif
|
||||
|
||||
// helpers for setting
|
||||
static void Clear(Error* errptr);
|
||||
static void SetErrno(Error* errptr, int err);
|
||||
static void SetErrno(Error* errptr, std::string_view prefix, int err);
|
||||
static void SetSocket(Error* errptr, int err);
|
||||
static void SetSocket(Error* errptr, std::string_view prefix, int err);
|
||||
static void SetString(Error* errptr, std::string description);
|
||||
static void SetStringView(Error* errptr, std::string_view description);
|
||||
|
||||
#ifdef _WIN32
|
||||
static void SetWin32(Error* errptr, unsigned long err);
|
||||
static void SetWin32(Error* errptr, std::string_view prefix, unsigned long err);
|
||||
static void SetHResult(Error* errptr, long err);
|
||||
static void SetHResult(Error* errptr, std::string_view prefix, long err);
|
||||
#endif
|
||||
|
||||
/// Sets a formatted message.
|
||||
template <typename... T>
|
||||
static void SetStringFmt(Error* errptr, fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
if (errptr)
|
||||
Error::SetString(errptr, fmt::vformat(fmt, fmt::make_format_args(args...)));
|
||||
}
|
||||
|
||||
void AddPrefix(std::string_view prefix);
|
||||
void AddSuffix(std::string_view suffix);
|
||||
static void AddPrefix(Error* errptr, std::string_view prefix);
|
||||
static void AddSuffix(Error* errptr, std::string_view prefix);
|
||||
|
||||
Error& operator=(const Error& e);
|
||||
Error& operator=(Error&& e);
|
||||
bool operator==(const Error& e) const;
|
||||
bool operator!=(const Error& e) const;
|
||||
|
||||
private:
|
||||
Type m_type = Type::None;
|
||||
std::string m_description;
|
||||
};
|
||||
216
common/FPControl.h
Normal file
216
common/FPControl.h
Normal file
@@ -0,0 +1,216 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
// This file abstracts the floating-point control registers, known as MXCSR on x86, and FPCR on AArch64.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
#include "common/VectorIntrin.h"
|
||||
|
||||
enum class FPRoundMode : u8
|
||||
{
|
||||
Nearest,
|
||||
NegativeInfinity,
|
||||
PositiveInfinity,
|
||||
ChopZero,
|
||||
|
||||
MaxCount
|
||||
};
|
||||
|
||||
struct FPControlRegister
|
||||
{
|
||||
#ifdef _M_X86
|
||||
u32 bitmask;
|
||||
|
||||
static constexpr u32 EXCEPTION_MASK = (0x3Fu << 7);
|
||||
static constexpr u32 ROUNDING_CONTROL_SHIFT = 13;
|
||||
static constexpr u32 ROUNDING_CONTROL_MASK = 3u;
|
||||
static constexpr u32 ROUNDING_CONTROL_BITS = (ROUNDING_CONTROL_MASK << ROUNDING_CONTROL_SHIFT);
|
||||
static constexpr u32 DENORMALS_ARE_ZERO_BIT = (1u << 6);
|
||||
static constexpr u32 FLUSH_TO_ZERO_BIT = (1u << 15);
|
||||
|
||||
__fi static FPControlRegister GetCurrent()
|
||||
{
|
||||
return FPControlRegister{_mm_getcsr()};
|
||||
}
|
||||
|
||||
__fi static void SetCurrent(FPControlRegister value)
|
||||
{
|
||||
_mm_setcsr(value.bitmask);
|
||||
}
|
||||
|
||||
__fi static constexpr FPControlRegister GetDefault()
|
||||
{
|
||||
// 0x1f80 - all exceptions masked, nearest rounding
|
||||
return FPControlRegister{0x1f80};
|
||||
}
|
||||
|
||||
__fi constexpr FPControlRegister& EnableExceptions()
|
||||
{
|
||||
bitmask &= ~EXCEPTION_MASK;
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr FPControlRegister DisableExceptions()
|
||||
{
|
||||
bitmask |= EXCEPTION_MASK;
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr FPRoundMode GetRoundMode() const
|
||||
{
|
||||
return static_cast<FPRoundMode>((bitmask >> ROUNDING_CONTROL_SHIFT) & ROUNDING_CONTROL_MASK);
|
||||
}
|
||||
|
||||
__fi constexpr FPControlRegister& SetRoundMode(FPRoundMode mode)
|
||||
{
|
||||
// These bits match on x86.
|
||||
bitmask = (bitmask & ~ROUNDING_CONTROL_BITS) | ((static_cast<u32>(mode) & ROUNDING_CONTROL_MASK) << ROUNDING_CONTROL_SHIFT);
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr bool GetDenormalsAreZero() const
|
||||
{
|
||||
return ((bitmask & DENORMALS_ARE_ZERO_BIT) != 0);
|
||||
}
|
||||
|
||||
__fi constexpr FPControlRegister SetDenormalsAreZero(bool daz)
|
||||
{
|
||||
if (daz)
|
||||
bitmask |= DENORMALS_ARE_ZERO_BIT;
|
||||
else
|
||||
bitmask &= ~DENORMALS_ARE_ZERO_BIT;
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr bool GetFlushToZero() const
|
||||
{
|
||||
return ((bitmask & FLUSH_TO_ZERO_BIT) != 0);
|
||||
}
|
||||
|
||||
__fi constexpr FPControlRegister SetFlushToZero(bool ftz)
|
||||
{
|
||||
if (ftz)
|
||||
bitmask |= FLUSH_TO_ZERO_BIT;
|
||||
else
|
||||
bitmask &= ~FLUSH_TO_ZERO_BIT;
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr bool operator==(const FPControlRegister& rhs) const { return bitmask == rhs.bitmask; }
|
||||
__fi constexpr bool operator!=(const FPControlRegister& rhs) const { return bitmask != rhs.bitmask; }
|
||||
|
||||
#elif defined(_M_ARM64)
|
||||
u64 bitmask;
|
||||
|
||||
static constexpr u64 FZ_BIT = (0x1ULL << 24);
|
||||
static constexpr u32 RMODE_SHIFT = 22;
|
||||
static constexpr u64 RMODE_MASK = 0x3ULL;
|
||||
static constexpr u64 RMODE_BITS = (RMODE_MASK << RMODE_SHIFT);
|
||||
static constexpr u32 EXCEPTION_MASK = (0x3Fu << 5);
|
||||
|
||||
__fi static FPControlRegister GetCurrent()
|
||||
{
|
||||
u64 value;
|
||||
asm volatile("\tmrs %0, FPCR\n"
|
||||
: "=r"(value));
|
||||
return FPControlRegister{value};
|
||||
}
|
||||
|
||||
__fi static void SetCurrent(FPControlRegister value)
|
||||
{
|
||||
asm volatile("\tmsr FPCR, %0\n" ::"r"(value.bitmask));
|
||||
}
|
||||
|
||||
__fi static constexpr FPControlRegister GetDefault()
|
||||
{
|
||||
// 0x0 - all exceptions masked, nearest rounding
|
||||
return FPControlRegister{0x0};
|
||||
}
|
||||
|
||||
__fi constexpr FPControlRegister& EnableExceptions()
|
||||
{
|
||||
bitmask |= EXCEPTION_MASK;
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr FPControlRegister& DisableExceptions()
|
||||
{
|
||||
bitmask &= ~EXCEPTION_MASK;
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr FPRoundMode GetRoundMode() const
|
||||
{
|
||||
// Negative/Positive infinity rounding is flipped on A64.
|
||||
const u64 RMode = (bitmask >> RMODE_SHIFT) & RMODE_MASK;
|
||||
return static_cast<FPRoundMode>((RMode == 0b00 || RMode == 0b11) ? RMode : (RMode ^ 0b11));
|
||||
}
|
||||
|
||||
__fi constexpr FPControlRegister& SetRoundMode(FPRoundMode mode)
|
||||
{
|
||||
const u64 RMode = ((mode == FPRoundMode::Nearest || mode == FPRoundMode::ChopZero) ? static_cast<u64>(mode) : (static_cast<u64>(mode) ^ 0b11));
|
||||
bitmask = (bitmask & ~RMODE_BITS) | ((RMode & RMODE_MASK) << RMODE_SHIFT);
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr bool GetDenormalsAreZero() const
|
||||
{
|
||||
// Without FEAT_AFP, most ARM chips don't have separate DaZ/FtZ. This includes Apple Silicon, which
|
||||
// implements x86-like behavior with a vendor-specific extension that we cannot access from usermode.
|
||||
// The FZ bit causes both inputs and outputs to be flushed to zero.
|
||||
return ((bitmask & FZ_BIT) != 0);
|
||||
}
|
||||
|
||||
__fi constexpr FPControlRegister SetDenormalsAreZero(bool daz)
|
||||
{
|
||||
if (daz)
|
||||
bitmask |= FZ_BIT;
|
||||
else
|
||||
bitmask &= ~FZ_BIT;
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr bool GetFlushToZero() const
|
||||
{
|
||||
// See note in GetDenormalsAreZero().
|
||||
return ((bitmask & FZ_BIT) != 0);
|
||||
}
|
||||
|
||||
__fi constexpr FPControlRegister SetFlushToZero(bool ftz)
|
||||
{
|
||||
if (ftz)
|
||||
bitmask |= FZ_BIT;
|
||||
else
|
||||
bitmask &= ~FZ_BIT;
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr bool operator==(const FPControlRegister& rhs) const { return bitmask == rhs.bitmask; }
|
||||
__fi constexpr bool operator!=(const FPControlRegister& rhs) const { return bitmask != rhs.bitmask; }
|
||||
#else
|
||||
#error Unknown architecture.
|
||||
#endif
|
||||
};
|
||||
|
||||
/// Helper to back up/restore FPCR.
|
||||
class FPControlRegisterBackup
|
||||
{
|
||||
public:
|
||||
__fi FPControlRegisterBackup(FPControlRegister new_value)
|
||||
: m_prev_val(FPControlRegister::GetCurrent())
|
||||
{
|
||||
FPControlRegister::SetCurrent(new_value);
|
||||
}
|
||||
__fi ~FPControlRegisterBackup()
|
||||
{
|
||||
FPControlRegister::SetCurrent(m_prev_val);
|
||||
}
|
||||
|
||||
FPControlRegisterBackup(const FPControlRegisterBackup&) = delete;
|
||||
FPControlRegisterBackup& operator=(const FPControlRegisterBackup&) = delete;
|
||||
|
||||
private:
|
||||
FPControlRegister m_prev_val;
|
||||
};
|
||||
92
common/FastJmp.cpp
Normal file
92
common/FastJmp.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "FastJmp.h"
|
||||
|
||||
// Win32 uses Fastjmp.asm, because MSVC doesn't support inline asm.
|
||||
#if !defined(_WIN32) || defined(_M_ARM64)
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#define PREFIX "_"
|
||||
#else
|
||||
#define PREFIX ""
|
||||
#endif
|
||||
|
||||
#if defined(_M_X86)
|
||||
|
||||
asm(
|
||||
"\t.global " PREFIX "fastjmp_set\n"
|
||||
"\t.global " PREFIX "fastjmp_jmp\n"
|
||||
"\t.text\n"
|
||||
"\t" PREFIX "fastjmp_set:" R"(
|
||||
movq 0(%rsp), %rax
|
||||
movq %rsp, %rdx # fixup stack pointer, so it doesn't include the call to fastjmp_set
|
||||
addq $8, %rdx
|
||||
movq %rax, 0(%rdi) # actually rip
|
||||
movq %rbx, 8(%rdi)
|
||||
movq %rdx, 16(%rdi) # actually rsp
|
||||
movq %rbp, 24(%rdi)
|
||||
movq %r12, 32(%rdi)
|
||||
movq %r13, 40(%rdi)
|
||||
movq %r14, 48(%rdi)
|
||||
movq %r15, 56(%rdi)
|
||||
xorl %eax, %eax
|
||||
ret
|
||||
)"
|
||||
"\t" PREFIX "fastjmp_jmp:" R"(
|
||||
movl %esi, %eax
|
||||
movq 0(%rdi), %rdx # actually rip
|
||||
movq 8(%rdi), %rbx
|
||||
movq 16(%rdi), %rsp # actually rsp
|
||||
movq 24(%rdi), %rbp
|
||||
movq 32(%rdi), %r12
|
||||
movq 40(%rdi), %r13
|
||||
movq 48(%rdi), %r14
|
||||
movq 56(%rdi), %r15
|
||||
jmp *%rdx
|
||||
)");
|
||||
|
||||
#elif defined(_M_ARM64)
|
||||
|
||||
asm(
|
||||
"\t.global " PREFIX "fastjmp_set\n"
|
||||
"\t.global " PREFIX "fastjmp_jmp\n"
|
||||
"\t.text\n"
|
||||
"\t.align 16\n"
|
||||
"\t" PREFIX "fastjmp_set:" R"(
|
||||
mov x16, sp
|
||||
stp x16, x30, [x0]
|
||||
stp x19, x20, [x0, #16]
|
||||
stp x21, x22, [x0, #32]
|
||||
stp x23, x24, [x0, #48]
|
||||
stp x25, x26, [x0, #64]
|
||||
stp x27, x28, [x0, #80]
|
||||
str x29, [x0, #96]
|
||||
stp d8, d9, [x0, #112]
|
||||
stp d10, d11, [x0, #128]
|
||||
stp d12, d13, [x0, #144]
|
||||
stp d14, d15, [x0, #160]
|
||||
mov w0, wzr
|
||||
br x30
|
||||
)"
|
||||
".align 16\n"
|
||||
"\t" PREFIX "fastjmp_jmp:" R"(
|
||||
ldp x16, x30, [x0]
|
||||
mov sp, x16
|
||||
ldp x19, x20, [x0, #16]
|
||||
ldp x21, x22, [x0, #32]
|
||||
ldp x23, x24, [x0, #48]
|
||||
ldp x25, x26, [x0, #64]
|
||||
ldp x27, x28, [x0, #80]
|
||||
ldr x29, [x0, #96]
|
||||
ldp d8, d9, [x0, #112]
|
||||
ldp d10, d11, [x0, #128]
|
||||
ldp d12, d13, [x0, #144]
|
||||
ldp d14, d15, [x0, #160]
|
||||
mov w0, w1
|
||||
br x30
|
||||
)");
|
||||
|
||||
#endif
|
||||
|
||||
#endif // __WIN32
|
||||
25
common/FastJmp.h
Normal file
25
common/FastJmp.h
Normal file
@@ -0,0 +1,25 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include "Pcsx2Defs.h"
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
struct fastjmp_buf
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
static constexpr std::size_t BUF_SIZE = 240;
|
||||
#elif defined(_M_ARM64)
|
||||
static constexpr std::size_t BUF_SIZE = 168;
|
||||
#else
|
||||
static constexpr std::size_t BUF_SIZE = 64;
|
||||
#endif
|
||||
|
||||
alignas(16) std::uint8_t buf[BUF_SIZE];
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
int fastjmp_set(fastjmp_buf* buf);
|
||||
__noreturn void fastjmp_jmp(const fastjmp_buf* buf, int ret);
|
||||
}
|
||||
2646
common/FileSystem.cpp
Normal file
2646
common/FileSystem.cpp
Normal file
File diff suppressed because it is too large
Load Diff
212
common/FileSystem.h
Normal file
212
common/FileSystem.h
Normal file
@@ -0,0 +1,212 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Pcsx2Defs.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sys/stat.h>
|
||||
|
||||
class Error;
|
||||
class ProgressCallback;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define FS_OSPATH_SEPARATOR_CHARACTER '\\'
|
||||
#define FS_OSPATH_SEPARATOR_STR "\\"
|
||||
#else
|
||||
#define FS_OSPATH_SEPARATOR_CHARACTER '/'
|
||||
#define FS_OSPATH_SEPARATOR_STR "/"
|
||||
#endif
|
||||
|
||||
enum FILESYSTEM_FILE_ATTRIBUTES
|
||||
{
|
||||
FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY = 1,
|
||||
FILESYSTEM_FILE_ATTRIBUTE_READ_ONLY = 2,
|
||||
FILESYSTEM_FILE_ATTRIBUTE_COMPRESSED = 4,
|
||||
};
|
||||
|
||||
enum FILESYSTEM_FIND_FLAGS
|
||||
{
|
||||
FILESYSTEM_FIND_RECURSIVE = (1 << 0),
|
||||
FILESYSTEM_FIND_RELATIVE_PATHS = (1 << 1),
|
||||
FILESYSTEM_FIND_HIDDEN_FILES = (1 << 2),
|
||||
FILESYSTEM_FIND_FOLDERS = (1 << 3),
|
||||
FILESYSTEM_FIND_FILES = (1 << 4),
|
||||
FILESYSTEM_FIND_KEEP_ARRAY = (1 << 5),
|
||||
FILESYSTEM_FIND_SORT_BY_NAME = (1 << 6),
|
||||
};
|
||||
|
||||
struct FILESYSTEM_STAT_DATA
|
||||
{
|
||||
std::time_t CreationTime; // actually inode change time on linux
|
||||
std::time_t ModificationTime;
|
||||
s64 Size;
|
||||
u32 Attributes;
|
||||
};
|
||||
|
||||
struct FILESYSTEM_FIND_DATA
|
||||
{
|
||||
std::time_t CreationTime; // actually inode change time on linux
|
||||
std::time_t ModificationTime;
|
||||
std::string FileName;
|
||||
s64 Size;
|
||||
u32 Attributes;
|
||||
};
|
||||
|
||||
namespace FileSystem
|
||||
{
|
||||
using FindResultsArray = std::vector<FILESYSTEM_FIND_DATA>;
|
||||
|
||||
/// Returns a list of "root directories" (i.e. root/home directories on Linux, drive letters on Windows).
|
||||
std::vector<std::string> GetRootDirectoryList();
|
||||
|
||||
/// Search for files
|
||||
bool FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results, ProgressCallback* cancel = nullptr);
|
||||
|
||||
/// Stat file
|
||||
bool StatFile(const char* path, struct stat* st);
|
||||
bool StatFile(std::FILE* fp, struct stat* st);
|
||||
bool StatFile(const char* path, FILESYSTEM_STAT_DATA* pStatData);
|
||||
bool StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* pStatData);
|
||||
s64 GetPathFileSize(const char* path);
|
||||
|
||||
/// Returns the last modified timestamp for a file.
|
||||
std::optional<std::time_t> GetFileTimestamp(const char* path);
|
||||
|
||||
/// File exists?
|
||||
bool FileExists(const char* path);
|
||||
|
||||
/// Directory exists?
|
||||
bool DirectoryExists(const char* path);
|
||||
|
||||
/// Directory does not contain any files?
|
||||
bool DirectoryIsEmpty(const char* path);
|
||||
|
||||
/// Delete file
|
||||
bool DeleteFilePath(const char* path, Error* error = nullptr);
|
||||
|
||||
/// Rename file
|
||||
bool RenamePath(const char* OldPath, const char* NewPath, Error* error = nullptr);
|
||||
|
||||
/// Deleter functor for managed file pointers
|
||||
struct FileDeleter
|
||||
{
|
||||
void operator()(std::FILE* fp)
|
||||
{
|
||||
std::fclose(fp);
|
||||
}
|
||||
};
|
||||
|
||||
/// open files
|
||||
using ManagedCFilePtr = std::unique_ptr<std::FILE, FileDeleter>;
|
||||
ManagedCFilePtr OpenManagedCFile(const char* filename, const char* mode, Error* error = nullptr);
|
||||
// Tries to open a file using the given filename, but if that fails searches
|
||||
// the directory for a file with a case-insensitive match.
|
||||
// This is the same as OpenManagedCFile on Windows and MacOS
|
||||
ManagedCFilePtr OpenManagedCFileTryIgnoreCase(const char* filename, const char* mode, Error* error = nullptr);
|
||||
std::FILE* OpenCFile(const char* filename, const char* mode, Error* error = nullptr);
|
||||
// Tries to open a file using the given filename, but if that fails searches
|
||||
// the directory for a file with a case-insensitive match.
|
||||
// This is the same as OpenCFile on Windows and MacOS
|
||||
std::FILE* OpenCFileTryIgnoreCase(const char* filename, const char* mode, Error* error = nullptr);
|
||||
|
||||
int FSeek64(std::FILE* fp, s64 offset, int whence);
|
||||
s64 FTell64(std::FILE* fp);
|
||||
s64 FSize64(std::FILE* fp);
|
||||
|
||||
int OpenFDFile(const char* filename, int flags, int mode, Error* error = nullptr);
|
||||
|
||||
/// Sharing modes for OpenSharedCFile().
|
||||
enum class FileShareMode
|
||||
{
|
||||
DenyReadWrite, /// Exclusive access.
|
||||
DenyWrite, /// Other processes can read from this file.
|
||||
DenyRead, /// Other processes can write to this file.
|
||||
DenyNone, /// Other processes can read and write to this file.
|
||||
};
|
||||
|
||||
/// Opens a file in shareable mode (where other processes can access it concurrently).
|
||||
/// Only has an effect on Windows systems.
|
||||
ManagedCFilePtr OpenManagedSharedCFile(const char* filename, const char* mode, FileShareMode share_mode, Error* error = nullptr);
|
||||
std::FILE* OpenSharedCFile(const char* filename, const char* mode, FileShareMode share_mode, Error* error = nullptr);
|
||||
|
||||
std::optional<std::vector<u8>> ReadBinaryFile(const char* filename);
|
||||
std::optional<std::vector<u8>> ReadBinaryFile(std::FILE* fp);
|
||||
std::optional<std::string> ReadFileToString(const char* filename);
|
||||
std::optional<std::string> ReadFileToString(std::FILE* fp);
|
||||
bool WriteBinaryFile(const char* filename, const void* data, size_t data_length);
|
||||
bool WriteStringToFile(const char* filename, const std::string_view sv);
|
||||
size_t ReadFileWithProgress(std::FILE* fp, void* dst, size_t length, ProgressCallback* progress,
|
||||
Error* error = nullptr, size_t chunk_size = 16 * 1024 * 1024);
|
||||
size_t ReadFileWithPartialProgress(std::FILE* fp, void* dst, size_t length, ProgressCallback* progress,
|
||||
int startPercent, int endPercent, Error* error = nullptr, size_t chunk_size = 16 * 1024 * 1024);
|
||||
|
||||
/// creates a directory in the local filesystem
|
||||
/// if the directory already exists, the return value will be true.
|
||||
/// if Recursive is specified, all parent directories will be created
|
||||
/// if they do not exist.
|
||||
bool CreateDirectoryPath(const char* path, bool recursive, Error* error = nullptr);
|
||||
|
||||
/// Creates a directory if it doesn't already exist.
|
||||
/// Returns false if it does not exist and creation failed.
|
||||
bool EnsureDirectoryExists(const char* path, bool recursive, Error* error = nullptr);
|
||||
|
||||
/// Removes a directory.
|
||||
bool DeleteDirectory(const char* path);
|
||||
|
||||
/// Recursively removes a directory and all subdirectories/files.
|
||||
bool RecursiveDeleteDirectory(const char* path);
|
||||
|
||||
/// Copies one file to another, optionally replacing it if it already exists.
|
||||
bool CopyFilePath(const char* source, const char* destination, bool replace);
|
||||
|
||||
/// Returns the path to the current executable.
|
||||
std::string GetProgramPath();
|
||||
|
||||
/// Retrieves the current working directory.
|
||||
std::string GetWorkingDirectory();
|
||||
|
||||
/// Sets the current working directory. Returns true if successful.
|
||||
bool SetWorkingDirectory(const char* path);
|
||||
|
||||
/// Enables/disables NTFS compression on a file or directory.
|
||||
/// Does not apply the compression flag recursively if called for a directory.
|
||||
/// Does nothing and returns false on non-Windows platforms.
|
||||
bool SetPathCompression(const char* path, bool enable);
|
||||
|
||||
// Creates a symbolic link. Note that on Windows this requires elevated
|
||||
// privileges so this is mostly useful for testing purposes.
|
||||
bool CreateSymLink(const char* link, const char* target);
|
||||
|
||||
/// Checks if a file or directory is a symbolic link.
|
||||
bool IsSymbolicLink(const char* path);
|
||||
|
||||
/// Deletes a symbolic link (either a file or directory).
|
||||
bool DeleteSymbolicLink(const char* path, Error* error = nullptr);
|
||||
|
||||
#ifdef _WIN32
|
||||
// Path limit remover, but also converts to a wide string at the same time.
|
||||
bool GetWin32Path(std::wstring* dest, std::string_view str);
|
||||
std::wstring GetWin32Path(std::string_view str);
|
||||
#endif
|
||||
|
||||
/// Abstracts a POSIX file lock.
|
||||
#ifndef _WIN32
|
||||
class POSIXLock
|
||||
{
|
||||
public:
|
||||
POSIXLock(int fd);
|
||||
POSIXLock(std::FILE* fp);
|
||||
~POSIXLock();
|
||||
|
||||
private:
|
||||
int m_fd;
|
||||
};
|
||||
#endif
|
||||
}; // namespace FileSystem
|
||||
321
common/HTTPDownloader.cpp
Normal file
321
common/HTTPDownloader.cpp
Normal file
@@ -0,0 +1,321 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/HTTPDownloader.h"
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/ProgressCallback.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Timer.h"
|
||||
#include "common/Threading.h"
|
||||
|
||||
static constexpr float DEFAULT_TIMEOUT_IN_SECONDS = 30;
|
||||
static constexpr u32 DEFAULT_MAX_ACTIVE_REQUESTS = 4;
|
||||
|
||||
const char HTTPDownloader::DEFAULT_USER_AGENT[] =
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0";
|
||||
|
||||
HTTPDownloader::HTTPDownloader()
|
||||
: m_timeout(DEFAULT_TIMEOUT_IN_SECONDS)
|
||||
, m_max_active_requests(DEFAULT_MAX_ACTIVE_REQUESTS)
|
||||
{
|
||||
}
|
||||
|
||||
HTTPDownloader::~HTTPDownloader() = default;
|
||||
|
||||
void HTTPDownloader::SetTimeout(float timeout)
|
||||
{
|
||||
m_timeout = timeout;
|
||||
}
|
||||
|
||||
void HTTPDownloader::SetMaxActiveRequests(u32 max_active_requests)
|
||||
{
|
||||
pxAssert(max_active_requests > 0);
|
||||
m_max_active_requests = max_active_requests;
|
||||
}
|
||||
|
||||
void HTTPDownloader::CreateRequest(std::string url, Request::Callback callback, ProgressCallback* progress)
|
||||
{
|
||||
Request* req = InternalCreateRequest();
|
||||
req->parent = this;
|
||||
req->type = Request::Type::Get;
|
||||
req->url = std::move(url);
|
||||
req->callback = std::move(callback);
|
||||
req->progress = progress;
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_pending_http_request_lock);
|
||||
if (LockedGetActiveRequestCount() < m_max_active_requests)
|
||||
{
|
||||
if (!StartRequest(req))
|
||||
return;
|
||||
}
|
||||
|
||||
LockedAddRequest(req);
|
||||
}
|
||||
|
||||
void HTTPDownloader::CreatePostRequest(std::string url, std::string post_data, Request::Callback callback, ProgressCallback* progress)
|
||||
{
|
||||
Request* req = InternalCreateRequest();
|
||||
req->parent = this;
|
||||
req->type = Request::Type::Post;
|
||||
req->url = std::move(url);
|
||||
req->post_data = std::move(post_data);
|
||||
req->callback = std::move(callback);
|
||||
req->progress = progress;
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_pending_http_request_lock);
|
||||
if (LockedGetActiveRequestCount() < m_max_active_requests)
|
||||
{
|
||||
if (!StartRequest(req))
|
||||
return;
|
||||
}
|
||||
|
||||
LockedAddRequest(req);
|
||||
}
|
||||
|
||||
void HTTPDownloader::LockedPollRequests(std::unique_lock<std::mutex>& lock)
|
||||
{
|
||||
if (m_pending_http_requests.empty())
|
||||
return;
|
||||
|
||||
InternalPollRequests();
|
||||
|
||||
const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
|
||||
u32 active_requests = 0;
|
||||
u32 unstarted_requests = 0;
|
||||
|
||||
for (size_t index = 0; index < m_pending_http_requests.size();)
|
||||
{
|
||||
Request* req = m_pending_http_requests[index];
|
||||
if (req->state == Request::State::Pending)
|
||||
{
|
||||
unstarted_requests++;
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((req->state == Request::State::Started || req->state == Request::State::Receiving) &&
|
||||
current_time >= req->start_time && Common::Timer::ConvertValueToSeconds(current_time - req->start_time) >= m_timeout)
|
||||
{
|
||||
// request timed out
|
||||
Console.Error("Request for '%s' timed out", req->url.c_str());
|
||||
|
||||
req->state.store(Request::State::Cancelled);
|
||||
m_pending_http_requests.erase(m_pending_http_requests.begin() + index);
|
||||
lock.unlock();
|
||||
|
||||
req->callback(HTTP_STATUS_TIMEOUT, std::string(), Request::Data());
|
||||
|
||||
CloseRequest(req);
|
||||
|
||||
lock.lock();
|
||||
continue;
|
||||
}
|
||||
else if ((req->state == Request::State::Started || req->state == Request::State::Receiving) && req->progress &&
|
||||
req->progress->IsCancelled())
|
||||
{
|
||||
// request timed out
|
||||
Console.Error("Request for '%s' cancelled", req->url.c_str());
|
||||
|
||||
req->state.store(Request::State::Cancelled);
|
||||
m_pending_http_requests.erase(m_pending_http_requests.begin() + index);
|
||||
lock.unlock();
|
||||
|
||||
req->callback(HTTP_STATUS_CANCELLED, std::string(), Request::Data());
|
||||
|
||||
CloseRequest(req);
|
||||
|
||||
lock.lock();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (req->state != Request::State::Complete)
|
||||
{
|
||||
if (req->progress)
|
||||
{
|
||||
const u32 size = static_cast<u32>(req->data.size());
|
||||
if (size != req->last_progress_update)
|
||||
{
|
||||
req->last_progress_update = size;
|
||||
req->progress->SetProgressRange(req->content_length);
|
||||
req->progress->SetProgressValue(req->last_progress_update);
|
||||
}
|
||||
}
|
||||
|
||||
active_requests++;
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// request complete
|
||||
DevCon.WriteLn("Request for '%s' complete, returned status code %u and %zu bytes", req->url.c_str(),
|
||||
req->status_code, req->data.size());
|
||||
m_pending_http_requests.erase(m_pending_http_requests.begin() + index);
|
||||
|
||||
// run callback with lock unheld
|
||||
lock.unlock();
|
||||
req->callback(req->status_code, req->content_type, std::move(req->data));
|
||||
CloseRequest(req);
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
// start new requests when we finished some
|
||||
if (unstarted_requests > 0 && active_requests < m_max_active_requests)
|
||||
{
|
||||
for (size_t index = 0; index < m_pending_http_requests.size();)
|
||||
{
|
||||
Request* req = m_pending_http_requests[index];
|
||||
if (req->state != Request::State::Pending)
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!StartRequest(req))
|
||||
{
|
||||
m_pending_http_requests.erase(m_pending_http_requests.begin() + index);
|
||||
continue;
|
||||
}
|
||||
|
||||
active_requests++;
|
||||
index++;
|
||||
|
||||
if (active_requests >= m_max_active_requests)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPDownloader::PollRequests()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_pending_http_request_lock);
|
||||
LockedPollRequests(lock);
|
||||
}
|
||||
|
||||
void HTTPDownloader::WaitForAllRequests()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_pending_http_request_lock);
|
||||
while (!m_pending_http_requests.empty())
|
||||
{
|
||||
// Don't burn too much CPU.
|
||||
Threading::Sleep(1);
|
||||
LockedPollRequests(lock);
|
||||
}
|
||||
}
|
||||
|
||||
void HTTPDownloader::LockedAddRequest(Request* request)
|
||||
{
|
||||
m_pending_http_requests.push_back(request);
|
||||
}
|
||||
|
||||
u32 HTTPDownloader::LockedGetActiveRequestCount()
|
||||
{
|
||||
u32 count = 0;
|
||||
for (Request* req : m_pending_http_requests)
|
||||
{
|
||||
if (req->state == Request::State::Started || req->state == Request::State::Receiving)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool HTTPDownloader::HasAnyRequests()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_pending_http_request_lock);
|
||||
return !m_pending_http_requests.empty();
|
||||
}
|
||||
|
||||
std::string HTTPDownloader::GetExtensionForContentType(const std::string& content_type)
|
||||
{
|
||||
// Based on https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||
static constexpr const char* table[][2] = {
|
||||
{"audio/aac", "aac"},
|
||||
{"application/x-abiword", "abw"},
|
||||
{"application/x-freearc", "arc"},
|
||||
{"image/avif", "avif"},
|
||||
{"video/x-msvideo", "avi"},
|
||||
{"application/vnd.amazon.ebook", "azw"},
|
||||
{"application/octet-stream", "bin"},
|
||||
{"image/bmp", "bmp"},
|
||||
{"application/x-bzip", "bz"},
|
||||
{"application/x-bzip2", "bz2"},
|
||||
{"application/x-cdf", "cda"},
|
||||
{"application/x-csh", "csh"},
|
||||
{"text/css", "css"},
|
||||
{"text/csv", "csv"},
|
||||
{"application/msword", "doc"},
|
||||
{"application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx"},
|
||||
{"application/vnd.ms-fontobject", "eot"},
|
||||
{"application/epub+zip", "epub"},
|
||||
{"application/gzip", "gz"},
|
||||
{"image/gif", "gif"},
|
||||
{"text/html", "htm"},
|
||||
{"image/vnd.microsoft.icon", "ico"},
|
||||
{"text/calendar", "ics"},
|
||||
{"application/java-archive", "jar"},
|
||||
{"image/jpeg", "jpg"},
|
||||
{"text/javascript", "js"},
|
||||
{"application/json", "json"},
|
||||
{"application/ld+json", "jsonld"},
|
||||
{"audio/midi audio/x-midi", "mid"},
|
||||
{"text/javascript", "mjs"},
|
||||
{"audio/mpeg", "mp3"},
|
||||
{"video/mp4", "mp4"},
|
||||
{"video/mpeg", "mpeg"},
|
||||
{"application/vnd.apple.installer+xml", "mpkg"},
|
||||
{"application/vnd.oasis.opendocument.presentation", "odp"},
|
||||
{"application/vnd.oasis.opendocument.spreadsheet", "ods"},
|
||||
{"application/vnd.oasis.opendocument.text", "odt"},
|
||||
{"audio/ogg", "oga"},
|
||||
{"video/ogg", "ogv"},
|
||||
{"application/ogg", "ogx"},
|
||||
{"audio/opus", "opus"},
|
||||
{"font/otf", "otf"},
|
||||
{"image/png", "png"},
|
||||
{"application/pdf", "pdf"},
|
||||
{"application/x-httpd-php", "php"},
|
||||
{"application/vnd.ms-powerpoint", "ppt"},
|
||||
{"application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx"},
|
||||
{"application/vnd.rar", "rar"},
|
||||
{"application/rtf", "rtf"},
|
||||
{"application/x-sh", "sh"},
|
||||
{"image/svg+xml", "svg"},
|
||||
{"application/x-tar", "tar"},
|
||||
{"image/tiff", "tif"},
|
||||
{"video/mp2t", "ts"},
|
||||
{"font/ttf", "ttf"},
|
||||
{"text/plain", "txt"},
|
||||
{"application/vnd.visio", "vsd"},
|
||||
{"audio/wav", "wav"},
|
||||
{"audio/webm", "weba"},
|
||||
{"video/webm", "webm"},
|
||||
{"image/webp", "webp"},
|
||||
{"font/woff", "woff"},
|
||||
{"font/woff2", "woff2"},
|
||||
{"application/xhtml+xml", "xhtml"},
|
||||
{"application/vnd.ms-excel", "xls"},
|
||||
{"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx"},
|
||||
{"application/xml", "xml"},
|
||||
{"text/xml", "xml"},
|
||||
{"application/vnd.mozilla.xul+xml", "xul"},
|
||||
{"application/zip", "zip"},
|
||||
{"video/3gpp", "3gp"},
|
||||
{"audio/3gpp", "3gp"},
|
||||
{"video/3gpp2", "3g2"},
|
||||
{"audio/3gpp2", "3g2"},
|
||||
{"application/x-7z-compressed", "7z"},
|
||||
};
|
||||
|
||||
std::string ret;
|
||||
for (size_t i = 0; i < std::size(table); i++)
|
||||
{
|
||||
if (StringUtil::compareNoCase(table[i][0], content_type))
|
||||
{
|
||||
ret = table[i][1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
98
common/HTTPDownloader.h
Normal file
98
common/HTTPDownloader.h
Normal file
@@ -0,0 +1,98 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
class ProgressCallback;
|
||||
|
||||
class HTTPDownloader
|
||||
{
|
||||
public:
|
||||
enum : s32
|
||||
{
|
||||
HTTP_STATUS_CANCELLED = -3,
|
||||
HTTP_STATUS_TIMEOUT = -2,
|
||||
HTTP_STATUS_ERROR = -1,
|
||||
HTTP_STATUS_OK = 200
|
||||
};
|
||||
|
||||
struct Request
|
||||
{
|
||||
using Data = std::vector<u8>;
|
||||
using Callback = std::function<void(s32 status_code, const std::string& content_type, Data data)>;
|
||||
|
||||
enum class Type
|
||||
{
|
||||
Get,
|
||||
Post,
|
||||
};
|
||||
|
||||
enum class State
|
||||
{
|
||||
Pending,
|
||||
Cancelled,
|
||||
Started,
|
||||
Receiving,
|
||||
Complete,
|
||||
};
|
||||
|
||||
HTTPDownloader* parent;
|
||||
Callback callback;
|
||||
ProgressCallback* progress;
|
||||
std::string url;
|
||||
std::string post_data;
|
||||
std::string content_type;
|
||||
Data data;
|
||||
u64 start_time;
|
||||
s32 status_code = 0;
|
||||
u32 content_length = 0;
|
||||
u32 last_progress_update = 0;
|
||||
Type type = Type::Get;
|
||||
std::atomic<State> state{State::Pending};
|
||||
};
|
||||
|
||||
HTTPDownloader();
|
||||
virtual ~HTTPDownloader();
|
||||
|
||||
static std::unique_ptr<HTTPDownloader> Create(std::string user_agent = DEFAULT_USER_AGENT);
|
||||
static std::string GetExtensionForContentType(const std::string& content_type);
|
||||
|
||||
void SetTimeout(float timeout);
|
||||
void SetMaxActiveRequests(u32 max_active_requests);
|
||||
|
||||
void CreateRequest(std::string url, Request::Callback callback, ProgressCallback* progress = nullptr);
|
||||
void CreatePostRequest(std::string url, std::string post_data, Request::Callback callback, ProgressCallback* progress = nullptr);
|
||||
void PollRequests();
|
||||
void WaitForAllRequests();
|
||||
bool HasAnyRequests();
|
||||
|
||||
static const char DEFAULT_USER_AGENT[];
|
||||
|
||||
protected:
|
||||
virtual Request* InternalCreateRequest() = 0;
|
||||
virtual void InternalPollRequests() = 0;
|
||||
|
||||
virtual bool StartRequest(Request* request) = 0;
|
||||
virtual void CloseRequest(Request* request) = 0;
|
||||
|
||||
void LockedAddRequest(Request* request);
|
||||
u32 LockedGetActiveRequestCount();
|
||||
void LockedPollRequests(std::unique_lock<std::mutex>& lock);
|
||||
|
||||
float m_timeout;
|
||||
u32 m_max_active_requests;
|
||||
|
||||
std::mutex m_pending_http_request_lock;
|
||||
std::vector<Request*> m_pending_http_requests;
|
||||
};
|
||||
206
common/HTTPDownloaderCurl.cpp
Normal file
206
common/HTTPDownloaderCurl.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/HTTPDownloaderCurl.h"
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Timer.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
|
||||
HTTPDownloaderCurl::HTTPDownloaderCurl()
|
||||
: HTTPDownloader()
|
||||
{
|
||||
}
|
||||
|
||||
HTTPDownloaderCurl::~HTTPDownloaderCurl()
|
||||
{
|
||||
if (m_multi_handle)
|
||||
curl_multi_cleanup(m_multi_handle);
|
||||
}
|
||||
|
||||
std::unique_ptr<HTTPDownloader> HTTPDownloader::Create(std::string user_agent)
|
||||
{
|
||||
std::unique_ptr<HTTPDownloaderCurl> instance(std::make_unique<HTTPDownloaderCurl>());
|
||||
if (!instance->Initialize(std::move(user_agent)))
|
||||
return {};
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static bool s_curl_initialized = false;
|
||||
static std::once_flag s_curl_initialized_once_flag;
|
||||
|
||||
bool HTTPDownloaderCurl::Initialize(std::string user_agent)
|
||||
{
|
||||
if (!s_curl_initialized)
|
||||
{
|
||||
std::call_once(s_curl_initialized_once_flag, []() {
|
||||
s_curl_initialized = curl_global_init(CURL_GLOBAL_ALL) == CURLE_OK;
|
||||
if (s_curl_initialized)
|
||||
{
|
||||
std::atexit([]() {
|
||||
curl_global_cleanup();
|
||||
s_curl_initialized = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!s_curl_initialized)
|
||||
{
|
||||
Console.Error("curl_global_init() failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_multi_handle = curl_multi_init();
|
||||
if (!m_multi_handle)
|
||||
{
|
||||
Console.Error("curl_multi_init() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_user_agent = std::move(user_agent);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t HTTPDownloaderCurl::WriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
|
||||
{
|
||||
Request* req = static_cast<Request*>(userdata);
|
||||
const size_t current_size = req->data.size();
|
||||
const size_t transfer_size = size * nmemb;
|
||||
const size_t new_size = current_size + transfer_size;
|
||||
req->data.resize(new_size);
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
std::memcpy(&req->data[current_size], ptr, transfer_size);
|
||||
|
||||
if (req->content_length == 0)
|
||||
{
|
||||
curl_off_t length;
|
||||
if (curl_easy_getinfo(req->handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &length) == CURLE_OK)
|
||||
req->content_length = static_cast<u32>(length);
|
||||
}
|
||||
|
||||
return nmemb;
|
||||
}
|
||||
|
||||
HTTPDownloader::Request* HTTPDownloaderCurl::InternalCreateRequest()
|
||||
{
|
||||
Request* req = new Request();
|
||||
req->handle = curl_easy_init();
|
||||
if (!req->handle)
|
||||
{
|
||||
delete req;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
void HTTPDownloaderCurl::InternalPollRequests()
|
||||
{
|
||||
// Apparently OpenSSL can fire SIGPIPE...
|
||||
sigset_t old_block_mask = {};
|
||||
sigset_t new_block_mask = {};
|
||||
sigemptyset(&old_block_mask);
|
||||
sigemptyset(&new_block_mask);
|
||||
sigaddset(&new_block_mask, SIGPIPE);
|
||||
if (pthread_sigmask(SIG_BLOCK, &new_block_mask, &old_block_mask) != 0)
|
||||
Console.Warning("Failed to block SIGPIPE");
|
||||
|
||||
int running_handles;
|
||||
const CURLMcode err = curl_multi_perform(m_multi_handle, &running_handles);
|
||||
if (err != CURLM_OK)
|
||||
Console.Error(fmt::format("curl_multi_perform() returned {}", static_cast<int>(err)));
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int msgq;
|
||||
struct CURLMsg* msg = curl_multi_info_read(m_multi_handle, &msgq);
|
||||
if (!msg)
|
||||
break;
|
||||
|
||||
if (msg->msg != CURLMSG_DONE)
|
||||
{
|
||||
Console.Warning(fmt::format("Unexpected multi message {}", static_cast<int>(msg->msg)));
|
||||
continue;
|
||||
}
|
||||
|
||||
Request* req;
|
||||
if (curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &req) != CURLE_OK)
|
||||
{
|
||||
Console.Error("curl_easy_getinfo() failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msg->data.result == CURLE_OK)
|
||||
{
|
||||
long response_code = 0;
|
||||
curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
req->status_code = static_cast<s32>(response_code);
|
||||
|
||||
char* content_type = nullptr;
|
||||
if (curl_easy_getinfo(req->handle, CURLINFO_CONTENT_TYPE, &content_type) == CURLE_OK && content_type)
|
||||
req->content_type = content_type;
|
||||
|
||||
DevCon.WriteLn(fmt::format("Request for '{}' returned status code {} and {} bytes", req->url, req->status_code, req->data.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error(fmt::format("Request for '{}' returned error {}", req->url, static_cast<int>(msg->data.result)));
|
||||
}
|
||||
|
||||
req->state.store(Request::State::Complete, std::memory_order_release);
|
||||
}
|
||||
|
||||
if (pthread_sigmask(SIG_UNBLOCK, &new_block_mask, &old_block_mask) != 0)
|
||||
Console.Warning("Failed to unblock SIGPIPE");
|
||||
}
|
||||
|
||||
bool HTTPDownloaderCurl::StartRequest(HTTPDownloader::Request* request)
|
||||
{
|
||||
Request* req = static_cast<Request*>(request);
|
||||
curl_easy_setopt(req->handle, CURLOPT_URL, request->url.c_str());
|
||||
curl_easy_setopt(req->handle, CURLOPT_USERAGENT, m_user_agent.c_str());
|
||||
curl_easy_setopt(req->handle, CURLOPT_WRITEFUNCTION, &HTTPDownloaderCurl::WriteCallback);
|
||||
curl_easy_setopt(req->handle, CURLOPT_WRITEDATA, req);
|
||||
curl_easy_setopt(req->handle, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(req->handle, CURLOPT_PRIVATE, req);
|
||||
curl_easy_setopt(req->handle, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
if (request->type == Request::Type::Post)
|
||||
{
|
||||
curl_easy_setopt(req->handle, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(req->handle, CURLOPT_POSTFIELDS, request->post_data.c_str());
|
||||
}
|
||||
|
||||
DevCon.WriteLn(fmt::format("Started HTTP request for '{}'", req->url));
|
||||
req->state.store(Request::State::Started, std::memory_order_release);
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
|
||||
const CURLMcode err = curl_multi_add_handle(m_multi_handle, req->handle);
|
||||
if (err != CURLM_OK)
|
||||
{
|
||||
Console.Error(fmt::format("curl_multi_add_handle() returned {}", static_cast<int>(err)));
|
||||
req->callback(HTTP_STATUS_ERROR, std::string(), req->data);
|
||||
curl_easy_cleanup(req->handle);
|
||||
delete req;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HTTPDownloaderCurl::CloseRequest(HTTPDownloader::Request* request)
|
||||
{
|
||||
Request* req = static_cast<Request*>(request);
|
||||
pxAssert(req->handle);
|
||||
curl_multi_remove_handle(m_multi_handle, req->handle);
|
||||
curl_easy_cleanup(req->handle);
|
||||
delete req;
|
||||
}
|
||||
37
common/HTTPDownloaderCurl.h
Normal file
37
common/HTTPDownloaderCurl.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/HTTPDownloader.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <curl/curl.h>
|
||||
|
||||
class HTTPDownloaderCurl final : public HTTPDownloader
|
||||
{
|
||||
public:
|
||||
HTTPDownloaderCurl();
|
||||
~HTTPDownloaderCurl() override;
|
||||
|
||||
bool Initialize(std::string user_agent);
|
||||
|
||||
protected:
|
||||
Request* InternalCreateRequest() override;
|
||||
void InternalPollRequests() override;
|
||||
bool StartRequest(HTTPDownloader::Request* request) override;
|
||||
void CloseRequest(HTTPDownloader::Request* request) override;
|
||||
|
||||
private:
|
||||
struct Request : HTTPDownloader::Request
|
||||
{
|
||||
CURL* handle = nullptr;
|
||||
};
|
||||
|
||||
static size_t WriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata);
|
||||
|
||||
CURLM* m_multi_handle = nullptr;
|
||||
std::string m_user_agent;
|
||||
};
|
||||
316
common/HTTPDownloaderWinHTTP.cpp
Normal file
316
common/HTTPDownloaderWinHTTP.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/HTTPDownloaderWinHTTP.h"
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Timer.h"
|
||||
|
||||
#include <VersionHelpers.h>
|
||||
#include <algorithm>
|
||||
|
||||
#pragma comment(lib, "winhttp.lib")
|
||||
|
||||
HTTPDownloaderWinHttp::HTTPDownloaderWinHttp()
|
||||
: HTTPDownloader()
|
||||
{
|
||||
}
|
||||
|
||||
HTTPDownloaderWinHttp::~HTTPDownloaderWinHttp()
|
||||
{
|
||||
if (m_hSession)
|
||||
{
|
||||
WinHttpSetStatusCallback(m_hSession, nullptr, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, NULL);
|
||||
WinHttpCloseHandle(m_hSession);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<HTTPDownloader> HTTPDownloader::Create(std::string user_agent)
|
||||
{
|
||||
std::unique_ptr<HTTPDownloaderWinHttp> instance(std::make_unique<HTTPDownloaderWinHttp>());
|
||||
if (!instance->Initialize(std::move(user_agent)))
|
||||
return {};
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool HTTPDownloaderWinHttp::Initialize(std::string user_agent)
|
||||
{
|
||||
const DWORD dwAccessType = WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY;
|
||||
|
||||
m_hSession = WinHttpOpen(StringUtil::UTF8StringToWideString(user_agent).c_str(), dwAccessType, nullptr, nullptr,
|
||||
WINHTTP_FLAG_ASYNC);
|
||||
if (m_hSession == NULL)
|
||||
{
|
||||
Console.Error("WinHttpOpen() failed: %u", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
const DWORD notification_flags = WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | WINHTTP_CALLBACK_FLAG_REQUEST_ERROR |
|
||||
WINHTTP_CALLBACK_FLAG_HANDLES | WINHTTP_CALLBACK_FLAG_SECURE_FAILURE;
|
||||
if (WinHttpSetStatusCallback(m_hSession, HTTPStatusCallback, notification_flags, NULL) ==
|
||||
WINHTTP_INVALID_STATUS_CALLBACK)
|
||||
{
|
||||
Console.Error("WinHttpSetStatusCallback() failed: %u", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWORD_PTR dwContext, DWORD dwInternetStatus,
|
||||
LPVOID lpvStatusInformation, DWORD dwStatusInformationLength)
|
||||
{
|
||||
Request* req = reinterpret_cast<Request*>(dwContext);
|
||||
switch (dwInternetStatus)
|
||||
{
|
||||
case WINHTTP_CALLBACK_STATUS_HANDLE_CREATED:
|
||||
return;
|
||||
|
||||
case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
|
||||
{
|
||||
if (!req)
|
||||
return;
|
||||
|
||||
pxAssert(hRequest == req->hRequest);
|
||||
|
||||
HTTPDownloaderWinHttp* parent = static_cast<HTTPDownloaderWinHttp*>(req->parent);
|
||||
std::unique_lock<std::mutex> lock(parent->m_pending_http_request_lock);
|
||||
pxAssertRel(std::none_of(parent->m_pending_http_requests.begin(), parent->m_pending_http_requests.end(),
|
||||
[req](HTTPDownloader::Request* it) { return it == req; }),
|
||||
"Request is not pending at close time");
|
||||
|
||||
// we can clean up the connection as well
|
||||
pxAssert(req->hConnection != NULL);
|
||||
WinHttpCloseHandle(req->hConnection);
|
||||
delete req;
|
||||
return;
|
||||
}
|
||||
|
||||
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
|
||||
{
|
||||
const WINHTTP_ASYNC_RESULT* res = reinterpret_cast<const WINHTTP_ASYNC_RESULT*>(lpvStatusInformation);
|
||||
Console.Error("WinHttp async function %p returned error %u", res->dwResult, res->dwError);
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->state.store(Request::State::Complete);
|
||||
return;
|
||||
}
|
||||
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
|
||||
{
|
||||
DbgCon.WriteLn("SendRequest complete");
|
||||
if (!WinHttpReceiveResponse(hRequest, nullptr))
|
||||
{
|
||||
Console.Error("WinHttpReceiveResponse() failed: %u", GetLastError());
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->state.store(Request::State::Complete);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
|
||||
{
|
||||
DbgCon.WriteLn("Headers available");
|
||||
|
||||
DWORD buffer_size = sizeof(req->status_code);
|
||||
if (!WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX, &req->status_code, &buffer_size, WINHTTP_NO_HEADER_INDEX))
|
||||
{
|
||||
Console.Error("WinHttpQueryHeaders() for status code failed: %u", GetLastError());
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->state.store(Request::State::Complete);
|
||||
return;
|
||||
}
|
||||
|
||||
buffer_size = sizeof(req->content_length);
|
||||
if (!WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX, &req->content_length, &buffer_size,
|
||||
WINHTTP_NO_HEADER_INDEX))
|
||||
{
|
||||
if (GetLastError() != ERROR_WINHTTP_HEADER_NOT_FOUND)
|
||||
Console.Warning("WinHttpQueryHeaders() for content length failed: %u", GetLastError());
|
||||
|
||||
req->content_length = 0;
|
||||
}
|
||||
|
||||
DWORD content_type_length = 0;
|
||||
if (!WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_TYPE, WINHTTP_HEADER_NAME_BY_INDEX,
|
||||
WINHTTP_NO_OUTPUT_BUFFER, &content_type_length, WINHTTP_NO_HEADER_INDEX) &&
|
||||
GetLastError() == ERROR_INSUFFICIENT_BUFFER && content_type_length >= sizeof(content_type_length))
|
||||
{
|
||||
std::wstring content_type_wstring;
|
||||
content_type_wstring.resize((content_type_length / sizeof(wchar_t)) - 1);
|
||||
if (WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_TYPE, WINHTTP_HEADER_NAME_BY_INDEX,
|
||||
content_type_wstring.data(), &content_type_length, WINHTTP_NO_HEADER_INDEX))
|
||||
{
|
||||
req->content_type = StringUtil::WideStringToUTF8String(content_type_wstring);
|
||||
}
|
||||
}
|
||||
|
||||
DbgCon.WriteLn("Status code %d, content-length is %u, content-type is %s", req->status_code, req->content_length,
|
||||
req->content_type.c_str());
|
||||
req->data.reserve(req->content_length);
|
||||
req->state = Request::State::Receiving;
|
||||
|
||||
// start reading
|
||||
if (!WinHttpQueryDataAvailable(hRequest, nullptr) && GetLastError() != ERROR_IO_PENDING)
|
||||
{
|
||||
Console.Error("WinHttpQueryDataAvailable() failed: %u", GetLastError());
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->state.store(Request::State::Complete);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
|
||||
{
|
||||
DWORD bytes_available;
|
||||
std::memcpy(&bytes_available, lpvStatusInformation, sizeof(bytes_available));
|
||||
if (bytes_available == 0)
|
||||
{
|
||||
// end of request
|
||||
DbgCon.WriteLn("End of request '%s', %zu bytes received", req->url.c_str(), req->data.size());
|
||||
req->state.store(Request::State::Complete);
|
||||
return;
|
||||
}
|
||||
|
||||
// start the transfer
|
||||
DbgCon.WriteLn("%u bytes available", bytes_available);
|
||||
req->io_position = static_cast<u32>(req->data.size());
|
||||
req->data.resize(req->io_position + bytes_available);
|
||||
if (!WinHttpReadData(hRequest, req->data.data() + req->io_position, bytes_available, nullptr) &&
|
||||
GetLastError() != ERROR_IO_PENDING)
|
||||
{
|
||||
Console.Error("WinHttpReadData() failed: %u", GetLastError());
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->state.store(Request::State::Complete);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
|
||||
{
|
||||
DbgCon.WriteLn("Read of %u complete", dwStatusInformationLength);
|
||||
|
||||
const u32 new_size = req->io_position + dwStatusInformationLength;
|
||||
pxAssertRel(new_size <= req->data.size(), "HTTP overread occurred");
|
||||
req->data.resize(new_size);
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
|
||||
if (!WinHttpQueryDataAvailable(hRequest, nullptr) && GetLastError() != ERROR_IO_PENDING)
|
||||
{
|
||||
Console.Error("WinHttpQueryDataAvailable() failed: %u", GetLastError());
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->state.store(Request::State::Complete);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
default:
|
||||
// unhandled, ignore
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
HTTPDownloader::Request* HTTPDownloaderWinHttp::InternalCreateRequest()
|
||||
{
|
||||
Request* req = new Request();
|
||||
return req;
|
||||
}
|
||||
|
||||
void HTTPDownloaderWinHttp::InternalPollRequests()
|
||||
{
|
||||
// noop - it uses windows's worker threads
|
||||
}
|
||||
|
||||
bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request)
|
||||
{
|
||||
Request* req = static_cast<Request*>(request);
|
||||
|
||||
std::wstring host_name;
|
||||
host_name.resize(req->url.size());
|
||||
req->object_name.resize(req->url.size());
|
||||
|
||||
URL_COMPONENTSW uc = {};
|
||||
uc.dwStructSize = sizeof(uc);
|
||||
uc.lpszHostName = host_name.data();
|
||||
uc.dwHostNameLength = static_cast<DWORD>(host_name.size());
|
||||
uc.lpszUrlPath = req->object_name.data();
|
||||
uc.dwUrlPathLength = static_cast<DWORD>(req->object_name.size());
|
||||
|
||||
const std::wstring url_wide(StringUtil::UTF8StringToWideString(req->url));
|
||||
if (!WinHttpCrackUrl(url_wide.c_str(), static_cast<DWORD>(url_wide.size()), 0, &uc))
|
||||
{
|
||||
Console.Error("WinHttpCrackUrl() failed: %u", GetLastError());
|
||||
req->callback(HTTP_STATUS_ERROR, req->content_type, Request::Data());
|
||||
delete req;
|
||||
return false;
|
||||
}
|
||||
|
||||
host_name.resize(uc.dwHostNameLength);
|
||||
req->object_name.resize(uc.dwUrlPathLength);
|
||||
|
||||
req->hConnection = WinHttpConnect(m_hSession, host_name.c_str(), uc.nPort, 0);
|
||||
if (!req->hConnection)
|
||||
{
|
||||
Console.Error("Failed to start HTTP request for '%s': %u", req->url.c_str(), GetLastError());
|
||||
req->callback(HTTP_STATUS_ERROR, req->content_type, Request::Data());
|
||||
delete req;
|
||||
return false;
|
||||
}
|
||||
|
||||
const DWORD request_flags = uc.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0;
|
||||
req->hRequest =
|
||||
WinHttpOpenRequest(req->hConnection, (req->type == HTTPDownloader::Request::Type::Post) ? L"POST" : L"GET",
|
||||
req->object_name.c_str(), NULL, NULL, NULL, request_flags);
|
||||
if (!req->hRequest)
|
||||
{
|
||||
Console.Error("WinHttpOpenRequest() failed: %u", GetLastError());
|
||||
WinHttpCloseHandle(req->hConnection);
|
||||
return false;
|
||||
}
|
||||
|
||||
BOOL result;
|
||||
if (req->type == HTTPDownloader::Request::Type::Post)
|
||||
{
|
||||
const std::wstring_view additional_headers(L"Content-Type: application/x-www-form-urlencoded\r\n");
|
||||
result = WinHttpSendRequest(req->hRequest, additional_headers.data(), static_cast<DWORD>(additional_headers.size()),
|
||||
req->post_data.data(), static_cast<DWORD>(req->post_data.size()),
|
||||
static_cast<DWORD>(req->post_data.size()), reinterpret_cast<DWORD_PTR>(req));
|
||||
}
|
||||
else
|
||||
{
|
||||
result = WinHttpSendRequest(req->hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0,
|
||||
reinterpret_cast<DWORD_PTR>(req));
|
||||
}
|
||||
|
||||
if (!result && GetLastError() != ERROR_IO_PENDING)
|
||||
{
|
||||
Console.Error("WinHttpSendRequest() failed: %u", GetLastError());
|
||||
req->status_code = HTTP_STATUS_ERROR;
|
||||
req->state.store(Request::State::Complete);
|
||||
}
|
||||
|
||||
DevCon.WriteLn("Started HTTP request for '%s'", req->url.c_str());
|
||||
req->state = Request::State::Started;
|
||||
req->start_time = Common::Timer::GetCurrentValue();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HTTPDownloaderWinHttp::CloseRequest(HTTPDownloader::Request* request)
|
||||
{
|
||||
Request* req = static_cast<Request*>(request);
|
||||
|
||||
if (req->hRequest != NULL)
|
||||
{
|
||||
// req will be freed by the callback.
|
||||
// the callback can fire immediately here if there's nothing running async, so don't touch req afterwards
|
||||
WinHttpCloseHandle(req->hRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req->hConnection != NULL)
|
||||
WinHttpCloseHandle(req->hConnection);
|
||||
|
||||
delete req;
|
||||
}
|
||||
40
common/HTTPDownloaderWinHTTP.h
Normal file
40
common/HTTPDownloaderWinHTTP.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/HTTPDownloader.h"
|
||||
#include "common/RedtapeWindows.h"
|
||||
|
||||
#include <winhttp.h>
|
||||
|
||||
class HTTPDownloaderWinHttp final : public HTTPDownloader
|
||||
{
|
||||
public:
|
||||
HTTPDownloaderWinHttp();
|
||||
~HTTPDownloaderWinHttp() override;
|
||||
|
||||
bool Initialize(std::string user_agent);
|
||||
|
||||
protected:
|
||||
Request* InternalCreateRequest() override;
|
||||
void InternalPollRequests() override;
|
||||
bool StartRequest(HTTPDownloader::Request* request) override;
|
||||
void CloseRequest(HTTPDownloader::Request* request) override;
|
||||
|
||||
private:
|
||||
struct Request : HTTPDownloader::Request
|
||||
{
|
||||
std::wstring object_name;
|
||||
HINTERNET hConnection = NULL;
|
||||
HINTERNET hRequest = NULL;
|
||||
u32 io_position = 0;
|
||||
};
|
||||
|
||||
static void CALLBACK HTTPStatusCallback(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus,
|
||||
LPVOID lpvStatusInformation, DWORD dwStatusInformationLength);
|
||||
|
||||
static bool CheckCancelled(Request* request);
|
||||
|
||||
HINTERNET m_hSession = NULL;
|
||||
};
|
||||
14
common/HashCombine.h
Normal file
14
common/HashCombine.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
template <typename T, typename... Rest>
|
||||
static inline void HashCombine(std::size_t& seed, const T& v, Rest&&... rest)
|
||||
{
|
||||
seed ^= std::hash<T>{}(v) + 0x9e3779b9u + (seed << 6) + (seed >> 2);
|
||||
(HashCombine(seed, std::forward<Rest>(rest)), ...);
|
||||
}
|
||||
401
common/HeapArray.h
Normal file
401
common/HeapArray.h
Normal file
@@ -0,0 +1,401 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Assertions.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
template <typename T, std::size_t SIZE, std::size_t ALIGNMENT = 0>
|
||||
class FixedHeapArray
|
||||
{
|
||||
public:
|
||||
using value_type = T;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using this_type = FixedHeapArray<T, SIZE>;
|
||||
|
||||
FixedHeapArray() { allocate(); }
|
||||
|
||||
FixedHeapArray(const this_type& copy)
|
||||
{
|
||||
allocate();
|
||||
std::copy(copy.cbegin(), copy.cend(), begin());
|
||||
}
|
||||
|
||||
FixedHeapArray(this_type&& move)
|
||||
{
|
||||
m_data = move.m_data;
|
||||
move.m_data = nullptr;
|
||||
}
|
||||
|
||||
~FixedHeapArray() { deallocate(); }
|
||||
|
||||
size_type size() const { return SIZE; }
|
||||
size_type capacity() const { return SIZE; }
|
||||
bool empty() const { return false; }
|
||||
|
||||
pointer begin() { return m_data; }
|
||||
pointer end() { return m_data + SIZE; }
|
||||
|
||||
const_pointer data() const { return m_data; }
|
||||
pointer data() { return m_data; }
|
||||
|
||||
const_pointer cbegin() const { return m_data; }
|
||||
const_pointer cend() const { return m_data + SIZE; }
|
||||
|
||||
const_reference operator[](size_type index) const
|
||||
{
|
||||
assert(index < SIZE);
|
||||
return m_data[index];
|
||||
}
|
||||
reference operator[](size_type index)
|
||||
{
|
||||
assert(index < SIZE);
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
const_reference front() const { return m_data[0]; }
|
||||
const_reference back() const { return m_data[SIZE - 1]; }
|
||||
reference front() { return m_data[0]; }
|
||||
reference back() { return m_data[SIZE - 1]; }
|
||||
|
||||
void fill(const_reference value) { std::fill(begin(), end(), value); }
|
||||
|
||||
void swap(this_type& move) { std::swap(m_data, move.m_data); }
|
||||
|
||||
this_type& operator=(const this_type& rhs)
|
||||
{
|
||||
std::copy(begin(), end(), rhs.cbegin());
|
||||
return *this;
|
||||
}
|
||||
|
||||
this_type& operator=(this_type&& move)
|
||||
{
|
||||
deallocate();
|
||||
m_data = move.m_data;
|
||||
move.m_data = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
#define RELATIONAL_OPERATOR(op) \
|
||||
bool operator op(const this_type& rhs) const \
|
||||
{ \
|
||||
for (size_type i = 0; i < SIZE; i++) \
|
||||
{ \
|
||||
if (!(m_data[i] op rhs.m_data[i])) \
|
||||
return false; \
|
||||
} \
|
||||
}
|
||||
|
||||
RELATIONAL_OPERATOR(==);
|
||||
RELATIONAL_OPERATOR(!=);
|
||||
RELATIONAL_OPERATOR(<);
|
||||
RELATIONAL_OPERATOR(<=);
|
||||
RELATIONAL_OPERATOR(>);
|
||||
RELATIONAL_OPERATOR(>=);
|
||||
|
||||
#undef RELATIONAL_OPERATOR
|
||||
|
||||
private:
|
||||
void allocate()
|
||||
{
|
||||
if constexpr (ALIGNMENT > 0)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
m_data = static_cast<T*>(_aligned_malloc(SIZE * sizeof(T), ALIGNMENT));
|
||||
if (!m_data)
|
||||
pxFailRel("Memory allocation failed.");
|
||||
#else
|
||||
if (posix_memalign(reinterpret_cast<void**>(&m_data), ALIGNMENT, SIZE * sizeof(T)) != 0)
|
||||
pxFailRel("Memory allocation failed.");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data = static_cast<T*>(std::malloc(SIZE * sizeof(T)));
|
||||
if (!m_data)
|
||||
pxFailRel("Memory allocation failed.");
|
||||
}
|
||||
}
|
||||
void deallocate()
|
||||
{
|
||||
if constexpr (ALIGNMENT > 0)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
_aligned_free(m_data);
|
||||
#else
|
||||
std::free(m_data);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
std::free(m_data);
|
||||
}
|
||||
}
|
||||
|
||||
T* m_data;
|
||||
};
|
||||
|
||||
template <typename T, size_t alignment = 0>
|
||||
class DynamicHeapArray
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T is trivially copyable");
|
||||
static_assert(std::is_standard_layout_v<T>, "T is standard layout");
|
||||
|
||||
public:
|
||||
using value_type = T;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using this_type = DynamicHeapArray<T>;
|
||||
|
||||
DynamicHeapArray()
|
||||
: m_data(nullptr)
|
||||
, m_size(0)
|
||||
{
|
||||
}
|
||||
DynamicHeapArray(size_t size) { internal_resize(size, nullptr, 0); }
|
||||
DynamicHeapArray(const T* begin, const T* end)
|
||||
{
|
||||
const size_t size = reinterpret_cast<const char*>(end) - reinterpret_cast<const char*>(begin);
|
||||
if (size > 0)
|
||||
{
|
||||
internal_resize(size / sizeof(T), nullptr, 0);
|
||||
std::memcpy(m_data, begin, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data = nullptr;
|
||||
m_size = 0;
|
||||
}
|
||||
}
|
||||
DynamicHeapArray(const T* begin, size_t count)
|
||||
{
|
||||
if (count > 0)
|
||||
{
|
||||
internal_resize(count, nullptr, 0);
|
||||
std::memcpy(m_data, begin, sizeof(T) * count);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data = nullptr;
|
||||
m_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
DynamicHeapArray(const this_type& copy)
|
||||
{
|
||||
if (copy.m_size > 0)
|
||||
{
|
||||
internal_resize(copy.m_size, nullptr, 0);
|
||||
std::memcpy(m_data, copy.m_data, sizeof(T) * copy.m_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data = nullptr;
|
||||
m_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
DynamicHeapArray(this_type&& move)
|
||||
{
|
||||
m_data = move.m_data;
|
||||
m_size = move.m_size;
|
||||
move.m_data = nullptr;
|
||||
move.m_size = 0;
|
||||
}
|
||||
|
||||
~DynamicHeapArray() { internal_deallocate(); }
|
||||
|
||||
size_type size() const { return m_size; }
|
||||
size_type capacity() const { return m_size; }
|
||||
bool empty() const { return (m_size == 0); }
|
||||
|
||||
pointer begin() { return m_data; }
|
||||
pointer end() { return m_data + m_size; }
|
||||
|
||||
const_pointer data() const { return m_data; }
|
||||
pointer data() { return m_data; }
|
||||
|
||||
const_pointer cbegin() const { return m_data; }
|
||||
const_pointer cend() const { return m_data + m_size; }
|
||||
|
||||
const_reference operator[](size_type index) const
|
||||
{
|
||||
assert(index < m_size);
|
||||
return m_data[index];
|
||||
}
|
||||
reference operator[](size_type index)
|
||||
{
|
||||
assert(index < m_size);
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
const_reference front() const { return m_data[0]; }
|
||||
const_reference back() const { return m_data[m_size - 1]; }
|
||||
reference front() { return m_data[0]; }
|
||||
reference back() { return m_data[m_size - 1]; }
|
||||
|
||||
void fill(const_reference value) { std::fill(begin(), end(), value); }
|
||||
|
||||
void swap(this_type& move)
|
||||
{
|
||||
std::swap(m_data, move.m_data);
|
||||
std::swap(m_size, move.m_size);
|
||||
}
|
||||
|
||||
void resize(size_t new_size) { internal_resize(new_size, m_data, m_size); }
|
||||
|
||||
void deallocate()
|
||||
{
|
||||
internal_deallocate();
|
||||
m_data = nullptr;
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
void assign(const T* begin, const T* end)
|
||||
{
|
||||
const size_t size = reinterpret_cast<const char*>(end) - reinterpret_cast<const char*>(begin);
|
||||
const size_t count = size / sizeof(T);
|
||||
if (count > 0)
|
||||
{
|
||||
if (m_size != count)
|
||||
{
|
||||
internal_deallocate();
|
||||
internal_resize(count, nullptr, 0);
|
||||
}
|
||||
|
||||
std::memcpy(m_data, begin, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
internal_deallocate();
|
||||
|
||||
m_data = nullptr;
|
||||
m_size = 0;
|
||||
}
|
||||
}
|
||||
void assign(const T* begin, size_t count)
|
||||
{
|
||||
if (count > 0)
|
||||
{
|
||||
if (m_size != count)
|
||||
{
|
||||
internal_deallocate();
|
||||
internal_resize(count, nullptr, 0);
|
||||
}
|
||||
|
||||
std::memcpy(m_data, begin, sizeof(T) * count);
|
||||
}
|
||||
else
|
||||
{
|
||||
internal_deallocate();
|
||||
|
||||
m_data = nullptr;
|
||||
m_size = 0;
|
||||
}
|
||||
}
|
||||
void assign(const this_type& copy) { assign(copy.m_data, copy.m_size); }
|
||||
void assign(this_type&& move)
|
||||
{
|
||||
internal_deallocate();
|
||||
m_data = move.m_data;
|
||||
m_size = move.m_size;
|
||||
move.m_data = nullptr;
|
||||
move.m_size = 0;
|
||||
}
|
||||
|
||||
this_type& operator=(const this_type& rhs)
|
||||
{
|
||||
assign(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
this_type& operator=(this_type&& move)
|
||||
{
|
||||
assign(std::move(move));
|
||||
return *this;
|
||||
}
|
||||
|
||||
#define RELATIONAL_OPERATOR(op, size_op) \
|
||||
bool operator op(const this_type& rhs) const \
|
||||
{ \
|
||||
if (m_size != rhs.m_size) \
|
||||
return m_size size_op rhs.m_size; \
|
||||
for (size_type i = 0; i < m_size; i++) \
|
||||
{ \
|
||||
if (!(m_data[i] op rhs.m_data[i])) \
|
||||
return false; \
|
||||
} \
|
||||
}
|
||||
|
||||
RELATIONAL_OPERATOR(==, !=);
|
||||
RELATIONAL_OPERATOR(!=, ==);
|
||||
RELATIONAL_OPERATOR(<, <);
|
||||
RELATIONAL_OPERATOR(<=, <=);
|
||||
RELATIONAL_OPERATOR(>, >);
|
||||
RELATIONAL_OPERATOR(>=, >=);
|
||||
|
||||
#undef RELATIONAL_OPERATOR
|
||||
|
||||
private:
|
||||
void internal_resize(size_t size, T* prev_ptr, size_t prev_size)
|
||||
{
|
||||
if constexpr (alignment > 0)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
m_data = static_cast<T*>(_aligned_realloc(prev_ptr, size * sizeof(T), alignment));
|
||||
if (!m_data)
|
||||
pxFailRel("Memory allocation failed.");
|
||||
#else
|
||||
if (posix_memalign(reinterpret_cast<void**>(&m_data), alignment, size * sizeof(T)) != 0)
|
||||
pxFailRel("Memory allocation failed.");
|
||||
|
||||
if (prev_ptr)
|
||||
{
|
||||
std::memcpy(m_data, prev_ptr, std::min(size, prev_size) * sizeof(T));
|
||||
std::free(prev_ptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data = static_cast<T*>(std::realloc(prev_ptr, size * sizeof(T)));
|
||||
if (!m_data)
|
||||
pxFailRel("Memory allocation failed.");
|
||||
}
|
||||
|
||||
m_size = size;
|
||||
}
|
||||
void internal_deallocate()
|
||||
{
|
||||
if constexpr (alignment > 0)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
_aligned_free(m_data);
|
||||
#else
|
||||
std::free(m_data);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
std::free(m_data);
|
||||
}
|
||||
}
|
||||
|
||||
T* m_data;
|
||||
size_t m_size;
|
||||
};
|
||||
67
common/HeterogeneousContainers.h
Normal file
67
common/HeterogeneousContainers.h
Normal file
@@ -0,0 +1,67 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
/**
|
||||
* Provides a map template which doesn't require heap allocations for lookups.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Pcsx2Defs.h"
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace detail
|
||||
{
|
||||
struct transparent_string_hash
|
||||
{
|
||||
using is_transparent = void;
|
||||
|
||||
std::size_t operator()(const std::string_view& v) const { return std::hash<std::string_view>{}(v); }
|
||||
std::size_t operator()(const std::string& s) const { return std::hash<std::string>{}(s); }
|
||||
std::size_t operator()(const char* s) const { return operator()(std::string_view(s)); }
|
||||
};
|
||||
|
||||
struct transparent_string_equal
|
||||
{
|
||||
using is_transparent = void;
|
||||
|
||||
bool operator()(const std::string& lhs, const std::string_view& rhs) const { return lhs == rhs; }
|
||||
bool operator()(const std::string& lhs, const std::string& rhs) const { return lhs == rhs; }
|
||||
bool operator()(const std::string& lhs, const char* rhs) const { return lhs == rhs; }
|
||||
bool operator()(const std::string_view& lhs, const std::string& rhs) const { return lhs == rhs; }
|
||||
bool operator()(const char* lhs, const std::string& rhs) const { return lhs == rhs; }
|
||||
};
|
||||
|
||||
struct transparent_string_less
|
||||
{
|
||||
using is_transparent = void;
|
||||
|
||||
bool operator()(const std::string& lhs, const std::string_view& rhs) const { return lhs < rhs; }
|
||||
bool operator()(const std::string& lhs, const std::string& rhs) const { return lhs < rhs; }
|
||||
bool operator()(const std::string& lhs, const char* rhs) const { return lhs < rhs; }
|
||||
bool operator()(const std::string_view& lhs, const std::string& rhs) const { return lhs < rhs; }
|
||||
bool operator()(const char* lhs, const std::string& rhs) const { return lhs < rhs; }
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <typename ValueType>
|
||||
using UnorderedStringMap =
|
||||
std::unordered_map<std::string, ValueType, detail::transparent_string_hash, detail::transparent_string_equal>;
|
||||
template <typename ValueType>
|
||||
using UnorderedStringMultimap =
|
||||
std::unordered_multimap<std::string, ValueType, detail::transparent_string_hash, detail::transparent_string_equal>;
|
||||
using UnorderedStringSet =
|
||||
std::unordered_set<std::string, detail::transparent_string_hash, detail::transparent_string_equal>;
|
||||
using UnorderedStringMultiSet =
|
||||
std::unordered_multiset<std::string, detail::transparent_string_hash, detail::transparent_string_equal>;
|
||||
|
||||
template <typename ValueType>
|
||||
using StringMap = std::map<std::string, ValueType, detail::transparent_string_less>;
|
||||
template <typename ValueType>
|
||||
using StringMultiMap = std::multimap<std::string, ValueType, detail::transparent_string_less>;
|
||||
using StringSet = std::set<std::string, detail::transparent_string_less>;
|
||||
using StringMultiSet = std::multiset<std::string, detail::transparent_string_less>;
|
||||
137
common/HostSys.cpp
Normal file
137
common/HostSys.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "HostSys.h"
|
||||
#include "Console.h"
|
||||
#include "VectorIntrin.h"
|
||||
|
||||
static u32 PAUSE_TIME = 0;
|
||||
|
||||
static void MultiPause()
|
||||
{
|
||||
#ifdef _M_X86
|
||||
_mm_pause();
|
||||
_mm_pause();
|
||||
_mm_pause();
|
||||
_mm_pause();
|
||||
_mm_pause();
|
||||
_mm_pause();
|
||||
_mm_pause();
|
||||
_mm_pause();
|
||||
#elif defined(_M_ARM64) && defined(_MSC_VER)
|
||||
__isb(_ARM64_BARRIER_SY);
|
||||
__isb(_ARM64_BARRIER_SY);
|
||||
__isb(_ARM64_BARRIER_SY);
|
||||
__isb(_ARM64_BARRIER_SY);
|
||||
__isb(_ARM64_BARRIER_SY);
|
||||
__isb(_ARM64_BARRIER_SY);
|
||||
__isb(_ARM64_BARRIER_SY);
|
||||
__isb(_ARM64_BARRIER_SY);
|
||||
#elif defined(_M_ARM64)
|
||||
__asm__ __volatile__("isb");
|
||||
__asm__ __volatile__("isb");
|
||||
__asm__ __volatile__("isb");
|
||||
__asm__ __volatile__("isb");
|
||||
__asm__ __volatile__("isb");
|
||||
__asm__ __volatile__("isb");
|
||||
__asm__ __volatile__("isb");
|
||||
__asm__ __volatile__("isb");
|
||||
#else
|
||||
#error Unknown architecture.
|
||||
#endif
|
||||
}
|
||||
|
||||
static u32 MeasurePauseTime()
|
||||
{
|
||||
// GetCPUTicks may have resolution as low as 1µs
|
||||
// One call to MultiPause could take anywhere from 20ns (fast Haswell) to 400ns (slow Skylake)
|
||||
// We want a measurement of reasonable resolution, but don't want to take too long
|
||||
// So start at a fairly small number and increase it if it's too fast
|
||||
for (int testcnt = 64; true; testcnt *= 2)
|
||||
{
|
||||
u64 start = GetCPUTicks();
|
||||
for (int i = 0; i < testcnt; i++)
|
||||
{
|
||||
MultiPause();
|
||||
}
|
||||
u64 time = GetCPUTicks() - start;
|
||||
if (time > 100)
|
||||
{
|
||||
u64 nanos = (time * 1000000000) / GetTickFrequency();
|
||||
return (nanos / testcnt) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__noinline static void UpdatePauseTime()
|
||||
{
|
||||
u64 wait = GetCPUTicks() + GetTickFrequency() / 100; // Wake up processor (spin for 10ms)
|
||||
while (GetCPUTicks() < wait)
|
||||
;
|
||||
u32 pause = MeasurePauseTime();
|
||||
// Take a few measurements in case something weird happens during one
|
||||
// (e.g. OS interrupt)
|
||||
for (int i = 0; i < 4; i++)
|
||||
pause = std::min(pause, MeasurePauseTime());
|
||||
PAUSE_TIME = pause;
|
||||
DevCon.WriteLn("MultiPause time: %uns", pause);
|
||||
}
|
||||
|
||||
u32 ShortSpin()
|
||||
{
|
||||
u32 inc = PAUSE_TIME;
|
||||
if (inc == 0) [[unlikely]]
|
||||
{
|
||||
UpdatePauseTime();
|
||||
inc = PAUSE_TIME;
|
||||
}
|
||||
|
||||
u32 time = 0;
|
||||
// Sleep for approximately 500ns
|
||||
for (; time < 500; time += inc)
|
||||
MultiPause();
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
static u32 GetSpinTime()
|
||||
{
|
||||
if (char* req = getenv("WAIT_SPIN_MICROSECONDS"))
|
||||
{
|
||||
return 1000 * atoi(req);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 50 * 1000; // 50µs
|
||||
}
|
||||
}
|
||||
|
||||
const u32 SPIN_TIME_NS = GetSpinTime();
|
||||
|
||||
#ifdef __APPLE__
|
||||
// https://alastairs-place.net/blog/2013/01/10/interesting-os-x-crash-report-tidbits/
|
||||
// https://opensource.apple.com/source/WebKit2/WebKit2-7608.3.10.0.3/Platform/spi/Cocoa/CrashReporterClientSPI.h.auto.html
|
||||
struct crash_info_t
|
||||
{
|
||||
u64 version;
|
||||
u64 message;
|
||||
u64 signature;
|
||||
u64 backtrace;
|
||||
u64 message2;
|
||||
u64 reserved;
|
||||
u64 reserved2;
|
||||
};
|
||||
#define CRASH_ANNOTATION __attribute__((used, section("__DATA,__crash_info")))
|
||||
#define CRASH_VERSION 4
|
||||
extern "C" crash_info_t gCRAnnotations CRASH_ANNOTATION = { CRASH_VERSION };
|
||||
#endif
|
||||
|
||||
void AbortWithMessage(const char* msg)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
gCRAnnotations.message = reinterpret_cast<size_t>(msg);
|
||||
// Some macOS's seem to have issues displaying non-static `message`s, so throw it in here too
|
||||
gCRAnnotations.backtrace = gCRAnnotations.message;
|
||||
#endif
|
||||
abort();
|
||||
}
|
||||
207
common/HostSys.h
Normal file
207
common/HostSys.h
Normal file
@@ -0,0 +1,207 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Error;
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// PageProtectionMode
|
||||
// --------------------------------------------------------------------------------------
|
||||
class PageProtectionMode
|
||||
{
|
||||
protected:
|
||||
bool m_read = false;
|
||||
bool m_write = false;
|
||||
bool m_exec = false;
|
||||
|
||||
public:
|
||||
__fi constexpr PageProtectionMode() = default;
|
||||
|
||||
__fi constexpr PageProtectionMode& Read(bool allow = true)
|
||||
{
|
||||
m_read = allow;
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr PageProtectionMode& Write(bool allow = true)
|
||||
{
|
||||
m_write = allow;
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr PageProtectionMode& Execute(bool allow = true)
|
||||
{
|
||||
m_exec = allow;
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr PageProtectionMode& All(bool allow = true)
|
||||
{
|
||||
m_read = m_write = m_exec = allow;
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi constexpr bool CanRead() const { return m_read; }
|
||||
__fi constexpr bool CanWrite() const { return m_write; }
|
||||
__fi constexpr bool CanExecute() const { return m_exec && m_read; }
|
||||
__fi constexpr bool IsNone() const { return !m_read && !m_write; }
|
||||
};
|
||||
|
||||
static __fi PageProtectionMode PageAccess_None()
|
||||
{
|
||||
return PageProtectionMode();
|
||||
}
|
||||
|
||||
static __fi PageProtectionMode PageAccess_ReadOnly()
|
||||
{
|
||||
return PageProtectionMode().Read();
|
||||
}
|
||||
|
||||
static __fi PageProtectionMode PageAccess_WriteOnly()
|
||||
{
|
||||
return PageProtectionMode().Write();
|
||||
}
|
||||
|
||||
static __fi PageProtectionMode PageAccess_ReadWrite()
|
||||
{
|
||||
return PageAccess_ReadOnly().Write();
|
||||
}
|
||||
|
||||
static __fi PageProtectionMode PageAccess_ExecOnly()
|
||||
{
|
||||
return PageAccess_ReadOnly().Execute();
|
||||
}
|
||||
|
||||
static __fi PageProtectionMode PageAccess_Any()
|
||||
{
|
||||
return PageProtectionMode().All();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// HostSys
|
||||
// --------------------------------------------------------------------------------------
|
||||
namespace HostSys
|
||||
{
|
||||
// Maps a block of memory for use as a recompiled code buffer.
|
||||
// Returns NULL on allocation failure.
|
||||
extern void* Mmap(void* base, size_t size, const PageProtectionMode& mode);
|
||||
|
||||
// Unmaps a block allocated by SysMmap
|
||||
extern void Munmap(void* base, size_t size);
|
||||
|
||||
extern void MemProtect(void* baseaddr, size_t size, const PageProtectionMode& mode);
|
||||
|
||||
extern std::string GetFileMappingName(const char* prefix);
|
||||
extern void* CreateSharedMemory(const char* name, size_t size);
|
||||
extern void DestroySharedMemory(void* ptr);
|
||||
extern void* MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, const PageProtectionMode& mode);
|
||||
extern void UnmapSharedMemory(void* baseaddr, size_t size);
|
||||
|
||||
/// JIT write protect for Apple Silicon. Needs to be called prior to writing to any RWX pages.
|
||||
#if !defined(__APPLE__) || !defined(_M_ARM64)
|
||||
// clang-format -off
|
||||
[[maybe_unused]] __fi static void BeginCodeWrite() {}
|
||||
[[maybe_unused]] __fi static void EndCodeWrite() {}
|
||||
// clang-format on
|
||||
#else
|
||||
void BeginCodeWrite();
|
||||
void EndCodeWrite();
|
||||
#endif
|
||||
|
||||
/// Flushes the instruction cache on the host for the specified range.
|
||||
/// Only needed on ARM64, X86 has coherent D/I cache.
|
||||
#ifdef _M_X86
|
||||
[[maybe_unused]] __fi static void FlushInstructionCache(void* address, u32 size) {}
|
||||
#else
|
||||
void FlushInstructionCache(void* address, u32 size);
|
||||
#endif
|
||||
|
||||
/// Returns the size of pages for the current host.
|
||||
size_t GetRuntimePageSize();
|
||||
|
||||
/// Returns the size of a cache line for the current host.
|
||||
size_t GetRuntimeCacheLineSize();
|
||||
} // namespace HostSys
|
||||
|
||||
namespace PageFaultHandler
|
||||
{
|
||||
enum class HandlerResult
|
||||
{
|
||||
ContinueExecution,
|
||||
ExecuteNextHandler,
|
||||
};
|
||||
|
||||
HandlerResult HandlePageFault(void* exception_pc, void* fault_address, bool is_write);
|
||||
bool Install(Error* error = nullptr);
|
||||
} // namespace PageFaultHandler
|
||||
|
||||
class SharedMemoryMappingArea
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<SharedMemoryMappingArea> Create(size_t size);
|
||||
|
||||
~SharedMemoryMappingArea();
|
||||
|
||||
__fi size_t GetSize() const { return m_size; }
|
||||
__fi size_t GetNumPages() const { return m_num_pages; }
|
||||
|
||||
__fi u8* BasePointer() const { return m_base_ptr; }
|
||||
__fi u8* OffsetPointer(size_t offset) const { return m_base_ptr + offset; }
|
||||
__fi u8* PagePointer(size_t page) const { return m_base_ptr + __pagesize * page; }
|
||||
|
||||
u8* Map(void* file_handle, size_t file_offset, void* map_base, size_t map_size, const PageProtectionMode& mode);
|
||||
bool Unmap(void* map_base, size_t map_size);
|
||||
|
||||
private:
|
||||
SharedMemoryMappingArea(u8* base_ptr, size_t size, size_t num_pages);
|
||||
|
||||
u8* m_base_ptr;
|
||||
size_t m_size;
|
||||
size_t m_num_pages;
|
||||
size_t m_num_mappings = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
using PlaceholderMap = std::map<size_t, size_t>;
|
||||
|
||||
PlaceholderMap::iterator FindPlaceholder(size_t page);
|
||||
|
||||
PlaceholderMap m_placeholder_ranges;
|
||||
#endif
|
||||
};
|
||||
|
||||
extern u64 GetTickFrequency();
|
||||
extern u64 GetCPUTicks();
|
||||
extern u64 GetPhysicalMemory();
|
||||
extern u64 GetAvailablePhysicalMemory();
|
||||
/// Spin for a short period of time (call while spinning waiting for a lock)
|
||||
/// Returns the approximate number of ns that passed
|
||||
extern u32 ShortSpin();
|
||||
/// Number of ns to spin for before sleeping a thread
|
||||
extern const u32 SPIN_TIME_NS;
|
||||
/// Like C abort() but adds the given message to the crashlog
|
||||
[[noreturn]] void AbortWithMessage(const char* msg);
|
||||
|
||||
extern std::string GetOSVersionString();
|
||||
|
||||
namespace Common
|
||||
{
|
||||
/// Enables or disables the screen saver from starting.
|
||||
bool InhibitScreensaver(bool inhibit);
|
||||
|
||||
/// Abstracts platform-specific code for asynchronously playing a sound.
|
||||
/// On Windows, this will use PlaySound(). On Linux, it will shell out to aplay. On MacOS, it uses NSSound.
|
||||
bool PlaySoundAsync(const char* path);
|
||||
|
||||
void SetMousePosition(int x, int y);
|
||||
bool AttachMousePositionCb(std::function<void(int,int)> cb);
|
||||
void DetachMousePositionCb();
|
||||
} // namespace Common
|
||||
736
common/Image.cpp
Normal file
736
common/Image.cpp
Normal file
@@ -0,0 +1,736 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "Image.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Console.h"
|
||||
#include "Path.h"
|
||||
#include "ScopedGuard.h"
|
||||
#include "StringUtil.h"
|
||||
|
||||
#include <common/FastJmp.h>
|
||||
#include <jpeglib.h>
|
||||
#include <png.h>
|
||||
#include <webp/decode.h>
|
||||
#include <webp/encode.h>
|
||||
|
||||
// Compute the address of a base type given a field offset.
|
||||
#define BASE_FROM_RECORD_FIELD(ptr, base_type, field) ((base_type*)(((char*)ptr) - offsetof(base_type, field)))
|
||||
|
||||
static bool PNGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
||||
static bool PNGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality);
|
||||
static bool PNGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||
static bool PNGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality);
|
||||
|
||||
static bool JPEGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
||||
static bool JPEGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality);
|
||||
static bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||
static bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality);
|
||||
|
||||
static bool WebPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
||||
static bool WebPBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality);
|
||||
static bool WebPFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||
static bool WebPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality);
|
||||
|
||||
struct FormatHandler
|
||||
{
|
||||
const char* extension;
|
||||
bool (*buffer_loader)(RGBA8Image*, const void*, size_t);
|
||||
bool (*buffer_saver)(const RGBA8Image&, std::vector<u8>*, u8);
|
||||
bool (*file_loader)(RGBA8Image*, const char*, std::FILE*);
|
||||
bool (*file_saver)(const RGBA8Image&, const char*, std::FILE*, u8);
|
||||
};
|
||||
|
||||
static constexpr FormatHandler s_format_handlers[] = {
|
||||
{"png", PNGBufferLoader, PNGBufferSaver, PNGFileLoader, PNGFileSaver},
|
||||
{"jpg", JPEGBufferLoader, JPEGBufferSaver, JPEGFileLoader, JPEGFileSaver},
|
||||
{"jpeg", JPEGBufferLoader, JPEGBufferSaver, JPEGFileLoader, JPEGFileSaver},
|
||||
{"webp", WebPBufferLoader, WebPBufferSaver, WebPFileLoader, WebPFileSaver},
|
||||
};
|
||||
|
||||
static const FormatHandler* GetFormatHandler(const std::string_view extension)
|
||||
{
|
||||
for (const FormatHandler& handler : s_format_handlers)
|
||||
{
|
||||
if (StringUtil::Strncasecmp(extension.data(), handler.extension, extension.size()) == 0)
|
||||
return &handler;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RGBA8Image::RGBA8Image() = default;
|
||||
|
||||
RGBA8Image::RGBA8Image(const RGBA8Image& copy)
|
||||
: Image(copy)
|
||||
{
|
||||
}
|
||||
|
||||
RGBA8Image::RGBA8Image(u32 width, u32 height, const u32* pixels)
|
||||
: Image(width, height, pixels)
|
||||
{
|
||||
}
|
||||
|
||||
RGBA8Image::RGBA8Image(RGBA8Image&& move)
|
||||
: Image(move)
|
||||
{
|
||||
}
|
||||
|
||||
RGBA8Image::RGBA8Image(u32 width, u32 height)
|
||||
: Image(width, height)
|
||||
{
|
||||
}
|
||||
|
||||
RGBA8Image::RGBA8Image(u32 width, u32 height, std::vector<u32> pixels)
|
||||
: Image(width, height, std::move(pixels))
|
||||
{
|
||||
}
|
||||
|
||||
RGBA8Image& RGBA8Image::operator=(const RGBA8Image& copy)
|
||||
{
|
||||
Image<u32>::operator=(copy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RGBA8Image& RGBA8Image::operator=(RGBA8Image&& move)
|
||||
{
|
||||
Image<u32>::operator=(move);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool RGBA8Image::LoadFromFile(const char* filename)
|
||||
{
|
||||
auto fp = FileSystem::OpenManagedCFile(filename, "rb");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
return LoadFromFile(filename, fp.get());
|
||||
}
|
||||
|
||||
bool RGBA8Image::SaveToFile(const char* filename, u8 quality) const
|
||||
{
|
||||
auto fp = FileSystem::OpenManagedCFile(filename, "wb");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
if (SaveToFile(filename, fp.get(), quality))
|
||||
return true;
|
||||
|
||||
// save failed
|
||||
fp.reset();
|
||||
FileSystem::DeleteFilePath(filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RGBA8Image::LoadFromFile(const char* filename, std::FILE* fp)
|
||||
{
|
||||
const std::string_view extension(Path::GetExtension(filename));
|
||||
const FormatHandler* handler = GetFormatHandler(extension);
|
||||
if (!handler || !handler->file_loader)
|
||||
{
|
||||
Console.ErrorFmt("Unknown extension '{}'", extension);
|
||||
return false;
|
||||
}
|
||||
|
||||
return handler->file_loader(this, filename, fp);
|
||||
}
|
||||
|
||||
bool RGBA8Image::LoadFromBuffer(const char* filename, const void* buffer, size_t buffer_size)
|
||||
{
|
||||
const std::string_view extension(Path::GetExtension(filename));
|
||||
const FormatHandler* handler = GetFormatHandler(extension);
|
||||
if (!handler || !handler->buffer_loader)
|
||||
{
|
||||
Console.ErrorFmt("Unknown extension '{}'", extension);
|
||||
return false;
|
||||
}
|
||||
|
||||
return handler->buffer_loader(this, buffer, buffer_size);
|
||||
}
|
||||
|
||||
bool RGBA8Image::SaveToFile(const char* filename, std::FILE* fp, u8 quality) const
|
||||
{
|
||||
const std::string_view extension(Path::GetExtension(filename));
|
||||
const FormatHandler* handler = GetFormatHandler(extension);
|
||||
if (!handler || !handler->file_saver)
|
||||
{
|
||||
Console.ErrorFmt("Unknown extension '{}'", extension);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!handler->file_saver(*this, filename, fp, quality))
|
||||
return false;
|
||||
|
||||
return (std::fflush(fp) == 0);
|
||||
}
|
||||
|
||||
std::optional<std::vector<u8>> RGBA8Image::SaveToBuffer(const char* filename, u8 quality) const
|
||||
{
|
||||
std::optional<std::vector<u8>> ret;
|
||||
|
||||
const std::string_view extension(Path::GetExtension(filename));
|
||||
const FormatHandler* handler = GetFormatHandler(extension);
|
||||
if (!handler || !handler->file_saver)
|
||||
{
|
||||
Console.ErrorFmt("Unknown extension '{}'", extension);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = std::vector<u8>();
|
||||
if (!handler->buffer_saver(*this, &ret.value(), quality))
|
||||
ret.reset();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool PNGCommonLoader(RGBA8Image* image, png_structp png_ptr, png_infop info_ptr, std::vector<u32>& new_data,
|
||||
std::vector<png_bytep>& row_pointers)
|
||||
{
|
||||
png_read_info(png_ptr, info_ptr);
|
||||
|
||||
const u32 width = png_get_image_width(png_ptr, info_ptr);
|
||||
const u32 height = png_get_image_height(png_ptr, info_ptr);
|
||||
const png_byte color_type = png_get_color_type(png_ptr, info_ptr);
|
||||
const png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
|
||||
|
||||
// Read any color_type into 8bit depth, RGBA format.
|
||||
// See http://www.libpng.org/pub/png/libpng-manual.txt
|
||||
|
||||
if (bit_depth == 16)
|
||||
png_set_strip_16(png_ptr);
|
||||
|
||||
if (color_type == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_palette_to_rgb(png_ptr);
|
||||
|
||||
// PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
|
||||
png_set_expand_gray_1_2_4_to_8(png_ptr);
|
||||
|
||||
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
|
||||
png_set_tRNS_to_alpha(png_ptr);
|
||||
|
||||
// These color_type don't have an alpha channel then fill it with 0xff.
|
||||
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
|
||||
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
|
||||
png_set_gray_to_rgb(png_ptr);
|
||||
|
||||
png_read_update_info(png_ptr, info_ptr);
|
||||
|
||||
new_data.resize(width * height);
|
||||
row_pointers.reserve(height);
|
||||
for (u32 y = 0; y < height; y++)
|
||||
row_pointers.push_back(reinterpret_cast<png_bytep>(new_data.data() + y * width));
|
||||
|
||||
png_read_image(png_ptr, row_pointers.data());
|
||||
image->SetPixels(width, height, std::move(new_data));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PNGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
{
|
||||
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (!png_ptr)
|
||||
return false;
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr)
|
||||
{
|
||||
png_destroy_read_struct(&png_ptr, nullptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); });
|
||||
|
||||
std::vector<u32> new_data;
|
||||
std::vector<png_bytep> row_pointers;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
return false;
|
||||
|
||||
png_set_read_fn(png_ptr, fp, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
|
||||
std::FILE* fp = static_cast<std::FILE*>(png_get_io_ptr(png_ptr));
|
||||
if (std::fread(data_ptr, size, 1, fp) != 1)
|
||||
png_error(png_ptr, "Read error");
|
||||
});
|
||||
|
||||
return PNGCommonLoader(image, png_ptr, info_ptr, new_data, row_pointers);
|
||||
}
|
||||
|
||||
bool PNGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||
{
|
||||
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (!png_ptr)
|
||||
return false;
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr)
|
||||
{
|
||||
png_destroy_read_struct(&png_ptr, nullptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); });
|
||||
|
||||
std::vector<u32> new_data;
|
||||
std::vector<png_bytep> row_pointers;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
return false;
|
||||
|
||||
struct IOData
|
||||
{
|
||||
const u8* buffer;
|
||||
size_t buffer_size;
|
||||
size_t buffer_pos;
|
||||
};
|
||||
IOData data = {static_cast<const u8*>(buffer), buffer_size, 0};
|
||||
|
||||
png_set_read_fn(png_ptr, &data, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
|
||||
IOData* data = static_cast<IOData*>(png_get_io_ptr(png_ptr));
|
||||
const size_t read_size = std::min<size_t>(data->buffer_size - data->buffer_pos, size);
|
||||
if (read_size > 0)
|
||||
{
|
||||
std::memcpy(data_ptr, data->buffer + data->buffer_pos, read_size);
|
||||
data->buffer_pos += read_size;
|
||||
}
|
||||
});
|
||||
|
||||
return PNGCommonLoader(image, png_ptr, info_ptr, new_data, row_pointers);
|
||||
}
|
||||
|
||||
static void PNGSaveCommon(const RGBA8Image& image, png_structp png_ptr, png_infop info_ptr, u8 quality)
|
||||
{
|
||||
png_set_compression_level(png_ptr, std::clamp(quality / 10, 0, 9));
|
||||
png_set_IHDR(png_ptr, info_ptr, image.GetWidth(), image.GetHeight(), 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE,
|
||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
png_write_info(png_ptr, info_ptr);
|
||||
|
||||
for (u32 y = 0; y < image.GetHeight(); ++y)
|
||||
png_write_row(png_ptr, (png_bytep)image.GetRowPixels(y));
|
||||
|
||||
png_write_end(png_ptr, nullptr);
|
||||
}
|
||||
|
||||
bool PNGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality)
|
||||
{
|
||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
png_infop info_ptr = nullptr;
|
||||
if (!png_ptr)
|
||||
return false;
|
||||
|
||||
ScopedGuard cleanup([&png_ptr, &info_ptr]() {
|
||||
if (png_ptr)
|
||||
png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr);
|
||||
});
|
||||
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr)
|
||||
return false;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
return false;
|
||||
|
||||
png_set_write_fn(
|
||||
png_ptr, fp,
|
||||
[](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
|
||||
if (std::fwrite(data_ptr, size, 1, static_cast<std::FILE*>(png_get_io_ptr(png_ptr))) != 1)
|
||||
png_error(png_ptr, "file write error");
|
||||
},
|
||||
[](png_structp png_ptr) {});
|
||||
|
||||
PNGSaveCommon(image, png_ptr, info_ptr, quality);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PNGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality)
|
||||
{
|
||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
png_infop info_ptr = nullptr;
|
||||
if (!png_ptr)
|
||||
return false;
|
||||
|
||||
ScopedGuard cleanup([&png_ptr, &info_ptr]() {
|
||||
if (png_ptr)
|
||||
png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr);
|
||||
});
|
||||
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr)
|
||||
return false;
|
||||
|
||||
buffer->reserve(image.GetWidth() * image.GetHeight() * 2);
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
return false;
|
||||
|
||||
png_set_write_fn(
|
||||
png_ptr, buffer,
|
||||
[](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
|
||||
std::vector<u8>* buffer = static_cast<std::vector<u8>*>(png_get_io_ptr(png_ptr));
|
||||
const size_t old_pos = buffer->size();
|
||||
buffer->resize(old_pos + size);
|
||||
std::memcpy(buffer->data() + old_pos, data_ptr, size);
|
||||
},
|
||||
[](png_structp png_ptr) {});
|
||||
|
||||
PNGSaveCommon(image, png_ptr, info_ptr, quality);
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct JPEGErrorHandler
|
||||
{
|
||||
jpeg_error_mgr err;
|
||||
fastjmp_buf jbuf;
|
||||
|
||||
JPEGErrorHandler()
|
||||
{
|
||||
jpeg_std_error(&err);
|
||||
err.error_exit = &ErrorExit;
|
||||
}
|
||||
|
||||
static void ErrorExit(j_common_ptr cinfo)
|
||||
{
|
||||
JPEGErrorHandler* eh = (JPEGErrorHandler*)cinfo->err;
|
||||
char msg[JMSG_LENGTH_MAX];
|
||||
eh->err.format_message(cinfo, msg);
|
||||
Console.ErrorFmt("libjpeg fatal error: {}", msg);
|
||||
fastjmp_jmp(&eh->jbuf, 1);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
template <typename T>
|
||||
static bool WrapJPEGDecompress(RGBA8Image* image, T setup_func)
|
||||
{
|
||||
std::vector<u8> scanline;
|
||||
jpeg_decompress_struct info = {};
|
||||
|
||||
// NOTE: Be **very** careful not to allocate memory after calling this function.
|
||||
// It won't get freed, because fastjmp does not unwind the stack.
|
||||
JPEGErrorHandler errhandler;
|
||||
if (fastjmp_set(&errhandler.jbuf) != 0)
|
||||
{
|
||||
jpeg_destroy_decompress(&info);
|
||||
return false;
|
||||
}
|
||||
info.err = &errhandler.err;
|
||||
jpeg_create_decompress(&info);
|
||||
setup_func(info);
|
||||
|
||||
const int herr = jpeg_read_header(&info, TRUE);
|
||||
if (herr != JPEG_HEADER_OK)
|
||||
{
|
||||
Console.ErrorFmt("jpeg_read_header() returned {}", herr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info.image_width == 0 || info.image_height == 0 || info.num_components < 3)
|
||||
{
|
||||
Console.ErrorFmt("Invalid image dimensions: {}x{}x{}", info.image_width, info.image_height, info.num_components);
|
||||
return false;
|
||||
}
|
||||
|
||||
info.out_color_space = JCS_RGB;
|
||||
info.out_color_components = 3;
|
||||
|
||||
if (!jpeg_start_decompress(&info))
|
||||
{
|
||||
Console.ErrorFmt("jpeg_start_decompress() returned failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
image->SetSize(info.image_width, info.image_height);
|
||||
scanline.resize(info.image_width * 3);
|
||||
|
||||
u8* scanline_buffer[1] = {scanline.data()};
|
||||
bool result = true;
|
||||
for (u32 y = 0; y < info.image_height; y++)
|
||||
{
|
||||
if (jpeg_read_scanlines(&info, scanline_buffer, 1) != 1)
|
||||
{
|
||||
Console.ErrorFmt("jpeg_read_scanlines() failed at row {}", y);
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// RGB -> RGBA
|
||||
const u8* src_ptr = scanline.data();
|
||||
u32* dst_ptr = image->GetRowPixels(y);
|
||||
for (u32 x = 0; x < info.image_width; x++)
|
||||
{
|
||||
*(dst_ptr++) = (static_cast<u32>(src_ptr[0]) | (static_cast<u32>(src_ptr[1]) << 8) | (static_cast<u32>(src_ptr[2]) << 16) | 0xFF000000u);
|
||||
src_ptr += 3;
|
||||
}
|
||||
}
|
||||
|
||||
jpeg_finish_decompress(&info);
|
||||
jpeg_destroy_decompress(&info);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool JPEGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||
{
|
||||
return WrapJPEGDecompress(image, [buffer, buffer_size](jpeg_decompress_struct& info) {
|
||||
jpeg_mem_src(&info, static_cast<const unsigned char*>(buffer), buffer_size);
|
||||
});
|
||||
}
|
||||
|
||||
bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
{
|
||||
static constexpr u32 BUFFER_SIZE = 16384;
|
||||
|
||||
struct FileCallback
|
||||
{
|
||||
jpeg_source_mgr mgr;
|
||||
|
||||
std::FILE* fp;
|
||||
std::unique_ptr<u8[]> buffer;
|
||||
bool end_of_file;
|
||||
};
|
||||
|
||||
FileCallback cb = {
|
||||
.mgr = {
|
||||
.init_source = [](j_decompress_ptr cinfo) {},
|
||||
.fill_input_buffer = [](j_decompress_ptr cinfo) -> boolean {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->src, FileCallback, mgr);
|
||||
cb->mgr.next_input_byte = cb->buffer.get();
|
||||
if (cb->end_of_file)
|
||||
{
|
||||
cb->buffer[0] = 0xFF;
|
||||
cb->buffer[1] = JPEG_EOI;
|
||||
cb->mgr.bytes_in_buffer = 2;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
const size_t r = std::fread(cb->buffer.get(), 1, BUFFER_SIZE, cb->fp);
|
||||
cb->end_of_file |= (std::feof(cb->fp) != 0);
|
||||
cb->mgr.bytes_in_buffer = r;
|
||||
return TRUE;
|
||||
},
|
||||
.skip_input_data =
|
||||
[](j_decompress_ptr cinfo, long num_bytes) {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->src, FileCallback, mgr);
|
||||
const size_t skip_in_buffer = std::min<size_t>(cb->mgr.bytes_in_buffer, static_cast<size_t>(num_bytes));
|
||||
cb->mgr.next_input_byte += skip_in_buffer;
|
||||
cb->mgr.bytes_in_buffer -= skip_in_buffer;
|
||||
|
||||
const size_t seek_cur = static_cast<size_t>(num_bytes) - skip_in_buffer;
|
||||
if (seek_cur > 0)
|
||||
{
|
||||
if (FileSystem::FSeek64(cb->fp, static_cast<size_t>(seek_cur), SEEK_CUR) != 0)
|
||||
{
|
||||
cb->end_of_file = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
.resync_to_restart = jpeg_resync_to_restart,
|
||||
.term_source = [](j_decompress_ptr cinfo) {},
|
||||
},
|
||||
.fp = fp,
|
||||
.buffer = std::make_unique<u8[]>(BUFFER_SIZE),
|
||||
.end_of_file = false,
|
||||
};
|
||||
|
||||
return WrapJPEGDecompress(image, [&cb](jpeg_decompress_struct& info) { info.src = &cb.mgr; });
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool WrapJPEGCompress(const RGBA8Image& image, u8 quality, T setup_func)
|
||||
{
|
||||
std::vector<u8> scanline;
|
||||
jpeg_compress_struct info = {};
|
||||
|
||||
// NOTE: Be **very** careful not to allocate memory after calling this function.
|
||||
// It won't get freed, because fastjmp does not unwind the stack.
|
||||
JPEGErrorHandler errhandler;
|
||||
if (fastjmp_set(&errhandler.jbuf) != 0)
|
||||
{
|
||||
jpeg_destroy_compress(&info);
|
||||
return false;
|
||||
}
|
||||
info.err = &errhandler.err;
|
||||
jpeg_create_compress(&info);
|
||||
setup_func(info);
|
||||
|
||||
info.image_width = image.GetWidth();
|
||||
info.image_height = image.GetHeight();
|
||||
info.in_color_space = JCS_RGB;
|
||||
info.input_components = 3;
|
||||
|
||||
jpeg_set_defaults(&info);
|
||||
jpeg_set_quality(&info, quality, TRUE);
|
||||
jpeg_start_compress(&info, TRUE);
|
||||
|
||||
scanline.resize(image.GetWidth() * 3);
|
||||
u8* scanline_buffer[1] = {scanline.data()};
|
||||
bool result = true;
|
||||
for (u32 y = 0; y < info.image_height; y++)
|
||||
{
|
||||
// RGBA -> RGB
|
||||
u8* dst_ptr = scanline.data();
|
||||
const u32* src_ptr = image.GetRowPixels(y);
|
||||
for (u32 x = 0; x < info.image_width; x++)
|
||||
{
|
||||
const u32 rgba = *(src_ptr++);
|
||||
*(dst_ptr++) = static_cast<u8>(rgba);
|
||||
*(dst_ptr++) = static_cast<u8>(rgba >> 8);
|
||||
*(dst_ptr++) = static_cast<u8>(rgba >> 16);
|
||||
}
|
||||
|
||||
if (jpeg_write_scanlines(&info, scanline_buffer, 1) != 1)
|
||||
{
|
||||
Console.ErrorFmt("jpeg_write_scanlines() failed at row {}", y);
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
jpeg_finish_compress(&info);
|
||||
jpeg_destroy_compress(&info);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool JPEGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality)
|
||||
{
|
||||
// give enough space to avoid reallocs
|
||||
buffer->resize(image.GetWidth() * image.GetHeight() * 2);
|
||||
|
||||
struct MemCallback
|
||||
{
|
||||
jpeg_destination_mgr mgr;
|
||||
std::vector<u8>* buffer;
|
||||
size_t buffer_used;
|
||||
};
|
||||
|
||||
MemCallback cb;
|
||||
cb.buffer = buffer;
|
||||
cb.buffer_used = 0;
|
||||
cb.mgr.next_output_byte = buffer->data();
|
||||
cb.mgr.free_in_buffer = buffer->size();
|
||||
cb.mgr.init_destination = [](j_compress_ptr cinfo) {};
|
||||
cb.mgr.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean {
|
||||
MemCallback* cb = (MemCallback*)cinfo->dest;
|
||||
|
||||
// double size
|
||||
cb->buffer_used = cb->buffer->size();
|
||||
cb->buffer->resize(cb->buffer->size() * 2);
|
||||
cb->mgr.next_output_byte = cb->buffer->data() + cb->buffer_used;
|
||||
cb->mgr.free_in_buffer = cb->buffer->size() - cb->buffer_used;
|
||||
return TRUE;
|
||||
};
|
||||
cb.mgr.term_destination = [](j_compress_ptr cinfo) {
|
||||
MemCallback* cb = (MemCallback*)cinfo->dest;
|
||||
|
||||
// get final size
|
||||
cb->buffer->resize(cb->buffer->size() - cb->mgr.free_in_buffer);
|
||||
};
|
||||
|
||||
return WrapJPEGCompress(image, quality, [&cb](jpeg_compress_struct& info) { info.dest = &cb.mgr; });
|
||||
}
|
||||
|
||||
bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality)
|
||||
{
|
||||
static constexpr u32 BUFFER_SIZE = 16384;
|
||||
|
||||
struct FileCallback
|
||||
{
|
||||
jpeg_destination_mgr mgr;
|
||||
|
||||
std::FILE* fp;
|
||||
std::unique_ptr<u8[]> buffer;
|
||||
bool write_error;
|
||||
};
|
||||
|
||||
FileCallback cb = {
|
||||
.mgr = {
|
||||
.init_destination =
|
||||
[](j_compress_ptr cinfo) {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->dest, FileCallback, mgr);
|
||||
cb->mgr.next_output_byte = cb->buffer.get();
|
||||
cb->mgr.free_in_buffer = BUFFER_SIZE;
|
||||
},
|
||||
.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->dest, FileCallback, mgr);
|
||||
if (!cb->write_error)
|
||||
cb->write_error |= (std::fwrite(cb->buffer.get(), 1, BUFFER_SIZE, cb->fp) != BUFFER_SIZE);
|
||||
|
||||
cb->mgr.next_output_byte = cb->buffer.get();
|
||||
cb->mgr.free_in_buffer = BUFFER_SIZE;
|
||||
return TRUE;
|
||||
},
|
||||
.term_destination =
|
||||
[](j_compress_ptr cinfo) {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->dest, FileCallback, mgr);
|
||||
const size_t left = BUFFER_SIZE - cb->mgr.free_in_buffer;
|
||||
if (left > 0 && !cb->write_error)
|
||||
cb->write_error |= (std::fwrite(cb->buffer.get(), 1, left, cb->fp) != left);
|
||||
},
|
||||
},
|
||||
.fp = fp,
|
||||
.buffer = std::make_unique<u8[]>(BUFFER_SIZE),
|
||||
.write_error = false,
|
||||
};
|
||||
|
||||
return (WrapJPEGCompress(image, quality, [&cb](jpeg_compress_struct& info) { info.dest = &cb.mgr; }) &&
|
||||
!cb.write_error);
|
||||
}
|
||||
|
||||
bool WebPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||
{
|
||||
int width, height;
|
||||
if (!WebPGetInfo(static_cast<const u8*>(buffer), buffer_size, &width, &height) || width <= 0 || height <= 0)
|
||||
{
|
||||
Console.Error("WebPGetInfo() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<u32> pixels;
|
||||
pixels.resize(static_cast<u32>(width) * static_cast<u32>(height));
|
||||
if (!WebPDecodeRGBAInto(static_cast<const u8*>(buffer), buffer_size, reinterpret_cast<u8*>(pixels.data()),
|
||||
sizeof(u32) * pixels.size(), sizeof(u32) * static_cast<u32>(width)))
|
||||
{
|
||||
Console.Error("WebPDecodeRGBAInto() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
image->SetPixels(static_cast<u32>(width), static_cast<u32>(height), std::move(pixels));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebPBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality)
|
||||
{
|
||||
u8* encoded_data;
|
||||
const size_t encoded_size =
|
||||
WebPEncodeRGBA(reinterpret_cast<const u8*>(image.GetPixels()), image.GetWidth(), image.GetHeight(),
|
||||
image.GetPitch(), static_cast<float>(quality), &encoded_data);
|
||||
if (encoded_size == 0)
|
||||
return false;
|
||||
|
||||
buffer->resize(encoded_size);
|
||||
std::memcpy(buffer->data(), encoded_data, encoded_size);
|
||||
WebPFree(encoded_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebPFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
{
|
||||
std::optional<std::vector<u8>> data = FileSystem::ReadBinaryFile(fp);
|
||||
if (!data.has_value())
|
||||
return false;
|
||||
|
||||
return WebPBufferLoader(image, data->data(), data->size());
|
||||
}
|
||||
|
||||
bool WebPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality)
|
||||
{
|
||||
std::vector<u8> buffer;
|
||||
if (!WebPBufferSaver(image, &buffer, quality))
|
||||
return false;
|
||||
|
||||
return (std::fwrite(buffer.data(), buffer.size(), 1, fp) == 1);
|
||||
}
|
||||
133
common/Image.h
Normal file
133
common/Image.h
Normal file
@@ -0,0 +1,133 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include "Pcsx2Defs.h"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
template <typename PixelType>
|
||||
class Image
|
||||
{
|
||||
public:
|
||||
Image() = default;
|
||||
Image(u32 width, u32 height) { SetSize(width, height); }
|
||||
Image(u32 width, u32 height, const PixelType* pixels) { SetPixels(width, height, pixels); }
|
||||
Image(u32 width, u32 height, std::vector<PixelType> pixels) { SetPixels(width, height, std::move(pixels)); }
|
||||
Image(const Image& copy)
|
||||
{
|
||||
m_width = copy.m_width;
|
||||
m_height = copy.m_height;
|
||||
m_pixels = copy.m_pixels;
|
||||
}
|
||||
Image(Image&& move)
|
||||
{
|
||||
m_width = move.m_width;
|
||||
m_height = move.m_height;
|
||||
m_pixels = std::move(move.m_pixels);
|
||||
move.m_width = 0;
|
||||
move.m_height = 0;
|
||||
}
|
||||
|
||||
Image& operator=(const Image& copy)
|
||||
{
|
||||
m_width = copy.m_width;
|
||||
m_height = copy.m_height;
|
||||
m_pixels = copy.m_pixels;
|
||||
return *this;
|
||||
}
|
||||
Image& operator=(Image&& move)
|
||||
{
|
||||
m_width = move.m_width;
|
||||
m_height = move.m_height;
|
||||
m_pixels = std::move(move.m_pixels);
|
||||
move.m_width = 0;
|
||||
move.m_height = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi bool IsValid() const { return (m_width > 0 && m_height > 0); }
|
||||
__fi u32 GetWidth() const { return m_width; }
|
||||
__fi u32 GetHeight() const { return m_height; }
|
||||
__fi u32 GetPitch() const { return (sizeof(PixelType) * m_width); }
|
||||
__fi const PixelType* GetPixels() const { return m_pixels.data(); }
|
||||
__fi PixelType* GetPixels() { return m_pixels.data(); }
|
||||
__fi const PixelType* GetRowPixels(u32 y) const { return &m_pixels[y * m_width]; }
|
||||
__fi PixelType* GetRowPixels(u32 y) { return &m_pixels[y * m_width]; }
|
||||
__fi void SetPixel(u32 x, u32 y, PixelType pixel) { m_pixels[y * m_width + x] = pixel; }
|
||||
__fi PixelType GetPixel(u32 x, u32 y) const { return m_pixels[y * m_width + x]; }
|
||||
|
||||
void Clear(PixelType fill_value = static_cast<PixelType>(0))
|
||||
{
|
||||
std::fill(m_pixels.begin(), m_pixels.end(), fill_value);
|
||||
}
|
||||
|
||||
void Invalidate()
|
||||
{
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
m_pixels.clear();
|
||||
}
|
||||
|
||||
void SetSize(u32 new_width, u32 new_height, PixelType fill_value = static_cast<PixelType>(0))
|
||||
{
|
||||
m_width = new_width;
|
||||
m_height = new_height;
|
||||
m_pixels.resize(new_width * new_height);
|
||||
Clear(fill_value);
|
||||
}
|
||||
|
||||
void SetPixels(u32 width, u32 height, const PixelType* pixels)
|
||||
{
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_pixels.resize(width * height);
|
||||
std::memcpy(m_pixels.data(), pixels, width * height * sizeof(PixelType));
|
||||
}
|
||||
|
||||
void SetPixels(u32 width, u32 height, std::vector<PixelType> pixels)
|
||||
{
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_pixels = std::move(pixels);
|
||||
}
|
||||
|
||||
std::vector<PixelType> TakePixels()
|
||||
{
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
return std::move(m_pixels);
|
||||
}
|
||||
|
||||
protected:
|
||||
u32 m_width = 0;
|
||||
u32 m_height = 0;
|
||||
std::vector<PixelType> m_pixels;
|
||||
};
|
||||
|
||||
class RGBA8Image : public Image<u32>
|
||||
{
|
||||
public:
|
||||
static constexpr u8 DEFAULT_SAVE_QUALITY = 85;
|
||||
|
||||
RGBA8Image();
|
||||
RGBA8Image(u32 width, u32 height);
|
||||
RGBA8Image(u32 width, u32 height, const u32* pixels);
|
||||
RGBA8Image(u32 width, u32 height, std::vector<u32> pixels);
|
||||
RGBA8Image(const RGBA8Image& copy);
|
||||
RGBA8Image(RGBA8Image&& move);
|
||||
|
||||
RGBA8Image& operator=(const RGBA8Image& copy);
|
||||
RGBA8Image& operator=(RGBA8Image&& move);
|
||||
|
||||
bool LoadFromFile(const char* filename);
|
||||
bool LoadFromFile(const char* filename, std::FILE* fp);
|
||||
bool LoadFromBuffer(const char* filename, const void* buffer, size_t buffer_size);
|
||||
|
||||
bool SaveToFile(const char* filename, u8 quality = DEFAULT_SAVE_QUALITY) const;
|
||||
bool SaveToFile(const char* filename, std::FILE* fp, u8 quality = DEFAULT_SAVE_QUALITY) const;
|
||||
std::optional<std::vector<u8>> SaveToBuffer(const char* filename, u8 quality = DEFAULT_SAVE_QUALITY) const;
|
||||
};
|
||||
124
common/LRUCache.h
Normal file
124
common/LRUCache.h
Normal file
@@ -0,0 +1,124 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include "HeterogeneousContainers.h"
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
|
||||
template <class K, class V>
|
||||
class LRUCache
|
||||
{
|
||||
using CounterType = std::uint64_t;
|
||||
|
||||
struct Item
|
||||
{
|
||||
V value;
|
||||
CounterType last_access;
|
||||
};
|
||||
|
||||
using MapType = std::conditional_t<std::is_same_v<K, std::string>, StringMap<Item>, std::map<K, Item>>;
|
||||
|
||||
public:
|
||||
LRUCache(std::size_t max_capacity = 16, bool manual_evict = false)
|
||||
: m_max_capacity(max_capacity)
|
||||
, m_manual_evict(manual_evict)
|
||||
{
|
||||
}
|
||||
~LRUCache() = default;
|
||||
|
||||
std::size_t GetSize() const { return m_items.size(); }
|
||||
std::size_t GetMaxCapacity() const { return m_max_capacity; }
|
||||
|
||||
void Clear() { m_items.clear(); }
|
||||
|
||||
void SetMaxCapacity(std::size_t capacity)
|
||||
{
|
||||
m_max_capacity = capacity;
|
||||
if (m_items.size() > m_max_capacity)
|
||||
Evict(m_items.size() - m_max_capacity);
|
||||
}
|
||||
|
||||
template <typename KeyT>
|
||||
V* Lookup(const KeyT& key)
|
||||
{
|
||||
auto iter = m_items.find(key);
|
||||
if (iter == m_items.end())
|
||||
return nullptr;
|
||||
|
||||
iter->second.last_access = ++m_last_counter;
|
||||
return &iter->second.value;
|
||||
}
|
||||
|
||||
V* Insert(K key, V value)
|
||||
{
|
||||
ShrinkForNewItem();
|
||||
|
||||
auto iter = m_items.find(key);
|
||||
if (iter != m_items.end())
|
||||
{
|
||||
iter->second.value = std::move(value);
|
||||
iter->second.last_access = ++m_last_counter;
|
||||
return &iter->second.value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Item it;
|
||||
it.last_access = ++m_last_counter;
|
||||
it.value = std::move(value);
|
||||
auto ip = m_items.emplace(std::move(key), std::move(it));
|
||||
return &ip.first->second.value;
|
||||
}
|
||||
}
|
||||
|
||||
void Evict(std::size_t count = 1)
|
||||
{
|
||||
while (!m_items.empty() && count > 0)
|
||||
{
|
||||
typename MapType::iterator lowest = m_items.end();
|
||||
for (auto iter = m_items.begin(); iter != m_items.end(); ++iter)
|
||||
{
|
||||
if (lowest == m_items.end() || iter->second.last_access < lowest->second.last_access)
|
||||
lowest = iter;
|
||||
}
|
||||
m_items.erase(lowest);
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename KeyT>
|
||||
bool Remove(const KeyT& key)
|
||||
{
|
||||
auto iter = m_items.find(key);
|
||||
if (iter == m_items.end())
|
||||
return false;
|
||||
m_items.erase(iter);
|
||||
return true;
|
||||
}
|
||||
void SetManualEvict(bool block)
|
||||
{
|
||||
m_manual_evict = block;
|
||||
if (!m_manual_evict)
|
||||
ManualEvict();
|
||||
}
|
||||
void ManualEvict()
|
||||
{
|
||||
// evict if we went over
|
||||
while (m_items.size() > m_max_capacity)
|
||||
Evict(m_items.size() - m_max_capacity);
|
||||
}
|
||||
|
||||
private:
|
||||
void ShrinkForNewItem()
|
||||
{
|
||||
if (m_items.size() < m_max_capacity)
|
||||
return;
|
||||
|
||||
Evict(m_items.size() - (m_max_capacity - 1));
|
||||
}
|
||||
|
||||
MapType m_items;
|
||||
CounterType m_last_counter = 0;
|
||||
std::size_t m_max_capacity = 0;
|
||||
bool m_manual_evict = false;
|
||||
};
|
||||
372
common/Linux/LnxHostSys.cpp
Normal file
372
common/Linux/LnxHostSys.cpp
Normal file
@@ -0,0 +1,372 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "common/BitUtils.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/CrashHandler.h"
|
||||
#include "common/Error.h"
|
||||
#include "common/HostSys.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
#include <fcntl.h>
|
||||
#include <mutex>
|
||||
#include <sys/mman.h>
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#if defined(__FreeBSD__)
|
||||
#include "cpuinfo.h"
|
||||
#endif
|
||||
|
||||
// FreeBSD does not have MAP_FIXED_NOREPLACE, but does have MAP_EXCL.
|
||||
// MAP_FIXED combined with MAP_EXCL behaves like MAP_FIXED_NOREPLACE.
|
||||
#if defined(__FreeBSD__) && !defined(MAP_FIXED_NOREPLACE)
|
||||
#define MAP_FIXED_NOREPLACE (MAP_FIXED | MAP_EXCL)
|
||||
#endif
|
||||
|
||||
static __ri uint LinuxProt(const PageProtectionMode& mode)
|
||||
{
|
||||
u32 lnxmode = 0;
|
||||
|
||||
if (mode.CanWrite())
|
||||
lnxmode |= PROT_WRITE;
|
||||
if (mode.CanRead())
|
||||
lnxmode |= PROT_READ;
|
||||
if (mode.CanExecute())
|
||||
lnxmode |= PROT_EXEC | PROT_READ;
|
||||
|
||||
return lnxmode;
|
||||
}
|
||||
|
||||
void* HostSys::Mmap(void* base, size_t size, const PageProtectionMode& mode)
|
||||
{
|
||||
pxAssertMsg((size & (__pagesize - 1)) == 0, "Size is page aligned");
|
||||
|
||||
if (mode.IsNone())
|
||||
return nullptr;
|
||||
|
||||
const u32 prot = LinuxProt(mode);
|
||||
|
||||
u32 flags = MAP_PRIVATE | MAP_ANONYMOUS;
|
||||
if (base)
|
||||
flags |= MAP_FIXED_NOREPLACE;
|
||||
|
||||
void* res = mmap(base, size, prot, flags, -1, 0);
|
||||
if (res == MAP_FAILED)
|
||||
return nullptr;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void HostSys::Munmap(void* base, size_t size)
|
||||
{
|
||||
if (!base)
|
||||
return;
|
||||
|
||||
munmap((void*)base, size);
|
||||
}
|
||||
|
||||
void HostSys::MemProtect(void* baseaddr, size_t size, const PageProtectionMode& mode)
|
||||
{
|
||||
pxAssertMsg((size & (__pagesize - 1)) == 0, "Size is page aligned");
|
||||
|
||||
const u32 lnxmode = LinuxProt(mode);
|
||||
|
||||
const int result = mprotect(baseaddr, size, lnxmode);
|
||||
if (result != 0)
|
||||
pxFail("mprotect() failed");
|
||||
}
|
||||
|
||||
std::string HostSys::GetFileMappingName(const char* prefix)
|
||||
{
|
||||
const unsigned pid = static_cast<unsigned>(getpid());
|
||||
#if defined(__FreeBSD__)
|
||||
// FreeBSD's shm_open(3) requires name to be absolute
|
||||
return fmt::format("/tmp/{}_{}", prefix, pid);
|
||||
#else
|
||||
return fmt::format("{}_{}", prefix, pid);
|
||||
#endif
|
||||
}
|
||||
|
||||
void* HostSys::CreateSharedMemory(const char* name, size_t size)
|
||||
{
|
||||
const int fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR, 0600);
|
||||
if (fd < 0)
|
||||
{
|
||||
std::fprintf(stderr, "shm_open failed: %d\n", errno);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// we're not going to be opening this mapping in other processes, so remove the file
|
||||
shm_unlink(name);
|
||||
|
||||
// ensure it's the correct size
|
||||
if (ftruncate(fd, static_cast<off_t>(size)) < 0)
|
||||
{
|
||||
std::fprintf(stderr, "ftruncate(%zu) failed: %d\n", size, errno);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return reinterpret_cast<void*>(static_cast<intptr_t>(fd));
|
||||
}
|
||||
|
||||
void HostSys::DestroySharedMemory(void* ptr)
|
||||
{
|
||||
close(static_cast<int>(reinterpret_cast<intptr_t>(ptr)));
|
||||
}
|
||||
|
||||
void* HostSys::MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, const PageProtectionMode& mode)
|
||||
{
|
||||
const uint lnxmode = LinuxProt(mode);
|
||||
|
||||
const int flags = (baseaddr != nullptr) ? (MAP_SHARED | MAP_FIXED_NOREPLACE) : MAP_SHARED;
|
||||
void* ptr = mmap(baseaddr, size, lnxmode, flags, static_cast<int>(reinterpret_cast<intptr_t>(handle)), static_cast<off_t>(offset));
|
||||
if (ptr == MAP_FAILED)
|
||||
return nullptr;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void HostSys::UnmapSharedMemory(void* baseaddr, size_t size)
|
||||
{
|
||||
if (munmap(baseaddr, size) != 0)
|
||||
pxFailRel("Failed to unmap shared memory");
|
||||
}
|
||||
|
||||
size_t HostSys::GetRuntimePageSize()
|
||||
{
|
||||
int res = sysconf(_SC_PAGESIZE);
|
||||
return (res > 0) ? static_cast<size_t>(res) : 0;
|
||||
}
|
||||
|
||||
size_t HostSys::GetRuntimeCacheLineSize()
|
||||
{
|
||||
#if defined(__FreeBSD__)
|
||||
if (!cpuinfo_initialize())
|
||||
return 0;
|
||||
|
||||
u32 max_line_size = 0;
|
||||
for (u32 i = 0; i < cpuinfo_get_processors_count(); i++)
|
||||
{
|
||||
const u32 l1i = cpuinfo_get_processor(i)->cache.l1i->line_size;
|
||||
const u32 l1d = cpuinfo_get_processor(i)->cache.l1d->line_size;
|
||||
const u32 res = std::max<u32>(l1i, l1d);
|
||||
|
||||
max_line_size = std::max<u32>(max_line_size, res);
|
||||
}
|
||||
|
||||
return static_cast<size_t>(max_line_size);
|
||||
#else
|
||||
int l1i = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
|
||||
int l1d = sysconf(_SC_LEVEL1_ICACHE_LINESIZE);
|
||||
int res = (l1i > l1d) ? l1i : l1d;
|
||||
for (int index = 0; index < 16; index++)
|
||||
{
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "/sys/devices/system/cpu/cpu0/cache/index%d/coherency_line_size", index);
|
||||
std::FILE* fp = std::fopen(buf, "rb");
|
||||
if (!fp)
|
||||
break;
|
||||
|
||||
std::fread(buf, sizeof(buf), 1, fp);
|
||||
std::fclose(fp);
|
||||
int val = std::atoi(buf);
|
||||
res = (val > res) ? val : res;
|
||||
}
|
||||
|
||||
return (res > 0) ? static_cast<size_t>(res) : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
SharedMemoryMappingArea::SharedMemoryMappingArea(u8* base_ptr, size_t size, size_t num_pages)
|
||||
: m_base_ptr(base_ptr)
|
||||
, m_size(size)
|
||||
, m_num_pages(num_pages)
|
||||
{
|
||||
}
|
||||
|
||||
SharedMemoryMappingArea::~SharedMemoryMappingArea()
|
||||
{
|
||||
pxAssertRel(m_num_mappings == 0, "No mappings left");
|
||||
|
||||
if (munmap(m_base_ptr, m_size) != 0)
|
||||
pxFailRel("Failed to release shared memory area");
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<SharedMemoryMappingArea> SharedMemoryMappingArea::Create(size_t size)
|
||||
{
|
||||
pxAssertRel(Common::IsAlignedPow2(size, __pagesize), "Size is page aligned");
|
||||
|
||||
void* alloc = mmap(nullptr, size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
if (alloc == MAP_FAILED)
|
||||
return nullptr;
|
||||
|
||||
return std::unique_ptr<SharedMemoryMappingArea>(new SharedMemoryMappingArea(static_cast<u8*>(alloc), size, size / __pagesize));
|
||||
}
|
||||
|
||||
u8* SharedMemoryMappingArea::Map(void* file_handle, size_t file_offset, void* map_base, size_t map_size, const PageProtectionMode& mode)
|
||||
{
|
||||
pxAssert(static_cast<u8*>(map_base) >= m_base_ptr && static_cast<u8*>(map_base) < (m_base_ptr + m_size));
|
||||
|
||||
// MAP_FIXED is okay here, since we've reserved the entire region, and *want* to overwrite the mapping.
|
||||
const uint lnxmode = LinuxProt(mode);
|
||||
void* const ptr = mmap(map_base, map_size, lnxmode, MAP_SHARED | MAP_FIXED,
|
||||
static_cast<int>(reinterpret_cast<intptr_t>(file_handle)), static_cast<off_t>(file_offset));
|
||||
if (ptr == MAP_FAILED)
|
||||
return nullptr;
|
||||
|
||||
m_num_mappings++;
|
||||
return static_cast<u8*>(ptr);
|
||||
}
|
||||
|
||||
bool SharedMemoryMappingArea::Unmap(void* map_base, size_t map_size)
|
||||
{
|
||||
pxAssert(static_cast<u8*>(map_base) >= m_base_ptr && static_cast<u8*>(map_base) < (m_base_ptr + m_size));
|
||||
|
||||
if (mmap(map_base, map_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == MAP_FAILED)
|
||||
return false;
|
||||
|
||||
m_num_mappings--;
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace PageFaultHandler
|
||||
{
|
||||
static std::recursive_mutex s_exception_handler_mutex;
|
||||
static bool s_in_exception_handler = false;
|
||||
static bool s_installed = false;
|
||||
} // namespace PageFaultHandler
|
||||
|
||||
#ifdef _M_ARM64
|
||||
|
||||
void HostSys::FlushInstructionCache(void* address, u32 size)
|
||||
{
|
||||
__builtin___clear_cache(reinterpret_cast<char*>(address), reinterpret_cast<char*>(address) + size);
|
||||
}
|
||||
|
||||
[[maybe_unused]] static bool IsStoreInstruction(const void* ptr)
|
||||
{
|
||||
u32 bits;
|
||||
std::memcpy(&bits, ptr, sizeof(bits));
|
||||
|
||||
// Based on vixl's disassembler Instruction::IsStore().
|
||||
// if (Mask(LoadStoreAnyFMask) != LoadStoreAnyFixed)
|
||||
if ((bits & 0x0a000000) != 0x08000000)
|
||||
return false;
|
||||
|
||||
// if (Mask(LoadStorePairAnyFMask) == LoadStorePairAnyFixed)
|
||||
if ((bits & 0x3a000000) == 0x28000000)
|
||||
{
|
||||
// return Mask(LoadStorePairLBit) == 0
|
||||
return (bits & (1 << 22)) == 0;
|
||||
}
|
||||
|
||||
switch (bits & 0xC4C00000)
|
||||
{
|
||||
case 0x00000000: // STRB_w
|
||||
case 0x40000000: // STRH_w
|
||||
case 0x80000000: // STR_w
|
||||
case 0xC0000000: // STR_x
|
||||
case 0x04000000: // STR_b
|
||||
case 0x44000000: // STR_h
|
||||
case 0x84000000: // STR_s
|
||||
case 0xC4000000: // STR_d
|
||||
case 0x04800000: // STR_q
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _M_ARM64
|
||||
|
||||
namespace PageFaultHandler
|
||||
{
|
||||
static void SignalHandler(int sig, siginfo_t* info, void* ctx);
|
||||
} // namespace PageFaultHandler
|
||||
|
||||
void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx)
|
||||
{
|
||||
#if defined(__linux__)
|
||||
void* const exception_address = reinterpret_cast<void*>(info->si_addr);
|
||||
|
||||
#if defined(_M_X86)
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.gregs[REG_RIP]);
|
||||
const bool is_write = (static_cast<ucontext_t*>(ctx)->uc_mcontext.gregs[REG_ERR] & 2) != 0;
|
||||
#elif defined(_M_ARM64)
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.pc);
|
||||
const bool is_write = IsStoreInstruction(exception_pc);
|
||||
#endif
|
||||
|
||||
#elif defined(__FreeBSD__)
|
||||
|
||||
#if defined(_M_X86)
|
||||
void* const exception_address = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_addr);
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_rip);
|
||||
const bool is_write = (static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_err & 2) != 0;
|
||||
#elif defined(_M_ARM64)
|
||||
void* const exception_address = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__far);
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__pc);
|
||||
const bool is_write = IsStoreInstruction(exception_pc);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
// Executing the handler concurrently from multiple threads wouldn't go down well.
|
||||
s_exception_handler_mutex.lock();
|
||||
|
||||
// Prevent recursive exception filtering.
|
||||
HandlerResult result = HandlerResult::ExecuteNextHandler;
|
||||
if (!s_in_exception_handler)
|
||||
{
|
||||
s_in_exception_handler = true;
|
||||
result = HandlePageFault(exception_pc, exception_address, is_write);
|
||||
s_in_exception_handler = false;
|
||||
}
|
||||
|
||||
s_exception_handler_mutex.unlock();
|
||||
|
||||
// Resumes execution right where we left off (re-executes instruction that caused the SIGSEGV).
|
||||
if (result == HandlerResult::ContinueExecution)
|
||||
return;
|
||||
|
||||
// We couldn't handle it. Pass it off to the crash dumper.
|
||||
CrashHandler::CrashSignalHandler(sig, info, ctx);
|
||||
}
|
||||
|
||||
bool PageFaultHandler::Install(Error* error)
|
||||
{
|
||||
std::unique_lock lock(s_exception_handler_mutex);
|
||||
pxAssertRel(!s_installed, "Page fault handler has already been installed.");
|
||||
|
||||
struct sigaction sa;
|
||||
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_SIGINFO | SA_NODEFER;
|
||||
sa.sa_sigaction = SignalHandler;
|
||||
|
||||
if (sigaction(SIGSEGV, &sa, nullptr) != 0)
|
||||
{
|
||||
Error::SetErrno(error, "sigaction() for SIGSEGV failed: ", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _M_ARM64
|
||||
// We can get SIGBUS on ARM64.
|
||||
if (sigaction(SIGBUS, &sa, nullptr) != 0)
|
||||
{
|
||||
Error::SetErrno(error, "sigaction() for SIGBUS failed: ", errno);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
s_installed = true;
|
||||
return true;
|
||||
}
|
||||
380
common/Linux/LnxMisc.cpp
Normal file
380
common/Linux/LnxMisc.cpp
Normal file
@@ -0,0 +1,380 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/Pcsx2Types.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/HostSys.h"
|
||||
#include "common/Path.h"
|
||||
#include "common/ScopedGuard.h"
|
||||
#include "common/SmallString.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Threading.h"
|
||||
#include "common/WindowInfo.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <dbus/dbus.h>
|
||||
#include <spawn.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/XInput2.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <ctype.h>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
// Returns 0 on failure (not supported by the operating system).
|
||||
u64 GetPhysicalMemory()
|
||||
{
|
||||
u64 pages = 0;
|
||||
|
||||
#ifdef _SC_PHYS_PAGES
|
||||
pages = sysconf(_SC_PHYS_PAGES);
|
||||
#endif
|
||||
|
||||
return pages * getpagesize();
|
||||
}
|
||||
|
||||
u64 GetAvailablePhysicalMemory()
|
||||
{
|
||||
// Try to read MemAvailable from /proc/meminfo.
|
||||
FILE* file = fopen("/proc/meminfo", "r");
|
||||
if (file)
|
||||
{
|
||||
u64 mem_available = 0;
|
||||
u64 mem_free = 0, buffers = 0, cached = 0, sreclaimable = 0, shmem = 0;
|
||||
char line[256];
|
||||
|
||||
while (fgets(line, sizeof(line), file))
|
||||
{
|
||||
// Modern kernels provide MemAvailable directly - preferred and most accurate.
|
||||
if (sscanf(line, "MemAvailable: %lu kB", &mem_available) == 1)
|
||||
{
|
||||
fclose(file);
|
||||
return mem_available * _1kb;
|
||||
}
|
||||
|
||||
// Fallback values for manual approximation.
|
||||
sscanf(line, "MemFree: %lu kB", &mem_free);
|
||||
sscanf(line, "Buffers: %lu kB", &buffers);
|
||||
sscanf(line, "Cached: %lu kB", &cached);
|
||||
sscanf(line, "SReclaimable: %lu kB", &sreclaimable);
|
||||
sscanf(line, "Shmem: %lu kB", &shmem);
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
// Fallback approximation: Linux-like heuristic.
|
||||
// available = MemFree + Buffers + Cached + SReclaimable - Shmem.
|
||||
const u64 available_kb = mem_free + buffers + cached + sreclaimable - shmem;
|
||||
return available_kb * _1kb;
|
||||
}
|
||||
|
||||
// Fallback to sysinfo if /proc/meminfo couldn't be read.
|
||||
struct sysinfo info = {};
|
||||
if (sysinfo(&info) != 0)
|
||||
return 0;
|
||||
|
||||
// Note: This does NOT include cached memory - only free + buffer.
|
||||
return (static_cast<u64>(info.freeram) + static_cast<u64>(info.bufferram)) * static_cast<u64>(info.mem_unit);
|
||||
}
|
||||
|
||||
u64 GetTickFrequency()
|
||||
{
|
||||
return 1000000000; // unix measures in nanoseconds
|
||||
}
|
||||
|
||||
u64 GetCPUTicks()
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (static_cast<u64>(ts.tv_sec) * 1000000000ULL) + ts.tv_nsec;
|
||||
}
|
||||
|
||||
std::string GetOSVersionString()
|
||||
{
|
||||
#if defined(__linux__)
|
||||
FILE* file = fopen("/etc/os-release", "r");
|
||||
if (file)
|
||||
{
|
||||
char line[256];
|
||||
std::string distro;
|
||||
std::string version = "";
|
||||
while (fgets(line, sizeof(line), file))
|
||||
{
|
||||
std::string_view line_view(line);
|
||||
if (line_view.starts_with("NAME="))
|
||||
{
|
||||
distro = line_view.substr(5, line_view.size() - 6);
|
||||
}
|
||||
else if (line_view.starts_with("BUILD_ID="))
|
||||
{
|
||||
version = line_view.substr(9, line_view.size() - 10);
|
||||
}
|
||||
else if (line_view.starts_with("VERSION_ID="))
|
||||
{
|
||||
version = line_view.substr(11, line_view.size() - 12);
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
// Some distros put quotes around the name and or version.
|
||||
if (distro.starts_with("\"") && distro.ends_with("\""))
|
||||
distro = distro.substr(1, distro.size() - 2);
|
||||
|
||||
if (version.starts_with("\"") && version.ends_with("\""))
|
||||
version = version.substr(1, version.size() - 2);
|
||||
|
||||
if (!distro.empty() && !version.empty())
|
||||
return fmt::format("{} {}", distro, version);
|
||||
}
|
||||
|
||||
return "Linux";
|
||||
#else // freebsd
|
||||
return "Other Unix";
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool SetScreensaverInhibitDBus(const bool inhibit_requested, const char* program_name, const char* reason)
|
||||
{
|
||||
static dbus_uint32_t s_cookie;
|
||||
const char* bus_method = (inhibit_requested) ? "Inhibit" : "UnInhibit";
|
||||
DBusError error_dbus;
|
||||
DBusConnection* connection = nullptr;
|
||||
static DBusConnection* s_comparison_connection;
|
||||
DBusMessage* message = nullptr;
|
||||
DBusMessage* response = nullptr;
|
||||
DBusMessageIter message_itr;
|
||||
char* desktop_session = nullptr;
|
||||
|
||||
ScopedGuard cleanup = [&]() {
|
||||
if (dbus_error_is_set(&error_dbus))
|
||||
dbus_error_free(&error_dbus);
|
||||
if (message)
|
||||
dbus_message_unref(message);
|
||||
if (response)
|
||||
dbus_message_unref(response);
|
||||
};
|
||||
|
||||
dbus_error_init(&error_dbus);
|
||||
// Calling dbus_bus_get() after the first time returns a pointer to the existing connection.
|
||||
connection = dbus_bus_get(DBUS_BUS_SESSION, &error_dbus);
|
||||
if (!connection || (dbus_error_is_set(&error_dbus)))
|
||||
return false;
|
||||
if (s_comparison_connection != connection)
|
||||
{
|
||||
dbus_connection_set_exit_on_disconnect(connection, false);
|
||||
s_cookie = 0;
|
||||
s_comparison_connection = connection;
|
||||
}
|
||||
|
||||
desktop_session = std::getenv("DESKTOP_SESSION");
|
||||
if (desktop_session && std::strncmp(desktop_session, "mate", 4) == 0)
|
||||
{
|
||||
message = dbus_message_new_method_call("org.mate.ScreenSaver", "/org/mate/ScreenSaver", "org.mate.ScreenSaver", bus_method);
|
||||
}
|
||||
else
|
||||
{
|
||||
message = dbus_message_new_method_call("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", bus_method);
|
||||
}
|
||||
|
||||
if (!message)
|
||||
return false;
|
||||
// Initialize an append iterator for the message, gets freed with the message.
|
||||
dbus_message_iter_init_append(message, &message_itr);
|
||||
if (inhibit_requested)
|
||||
{
|
||||
// Guard against repeat inhibitions which would add extra inhibitors each generating a different cookie.
|
||||
if (s_cookie)
|
||||
return false;
|
||||
// Append process/window name.
|
||||
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &program_name))
|
||||
return false;
|
||||
// Append reason for inhibiting the screensaver.
|
||||
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &reason))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only Append the cookie.
|
||||
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_UINT32, &s_cookie))
|
||||
return false;
|
||||
}
|
||||
// Send message and get response.
|
||||
response = dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error_dbus);
|
||||
if (!response || dbus_error_is_set(&error_dbus))
|
||||
return false;
|
||||
s_cookie = 0;
|
||||
if (inhibit_requested)
|
||||
{
|
||||
// Get the cookie from the response message.
|
||||
if (!dbus_message_get_args(response, &error_dbus, DBUS_TYPE_UINT32, &s_cookie, DBUS_TYPE_INVALID) || dbus_error_is_set(&error_dbus))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Common::InhibitScreensaver(bool inhibit)
|
||||
{
|
||||
return SetScreensaverInhibitDBus(inhibit, "PCSX2", "PCSX2 VM is running.");
|
||||
}
|
||||
|
||||
void Common::SetMousePosition(int x, int y)
|
||||
{
|
||||
Display* display = XOpenDisplay(nullptr);
|
||||
if (!display)
|
||||
return;
|
||||
|
||||
Window root = DefaultRootWindow(display);
|
||||
XWarpPointer(display, None, root, 0, 0, 0, 0, x, y);
|
||||
XFlush(display);
|
||||
|
||||
XCloseDisplay(display);
|
||||
}
|
||||
|
||||
static std::function<void(int, int)> fnMouseMoveCb;
|
||||
static std::atomic<bool> trackingMouse = false;
|
||||
static std::thread mouseThread;
|
||||
|
||||
void mouseEventLoop()
|
||||
{
|
||||
Threading::SetNameOfCurrentThread("X11 Mouse Thread");
|
||||
Display* display = XOpenDisplay(nullptr);
|
||||
if (!display)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int opcode, eventcode, error;
|
||||
if (!XQueryExtension(display, "XInputExtension", &opcode, &eventcode, &error))
|
||||
{
|
||||
XCloseDisplay(display);
|
||||
return;
|
||||
}
|
||||
|
||||
const Window root = DefaultRootWindow(display);
|
||||
XIEventMask evmask;
|
||||
unsigned char mask[(XI_LASTEVENT + 7) / 8] = {0};
|
||||
|
||||
evmask.deviceid = XIAllDevices;
|
||||
evmask.mask_len = sizeof(mask);
|
||||
evmask.mask = mask;
|
||||
XISetMask(mask, XI_RawMotion);
|
||||
|
||||
XISelectEvents(display, root, &evmask, 1);
|
||||
XSync(display, False);
|
||||
|
||||
XEvent event;
|
||||
while (trackingMouse)
|
||||
{
|
||||
// XNextEvent is blocking, this is a zombie process risk if no events arrive
|
||||
// while we are trying to shutdown.
|
||||
// https://nrk.neocities.org/articles/x11-timeout-with-xsyncalarm might be
|
||||
// a better solution than using XPending.
|
||||
if (!XPending(display))
|
||||
{
|
||||
Threading::Sleep(1);
|
||||
Threading::SpinWait();
|
||||
continue;
|
||||
}
|
||||
|
||||
XNextEvent(display, &event);
|
||||
if (event.xcookie.type == GenericEvent &&
|
||||
event.xcookie.extension == opcode &&
|
||||
XGetEventData(display, &event.xcookie))
|
||||
{
|
||||
XIRawEvent* raw_event = reinterpret_cast<XIRawEvent*>(event.xcookie.data);
|
||||
if (raw_event->evtype == XI_RawMotion)
|
||||
{
|
||||
Window w;
|
||||
int root_x, root_y, win_x, win_y;
|
||||
unsigned int mask;
|
||||
XQueryPointer(display, root, &w, &w, &root_x, &root_y, &win_x, &win_y, &mask);
|
||||
|
||||
if (fnMouseMoveCb)
|
||||
fnMouseMoveCb(root_x, root_y);
|
||||
}
|
||||
XFreeEventData(display, &event.xcookie);
|
||||
}
|
||||
}
|
||||
|
||||
XCloseDisplay(display);
|
||||
}
|
||||
|
||||
bool Common::AttachMousePositionCb(std::function<void(int, int)> cb)
|
||||
{
|
||||
fnMouseMoveCb = cb;
|
||||
|
||||
if (trackingMouse)
|
||||
return true;
|
||||
|
||||
trackingMouse = true;
|
||||
mouseThread = std::thread(mouseEventLoop);
|
||||
mouseThread.detach();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Common::DetachMousePositionCb()
|
||||
{
|
||||
trackingMouse = false;
|
||||
fnMouseMoveCb = nullptr;
|
||||
if (mouseThread.joinable())
|
||||
{
|
||||
mouseThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool Common::PlaySoundAsync(const char* path)
|
||||
{
|
||||
#ifdef __linux__
|
||||
// This is... pretty awful. But I can't think of a better way without linking to e.g. gstreamer.
|
||||
const char* cmdname = "aplay";
|
||||
const char* argv[] = {cmdname, path, nullptr};
|
||||
pid_t pid;
|
||||
|
||||
// Since we set SA_NOCLDWAIT in Qt, we don't need to wait here.
|
||||
int res = posix_spawnp(&pid, cmdname, nullptr, nullptr, const_cast<char**>(argv), environ);
|
||||
if (res == 0)
|
||||
return true;
|
||||
|
||||
// Try gst-play-1.0.
|
||||
const char* gst_play_cmdname = "gst-play-1.0";
|
||||
const char* gst_play_argv[] = {cmdname, path, nullptr};
|
||||
res = posix_spawnp(&pid, gst_play_cmdname, nullptr, nullptr, const_cast<char**>(gst_play_argv), environ);
|
||||
if (res == 0)
|
||||
return true;
|
||||
|
||||
// gst-launch? Bit messier for sure.
|
||||
TinyString location_str = TinyString::from_format("location={}", path);
|
||||
TinyString parse_str = TinyString::from_format("{}parse", Path::GetExtension(path));
|
||||
const char* gst_launch_cmdname = "gst-launch-1.0";
|
||||
const char* gst_launch_argv[] = {
|
||||
gst_launch_cmdname, "filesrc", location_str.c_str(), "!", parse_str.c_str(), "!", "alsasink", nullptr};
|
||||
res = posix_spawnp(&pid, gst_launch_cmdname, nullptr, nullptr, const_cast<char**>(gst_launch_argv), environ);
|
||||
if (res == 0)
|
||||
return true;
|
||||
|
||||
Console.ErrorFmt("Failed to play sound effect {}. Make sure you have aplay, gst-play-1.0, or gst-launch-1.0 available.", path);
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Threading::Sleep(int ms)
|
||||
{
|
||||
usleep(1000 * ms);
|
||||
}
|
||||
|
||||
void Threading::SleepUntil(u64 ticks)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = static_cast<time_t>(ticks / 1000000000ULL);
|
||||
ts.tv_nsec = static_cast<long>(ticks % 1000000000ULL);
|
||||
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, nullptr);
|
||||
}
|
||||
348
common/Linux/LnxThreads.cpp
Normal file
348
common/Linux/LnxThreads.cpp
Normal file
@@ -0,0 +1,348 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include "common/Threading.h"
|
||||
#include "common/Assertions.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#if defined(__linux__)
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sched.h>
|
||||
|
||||
// glibc < v2.30 doesn't define gettid...
|
||||
#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 30
|
||||
#include <sys/syscall.h>
|
||||
#define gettid() syscall(SYS_gettid)
|
||||
#endif
|
||||
#else
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
|
||||
// Note: assuming multicore is safer because it forces the interlocked routines to use
|
||||
// the LOCK prefix. The prefix works on single core CPUs fine (but is slow), but not
|
||||
// having the LOCK prefix is very bad indeed.
|
||||
|
||||
__forceinline void Threading::Timeslice()
|
||||
{
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
// For use in spin/wait loops, Acts as a hint to Intel CPUs and should, in theory
|
||||
// improve performance and reduce cpu power consumption.
|
||||
__forceinline void Threading::SpinWait()
|
||||
{
|
||||
// If this doesn't compile you can just comment it out (it only serves as a
|
||||
// performance hint and isn't required).
|
||||
#if defined(_M_X86)
|
||||
__asm__("pause");
|
||||
#elif defined(_M_ARM64)
|
||||
__asm__ __volatile__("isb");
|
||||
#endif
|
||||
}
|
||||
|
||||
__forceinline void Threading::EnableHiresScheduler()
|
||||
{
|
||||
// Don't know if linux has a customizable scheduler resolution like Windows (doubtful)
|
||||
}
|
||||
|
||||
__forceinline void Threading::DisableHiresScheduler()
|
||||
{
|
||||
}
|
||||
|
||||
// Unit of time of GetThreadCpuTime/GetCpuTime
|
||||
u64 Threading::GetThreadTicksPerSecond()
|
||||
{
|
||||
return 1000000;
|
||||
}
|
||||
|
||||
// Helper function to get either either the current cpu usage
|
||||
// in called thread or in id thread
|
||||
static u64 get_thread_time(uptr id = 0)
|
||||
{
|
||||
clockid_t cid;
|
||||
if (id)
|
||||
{
|
||||
int err = pthread_getcpuclockid((pthread_t)id, &cid);
|
||||
if (err)
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
cid = CLOCK_THREAD_CPUTIME_ID;
|
||||
}
|
||||
|
||||
struct timespec ts;
|
||||
int err = clock_gettime(cid, &ts);
|
||||
if (err)
|
||||
return 0;
|
||||
|
||||
return (u64)ts.tv_sec * (u64)1e6 + (u64)ts.tv_nsec / (u64)1e3;
|
||||
}
|
||||
|
||||
// Returns the current timestamp (not relative to a real world clock)
|
||||
u64 Threading::GetThreadCpuTime()
|
||||
{
|
||||
return get_thread_time();
|
||||
}
|
||||
|
||||
Threading::ThreadHandle::ThreadHandle() = default;
|
||||
|
||||
Threading::ThreadHandle::ThreadHandle(const ThreadHandle& handle)
|
||||
: m_native_handle(handle.m_native_handle)
|
||||
#ifdef __linux__
|
||||
, m_native_id(handle.m_native_id)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
Threading::ThreadHandle::ThreadHandle(ThreadHandle&& handle)
|
||||
: m_native_handle(handle.m_native_handle)
|
||||
#ifdef __linux__
|
||||
, m_native_id(handle.m_native_id)
|
||||
#endif
|
||||
{
|
||||
handle.m_native_handle = nullptr;
|
||||
#ifdef __linux__
|
||||
handle.m_native_id = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
Threading::ThreadHandle::~ThreadHandle() = default;
|
||||
|
||||
Threading::ThreadHandle Threading::ThreadHandle::GetForCallingThread()
|
||||
{
|
||||
ThreadHandle ret;
|
||||
ret.m_native_handle = (void*)pthread_self();
|
||||
#ifdef __linux__
|
||||
ret.m_native_id = gettid();
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
Threading::ThreadHandle& Threading::ThreadHandle::operator=(ThreadHandle&& handle)
|
||||
{
|
||||
m_native_handle = handle.m_native_handle;
|
||||
handle.m_native_handle = nullptr;
|
||||
#ifdef __linux__
|
||||
m_native_id = handle.m_native_id;
|
||||
handle.m_native_id = 0;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
Threading::ThreadHandle& Threading::ThreadHandle::operator=(const ThreadHandle& handle)
|
||||
{
|
||||
m_native_handle = handle.m_native_handle;
|
||||
#ifdef __linux__
|
||||
m_native_id = handle.m_native_id;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
u64 Threading::ThreadHandle::GetCPUTime() const
|
||||
{
|
||||
return m_native_handle ? get_thread_time((uptr)m_native_handle) : 0;
|
||||
}
|
||||
|
||||
bool Threading::ThreadHandle::SetAffinity(u64 processor_mask) const
|
||||
{
|
||||
#if defined(__linux__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
|
||||
if (processor_mask != 0)
|
||||
{
|
||||
for (u32 i = 0; i < 64; i++)
|
||||
{
|
||||
if (processor_mask & (static_cast<u64>(1) << i))
|
||||
{
|
||||
CPU_SET(i, &set);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
long num_processors = sysconf(_SC_NPROCESSORS_CONF);
|
||||
for (long i = 0; i < num_processors; i++)
|
||||
{
|
||||
CPU_SET(i, &set);
|
||||
}
|
||||
}
|
||||
|
||||
return sched_setaffinity((pid_t)m_native_id, sizeof(set), &set) >= 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
Threading::Thread::Thread() = default;
|
||||
|
||||
Threading::Thread::Thread(Thread&& thread)
|
||||
: ThreadHandle(thread)
|
||||
, m_stack_size(thread.m_stack_size)
|
||||
{
|
||||
thread.m_stack_size = 0;
|
||||
}
|
||||
|
||||
Threading::Thread::Thread(EntryPoint func)
|
||||
: ThreadHandle()
|
||||
{
|
||||
if (!Start(std::move(func)))
|
||||
pxFailRel("Failed to start implicitly started thread.");
|
||||
}
|
||||
|
||||
Threading::Thread::~Thread()
|
||||
{
|
||||
pxAssertRel(!m_native_handle, "Thread should be detached or joined at destruction");
|
||||
}
|
||||
|
||||
void Threading::Thread::SetStackSize(u32 size)
|
||||
{
|
||||
pxAssertRel(!m_native_handle, "Can't change the stack size on a started thread");
|
||||
m_stack_size = size;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
// For Linux, we have to do a bit of trickery here to get the thread's ID back from
|
||||
// the thread itself, because it's not part of pthreads. We use a semaphore to signal
|
||||
// when the thread has started, and filled in thread_id_ptr.
|
||||
struct ThreadProcParameters
|
||||
{
|
||||
Threading::Thread::EntryPoint func;
|
||||
Threading::KernelSemaphore* start_semaphore;
|
||||
unsigned int* thread_id_ptr;
|
||||
};
|
||||
|
||||
void* Threading::Thread::ThreadProc(void* param)
|
||||
{
|
||||
std::unique_ptr<ThreadProcParameters> entry(static_cast<ThreadProcParameters*>(param));
|
||||
*entry->thread_id_ptr = gettid();
|
||||
entry->start_semaphore->Post();
|
||||
entry->func();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Threading::Thread::Start(EntryPoint func)
|
||||
{
|
||||
pxAssertRel(!m_native_handle, "Can't start an already-started thread");
|
||||
|
||||
KernelSemaphore start_semaphore;
|
||||
std::unique_ptr<ThreadProcParameters> params(std::make_unique<ThreadProcParameters>());
|
||||
params->func = std::move(func);
|
||||
params->start_semaphore = &start_semaphore;
|
||||
params->thread_id_ptr = &m_native_id;
|
||||
|
||||
pthread_attr_t attrs;
|
||||
bool has_attributes = false;
|
||||
|
||||
if (m_stack_size != 0)
|
||||
{
|
||||
has_attributes = true;
|
||||
pthread_attr_init(&attrs);
|
||||
}
|
||||
if (m_stack_size != 0)
|
||||
pthread_attr_setstacksize(&attrs, m_stack_size);
|
||||
|
||||
pthread_t handle;
|
||||
const int res = pthread_create(&handle, has_attributes ? &attrs : nullptr, ThreadProc, params.get());
|
||||
if (res != 0)
|
||||
return false;
|
||||
|
||||
// wait until it sets our native id
|
||||
start_semaphore.Wait();
|
||||
|
||||
// thread started, it'll release the memory
|
||||
m_native_handle = (void*)handle;
|
||||
params.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void* Threading::Thread::ThreadProc(void* param)
|
||||
{
|
||||
std::unique_ptr<EntryPoint> entry(static_cast<EntryPoint*>(param));
|
||||
(*entry.get())();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Threading::Thread::Start(EntryPoint func)
|
||||
{
|
||||
pxAssertRel(!m_native_handle, "Can't start an already-started thread");
|
||||
|
||||
std::unique_ptr<EntryPoint> func_clone(std::make_unique<EntryPoint>(std::move(func)));
|
||||
|
||||
pthread_attr_t attrs;
|
||||
bool has_attributes = false;
|
||||
|
||||
if (m_stack_size != 0)
|
||||
{
|
||||
has_attributes = true;
|
||||
pthread_attr_init(&attrs);
|
||||
}
|
||||
if (m_stack_size != 0)
|
||||
pthread_attr_setstacksize(&attrs, m_stack_size);
|
||||
|
||||
pthread_t handle;
|
||||
const int res = pthread_create(&handle, has_attributes ? &attrs : nullptr, ThreadProc, func_clone.get());
|
||||
if (res != 0)
|
||||
return false;
|
||||
|
||||
// thread started, it'll release the memory
|
||||
m_native_handle = (void*)handle;
|
||||
func_clone.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Threading::Thread::Detach()
|
||||
{
|
||||
pxAssertRel(m_native_handle, "Can't detach without a thread");
|
||||
pthread_detach((pthread_t)m_native_handle);
|
||||
m_native_handle = nullptr;
|
||||
#ifdef __linux__
|
||||
m_native_id = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Threading::Thread::Join()
|
||||
{
|
||||
pxAssertRel(m_native_handle, "Can't join without a thread");
|
||||
void* retval;
|
||||
const int res = pthread_join((pthread_t)m_native_handle, &retval);
|
||||
if (res != 0)
|
||||
pxFailRel("pthread_join() for thread join failed");
|
||||
|
||||
m_native_handle = nullptr;
|
||||
#ifdef __linux__
|
||||
m_native_id = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
Threading::ThreadHandle& Threading::Thread::operator=(Thread&& thread)
|
||||
{
|
||||
ThreadHandle::operator=(thread);
|
||||
m_stack_size = thread.m_stack_size;
|
||||
thread.m_stack_size = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Threading::SetNameOfCurrentThread(const char* name)
|
||||
{
|
||||
#if defined(__linux__)
|
||||
// Extract of manpage: "The name can be up to 16 bytes long, and should be
|
||||
// null-terminated if it contains fewer bytes."
|
||||
prctl(PR_SET_NAME, name, 0, 0, 0);
|
||||
#elif defined(__unix__)
|
||||
pthread_set_name_np(pthread_self(), name);
|
||||
#endif
|
||||
}
|
||||
210
common/MD5Digest.cpp
Normal file
210
common/MD5Digest.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "MD5Digest.h"
|
||||
#include <cstring>
|
||||
|
||||
// mostly based on this implementation (public domain): http://www.fourmilab.ch/md5/
|
||||
|
||||
/* The four core functions - F1 is optimized somewhat */
|
||||
|
||||
/* #define F1(x, y, z) (x & y | ~x & z) */
|
||||
#define F1(x, y, z) (z ^ (x & (y ^ z)))
|
||||
#define F2(x, y, z) F1(z, x, y)
|
||||
#define F3(x, y, z) (x ^ y ^ z)
|
||||
#define F4(x, y, z) (y ^ (x | ~z))
|
||||
|
||||
/* This is the central step in the MD5 algorithm. */
|
||||
#define MD5STEP(f, w, x, y, z, data, s) (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
|
||||
|
||||
/*
|
||||
* The core of the MD5 algorithm, this alters an existing MD5 hash to
|
||||
* reflect the addition of 16 longwords of new data. MD5Update blocks
|
||||
* the data and converts bytes into longwords for this routine.
|
||||
*/
|
||||
static void MD5Transform(u32 buf[4], u32 in[16])
|
||||
{
|
||||
u32 a, b, c, d;
|
||||
|
||||
a = buf[0];
|
||||
b = buf[1];
|
||||
c = buf[2];
|
||||
d = buf[3];
|
||||
|
||||
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
|
||||
|
||||
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
|
||||
|
||||
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
|
||||
|
||||
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
|
||||
|
||||
buf[0] += a;
|
||||
buf[1] += b;
|
||||
buf[2] += c;
|
||||
buf[3] += d;
|
||||
}
|
||||
|
||||
MD5Digest::MD5Digest()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
void MD5Digest::Reset()
|
||||
{
|
||||
buf[0] = 0x67452301;
|
||||
buf[1] = 0xefcdab89;
|
||||
buf[2] = 0x98badcfe;
|
||||
buf[3] = 0x10325476;
|
||||
|
||||
bits[0] = 0;
|
||||
bits[1] = 0;
|
||||
|
||||
std::memset(in, 0, sizeof(in));
|
||||
}
|
||||
|
||||
void MD5Digest::Update(const void* pData, u32 cbData)
|
||||
{
|
||||
u32 t;
|
||||
const u8* pByteData = reinterpret_cast<const u8*>(pData);
|
||||
|
||||
/* Update bitcount */
|
||||
|
||||
t = this->bits[0];
|
||||
if ((this->bits[0] = t + ((u32)cbData << 3)) < t)
|
||||
this->bits[1]++; /* Carry from low to high */
|
||||
this->bits[1] += cbData >> 29;
|
||||
|
||||
t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
|
||||
|
||||
/* Handle any leading odd-sized chunks */
|
||||
|
||||
if (t)
|
||||
{
|
||||
u8* p = (u8*)this->in + t;
|
||||
|
||||
t = 64 - t;
|
||||
if (cbData < t)
|
||||
{
|
||||
std::memcpy(p, pByteData, cbData);
|
||||
return;
|
||||
}
|
||||
std::memcpy(p, pByteData, t);
|
||||
MD5Transform(this->buf, (u32*)this->in);
|
||||
pByteData += t;
|
||||
cbData -= t;
|
||||
}
|
||||
/* Process data in 64-byte chunks */
|
||||
|
||||
while (cbData >= 64)
|
||||
{
|
||||
std::memcpy(this->in, pByteData, 64);
|
||||
MD5Transform(this->buf, (u32*)this->in);
|
||||
pByteData += 64;
|
||||
cbData -= 64;
|
||||
}
|
||||
|
||||
/* Handle any remaining bytes of data. */
|
||||
|
||||
std::memcpy(this->in, pByteData, cbData);
|
||||
}
|
||||
|
||||
void MD5Digest::Final(u8 Digest[16])
|
||||
{
|
||||
u32 count;
|
||||
u8* p;
|
||||
|
||||
/* Compute number of bytes mod 64 */
|
||||
count = (this->bits[0] >> 3) & 0x3F;
|
||||
|
||||
/* Set the first char of padding to 0x80. This is safe since there is
|
||||
always at least one byte free */
|
||||
p = this->in + count;
|
||||
*p++ = 0x80;
|
||||
|
||||
/* Bytes of padding needed to make 64 bytes */
|
||||
count = 64 - 1 - count;
|
||||
|
||||
/* Pad out to 56 mod 64 */
|
||||
if (count < 8)
|
||||
{
|
||||
/* Two lots of padding: Pad the first block to 64 bytes */
|
||||
std::memset(p, 0, count);
|
||||
MD5Transform(this->buf, (u32*)this->in);
|
||||
|
||||
/* Now fill the next block with 56 bytes */
|
||||
std::memset(this->in, 0, 56);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Pad block to 56 bytes */
|
||||
std::memset(p, 0, count - 8);
|
||||
}
|
||||
|
||||
/* Append length in bits and transform */
|
||||
((u32*)this->in)[14] = this->bits[0];
|
||||
((u32*)this->in)[15] = this->bits[1];
|
||||
|
||||
MD5Transform(this->buf, (u32*)this->in);
|
||||
std::memcpy(Digest, this->buf, 16);
|
||||
}
|
||||
20
common/MD5Digest.h
Normal file
20
common/MD5Digest.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include "Pcsx2Types.h"
|
||||
|
||||
class MD5Digest
|
||||
{
|
||||
public:
|
||||
MD5Digest();
|
||||
|
||||
void Update(const void* pData, u32 cbData);
|
||||
void Final(u8 Digest[16]);
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
u32 buf[4];
|
||||
u32 bits[2];
|
||||
u8 in[64];
|
||||
};
|
||||
85
common/MRCHelpers.h
Normal file
85
common/MRCHelpers.h
Normal file
@@ -0,0 +1,85 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#ifndef __OBJC__
|
||||
#error This header is for use with Objective-C++ only.
|
||||
#endif
|
||||
|
||||
#if __has_feature(objc_arc)
|
||||
#error This file is for manual reference counting! Compile without -fobjc-arc
|
||||
#endif
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
/// Managed Obj-C pointer
|
||||
template <typename T>
|
||||
class MRCOwned
|
||||
{
|
||||
T ptr;
|
||||
MRCOwned(T ptr): ptr(ptr) {}
|
||||
public:
|
||||
MRCOwned(): ptr(nullptr) {}
|
||||
MRCOwned(std::nullptr_t): ptr(nullptr) {}
|
||||
MRCOwned(MRCOwned&& other)
|
||||
: ptr(other.ptr)
|
||||
{
|
||||
other.ptr = nullptr;
|
||||
}
|
||||
MRCOwned(const MRCOwned& other)
|
||||
: ptr(other.ptr)
|
||||
{
|
||||
[ptr retain];
|
||||
}
|
||||
~MRCOwned()
|
||||
{
|
||||
if (ptr)
|
||||
[ptr release];
|
||||
}
|
||||
operator T() const { return ptr; }
|
||||
MRCOwned& operator=(const MRCOwned& other)
|
||||
{
|
||||
[other.ptr retain];
|
||||
if (ptr)
|
||||
[ptr release];
|
||||
ptr = other.ptr;
|
||||
return *this;
|
||||
}
|
||||
MRCOwned& operator=(MRCOwned&& other)
|
||||
{
|
||||
std::swap(ptr, other.ptr);
|
||||
return *this;
|
||||
}
|
||||
void Reset()
|
||||
{
|
||||
[ptr release];
|
||||
ptr = nullptr;
|
||||
}
|
||||
T Get() const { return ptr; }
|
||||
static MRCOwned Transfer(T ptr)
|
||||
{
|
||||
return MRCOwned(ptr);
|
||||
}
|
||||
static MRCOwned Retain(T ptr)
|
||||
{
|
||||
[ptr retain];
|
||||
return MRCOwned(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
/// Take ownership of an Obj-C pointer (equivalent to __bridge_transfer)
|
||||
template<typename T>
|
||||
static inline MRCOwned<T> MRCTransfer(T ptr)
|
||||
{
|
||||
return MRCOwned<T>::Transfer(ptr);
|
||||
}
|
||||
|
||||
/// Retain an Obj-C pointer (equivalent to __bridge)
|
||||
template<typename T>
|
||||
static inline MRCOwned<T> MRCRetain(T ptr)
|
||||
{
|
||||
return MRCOwned<T>::Retain(ptr);
|
||||
}
|
||||
|
||||
342
common/MemorySettingsInterface.cpp
Normal file
342
common/MemorySettingsInterface.cpp
Normal file
@@ -0,0 +1,342 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "MemorySettingsInterface.h"
|
||||
#include "Error.h"
|
||||
#include "StringUtil.h"
|
||||
|
||||
MemorySettingsInterface::MemorySettingsInterface() = default;
|
||||
|
||||
MemorySettingsInterface::~MemorySettingsInterface() = default;
|
||||
|
||||
bool MemorySettingsInterface::Save(Error* error)
|
||||
{
|
||||
Error::SetStringView(error, "Memory settings cannot be saved.");
|
||||
return false;
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::Clear()
|
||||
{
|
||||
m_sections.clear();
|
||||
}
|
||||
|
||||
bool MemorySettingsInterface::IsEmpty()
|
||||
{
|
||||
return m_sections.empty();
|
||||
}
|
||||
|
||||
bool MemorySettingsInterface::GetIntValue(const char* section, const char* key, s32* value) const
|
||||
{
|
||||
const auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
return false;
|
||||
|
||||
const auto iter = sit->second.find(key);
|
||||
if (iter == sit->second.end())
|
||||
return false;
|
||||
|
||||
std::optional<s32> parsed = StringUtil::FromChars<s32>(iter->second, 10);
|
||||
if (!parsed.has_value())
|
||||
return false;
|
||||
|
||||
*value = parsed.value();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemorySettingsInterface::GetUIntValue(const char* section, const char* key, u32* value) const
|
||||
{
|
||||
const auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
return false;
|
||||
|
||||
const auto iter = sit->second.find(key);
|
||||
if (iter == sit->second.end())
|
||||
return false;
|
||||
|
||||
std::optional<u32> parsed = StringUtil::FromChars<u32>(iter->second, 10);
|
||||
if (!parsed.has_value())
|
||||
return false;
|
||||
|
||||
*value = parsed.value();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemorySettingsInterface::GetFloatValue(const char* section, const char* key, float* value) const
|
||||
{
|
||||
const auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
return false;
|
||||
|
||||
const auto iter = sit->second.find(key);
|
||||
if (iter == sit->second.end())
|
||||
return false;
|
||||
|
||||
std::optional<float> parsed = StringUtil::FromChars<float>(iter->second);
|
||||
if (!parsed.has_value())
|
||||
return false;
|
||||
|
||||
*value = parsed.value();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemorySettingsInterface::GetDoubleValue(const char* section, const char* key, double* value) const
|
||||
{
|
||||
const auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
return false;
|
||||
|
||||
const auto iter = sit->second.find(key);
|
||||
if (iter == sit->second.end())
|
||||
return false;
|
||||
|
||||
std::optional<double> parsed = StringUtil::FromChars<double>(iter->second);
|
||||
if (!parsed.has_value())
|
||||
return false;
|
||||
|
||||
*value = parsed.value();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemorySettingsInterface::GetBoolValue(const char* section, const char* key, bool* value) const
|
||||
{
|
||||
const auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
return false;
|
||||
|
||||
const auto iter = sit->second.find(key);
|
||||
if (iter == sit->second.end())
|
||||
return false;
|
||||
|
||||
std::optional<bool> parsed = StringUtil::FromChars<bool>(iter->second);
|
||||
if (!parsed.has_value())
|
||||
return false;
|
||||
|
||||
*value = parsed.value();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemorySettingsInterface::GetStringValue(const char* section, const char* key, std::string* value) const
|
||||
{
|
||||
const auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
return false;
|
||||
|
||||
const auto iter = sit->second.find(key);
|
||||
if (iter == sit->second.end())
|
||||
return false;
|
||||
|
||||
*value = iter->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemorySettingsInterface::GetStringValue(const char* section, const char* key, SmallStringBase* value) const
|
||||
{
|
||||
const auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
return false;
|
||||
|
||||
const auto iter = sit->second.find(key);
|
||||
if (iter == sit->second.end())
|
||||
return false;
|
||||
|
||||
value->assign(iter->second);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::SetIntValue(const char* section, const char* key, s32 value)
|
||||
{
|
||||
SetValue(section, key, std::to_string(value));
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::SetUIntValue(const char* section, const char* key, u32 value)
|
||||
{
|
||||
SetValue(section, key, std::to_string(value));
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::SetFloatValue(const char* section, const char* key, float value)
|
||||
{
|
||||
SetValue(section, key, std::to_string(value));
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::SetDoubleValue(const char* section, const char* key, double value)
|
||||
{
|
||||
SetValue(section, key, std::to_string(value));
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::SetBoolValue(const char* section, const char* key, bool value)
|
||||
{
|
||||
SetValue(section, key, std::to_string(value));
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::SetStringValue(const char* section, const char* key, const char* value)
|
||||
{
|
||||
SetValue(section, key, value);
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> MemorySettingsInterface::GetKeyValueList(const char* section) const
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> output;
|
||||
auto sit = m_sections.find(section);
|
||||
if (sit != m_sections.end())
|
||||
{
|
||||
for (const auto& it : sit->second)
|
||||
output.emplace_back(it.first, it.second);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::SetKeyValueList(const char* section, const std::vector<std::pair<std::string, std::string>>& items)
|
||||
{
|
||||
auto sit = m_sections.find(section);
|
||||
sit->second.clear();
|
||||
for (const auto& [key, value] : items)
|
||||
sit->second.emplace(key, value);
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::SetValue(const char* section, const char* key, std::string value)
|
||||
{
|
||||
auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
sit = m_sections.emplace(std::make_pair(std::string(section), KeyMap())).first;
|
||||
|
||||
const auto range = sit->second.equal_range(key);
|
||||
if (range.first == sit->second.end())
|
||||
{
|
||||
sit->second.emplace(std::string(key), std::move(value));
|
||||
return;
|
||||
}
|
||||
|
||||
auto iter = range.first;
|
||||
iter->second = std::move(value);
|
||||
++iter;
|
||||
|
||||
// remove other values
|
||||
while (iter != range.second)
|
||||
{
|
||||
auto remove = iter++;
|
||||
sit->second.erase(remove);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> MemorySettingsInterface::GetStringList(const char* section, const char* key) const
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
|
||||
const auto sit = m_sections.find(section);
|
||||
if (sit != m_sections.end())
|
||||
{
|
||||
const auto range = sit->second.equal_range(key);
|
||||
for (auto iter = range.first; iter != range.second; ++iter)
|
||||
ret.emplace_back(iter->second);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::SetStringList(const char* section, const char* key, const std::vector<std::string>& items)
|
||||
{
|
||||
auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
sit = m_sections.emplace(std::make_pair(std::string(section), KeyMap())).first;
|
||||
|
||||
const auto range = sit->second.equal_range(key);
|
||||
for (auto iter = range.first; iter != range.second;)
|
||||
sit->second.erase(iter++);
|
||||
|
||||
std::string_view keysv(key);
|
||||
for (const std::string& value : items)
|
||||
sit->second.emplace(keysv, value);
|
||||
}
|
||||
|
||||
bool MemorySettingsInterface::RemoveFromStringList(const char* section, const char* key, const char* item)
|
||||
{
|
||||
auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
sit = m_sections.emplace(std::make_pair(std::string(section), KeyMap())).first;
|
||||
|
||||
const auto range = sit->second.equal_range(key);
|
||||
bool result = false;
|
||||
for (auto iter = range.first; iter != range.second;)
|
||||
{
|
||||
if (iter->second == item)
|
||||
{
|
||||
sit->second.erase(iter++);
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MemorySettingsInterface::AddToStringList(const char* section, const char* key, const char* item)
|
||||
{
|
||||
auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
sit = m_sections.emplace(std::make_pair(std::string(section), KeyMap())).first;
|
||||
|
||||
const auto range = sit->second.equal_range(key);
|
||||
for (auto iter = range.first; iter != range.second; ++iter)
|
||||
{
|
||||
if (iter->second == item)
|
||||
return false;
|
||||
}
|
||||
|
||||
sit->second.emplace(std::string(key), std::string(item));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemorySettingsInterface::ContainsValue(const char* section, const char* key) const
|
||||
{
|
||||
const auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
return false;
|
||||
|
||||
return (sit->second.find(key) != sit->second.end());
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::DeleteValue(const char* section, const char* key)
|
||||
{
|
||||
auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
return;
|
||||
|
||||
const auto range = sit->second.equal_range(key);
|
||||
for (auto iter = range.first; iter != range.second;)
|
||||
sit->second.erase(iter++);
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::ClearSection(const char* section)
|
||||
{
|
||||
auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
return;
|
||||
|
||||
m_sections.erase(sit);
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::RemoveSection(const char* section)
|
||||
{
|
||||
auto sit = m_sections.find(section);
|
||||
if (sit == m_sections.end())
|
||||
return;
|
||||
|
||||
m_sections.erase(sit);
|
||||
}
|
||||
|
||||
void MemorySettingsInterface::RemoveEmptySections()
|
||||
{
|
||||
for (auto sit = m_sections.begin(); sit != m_sections.end();)
|
||||
{
|
||||
if (sit->second.size() > 0)
|
||||
{
|
||||
++sit;
|
||||
continue;
|
||||
}
|
||||
|
||||
sit = m_sections.erase(sit);
|
||||
}
|
||||
}
|
||||
65
common/MemorySettingsInterface.h
Normal file
65
common/MemorySettingsInterface.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include "HeterogeneousContainers.h"
|
||||
#include "SettingsInterface.h"
|
||||
#include <string>
|
||||
|
||||
class MemorySettingsInterface final : public SettingsInterface
|
||||
{
|
||||
public:
|
||||
MemorySettingsInterface();
|
||||
~MemorySettingsInterface();
|
||||
|
||||
bool Save(Error* error = nullptr) override;
|
||||
|
||||
void Clear() override;
|
||||
|
||||
bool IsEmpty() override;
|
||||
|
||||
bool GetIntValue(const char* section, const char* key, s32* value) const override;
|
||||
bool GetUIntValue(const char* section, const char* key, u32* value) const override;
|
||||
bool GetFloatValue(const char* section, const char* key, float* value) const override;
|
||||
bool GetDoubleValue(const char* section, const char* key, double* value) const override;
|
||||
bool GetBoolValue(const char* section, const char* key, bool* value) const override;
|
||||
bool GetStringValue(const char* section, const char* key, std::string* value) const override;
|
||||
bool GetStringValue(const char* section, const char* key, SmallStringBase* value) const override;
|
||||
|
||||
void SetIntValue(const char* section, const char* key, s32 value) override;
|
||||
void SetUIntValue(const char* section, const char* key, u32 value) override;
|
||||
void SetFloatValue(const char* section, const char* key, float value) override;
|
||||
void SetDoubleValue(const char* section, const char* key, double value) override;
|
||||
void SetBoolValue(const char* section, const char* key, bool value) override;
|
||||
void SetStringValue(const char* section, const char* key, const char* value) override;
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> GetKeyValueList(const char* section) const override;
|
||||
void SetKeyValueList(const char* section, const std::vector<std::pair<std::string, std::string>>& items) override;
|
||||
|
||||
bool ContainsValue(const char* section, const char* key) const override;
|
||||
void DeleteValue(const char* section, const char* key) override;
|
||||
void ClearSection(const char* section) override;
|
||||
void RemoveSection(const char* section) override;
|
||||
void RemoveEmptySections() override;
|
||||
|
||||
std::vector<std::string> GetStringList(const char* section, const char* key) const override;
|
||||
void SetStringList(const char* section, const char* key, const std::vector<std::string>& items) override;
|
||||
bool RemoveFromStringList(const char* section, const char* key, const char* item) override;
|
||||
bool AddToStringList(const char* section, const char* key, const char* item) override;
|
||||
|
||||
// default parameter overloads
|
||||
using SettingsInterface::GetBoolValue;
|
||||
using SettingsInterface::GetDoubleValue;
|
||||
using SettingsInterface::GetFloatValue;
|
||||
using SettingsInterface::GetIntValue;
|
||||
using SettingsInterface::GetStringValue;
|
||||
using SettingsInterface::GetUIntValue;
|
||||
|
||||
private:
|
||||
using KeyMap = UnorderedStringMultimap<std::string>;
|
||||
using SectionMap = UnorderedStringMap<KeyMap>;
|
||||
|
||||
void SetValue(const char* section, const char* key, std::string value);
|
||||
|
||||
SectionMap m_sections;
|
||||
};
|
||||
87
common/Path.h
Normal file
87
common/Path.h
Normal file
@@ -0,0 +1,87 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace Path
|
||||
{
|
||||
/// Converts any forward slashes to backslashes on Win32.
|
||||
std::string ToNativePath(const std::string_view path);
|
||||
void ToNativePath(std::string* path);
|
||||
|
||||
/// Builds a path relative to the specified file
|
||||
std::string BuildRelativePath(const std::string_view filename, const std::string_view new_filename);
|
||||
|
||||
/// Joins path components together, producing a new path.
|
||||
std::string Combine(const std::string_view base, const std::string_view next);
|
||||
|
||||
/// Removes all .. and . components from a path.
|
||||
std::string Canonicalize(const std::string_view path);
|
||||
void Canonicalize(std::string* path);
|
||||
|
||||
/// Sanitizes a filename for use in a filesystem.
|
||||
std::string SanitizeFileName(const std::string_view str, bool strip_slashes = true);
|
||||
void SanitizeFileName(std::string* str, bool strip_slashes = true);
|
||||
|
||||
/// Returns true if the specified filename is valid on this operating system.
|
||||
bool IsValidFileName(const std::string_view str, bool allow_slashes = false);
|
||||
|
||||
/// Returns true if the specified path is an absolute path (C:\Path on Windows or /path on Unix).
|
||||
bool IsAbsolute(const std::string_view path);
|
||||
|
||||
/// Resolves any symbolic links in the specified path.
|
||||
std::string RealPath(const std::string_view path);
|
||||
|
||||
/// Makes the specified path relative to another (e.g. /a/b/c, /a/b -> ../c).
|
||||
/// Both paths must be relative, otherwise this function will just return the input path.
|
||||
std::string MakeRelative(const std::string_view path, const std::string_view relative_to);
|
||||
|
||||
/// Returns a view of the extension of a filename.
|
||||
std::string_view GetExtension(const std::string_view path);
|
||||
|
||||
/// Removes the extension of a filename.
|
||||
std::string_view StripExtension(const std::string_view path);
|
||||
|
||||
/// Replaces the extension of a filename with another.
|
||||
std::string ReplaceExtension(const std::string_view path, const std::string_view new_extension);
|
||||
|
||||
/// Returns the directory component of a filename.
|
||||
std::string_view GetDirectory(const std::string_view path);
|
||||
|
||||
/// Returns the filename component of a filename.
|
||||
std::string_view GetFileName(const std::string_view path);
|
||||
|
||||
/// Returns the file title (less the extension and path) from a filename.
|
||||
std::string_view GetFileTitle(const std::string_view path);
|
||||
|
||||
/// Changes the filename in a path.
|
||||
std::string ChangeFileName(const std::string_view path, const std::string_view new_filename);
|
||||
void ChangeFileName(std::string* path, const std::string_view new_filename);
|
||||
|
||||
/// Appends a directory to a path.
|
||||
std::string AppendDirectory(const std::string_view path, const std::string_view new_dir);
|
||||
void AppendDirectory(std::string* path, const std::string_view new_dir);
|
||||
|
||||
/// Splits a path into its components, handling both Windows and Unix separators.
|
||||
std::vector<std::string_view> SplitWindowsPath(const std::string_view path);
|
||||
std::string JoinWindowsPath(const std::vector<std::string_view>& components);
|
||||
|
||||
/// Splits a path into its components, only handling native separators.
|
||||
std::vector<std::string_view> SplitNativePath(const std::string_view path);
|
||||
std::string JoinNativePath(const std::vector<std::string_view>& components);
|
||||
|
||||
/// URL encodes the specified string.
|
||||
std::string URLEncode(std::string_view str);
|
||||
|
||||
/// Decodes the specified escaped string.
|
||||
std::string URLDecode(std::string_view str);
|
||||
|
||||
/// Returns a URL for a given path. The path should be absolute.
|
||||
std::string CreateFileURL(std::string_view path);
|
||||
} // namespace Path
|
||||
165
common/Pcsx2Defs.h
Normal file
165
common/Pcsx2Defs.h
Normal file
@@ -0,0 +1,165 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Pcsx2Types.h"
|
||||
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Dev / Debug conditionals - Consts for using if() statements instead of uglier #ifdef.
|
||||
// --------------------------------------------------------------------------------------
|
||||
#ifdef PCSX2_DEVBUILD
|
||||
static constexpr bool IsDevBuild = true;
|
||||
#else
|
||||
static constexpr bool IsDevBuild = false;
|
||||
#endif
|
||||
|
||||
#ifdef PCSX2_DEBUG
|
||||
static constexpr bool IsDebugBuild = true;
|
||||
#else
|
||||
static constexpr bool IsDebugBuild = false;
|
||||
#endif
|
||||
|
||||
// Defines the memory page size for the target platform at compilation.
|
||||
#if defined(OVERRIDE_HOST_PAGE_SIZE)
|
||||
static constexpr unsigned int __pagesize = OVERRIDE_HOST_PAGE_SIZE;
|
||||
static constexpr unsigned int __pagemask = __pagesize - 1;
|
||||
static constexpr unsigned int __pageshift = std::bit_width(__pagemask);
|
||||
#elif defined(_M_ARM64)
|
||||
// Apple Silicon uses 16KB pages and 128 byte cache lines.
|
||||
static constexpr unsigned int __pagesize = 0x4000;
|
||||
static constexpr unsigned int __pageshift = 14;
|
||||
static constexpr unsigned int __pagemask = __pagesize - 1;
|
||||
#else
|
||||
// X86 uses a 4KB granularity and 64 byte cache lines.
|
||||
static constexpr unsigned int __pagesize = 0x1000;
|
||||
static constexpr unsigned int __pageshift = 12;
|
||||
static constexpr unsigned int __pagemask = __pagesize - 1;
|
||||
#endif
|
||||
#if defined(OVERRIDE_HOST_CACHE_LINE_SIZE)
|
||||
static constexpr unsigned int __cachelinesize = OVERRIDE_HOST_CACHE_LINE_SIZE;
|
||||
#elif defined(_M_ARM64)
|
||||
static constexpr unsigned int __cachelinesize = 128;
|
||||
#else
|
||||
static constexpr unsigned int __cachelinesize = 64;
|
||||
#endif
|
||||
|
||||
// We use 4KB alignment for globals for both Apple and x86 platforms, since computing the
|
||||
// address on ARM64 is a single instruction (adrp).
|
||||
static constexpr unsigned int __pagealignsize = 0x1000;
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Microsoft Visual Studio
|
||||
// --------------------------------------------------------------------------------------
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#define __forceinline_odr __forceinline
|
||||
#define __noinline __declspec(noinline)
|
||||
#define __noreturn __declspec(noreturn)
|
||||
|
||||
#define RESTRICT __restrict
|
||||
#define ASSUME(x) __assume(x)
|
||||
|
||||
#else
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// GCC / Clang Compilers Section
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
// SysV ABI passes vector parameters through registers unconditionally.
|
||||
#ifndef _WIN32
|
||||
#define __vectorcall
|
||||
#endif
|
||||
|
||||
// Inlining note: GCC needs ((unused)) attributes defined on inlined functions to suppress
|
||||
// warnings when a static inlined function isn't used in the scope of a single file (which
|
||||
// happens *by design* like all the friggen time >_<)
|
||||
|
||||
// __forceinline_odr is for member functions that are defined in headers. MSVC can't specify
|
||||
// inline and __forceinline at the same time, but it required to not get ODR errors in GCC.
|
||||
|
||||
#define __forceinline __attribute__((always_inline, unused))
|
||||
#define __forceinline_odr __forceinline inline
|
||||
#define __noinline __attribute__((noinline))
|
||||
#define __noreturn __attribute__((noreturn))
|
||||
|
||||
#define RESTRICT __restrict__
|
||||
|
||||
#define ASSUME(x) \
|
||||
do \
|
||||
{ \
|
||||
if (!(x)) \
|
||||
__builtin_unreachable(); \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// __releaseinline / __ri -- a forceinline macro that is enabled for RELEASE/PUBLIC builds ONLY.
|
||||
// --------------------------------------------------------------------------------------
|
||||
// This is useful because forceinline can make certain types of debugging problematic since
|
||||
// functions that look like they should be called won't breakpoint since their code is
|
||||
// inlined, and it can make stack traces confusing or near useless.
|
||||
//
|
||||
// Use __releaseinline for things which are generally large functions where trace debugging
|
||||
// from Devel builds is likely useful; but which should be inlined in an optimized Release
|
||||
// environment.
|
||||
//
|
||||
#define __fi __forceinline
|
||||
#ifdef PCSX2_DEVBUILD
|
||||
#define __ri
|
||||
#else
|
||||
#define __ri __fi
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Safe deallocation macros -- checks pointer validity (non-null) when needed, and sets
|
||||
// pointer to null after deallocation.
|
||||
|
||||
#define safe_delete(ptr) (delete (ptr), (ptr) = nullptr)
|
||||
#define safe_delete_array(ptr) (delete[] (ptr), (ptr) = nullptr)
|
||||
#define safe_free(ptr) (std::free(ptr), (ptr) = nullptr)
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// DeclareNoncopyableObject
|
||||
// --------------------------------------------------------------------------------------
|
||||
// This macro provides an easy and clean method for ensuring objects are not copyable.
|
||||
// Simply add the macro to the head or tail of your class declaration, and attempts to
|
||||
// copy the class will give you a moderately obtuse compiler error.
|
||||
//
|
||||
#ifndef DeclareNoncopyableObject
|
||||
#define DeclareNoncopyableObject(classname) \
|
||||
public: \
|
||||
classname(const classname&) = delete; \
|
||||
classname& operator=(const classname&) = delete
|
||||
#endif
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Handy Human-readable constants for common immediate values (_16kb -> _4gb)
|
||||
|
||||
static constexpr sptr _1kb = 1024 * 1;
|
||||
static constexpr sptr _4kb = _1kb * 4;
|
||||
static constexpr sptr _16kb = _1kb * 16;
|
||||
static constexpr sptr _32kb = _1kb * 32;
|
||||
static constexpr sptr _64kb = _1kb * 64;
|
||||
static constexpr sptr _128kb = _1kb * 128;
|
||||
static constexpr sptr _256kb = _1kb * 256;
|
||||
|
||||
static constexpr s64 _1mb = 1024 * 1024;
|
||||
static constexpr s64 _8mb = _1mb * 8;
|
||||
static constexpr s64 _16mb = _1mb * 16;
|
||||
static constexpr s64 _32mb = _1mb * 32;
|
||||
static constexpr s64 _64mb = _1mb * 64;
|
||||
static constexpr s64 _256mb = _1mb * 256;
|
||||
static constexpr s64 _1gb = _1mb * 1024;
|
||||
static constexpr s64 _4gb = _1gb * 4;
|
||||
|
||||
// Disable some spammy warnings which wx appeared to disable.
|
||||
// We probably should fix these at some point.
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4244) // warning C4244: 'initializing': conversion from 'uptr' to 'uint', possible loss of data
|
||||
#pragma warning(disable : 4267) // warning C4267: 'initializing': conversion from 'size_t' to 'uint', possible loss of data
|
||||
#endif
|
||||
114
common/Pcsx2Types.h
Normal file
114
common/Pcsx2Types.h
Normal file
@@ -0,0 +1,114 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Basic Atomic Types
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
using s8 = int8_t;
|
||||
using s16 = int16_t;
|
||||
using s32 = int32_t;
|
||||
using s64 = int64_t;
|
||||
|
||||
using u8 = uint8_t;
|
||||
using u16 = uint16_t;
|
||||
using u32 = uint32_t;
|
||||
using u64 = uint64_t;
|
||||
|
||||
using uptr = uintptr_t;
|
||||
using sptr = intptr_t;
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// u128 / s128 - A rough-and-ready cross platform 128-bit datatype, Non-SSE style.
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Note: These structs don't provide any additional constructors because C++ doesn't allow
|
||||
// the use of datatypes with constructors in unions (and since unions aren't the primary
|
||||
// uses of these types, that means we can't have constructors). Embedded functions for
|
||||
// performing explicit conversion from 64 and 32 bit values are provided instead.
|
||||
//
|
||||
union u128
|
||||
{
|
||||
struct
|
||||
{
|
||||
u64 lo;
|
||||
u64 hi;
|
||||
};
|
||||
|
||||
u64 _u64[2];
|
||||
u32 _u32[4];
|
||||
u16 _u16[8];
|
||||
u8 _u8[16];
|
||||
|
||||
// Explicit conversion from u64. Zero-extends the source through 128 bits.
|
||||
static u128 From64(u64 src)
|
||||
{
|
||||
u128 retval;
|
||||
retval.lo = src;
|
||||
retval.hi = 0;
|
||||
return retval;
|
||||
}
|
||||
|
||||
// Explicit conversion from u32. Zero-extends the source through 128 bits.
|
||||
static u128 From32(u32 src)
|
||||
{
|
||||
u128 retval;
|
||||
retval._u32[0] = src;
|
||||
retval._u32[1] = 0;
|
||||
retval.hi = 0;
|
||||
return retval;
|
||||
}
|
||||
|
||||
operator u32() const { return _u32[0]; }
|
||||
operator u16() const { return _u16[0]; }
|
||||
operator u8() const { return _u8[0]; }
|
||||
|
||||
bool operator==(const u128& right) const
|
||||
{
|
||||
return (lo == right.lo) && (hi == right.hi);
|
||||
}
|
||||
|
||||
bool operator!=(const u128& right) const
|
||||
{
|
||||
return (lo != right.lo) || (hi != right.hi);
|
||||
}
|
||||
};
|
||||
|
||||
struct s128
|
||||
{
|
||||
s64 lo;
|
||||
s64 hi;
|
||||
|
||||
// explicit conversion from s64, with sign extension.
|
||||
static s128 From64(s64 src)
|
||||
{
|
||||
s128 retval = {src, (src < 0) ? -1 : 0};
|
||||
return retval;
|
||||
}
|
||||
|
||||
// explicit conversion from s32, with sign extension.
|
||||
static s128 From64(s32 src)
|
||||
{
|
||||
s128 retval = {src, (src < 0) ? -1 : 0};
|
||||
return retval;
|
||||
}
|
||||
|
||||
operator u32() const { return (s32)lo; }
|
||||
operator u16() const { return (s16)lo; }
|
||||
operator u8() const { return (s8)lo; }
|
||||
|
||||
bool operator==(const s128& right) const
|
||||
{
|
||||
return (lo == right.lo) && (hi == right.hi);
|
||||
}
|
||||
|
||||
bool operator!=(const s128& right) const
|
||||
{
|
||||
return (lo != right.lo) || (hi != right.hi);
|
||||
}
|
||||
};
|
||||
215
common/Perf.cpp
Normal file
215
common/Perf.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/Perf.h"
|
||||
#include "common/Pcsx2Defs.h"
|
||||
#include "common/Assertions.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#ifdef ENABLE_VTUNE
|
||||
#include "jitprofiling.h"
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <atomic>
|
||||
#include <ctime>
|
||||
#include <mutex>
|
||||
#include <elf.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#endif
|
||||
|
||||
//#define ProfileWithPerf
|
||||
//#define ProfileWithPerfJitDump
|
||||
|
||||
#if defined(ENABLE_VTUNE) && defined(_WIN32)
|
||||
#pragma comment(lib, "jitprofiling.lib")
|
||||
#endif
|
||||
|
||||
namespace Perf
|
||||
{
|
||||
Group any("");
|
||||
Group ee("EE");
|
||||
Group iop("IOP");
|
||||
Group vu0("VU0");
|
||||
Group vu1("VU1");
|
||||
Group vif("VIF");
|
||||
|
||||
// Perf is only supported on linux
|
||||
#if defined(__linux__) && defined(ProfileWithPerf)
|
||||
static std::FILE* s_map_file = nullptr;
|
||||
static bool s_map_file_opened = false;
|
||||
static std::mutex s_mutex;
|
||||
static void RegisterMethod(const void* ptr, size_t size, const char* symbol)
|
||||
{
|
||||
std::unique_lock lock(s_mutex);
|
||||
|
||||
if (!s_map_file)
|
||||
{
|
||||
if (s_map_file_opened)
|
||||
return;
|
||||
|
||||
char file[256];
|
||||
snprintf(file, std::size(file), "/tmp/perf-%d.map", getpid());
|
||||
s_map_file = std::fopen(file, "wb");
|
||||
s_map_file_opened = true;
|
||||
if (!s_map_file)
|
||||
return;
|
||||
}
|
||||
|
||||
std::fprintf(s_map_file, "%" PRIx64 " %zx %s\n", static_cast<u64>(reinterpret_cast<uintptr_t>(ptr)), size, symbol);
|
||||
std::fflush(s_map_file);
|
||||
}
|
||||
#elif defined(__linux__) && defined(ProfileWithPerfJitDump)
|
||||
enum : u32
|
||||
{
|
||||
JIT_CODE_LOAD = 0,
|
||||
JIT_CODE_MOVE = 1,
|
||||
JIT_CODE_DEBUG_INFO = 2,
|
||||
JIT_CODE_CLOSE = 3,
|
||||
JIT_CODE_UNWINDING_INFO = 4
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct JITDUMP_HEADER
|
||||
{
|
||||
u32 magic = 0x4A695444; // JiTD
|
||||
u32 version = 1;
|
||||
u32 header_size = sizeof(JITDUMP_HEADER);
|
||||
u32 elf_mach;
|
||||
u32 pad1 = 0;
|
||||
u32 pid;
|
||||
u64 timestamp;
|
||||
u64 flags = 0;
|
||||
};
|
||||
struct JITDUMP_RECORD_HEADER
|
||||
{
|
||||
u32 id;
|
||||
u32 total_size;
|
||||
u64 timestamp;
|
||||
};
|
||||
struct JITDUMP_CODE_LOAD
|
||||
{
|
||||
JITDUMP_RECORD_HEADER header;
|
||||
u32 pid;
|
||||
u32 tid;
|
||||
u64 vma;
|
||||
u64 code_addr;
|
||||
u64 code_size;
|
||||
u64 code_index;
|
||||
// name
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static u64 JitDumpTimestamp()
|
||||
{
|
||||
struct timespec ts = {};
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (static_cast<u64>(ts.tv_sec) * 1000000000ULL) + static_cast<u64>(ts.tv_nsec);
|
||||
}
|
||||
|
||||
static FILE* s_jitdump_file = nullptr;
|
||||
static bool s_jitdump_file_opened = false;
|
||||
static std::mutex s_jitdump_mutex;
|
||||
static u32 s_jitdump_record_id;
|
||||
|
||||
static void RegisterMethod(const void* ptr, size_t size, const char* symbol)
|
||||
{
|
||||
const u32 namelen = std::strlen(symbol) + 1;
|
||||
|
||||
std::unique_lock lock(s_jitdump_mutex);
|
||||
if (!s_jitdump_file)
|
||||
{
|
||||
if (!s_jitdump_file_opened)
|
||||
{
|
||||
char file[256];
|
||||
snprintf(file, std::size(file), "jit-%d.dump", getpid());
|
||||
s_jitdump_file = fopen(file, "w+b");
|
||||
s_jitdump_file_opened = true;
|
||||
if (!s_jitdump_file)
|
||||
return;
|
||||
}
|
||||
|
||||
void* perf_marker = mmap(nullptr, 4096, PROT_READ | PROT_EXEC, MAP_PRIVATE, fileno(s_jitdump_file), 0);
|
||||
pxAssertRel(perf_marker != MAP_FAILED, "Map perf marker");
|
||||
|
||||
JITDUMP_HEADER jh = {};
|
||||
#if defined(_M_X86)
|
||||
jh.elf_mach = EM_X86_64;
|
||||
#elif defined(_M_ARM64)
|
||||
jh.elf_mach = EM_AARCH64;
|
||||
#else
|
||||
#error Unhandled architecture.
|
||||
#endif
|
||||
jh.pid = getpid();
|
||||
jh.timestamp = JitDumpTimestamp();
|
||||
std::fwrite(&jh, sizeof(jh), 1, s_jitdump_file);
|
||||
}
|
||||
|
||||
JITDUMP_CODE_LOAD cl = {};
|
||||
cl.header.id = JIT_CODE_LOAD;
|
||||
cl.header.total_size = sizeof(cl) + namelen + static_cast<u32>(size);
|
||||
cl.header.timestamp = JitDumpTimestamp();
|
||||
cl.pid = getpid();
|
||||
cl.tid = syscall(SYS_gettid);
|
||||
cl.vma = 0;
|
||||
cl.code_addr = static_cast<u64>(reinterpret_cast<uintptr_t>(ptr));
|
||||
cl.code_size = static_cast<u64>(size);
|
||||
cl.code_index = s_jitdump_record_id++;
|
||||
std::fwrite(&cl, sizeof(cl), 1, s_jitdump_file);
|
||||
std::fwrite(symbol, namelen, 1, s_jitdump_file);
|
||||
std::fwrite(ptr, size, 1, s_jitdump_file);
|
||||
std::fflush(s_jitdump_file);
|
||||
}
|
||||
#elif defined(ENABLE_VTUNE)
|
||||
static void RegisterMethod(const void* ptr, size_t size, const char* symbol)
|
||||
{
|
||||
iJIT_Method_Load_V2 ml = {};
|
||||
ml.method_id = iJIT_GetNewMethodID();
|
||||
ml.method_name = const_cast<char*>(symbol);
|
||||
ml.method_load_address = const_cast<void*>(ptr);
|
||||
ml.method_size = static_cast<unsigned int>(size);
|
||||
iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED_V2, &ml);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (defined(__linux__) && (defined(ProfileWithPerf) || defined(ProfileWithPerfJitDump))) || defined(ENABLE_VTUNE)
|
||||
void Group::Register(const void* ptr, size_t size, const char* symbol)
|
||||
{
|
||||
char full_symbol[128];
|
||||
if (HasPrefix())
|
||||
std::snprintf(full_symbol, std::size(full_symbol), "%s_%s", m_prefix, symbol);
|
||||
else
|
||||
StringUtil::Strlcpy(full_symbol, symbol, std::size(full_symbol));
|
||||
RegisterMethod(ptr, size, full_symbol);
|
||||
}
|
||||
|
||||
void Group::RegisterPC(const void* ptr, size_t size, u32 pc)
|
||||
{
|
||||
char full_symbol[128];
|
||||
if (HasPrefix())
|
||||
std::snprintf(full_symbol, std::size(full_symbol), "%s_%08X", m_prefix, pc);
|
||||
else
|
||||
std::snprintf(full_symbol, std::size(full_symbol), "%08X", pc);
|
||||
RegisterMethod(ptr, size, full_symbol);
|
||||
}
|
||||
|
||||
void Group::RegisterKey(const void* ptr, size_t size, const char* prefix, u64 key)
|
||||
{
|
||||
char full_symbol[128];
|
||||
if (HasPrefix())
|
||||
std::snprintf(full_symbol, std::size(full_symbol), "%s_%s%016" PRIX64, m_prefix, prefix, key);
|
||||
else
|
||||
std::snprintf(full_symbol, std::size(full_symbol), "%s%016" PRIX64, prefix, key);
|
||||
RegisterMethod(ptr, size, full_symbol);
|
||||
}
|
||||
#else
|
||||
void Group::Register(const void* ptr, size_t size, const char* symbol) {}
|
||||
void Group::RegisterPC(const void* ptr, size_t size, u32 pc) {}
|
||||
void Group::RegisterKey(const void* ptr, size_t size, const char* prefix, u64 key) {}
|
||||
#endif
|
||||
} // namespace Perf
|
||||
31
common/Perf.h
Normal file
31
common/Perf.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
#include "common/Pcsx2Types.h"
|
||||
|
||||
namespace Perf
|
||||
{
|
||||
class Group
|
||||
{
|
||||
const char* m_prefix;
|
||||
|
||||
public:
|
||||
constexpr Group(const char* prefix) : m_prefix(prefix) {}
|
||||
bool HasPrefix() const { return (m_prefix && m_prefix[0]); }
|
||||
|
||||
void Register(const void* ptr, size_t size, const char* symbol);
|
||||
void RegisterPC(const void* ptr, size_t size, u32 pc);
|
||||
void RegisterKey(const void* ptr, size_t size, const char* prefix, u64 key);
|
||||
};
|
||||
|
||||
extern Group any;
|
||||
extern Group ee;
|
||||
extern Group iop;
|
||||
extern Group vu0;
|
||||
extern Group vu1;
|
||||
extern Group vif;
|
||||
} // namespace Perf
|
||||
4
common/PrecompiledHeader.cpp
Normal file
4
common/PrecompiledHeader.cpp
Normal file
@@ -0,0 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
8
common/PrecompiledHeader.h
Normal file
8
common/PrecompiledHeader.h
Normal file
@@ -0,0 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
245
common/ProgressCallback.cpp
Normal file
245
common/ProgressCallback.cpp
Normal file
@@ -0,0 +1,245 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "Assertions.h"
|
||||
#include "ProgressCallback.h"
|
||||
#include "StringUtil.h"
|
||||
#include "Console.h"
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
ProgressCallback::~ProgressCallback() {}
|
||||
|
||||
void ProgressCallback::SetFormattedStatusText(const char* Format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, Format);
|
||||
const std::string str(StringUtil::StdStringFromFormatV(Format, ap));
|
||||
va_end(ap);
|
||||
|
||||
SetStatusText(str.c_str());
|
||||
}
|
||||
|
||||
void ProgressCallback::DisplayFormattedError(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
const std::string str(StringUtil::StdStringFromFormatV(format, ap));
|
||||
va_end(ap);
|
||||
|
||||
DisplayError(str.c_str());
|
||||
}
|
||||
|
||||
void ProgressCallback::DisplayFormattedWarning(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
const std::string str(StringUtil::StdStringFromFormatV(format, ap));
|
||||
va_end(ap);
|
||||
|
||||
DisplayWarning(str.c_str());
|
||||
}
|
||||
|
||||
void ProgressCallback::DisplayFormattedInformation(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
const std::string str(StringUtil::StdStringFromFormatV(format, ap));
|
||||
va_end(ap);
|
||||
|
||||
DisplayInformation(str.c_str());
|
||||
}
|
||||
|
||||
void ProgressCallback::DisplayFormattedDebugMessage(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
const std::string str(StringUtil::StdStringFromFormatV(format, ap));
|
||||
va_end(ap);
|
||||
|
||||
DisplayDebugMessage(str.c_str());
|
||||
}
|
||||
|
||||
void ProgressCallback::DisplayFormattedModalError(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
const std::string str(StringUtil::StdStringFromFormatV(format, ap));
|
||||
va_end(ap);
|
||||
|
||||
ModalError(str.c_str());
|
||||
}
|
||||
|
||||
bool ProgressCallback::DisplayFormattedModalConfirmation(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
const std::string str(StringUtil::StdStringFromFormatV(format, ap));
|
||||
va_end(ap);
|
||||
|
||||
return ModalConfirmation(str.c_str());
|
||||
}
|
||||
|
||||
void ProgressCallback::DisplayFormattedModalInformation(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
const std::string str(StringUtil::StdStringFromFormatV(format, ap));
|
||||
va_end(ap);
|
||||
|
||||
ModalInformation(str.c_str());
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class NullProgressCallbacks final : public ProgressCallback
|
||||
{
|
||||
public:
|
||||
void PushState() override {}
|
||||
void PopState() override {}
|
||||
|
||||
bool IsCancelled() const override { return false; }
|
||||
bool IsCancellable() const override { return false; }
|
||||
|
||||
void SetCancellable(bool cancellable) override {}
|
||||
void SetTitle(const char* title) override {}
|
||||
void SetStatusText(const char* statusText) override {}
|
||||
void SetProgressRange(u32 range) override {}
|
||||
void SetProgressValue(u32 value) override {}
|
||||
void IncrementProgressValue() override {}
|
||||
void SetProgressState(ProgressState state) override {}
|
||||
|
||||
void DisplayError(const char* message) override { Console.Error("%s", message); }
|
||||
void DisplayWarning(const char* message) override { Console.Warning("%s", message); }
|
||||
void DisplayInformation(const char* message) override { Console.WriteLn("%s", message); }
|
||||
void DisplayDebugMessage(const char* message) override { DevCon.WriteLn("%s", message); }
|
||||
|
||||
void ModalError(const char* message) override { Console.Error(message); }
|
||||
bool ModalConfirmation(const char* message) override
|
||||
{
|
||||
Console.WriteLn("%s", message);
|
||||
return false;
|
||||
}
|
||||
void ModalInformation(const char* message) override { Console.WriteLn("%s", message); }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static NullProgressCallbacks s_nullProgressCallbacks;
|
||||
ProgressCallback* ProgressCallback::NullProgressCallback = &s_nullProgressCallbacks;
|
||||
|
||||
std::unique_ptr<ProgressCallback> ProgressCallback::CreateNullProgressCallback()
|
||||
{
|
||||
return std::make_unique<NullProgressCallbacks>();
|
||||
}
|
||||
|
||||
BaseProgressCallback::BaseProgressCallback()
|
||||
{
|
||||
}
|
||||
|
||||
BaseProgressCallback::~BaseProgressCallback()
|
||||
{
|
||||
State* pNextState = m_saved_state;
|
||||
while (pNextState != NULL)
|
||||
{
|
||||
State* pCurrentState = pNextState;
|
||||
pNextState = pCurrentState->next_saved_state;
|
||||
delete pCurrentState;
|
||||
}
|
||||
}
|
||||
|
||||
void BaseProgressCallback::PushState()
|
||||
{
|
||||
State* pNewState = new State;
|
||||
pNewState->cancellable = m_cancellable;
|
||||
pNewState->status_text = m_status_text;
|
||||
pNewState->progress_range = m_progress_range;
|
||||
pNewState->progress_value = m_progress_value;
|
||||
pNewState->base_progress_value = m_base_progress_value;
|
||||
pNewState->next_saved_state = m_saved_state;
|
||||
m_saved_state = pNewState;
|
||||
}
|
||||
|
||||
void BaseProgressCallback::PopState()
|
||||
{
|
||||
pxAssert(m_saved_state);
|
||||
State* state = m_saved_state;
|
||||
m_saved_state = nullptr;
|
||||
|
||||
// impose the current position into the previous range
|
||||
const u32 new_progress_value =
|
||||
(m_progress_range != 0) ?
|
||||
static_cast<u32>(((float)m_progress_value / (float)m_progress_range) * (float)state->progress_range) :
|
||||
state->progress_value;
|
||||
|
||||
m_cancellable = state->cancellable;
|
||||
m_status_text = std::move(state->status_text);
|
||||
m_progress_range = state->progress_range;
|
||||
m_progress_value = new_progress_value;
|
||||
|
||||
m_base_progress_value = state->base_progress_value;
|
||||
m_saved_state = state->next_saved_state;
|
||||
delete state;
|
||||
}
|
||||
|
||||
bool BaseProgressCallback::IsCancelled() const
|
||||
{
|
||||
return m_cancelled;
|
||||
}
|
||||
|
||||
bool BaseProgressCallback::IsCancellable() const
|
||||
{
|
||||
return m_cancellable;
|
||||
}
|
||||
|
||||
void BaseProgressCallback::SetCancellable(bool cancellable)
|
||||
{
|
||||
m_cancellable = cancellable;
|
||||
}
|
||||
|
||||
void BaseProgressCallback::SetStatusText(const char* text)
|
||||
{
|
||||
m_status_text = text;
|
||||
}
|
||||
|
||||
void BaseProgressCallback::SetProgressRange(u32 range)
|
||||
{
|
||||
if (m_saved_state)
|
||||
{
|
||||
// impose the previous range on this range
|
||||
m_progress_range = m_saved_state->progress_range * range;
|
||||
m_base_progress_value = m_progress_value = m_saved_state->progress_value * range;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_progress_range = range;
|
||||
m_progress_value = 0;
|
||||
m_base_progress_value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void BaseProgressCallback::SetProgressValue(u32 value)
|
||||
{
|
||||
SetProgressState(ProgressState::Normal);
|
||||
m_progress_value = m_base_progress_value + value;
|
||||
}
|
||||
|
||||
void BaseProgressCallback::IncrementProgressValue()
|
||||
{
|
||||
SetProgressValue((m_progress_value - m_base_progress_value) + 1);
|
||||
}
|
||||
|
||||
void BaseProgressCallback::SetProgressState(ProgressState state)
|
||||
{
|
||||
m_progress_state = state;
|
||||
}
|
||||
108
common/ProgressCallback.h
Normal file
108
common/ProgressCallback.h
Normal file
@@ -0,0 +1,108 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include "Pcsx2Defs.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Progress callbacks, abstracts a blocking operation and allows it to report progress
|
||||
* without having any dependency on the UI.
|
||||
*/
|
||||
|
||||
class ProgressCallback
|
||||
{
|
||||
public:
|
||||
enum class ProgressState
|
||||
{
|
||||
Normal,
|
||||
Indeterminate,
|
||||
Paused,
|
||||
Error
|
||||
};
|
||||
|
||||
virtual ~ProgressCallback();
|
||||
|
||||
virtual void PushState() = 0;
|
||||
virtual void PopState() = 0;
|
||||
|
||||
virtual bool IsCancelled() const = 0;
|
||||
virtual bool IsCancellable() const = 0;
|
||||
|
||||
virtual void SetCancellable(bool cancellable) = 0;
|
||||
|
||||
virtual void SetTitle(const char* title) = 0;
|
||||
virtual void SetStatusText(const char* text) = 0;
|
||||
virtual void SetProgressRange(u32 range) = 0;
|
||||
virtual void SetProgressValue(u32 value) = 0;
|
||||
virtual void IncrementProgressValue() = 0;
|
||||
virtual void SetProgressState(ProgressState state) = 0;
|
||||
|
||||
void SetFormattedStatusText(const char* Format, ...);
|
||||
|
||||
virtual void DisplayError(const char* message) = 0;
|
||||
virtual void DisplayWarning(const char* message) = 0;
|
||||
virtual void DisplayInformation(const char* message) = 0;
|
||||
virtual void DisplayDebugMessage(const char* message) = 0;
|
||||
|
||||
virtual void ModalError(const char* message) = 0;
|
||||
virtual bool ModalConfirmation(const char* message) = 0;
|
||||
virtual void ModalInformation(const char* message) = 0;
|
||||
|
||||
void DisplayFormattedError(const char* format, ...);
|
||||
void DisplayFormattedWarning(const char* format, ...);
|
||||
void DisplayFormattedInformation(const char* format, ...);
|
||||
void DisplayFormattedDebugMessage(const char* format, ...);
|
||||
void DisplayFormattedModalError(const char* format, ...);
|
||||
bool DisplayFormattedModalConfirmation(const char* format, ...);
|
||||
void DisplayFormattedModalInformation(const char* format, ...);
|
||||
|
||||
public:
|
||||
static ProgressCallback* NullProgressCallback;
|
||||
|
||||
static std::unique_ptr<ProgressCallback> CreateNullProgressCallback();
|
||||
};
|
||||
|
||||
class BaseProgressCallback : public ProgressCallback
|
||||
{
|
||||
public:
|
||||
BaseProgressCallback();
|
||||
virtual ~BaseProgressCallback();
|
||||
|
||||
virtual void PushState() override;
|
||||
virtual void PopState() override;
|
||||
|
||||
virtual bool IsCancelled() const override;
|
||||
virtual bool IsCancellable() const override;
|
||||
|
||||
virtual void SetCancellable(bool cancellable) override;
|
||||
virtual void SetStatusText(const char* text) override;
|
||||
virtual void SetProgressRange(u32 range) override;
|
||||
virtual void SetProgressValue(u32 value) override;
|
||||
virtual void IncrementProgressValue() override;
|
||||
virtual void SetProgressState(ProgressState state) override;
|
||||
|
||||
protected:
|
||||
struct State
|
||||
{
|
||||
State* next_saved_state;
|
||||
std::string status_text;
|
||||
u32 progress_range;
|
||||
u32 progress_value;
|
||||
u32 base_progress_value;
|
||||
bool cancellable;
|
||||
};
|
||||
|
||||
bool m_cancellable = false;
|
||||
bool m_cancelled = false;
|
||||
std::string m_status_text;
|
||||
u32 m_progress_range = 1;
|
||||
u32 m_progress_value = 0;
|
||||
ProgressState m_progress_state = ProgressState::Normal;
|
||||
|
||||
u32 m_base_progress_value = 0;
|
||||
|
||||
State* m_saved_state = nullptr;
|
||||
};
|
||||
218
common/ReadbackSpinManager.cpp
Normal file
218
common/ReadbackSpinManager.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "ReadbackSpinManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
static bool EventIsReadback(const ReadbackSpinManager::Event& event)
|
||||
{
|
||||
return event.size < 0;
|
||||
}
|
||||
|
||||
static bool EventIsDraw(const ReadbackSpinManager::Event& event)
|
||||
{
|
||||
return !EventIsReadback(event);
|
||||
}
|
||||
|
||||
static bool IsCompleted(const ReadbackSpinManager::Event& event)
|
||||
{
|
||||
return event.begin != event.end;
|
||||
}
|
||||
|
||||
static int Similarity(const std::vector<ReadbackSpinManager::Event>& a, std::vector<ReadbackSpinManager::Event>& b)
|
||||
{
|
||||
u32 a_num_readbacks = std::count_if(a.begin(), a.end(), EventIsReadback);
|
||||
u32 b_num_readbacks = std::count_if(b.begin(), b.end(), EventIsReadback);
|
||||
|
||||
int score = 0x10 - abs(static_cast<int>(a.size() - b.size()));
|
||||
|
||||
if (a_num_readbacks == b_num_readbacks)
|
||||
score += 0x10000;
|
||||
|
||||
auto a_idx = a.begin();
|
||||
auto b_idx = b.begin();
|
||||
while (a_idx != a.end() && b_idx != b.end())
|
||||
{
|
||||
if (EventIsReadback(*a_idx) && EventIsReadback(*b_idx))
|
||||
{
|
||||
// Same number of events between readbacks
|
||||
score += 0x1000;
|
||||
}
|
||||
// Try to match up on readbacks
|
||||
else if (EventIsReadback(*a_idx))
|
||||
{
|
||||
b_idx++;
|
||||
continue;
|
||||
}
|
||||
else if (EventIsReadback(*b_idx))
|
||||
{
|
||||
a_idx++;
|
||||
continue;
|
||||
}
|
||||
else if (a_idx->size == b_idx->size)
|
||||
{
|
||||
// Same size
|
||||
score += 0x100;
|
||||
}
|
||||
else if (a_idx->size / 2 <= b_idx->size && b_idx->size / 2 <= a_idx->size)
|
||||
{
|
||||
// Similar size
|
||||
score += 0x10;
|
||||
}
|
||||
a_idx++;
|
||||
b_idx++;
|
||||
continue;
|
||||
}
|
||||
// Both hit the end at the same time
|
||||
if (a_idx == a.end() && b_idx == b.end())
|
||||
score += 0x1000;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
static u32 PrevFrameNo(u32 frame, size_t total_frames)
|
||||
{
|
||||
s32 prev_frame = frame - 1;
|
||||
if (prev_frame < 0)
|
||||
prev_frame = total_frames - 1;
|
||||
return prev_frame;
|
||||
}
|
||||
|
||||
static u32 NextFrameNo(u32 frame, size_t total_frames)
|
||||
{
|
||||
u32 next_frame = frame + 1;
|
||||
if (next_frame >= total_frames)
|
||||
next_frame = 0;
|
||||
return next_frame;
|
||||
}
|
||||
|
||||
void ReadbackSpinManager::ReadbackRequested()
|
||||
{
|
||||
Event ev = {};
|
||||
ev.size = -1;
|
||||
m_frames[m_current_frame].push_back(ev);
|
||||
|
||||
// Advance reference frame idx to the next readback
|
||||
while (m_frames[m_reference_frame].size() > m_reference_frame_idx &&
|
||||
!EventIsReadback(m_frames[m_reference_frame][m_reference_frame_idx]))
|
||||
{
|
||||
m_reference_frame_idx++;
|
||||
}
|
||||
// ...and past it
|
||||
if (m_frames[m_reference_frame].size() > m_reference_frame_idx)
|
||||
m_reference_frame_idx++;
|
||||
}
|
||||
|
||||
void ReadbackSpinManager::NextFrame()
|
||||
{
|
||||
u32 prev_frame_0 = PrevFrameNo(m_current_frame, std::size(m_frames));
|
||||
u32 prev_frame_1 = PrevFrameNo(prev_frame_0, std::size(m_frames));
|
||||
int similarity_0 = Similarity(m_frames[m_current_frame], m_frames[prev_frame_0]);
|
||||
int similarity_1 = Similarity(m_frames[m_current_frame], m_frames[prev_frame_1]);
|
||||
|
||||
if (similarity_1 > similarity_0)
|
||||
m_reference_frame = prev_frame_0;
|
||||
else
|
||||
m_reference_frame = m_current_frame;
|
||||
m_reference_frame_idx = 0;
|
||||
|
||||
m_current_frame = NextFrameNo(m_current_frame, std::size(m_frames));
|
||||
m_frames[m_current_frame].clear();
|
||||
}
|
||||
|
||||
ReadbackSpinManager::DrawSubmittedReturn ReadbackSpinManager::DrawSubmitted(u64 size)
|
||||
{
|
||||
DrawSubmittedReturn out = {};
|
||||
u32 idx = m_frames[m_current_frame].size();
|
||||
out.id = idx | m_current_frame << 28;
|
||||
Event ev = {};
|
||||
ev.size = size;
|
||||
m_frames[m_current_frame].push_back(ev);
|
||||
|
||||
if (m_reference_frame != m_current_frame &&
|
||||
m_frames[m_reference_frame].size() > m_reference_frame_idx &&
|
||||
EventIsDraw(m_frames[m_reference_frame][m_reference_frame_idx]))
|
||||
{
|
||||
auto find_next_draw = [this](u32 frame) -> Event* {
|
||||
auto next = std::find_if(m_frames[frame].begin() + m_reference_frame_idx + 1,
|
||||
m_frames[frame].end(),
|
||||
EventIsDraw);
|
||||
bool found = next != m_frames[frame].end();
|
||||
if (!found)
|
||||
{
|
||||
u32 next_frame = NextFrameNo(frame, std::size(m_frames));
|
||||
next = std::find_if(m_frames[next_frame].begin(), m_frames[next_frame].end(), EventIsDraw);
|
||||
found = next != m_frames[next_frame].end();
|
||||
}
|
||||
return found ? &*next : nullptr;
|
||||
};
|
||||
Event* cur_draw = &m_frames[m_reference_frame][m_reference_frame_idx];
|
||||
Event* next_draw = find_next_draw(m_reference_frame);
|
||||
const bool is_one_frame_back = m_reference_frame == PrevFrameNo(m_current_frame, std::size(m_frames));
|
||||
if ((!next_draw || !IsCompleted(*cur_draw) || !IsCompleted(*next_draw)) && is_one_frame_back)
|
||||
{
|
||||
// Last frame's timing data hasn't arrived, try the same spot in the frame before
|
||||
u32 two_back = PrevFrameNo(m_reference_frame, std::size(m_frames));
|
||||
if (m_frames[two_back].size() > m_reference_frame_idx &&
|
||||
EventIsDraw(m_frames[two_back][m_reference_frame_idx]))
|
||||
{
|
||||
cur_draw = &m_frames[two_back][m_reference_frame_idx];
|
||||
next_draw = find_next_draw(two_back);
|
||||
}
|
||||
}
|
||||
if (next_draw && IsCompleted(*cur_draw) && IsCompleted(*next_draw) && m_spins_per_unit_time != 0)
|
||||
{
|
||||
u64 cur_size = cur_draw->size;
|
||||
bool is_similar = cur_size / 2 <= size && size / 2 <= cur_size;
|
||||
if (is_similar) // Only recommend spins if we're somewhat confident in what's going on
|
||||
{
|
||||
s32 current_draw_time = cur_draw->end - cur_draw->begin;
|
||||
s32 gap = next_draw->begin - cur_draw->end;
|
||||
// Give an extra bit of space for the draw to take a bit longer (we'll go with 1/8 longer)
|
||||
s32 fill = gap - (current_draw_time >> 3);
|
||||
if (fill > 0)
|
||||
out.recommended_spin = static_cast<u32>(static_cast<double>(fill) * m_spins_per_unit_time);
|
||||
}
|
||||
}
|
||||
|
||||
m_reference_frame_idx++;
|
||||
}
|
||||
|
||||
if (m_spins_per_unit_time == 0)
|
||||
{
|
||||
// Recommend some spinning so that we can get timing data
|
||||
out.recommended_spin = 128;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void ReadbackSpinManager::DrawCompleted(u32 id, u32 begin_time, u32 end_time)
|
||||
{
|
||||
u32 frame_id = id >> 28;
|
||||
u32 frame_off = id & ((1 << 28) - 1);
|
||||
if (frame_id < std::size(m_frames) && frame_off < m_frames[frame_id].size())
|
||||
{
|
||||
Event& ev = m_frames[frame_id][frame_off];
|
||||
ev.begin = begin_time;
|
||||
ev.end = end_time;
|
||||
}
|
||||
}
|
||||
|
||||
void ReadbackSpinManager::SpinCompleted(u32 cycles, u32 begin_time, u32 end_time)
|
||||
{
|
||||
double elapsed = static_cast<double>(end_time - begin_time);
|
||||
constexpr double decay = 15.0 / 16.0;
|
||||
|
||||
// Obviously it'll vary from GPU to GPU, but in my testing,
|
||||
// both a Radeon Pro 5600M and Intel UHD 630 spin at about 100ns/cycle
|
||||
|
||||
// Note: We assume spin time is some constant times the number of cycles
|
||||
// Obviously as the number of cycles gets really low, a constant offset may start being noticeable
|
||||
// But this is not the case as low as 512 cycles (~50µs) on the GPUs listed above
|
||||
|
||||
m_total_spin_cycles = m_total_spin_cycles * decay + cycles;
|
||||
m_total_spin_time = m_total_spin_time * decay + elapsed;
|
||||
m_spins_per_unit_time = m_total_spin_cycles / m_total_spin_time;
|
||||
}
|
||||
53
common/ReadbackSpinManager.h
Normal file
53
common/ReadbackSpinManager.h
Normal file
@@ -0,0 +1,53 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Pcsx2Defs.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
/// A class for calculating optimal spin values to trick OSes into not powering down GPUs while waiting for readbacks
|
||||
class ReadbackSpinManager
|
||||
{
|
||||
public:
|
||||
struct Event
|
||||
{
|
||||
s64 size;
|
||||
u32 begin;
|
||||
u32 end;
|
||||
};
|
||||
|
||||
private:
|
||||
double m_spins_per_unit_time = 0;
|
||||
double m_total_spin_time = 0;
|
||||
double m_total_spin_cycles = 0;
|
||||
std::vector<Event> m_frames[3];
|
||||
u32 m_current_frame = 0;
|
||||
u32 m_reference_frame = 0;
|
||||
u32 m_reference_frame_idx = 0;
|
||||
|
||||
public:
|
||||
struct DrawSubmittedReturn
|
||||
{
|
||||
u32 id;
|
||||
u32 recommended_spin;
|
||||
};
|
||||
|
||||
/// Call when a readback is requested
|
||||
void ReadbackRequested();
|
||||
/// Call at the end of a frame
|
||||
void NextFrame();
|
||||
/// Call when a command buffer is submitted to the GPU
|
||||
/// `size` is used to attempt to find patterns in submissions, and can be any metric that approximates the amount of work in a submission (draw calls, command encoders, etc)
|
||||
/// Returns an id to be passed to `DrawCompleted`, and the recommended number of spin cycles to perform on the GPU in order to keep it busy
|
||||
DrawSubmittedReturn DrawSubmitted(u64 size);
|
||||
/// Call once a draw has been finished by the GPU and you have begin/end data for it
|
||||
/// `begin_time` and `end_time` can be in any unit as long as it's consistent. It's okay if they roll over, as long as it happens less than once every few frames.
|
||||
void DrawCompleted(u32 id, u32 begin_time, u32 end_time);
|
||||
/// Call when a spin completes to help the manager figure out how quickly your GPU spins
|
||||
void SpinCompleted(u32 cycles, u32 begin_time, u32 end_time);
|
||||
/// Get the calculated number of spins per unit of time
|
||||
/// Note: May be zero when there's insufficient data
|
||||
double SpinsPerUnitTime() const { return m_spins_per_unit_time; }
|
||||
};
|
||||
22
common/RedtapeWilCom.h
Normal file
22
common/RedtapeWilCom.h
Normal file
@@ -0,0 +1,22 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "common/RedtapeWindows.h"
|
||||
|
||||
// warning : variable 's_hrErrorLast' set but not used [-Wunused-but-set-variable]
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-but-set-variable"
|
||||
#endif
|
||||
|
||||
#include <wil/com.h>
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif
|
||||
23
common/RedtapeWindows.h
Normal file
23
common/RedtapeWindows.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
// We require Windows 10+.
|
||||
#ifdef _WIN32_WINNT
|
||||
#undef _WIN32_WINNT
|
||||
#endif
|
||||
#define _WIN32_WINNT 0x0A00 // Windows 10
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#endif
|
||||
52
common/ScopedGuard.h
Normal file
52
common/ScopedGuard.h
Normal file
@@ -0,0 +1,52 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include "Pcsx2Defs.h"
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
/// ScopedGuard provides an object which runs a function (usually a lambda) when
|
||||
/// it goes out of scope. This can be useful for releasing resources or handles
|
||||
/// which do not normally have C++ types to automatically release.
|
||||
template <typename T>
|
||||
class ScopedGuard final
|
||||
{
|
||||
public:
|
||||
__fi ScopedGuard(T&& func)
|
||||
: m_func(std::forward<T>(func))
|
||||
{
|
||||
}
|
||||
__fi ScopedGuard(ScopedGuard&& other)
|
||||
: m_func(std::move(other.m_func))
|
||||
{
|
||||
other.m_func = nullptr;
|
||||
}
|
||||
|
||||
__fi ~ScopedGuard()
|
||||
{
|
||||
Run();
|
||||
}
|
||||
|
||||
ScopedGuard(const ScopedGuard&) = delete;
|
||||
void operator=(const ScopedGuard&) = delete;
|
||||
|
||||
/// Runs the destructor function now instead of when we go out of scope.
|
||||
__fi void Run()
|
||||
{
|
||||
if (!m_func.has_value())
|
||||
return;
|
||||
|
||||
m_func.value()();
|
||||
m_func.reset();
|
||||
}
|
||||
|
||||
/// Prevents the function from being invoked when we go out of scope.
|
||||
__fi void Cancel()
|
||||
{
|
||||
m_func.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<T> m_func;
|
||||
};
|
||||
187
common/Semaphore.cpp
Normal file
187
common/Semaphore.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/Threading.h"
|
||||
#include "common/Assertions.h"
|
||||
#include "common/HostSys.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "common/RedtapeWindows.h"
|
||||
#endif
|
||||
|
||||
#include <limits>
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Semaphore Implementations
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
bool Threading::WorkSema::CheckForWork()
|
||||
{
|
||||
s32 value = m_state.load(std::memory_order_relaxed);
|
||||
pxAssert(!IsDead(value));
|
||||
|
||||
// we want to switch to the running state, but preserve the waiting empty bit for RUNNING_N -> RUNNING_0
|
||||
// otherwise, we clear the waiting flag (since we're notifying the waiter that we're empty below)
|
||||
while (!m_state.compare_exchange_weak(value,
|
||||
IsReadyForSleep(value) ? STATE_RUNNING_0 : (value & STATE_FLAG_WAITING_EMPTY),
|
||||
std::memory_order_acq_rel, std::memory_order_relaxed))
|
||||
{
|
||||
}
|
||||
|
||||
// if we're not empty, we have work to do
|
||||
if (!IsReadyForSleep(value))
|
||||
return true;
|
||||
|
||||
// this means we're empty, so notify any waiters
|
||||
if (value & STATE_FLAG_WAITING_EMPTY)
|
||||
m_empty_sema.Post();
|
||||
|
||||
// no work to do
|
||||
return false;
|
||||
}
|
||||
|
||||
void Threading::WorkSema::WaitForWork()
|
||||
{
|
||||
// State change:
|
||||
// SLEEPING, SPINNING: This is the worker thread and it's clearly not asleep or spinning, so these states should be impossible
|
||||
// RUNNING_0: Change state to SLEEPING, wake up thread if WAITING_EMPTY
|
||||
// RUNNING_N: Change state to RUNNING_0 (and preserve WAITING_EMPTY flag)
|
||||
s32 value = m_state.load(std::memory_order_relaxed);
|
||||
pxAssert(!IsDead(value));
|
||||
while (!m_state.compare_exchange_weak(value, NextStateWaitForWork(value), std::memory_order_acq_rel, std::memory_order_relaxed))
|
||||
;
|
||||
if (IsReadyForSleep(value))
|
||||
{
|
||||
if (value & STATE_FLAG_WAITING_EMPTY)
|
||||
m_empty_sema.Post();
|
||||
m_sema.Wait();
|
||||
// Acknowledge any additional work added between wake up request and getting here
|
||||
m_state.fetch_and(STATE_FLAG_WAITING_EMPTY, std::memory_order_acquire);
|
||||
}
|
||||
}
|
||||
|
||||
void Threading::WorkSema::WaitForWorkWithSpin()
|
||||
{
|
||||
s32 value = m_state.load(std::memory_order_relaxed);
|
||||
pxAssert(!IsDead(value));
|
||||
while (IsReadyForSleep(value))
|
||||
{
|
||||
if (m_state.compare_exchange_weak(value, STATE_SPINNING, std::memory_order_release, std::memory_order_relaxed))
|
||||
{
|
||||
if (value & STATE_FLAG_WAITING_EMPTY)
|
||||
m_empty_sema.Post();
|
||||
value = STATE_SPINNING;
|
||||
break;
|
||||
}
|
||||
}
|
||||
u32 waited = 0;
|
||||
while (value < 0)
|
||||
{
|
||||
if (waited > SPIN_TIME_NS)
|
||||
{
|
||||
if (!m_state.compare_exchange_weak(value, STATE_SLEEPING, std::memory_order_relaxed))
|
||||
continue;
|
||||
m_sema.Wait();
|
||||
break;
|
||||
}
|
||||
waited += ShortSpin();
|
||||
value = m_state.load(std::memory_order_relaxed);
|
||||
}
|
||||
// Clear back to STATE_RUNNING_0 (but preserve waiting empty flag)
|
||||
m_state.fetch_and(STATE_FLAG_WAITING_EMPTY, std::memory_order_acquire);
|
||||
}
|
||||
|
||||
bool Threading::WorkSema::WaitForEmpty()
|
||||
{
|
||||
s32 value = m_state.load(std::memory_order_acquire);
|
||||
while (true)
|
||||
{
|
||||
if (value < 0)
|
||||
return !IsDead(value); // STATE_SLEEPING or STATE_SPINNING, queue is empty!
|
||||
// Note: We technically only need memory_order_acquire on *failure* (because that's when we could leave without sleeping), but libstdc++ still asserts on failure < success
|
||||
if (m_state.compare_exchange_weak(value, value | STATE_FLAG_WAITING_EMPTY, std::memory_order_acquire))
|
||||
break;
|
||||
}
|
||||
pxAssertMsg(!(value & STATE_FLAG_WAITING_EMPTY), "Multiple threads attempted to wait for empty (not currently supported)");
|
||||
m_empty_sema.Wait();
|
||||
return !IsDead(m_state.load(std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
bool Threading::WorkSema::WaitForEmptyWithSpin()
|
||||
{
|
||||
s32 value = m_state.load(std::memory_order_acquire);
|
||||
u32 waited = 0;
|
||||
while (true)
|
||||
{
|
||||
if (value < 0)
|
||||
return !IsDead(value); // STATE_SLEEPING or STATE_SPINNING, queue is empty!
|
||||
if (waited > SPIN_TIME_NS && m_state.compare_exchange_weak(value, value | STATE_FLAG_WAITING_EMPTY, std::memory_order_acquire))
|
||||
break;
|
||||
waited += ShortSpin();
|
||||
value = m_state.load(std::memory_order_acquire);
|
||||
}
|
||||
pxAssertMsg(!(value & STATE_FLAG_WAITING_EMPTY), "Multiple threads attempted to wait for empty (not currently supported)");
|
||||
m_empty_sema.Wait();
|
||||
return !IsDead(m_state.load(std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
void Threading::WorkSema::Kill()
|
||||
{
|
||||
s32 value = m_state.exchange(std::numeric_limits<s32>::min(), std::memory_order_release);
|
||||
if (value & STATE_FLAG_WAITING_EMPTY)
|
||||
m_empty_sema.Post();
|
||||
}
|
||||
|
||||
void Threading::WorkSema::Reset()
|
||||
{
|
||||
m_state = STATE_RUNNING_0;
|
||||
}
|
||||
|
||||
#if !defined(__APPLE__) // macOS implementations are in DarwinThreads
|
||||
|
||||
Threading::KernelSemaphore::KernelSemaphore()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
m_sema = CreateSemaphore(nullptr, 0, LONG_MAX, nullptr);
|
||||
#else
|
||||
sem_init(&m_sema, false, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
Threading::KernelSemaphore::~KernelSemaphore()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CloseHandle(m_sema);
|
||||
#else
|
||||
sem_destroy(&m_sema);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Threading::KernelSemaphore::Post()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
ReleaseSemaphore(m_sema, 1, nullptr);
|
||||
#else
|
||||
sem_post(&m_sema);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Threading::KernelSemaphore::Wait()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WaitForSingleObject(m_sema, INFINITE);
|
||||
#else
|
||||
sem_wait(&m_sema);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Threading::KernelSemaphore::TryWait()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return WaitForSingleObject(m_sema, 0) == WAIT_OBJECT_0;
|
||||
#else
|
||||
return sem_trywait(&m_sema) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
262
common/SettingsInterface.h
Normal file
262
common/SettingsInterface.h
Normal file
@@ -0,0 +1,262 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Pcsx2Defs.h"
|
||||
#include "SmallString.h"
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
class Error;
|
||||
|
||||
class SettingsInterface
|
||||
{
|
||||
public:
|
||||
virtual ~SettingsInterface() = default;
|
||||
|
||||
virtual bool Save(Error* error = nullptr) = 0;
|
||||
virtual void Clear() = 0;
|
||||
virtual bool IsEmpty() = 0;
|
||||
|
||||
virtual bool GetIntValue(const char* section, const char* key, int* value) const = 0;
|
||||
virtual bool GetUIntValue(const char* section, const char* key, uint* value) const = 0;
|
||||
virtual bool GetFloatValue(const char* section, const char* key, float* value) const = 0;
|
||||
virtual bool GetDoubleValue(const char* section, const char* key, double* value) const = 0;
|
||||
virtual bool GetBoolValue(const char* section, const char* key, bool* value) const = 0;
|
||||
virtual bool GetStringValue(const char* section, const char* key, std::string* value) const = 0;
|
||||
virtual bool GetStringValue(const char* section, const char* key, SmallStringBase* value) const = 0;
|
||||
|
||||
virtual void SetIntValue(const char* section, const char* key, int value) = 0;
|
||||
virtual void SetUIntValue(const char* section, const char* key, uint value) = 0;
|
||||
virtual void SetFloatValue(const char* section, const char* key, float value) = 0;
|
||||
virtual void SetDoubleValue(const char* section, const char* key, double value) = 0;
|
||||
virtual void SetBoolValue(const char* section, const char* key, bool value) = 0;
|
||||
virtual void SetStringValue(const char* section, const char* key, const char* value) = 0;
|
||||
|
||||
virtual std::vector<std::string> GetStringList(const char* section, const char* key) const = 0;
|
||||
virtual void SetStringList(const char* section, const char* key, const std::vector<std::string>& items) = 0;
|
||||
virtual bool RemoveFromStringList(const char* section, const char* key, const char* item) = 0;
|
||||
virtual bool AddToStringList(const char* section, const char* key, const char* item) = 0;
|
||||
|
||||
virtual std::vector<std::pair<std::string, std::string>> GetKeyValueList(const char* section) const = 0;
|
||||
virtual void SetKeyValueList(const char* section, const std::vector<std::pair<std::string, std::string>>& items) = 0;
|
||||
|
||||
virtual bool ContainsValue(const char* section, const char* key) const = 0;
|
||||
virtual void DeleteValue(const char* section, const char* key) = 0;
|
||||
virtual void ClearSection(const char* section) = 0;
|
||||
virtual void RemoveSection(const char* section) = 0;
|
||||
virtual void RemoveEmptySections() = 0;
|
||||
|
||||
__fi int GetIntValue(const char* section, const char* key, int default_value = 0) const
|
||||
{
|
||||
int value;
|
||||
return GetIntValue(section, key, &value) ? value : default_value;
|
||||
}
|
||||
|
||||
__fi uint GetUIntValue(const char* section, const char* key, uint default_value = 0) const
|
||||
{
|
||||
uint value;
|
||||
return GetUIntValue(section, key, &value) ? value : default_value;
|
||||
}
|
||||
|
||||
__fi float GetFloatValue(const char* section, const char* key, float default_value = 0.0f) const
|
||||
{
|
||||
float value;
|
||||
return GetFloatValue(section, key, &value) ? value : default_value;
|
||||
}
|
||||
|
||||
__fi float GetDoubleValue(const char* section, const char* key, double default_value = 0.0) const
|
||||
{
|
||||
double value;
|
||||
return GetDoubleValue(section, key, &value) ? value : default_value;
|
||||
}
|
||||
|
||||
__fi bool GetBoolValue(const char* section, const char* key, bool default_value = false) const
|
||||
{
|
||||
bool value;
|
||||
return GetBoolValue(section, key, &value) ? value : default_value;
|
||||
}
|
||||
|
||||
__fi std::string GetStringValue(const char* section, const char* key, const char* default_value = "") const
|
||||
{
|
||||
std::string value;
|
||||
if (!GetStringValue(section, key, &value))
|
||||
value.assign(default_value);
|
||||
return value;
|
||||
}
|
||||
|
||||
__fi SmallString GetSmallStringValue(const char* section, const char* key, const char* default_value = "") const
|
||||
{
|
||||
SmallString value;
|
||||
if (!GetStringValue(section, key, &value))
|
||||
value.assign(default_value);
|
||||
return value;
|
||||
}
|
||||
|
||||
__fi TinyString GetTinyStringValue(const char* section, const char* key, const char* default_value = "") const
|
||||
{
|
||||
TinyString value;
|
||||
if (!GetStringValue(section, key, &value))
|
||||
value.assign(default_value);
|
||||
return value;
|
||||
}
|
||||
|
||||
__fi std::optional<int> GetOptionalIntValue(const char* section, const char* key, std::optional<int> default_value = std::nullopt) const
|
||||
{
|
||||
int ret;
|
||||
return GetIntValue(section, key, &ret) ? std::optional<int>(ret) : default_value;
|
||||
}
|
||||
|
||||
__fi std::optional<uint> GetOptionalUIntValue(const char* section, const char* key, std::optional<uint> default_value = std::nullopt) const
|
||||
{
|
||||
uint ret;
|
||||
return GetUIntValue(section, key, &ret) ? std::optional<uint>(ret) : default_value;
|
||||
}
|
||||
|
||||
__fi std::optional<float> GetOptionalFloatValue(const char* section, const char* key, std::optional<float> default_value = std::nullopt) const
|
||||
{
|
||||
float ret;
|
||||
return GetFloatValue(section, key, &ret) ? std::optional<float>(ret) : default_value;
|
||||
}
|
||||
|
||||
__fi std::optional<double> GetOptionalDoubleValue(const char* section, const char* key, std::optional<double> default_value = std::nullopt) const
|
||||
{
|
||||
double ret;
|
||||
return GetDoubleValue(section, key, &ret) ? std::optional<double>(ret) : default_value;
|
||||
}
|
||||
|
||||
__fi std::optional<bool> GetOptionalBoolValue(const char* section, const char* key, std::optional<bool> default_value = std::nullopt) const
|
||||
{
|
||||
bool ret;
|
||||
return GetBoolValue(section, key, &ret) ? std::optional<bool>(ret) : default_value;
|
||||
}
|
||||
|
||||
__fi std::optional<std::string> GetOptionalStringValue(const char* section, const char* key, std::optional<const char*> default_value = std::nullopt) const
|
||||
{
|
||||
std::string ret;
|
||||
return GetStringValue(section, key, &ret) ? std::optional<std::string>(ret) :
|
||||
(default_value.has_value() ? std::optional<std::string>(default_value.value()) :
|
||||
std::optional<std::string>());
|
||||
}
|
||||
|
||||
__fi std::optional<SmallString> GetOptionalSmallStringValue(const char* section, const char* key,
|
||||
std::optional<const char*> default_value = std::nullopt) const
|
||||
{
|
||||
SmallString ret;
|
||||
return GetStringValue(section, key, &ret) ?
|
||||
std::optional<SmallString>(ret) :
|
||||
(default_value.has_value() ? std::optional<SmallString>(default_value.value()) :
|
||||
std::optional<SmallString>());
|
||||
}
|
||||
|
||||
__fi std::optional<TinyString> GetOptionalTinyStringValue(const char* section, const char* key,
|
||||
std::optional<const char*> default_value = std::nullopt) const
|
||||
{
|
||||
TinyString ret;
|
||||
return GetStringValue(section, key, &ret) ?
|
||||
std::optional<TinyString>(ret) :
|
||||
(default_value.has_value() ? std::optional<TinyString>(default_value.value()) :
|
||||
std::optional<TinyString>());
|
||||
}
|
||||
|
||||
__fi void SetOptionalIntValue(const char* section, const char* key, const std::optional<int>& value)
|
||||
{
|
||||
value.has_value() ? SetIntValue(section, key, value.value()) : DeleteValue(section, key);
|
||||
}
|
||||
|
||||
__fi void SetOptionalUIntValue(const char* section, const char* key, const std::optional<uint>& value)
|
||||
{
|
||||
value.has_value() ? SetUIntValue(section, key, value.value()) : DeleteValue(section, key);
|
||||
}
|
||||
|
||||
__fi void SetOptionalFloatValue(const char* section, const char* key, const std::optional<float>& value)
|
||||
{
|
||||
value.has_value() ? SetFloatValue(section, key, value.value()) : DeleteValue(section, key);
|
||||
}
|
||||
|
||||
__fi void SetOptionalDoubleValue(const char* section, const char* key, const std::optional<double>& value)
|
||||
{
|
||||
value.has_value() ? SetDoubleValue(section, key, value.value()) : DeleteValue(section, key);
|
||||
}
|
||||
|
||||
__fi void SetOptionalBoolValue(const char* section, const char* key, const std::optional<bool>& value)
|
||||
{
|
||||
value.has_value() ? SetBoolValue(section, key, value.value()) : DeleteValue(section, key);
|
||||
}
|
||||
|
||||
__fi void SetOptionalStringValue(const char* section, const char* key, const std::optional<const char*>& value)
|
||||
{
|
||||
value.has_value() ? SetStringValue(section, key, value.value()) : DeleteValue(section, key);
|
||||
}
|
||||
|
||||
__fi void CopyBoolValue(const SettingsInterface& si, const char* section, const char* key)
|
||||
{
|
||||
bool value;
|
||||
if (si.GetBoolValue(section, key, &value))
|
||||
SetBoolValue(section, key, value);
|
||||
else
|
||||
DeleteValue(section, key);
|
||||
}
|
||||
|
||||
__fi void CopyIntValue(const SettingsInterface& si, const char* section, const char* key)
|
||||
{
|
||||
int value;
|
||||
if (si.GetIntValue(section, key, &value))
|
||||
SetIntValue(section, key, value);
|
||||
else
|
||||
DeleteValue(section, key);
|
||||
}
|
||||
|
||||
__fi void CopyUIntValue(const SettingsInterface& si, const char* section, const char* key)
|
||||
{
|
||||
uint value;
|
||||
if (si.GetUIntValue(section, key, &value))
|
||||
SetUIntValue(section, key, value);
|
||||
else
|
||||
DeleteValue(section, key);
|
||||
}
|
||||
|
||||
__fi void CopyFloatValue(const SettingsInterface& si, const char* section, const char* key)
|
||||
{
|
||||
float value;
|
||||
if (si.GetFloatValue(section, key, &value))
|
||||
SetFloatValue(section, key, value);
|
||||
else
|
||||
DeleteValue(section, key);
|
||||
}
|
||||
|
||||
__fi void CopyDoubleValue(const SettingsInterface& si, const char* section, const char* key)
|
||||
{
|
||||
double value;
|
||||
if (si.GetDoubleValue(section, key, &value))
|
||||
SetDoubleValue(section, key, value);
|
||||
else
|
||||
DeleteValue(section, key);
|
||||
}
|
||||
|
||||
__fi void CopyStringValue(const SettingsInterface& si, const char* section, const char* key)
|
||||
{
|
||||
std::string value;
|
||||
if (si.GetStringValue(section, key, &value))
|
||||
SetStringValue(section, key, value.c_str());
|
||||
else
|
||||
DeleteValue(section, key);
|
||||
}
|
||||
|
||||
__fi void CopyStringListValue(const SettingsInterface& si, const char* section, const char* key)
|
||||
{
|
||||
std::vector<std::string> value(si.GetStringList(section, key));
|
||||
if (!value.empty())
|
||||
SetStringList(section, key, value);
|
||||
else
|
||||
DeleteValue(section, key);
|
||||
}
|
||||
|
||||
__fi void CopyKeysAndValues(const SettingsInterface& si, const char* section)
|
||||
{
|
||||
SetKeyValueList(section, si.GetKeyValueList(section));
|
||||
}
|
||||
};
|
||||
230
common/SettingsWrapper.cpp
Normal file
230
common/SettingsWrapper.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "SettingsWrapper.h"
|
||||
#include "Console.h"
|
||||
|
||||
static int _calcEnumLength(const char* const* enumArray)
|
||||
{
|
||||
int cnt = 0;
|
||||
while (*enumArray != nullptr)
|
||||
{
|
||||
enumArray++;
|
||||
cnt++;
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
SettingsWrapper::SettingsWrapper(SettingsInterface& si)
|
||||
: m_si(si)
|
||||
{
|
||||
}
|
||||
|
||||
SettingsLoadWrapper::SettingsLoadWrapper(SettingsInterface& si)
|
||||
: SettingsWrapper(si)
|
||||
{
|
||||
}
|
||||
|
||||
bool SettingsLoadWrapper::IsLoading() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SettingsLoadWrapper::IsSaving() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void SettingsLoadWrapper::Entry(const char* section, const char* var, int& value, const int defvalue /*= 0*/)
|
||||
{
|
||||
value = m_si.GetIntValue(section, var, defvalue);
|
||||
}
|
||||
|
||||
void SettingsLoadWrapper::Entry(const char* section, const char* var, uint& value, const uint defvalue /*= 0*/)
|
||||
{
|
||||
value = m_si.GetUIntValue(section, var, defvalue);
|
||||
}
|
||||
|
||||
void SettingsLoadWrapper::Entry(const char* section, const char* var, bool& value, const bool defvalue /*= false*/)
|
||||
{
|
||||
value = m_si.GetBoolValue(section, var, defvalue);
|
||||
}
|
||||
|
||||
void SettingsLoadWrapper::Entry(const char* section, const char* var, float& value, const float defvalue /*= 0.0*/)
|
||||
{
|
||||
value = m_si.GetFloatValue(section, var, defvalue);
|
||||
}
|
||||
|
||||
void SettingsLoadWrapper::Entry(const char* section, const char* var, std::string& value, const std::string& default_value /*= std::string()*/)
|
||||
{
|
||||
if (!m_si.GetStringValue(section, var, &value) && &value != &default_value)
|
||||
value = default_value;
|
||||
}
|
||||
|
||||
void SettingsLoadWrapper::Entry(const char* section, const char* var, SmallStringBase& value, std::string_view default_value /* = std::string_view() */)
|
||||
{
|
||||
if (!m_si.GetStringValue(section, var, &value) && value.data() != default_value.data())
|
||||
value = default_value;
|
||||
}
|
||||
|
||||
void SettingsLoadWrapper::_EnumEntry(const char* section, const char* var, int& value, const char* const* enumArray, int defvalue)
|
||||
{
|
||||
const int cnt = _calcEnumLength(enumArray);
|
||||
defvalue = std::clamp(defvalue, 0, cnt);
|
||||
|
||||
const std::string retval(m_si.GetStringValue(section, var, enumArray[defvalue]));
|
||||
|
||||
int i = 0;
|
||||
while (enumArray[i] != nullptr && (retval != enumArray[i]))
|
||||
i++;
|
||||
|
||||
if (enumArray[i] == nullptr)
|
||||
{
|
||||
Console.Warning("(LoadSettings) Warning: Unrecognized value '%s' on key '%s'\n\tUsing the default setting of '%s'.",
|
||||
retval.c_str(), var, enumArray[defvalue]);
|
||||
value = defvalue;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = i;
|
||||
}
|
||||
}
|
||||
|
||||
bool SettingsLoadWrapper::EntryBitBool(const char* section, const char* var, bool value, const bool defvalue /*= false*/)
|
||||
{
|
||||
return m_si.GetBoolValue(section, var, defvalue);
|
||||
}
|
||||
|
||||
int SettingsLoadWrapper::EntryBitfield(const char* section, const char* var, int value, const int defvalue /*= 0*/)
|
||||
{
|
||||
return m_si.GetIntValue(section, var, defvalue);
|
||||
}
|
||||
|
||||
SettingsSaveWrapper::SettingsSaveWrapper(SettingsInterface& si)
|
||||
: SettingsWrapper(si)
|
||||
{
|
||||
}
|
||||
|
||||
bool SettingsSaveWrapper::IsLoading() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SettingsSaveWrapper::IsSaving() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void SettingsSaveWrapper::Entry(const char* section, const char* var, int& value, const int defvalue /*= 0*/)
|
||||
{
|
||||
m_si.SetIntValue(section, var, value);
|
||||
}
|
||||
|
||||
void SettingsSaveWrapper::Entry(const char* section, const char* var, uint& value, const uint defvalue /*= 0*/)
|
||||
{
|
||||
m_si.SetUIntValue(section, var, value);
|
||||
}
|
||||
|
||||
void SettingsSaveWrapper::Entry(const char* section, const char* var, bool& value, const bool defvalue /*= false*/)
|
||||
{
|
||||
m_si.SetBoolValue(section, var, value);
|
||||
}
|
||||
|
||||
void SettingsSaveWrapper::Entry(const char* section, const char* var, float& value, const float defvalue /*= 0.0*/)
|
||||
{
|
||||
m_si.SetFloatValue(section, var, value);
|
||||
}
|
||||
|
||||
void SettingsSaveWrapper::Entry(const char* section, const char* var, std::string& value, const std::string& default_value /*= std::string()*/)
|
||||
{
|
||||
m_si.SetStringValue(section, var, value.c_str());
|
||||
}
|
||||
|
||||
void SettingsSaveWrapper::Entry(const char* section, const char* var, SmallStringBase& value, std::string_view default_value /* = std::string_view() */)
|
||||
{
|
||||
m_si.SetStringValue(section, var, value.c_str());
|
||||
}
|
||||
|
||||
bool SettingsSaveWrapper::EntryBitBool(const char* section, const char* var, bool value, const bool defvalue /*= false*/)
|
||||
{
|
||||
m_si.SetBoolValue(section, var, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
int SettingsSaveWrapper::EntryBitfield(const char* section, const char* var, int value, const int defvalue /*= 0*/)
|
||||
{
|
||||
m_si.SetIntValue(section, var, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
void SettingsSaveWrapper::_EnumEntry(const char* section, const char* var, int& value, const char* const* enumArray, int defvalue)
|
||||
{
|
||||
const int cnt = _calcEnumLength(enumArray);
|
||||
const int index = (value < 0 || value >= cnt) ? defvalue : value;
|
||||
m_si.SetStringValue(section, var, enumArray[index]);
|
||||
}
|
||||
|
||||
SettingsClearWrapper::SettingsClearWrapper(SettingsInterface& si)
|
||||
: SettingsWrapper(si)
|
||||
{
|
||||
}
|
||||
|
||||
bool SettingsClearWrapper::IsLoading() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SettingsClearWrapper::IsSaving() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void SettingsClearWrapper::Entry(const char* section, const char* var, int& value, const int defvalue /*= 0*/)
|
||||
{
|
||||
m_si.DeleteValue(section, var);
|
||||
}
|
||||
|
||||
void SettingsClearWrapper::Entry(const char* section, const char* var, uint& value, const uint defvalue /*= 0*/)
|
||||
{
|
||||
m_si.DeleteValue(section, var);
|
||||
}
|
||||
|
||||
void SettingsClearWrapper::Entry(const char* section, const char* var, bool& value, const bool defvalue /*= false*/)
|
||||
{
|
||||
m_si.DeleteValue(section, var);
|
||||
}
|
||||
|
||||
void SettingsClearWrapper::Entry(const char* section, const char* var, float& value, const float defvalue /*= 0.0*/)
|
||||
{
|
||||
m_si.DeleteValue(section, var);
|
||||
}
|
||||
|
||||
void SettingsClearWrapper::Entry(const char* section, const char* var, std::string& value, const std::string& default_value /*= std::string()*/)
|
||||
{
|
||||
m_si.DeleteValue(section, var);
|
||||
}
|
||||
|
||||
void SettingsClearWrapper::Entry(const char* section, const char* var, SmallStringBase& value, std::string_view default_value /* = std::string_view() */)
|
||||
{
|
||||
m_si.DeleteValue(section, var);
|
||||
}
|
||||
|
||||
bool SettingsClearWrapper::EntryBitBool(const char* section, const char* var, bool value, const bool defvalue /*= false*/)
|
||||
{
|
||||
m_si.DeleteValue(section, var);
|
||||
return defvalue;
|
||||
}
|
||||
|
||||
int SettingsClearWrapper::EntryBitfield(const char* section, const char* var, int value, const int defvalue /*= 0*/)
|
||||
{
|
||||
m_si.DeleteValue(section, var);
|
||||
return defvalue;
|
||||
}
|
||||
|
||||
void SettingsClearWrapper::_EnumEntry(const char* section, const char* var, int& value, const char* const* enumArray, int defvalue)
|
||||
{
|
||||
m_si.DeleteValue(section, var);
|
||||
}
|
||||
137
common/SettingsWrapper.h
Normal file
137
common/SettingsWrapper.h
Normal file
@@ -0,0 +1,137 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SettingsInterface.h"
|
||||
|
||||
#include "common/EnumOps.h"
|
||||
#include "common/SmallString.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
// Helper class which loads or saves depending on the derived class.
|
||||
class SettingsWrapper
|
||||
{
|
||||
public:
|
||||
SettingsWrapper(SettingsInterface& si);
|
||||
|
||||
virtual bool IsLoading() const = 0;
|
||||
virtual bool IsSaving() const = 0;
|
||||
|
||||
virtual void Entry(const char* section, const char* var, int& value, const int defvalue = 0) = 0;
|
||||
virtual void Entry(const char* section, const char* var, uint& value, const uint defvalue = 0) = 0;
|
||||
virtual void Entry(const char* section, const char* var, bool& value, const bool defvalue = false) = 0;
|
||||
virtual void Entry(const char* section, const char* var, float& value, const float defvalue = 0.0) = 0;
|
||||
virtual void Entry(const char* section, const char* var, std::string& value, const std::string& default_value = std::string()) = 0;
|
||||
virtual void Entry(const char* section, const char* var, SmallStringBase& value, std::string_view default_value = std::string_view()) = 0;
|
||||
|
||||
// This special form of Entry is provided for bitfields, which cannot be passed by reference.
|
||||
virtual bool EntryBitBool(const char* section, const char* var, bool value, const bool defvalue = false) = 0;
|
||||
virtual int EntryBitfield(const char* section, const char* var, int value, const int defvalue = 0) = 0;
|
||||
|
||||
template <typename T>
|
||||
void EnumEntry(const char* section, const char* var, T& value, const char* const* enumArray = nullptr, const T defvalue = (T)0)
|
||||
{
|
||||
int tstore = (int)value;
|
||||
auto defaultvalue = enum_cast(defvalue);
|
||||
if (enumArray == NULL)
|
||||
Entry(section, var, tstore, defaultvalue);
|
||||
else
|
||||
_EnumEntry(section, var, tstore, enumArray, defaultvalue);
|
||||
value = (T)tstore;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void EnumEntry(const char* section, const char* var, T& value, std::optional<T> (*parse_function)(const char*),
|
||||
const char*(name_function)(T value), T default_value)
|
||||
{
|
||||
TinyString str_value(name_function(value));
|
||||
Entry(section, var, str_value, name_function(default_value));
|
||||
if (std::optional<T> parsed_value = parse_function(str_value); parsed_value.has_value())
|
||||
value = parsed_value.value();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void _EnumEntry(const char* section, const char* var, int& value, const char* const* enumArray, int defvalue) = 0;
|
||||
|
||||
SettingsInterface& m_si;
|
||||
};
|
||||
|
||||
class SettingsLoadWrapper final : public SettingsWrapper
|
||||
{
|
||||
public:
|
||||
SettingsLoadWrapper(SettingsInterface& si);
|
||||
|
||||
bool IsLoading() const override;
|
||||
bool IsSaving() const override;
|
||||
|
||||
void Entry(const char* section, const char* var, int& value, const int defvalue = 0) override;
|
||||
void Entry(const char* section, const char* var, uint& value, const uint defvalue = 0) override;
|
||||
void Entry(const char* section, const char* var, bool& value, const bool defvalue = false) override;
|
||||
void Entry(const char* section, const char* var, float& value, const float defvalue = 0.0) override;
|
||||
void Entry(const char* section, const char* var, std::string& value, const std::string& default_value = std::string()) override;
|
||||
void Entry(const char* section, const char* var, SmallStringBase& value, std::string_view default_value = std::string_view()) override;
|
||||
|
||||
bool EntryBitBool(const char* section, const char* var, bool value, const bool defvalue = false) override;
|
||||
int EntryBitfield(const char* section, const char* var, int value, const int defvalue = 0) override;
|
||||
|
||||
protected:
|
||||
void _EnumEntry(const char* section, const char* var, int& value, const char* const* enumArray, int defvalue) override;
|
||||
};
|
||||
|
||||
class SettingsSaveWrapper final : public SettingsWrapper
|
||||
{
|
||||
public:
|
||||
SettingsSaveWrapper(SettingsInterface& si);
|
||||
|
||||
bool IsLoading() const override;
|
||||
bool IsSaving() const override;
|
||||
|
||||
void Entry(const char* section, const char* var, int& value, const int defvalue = 0) override;
|
||||
void Entry(const char* section, const char* var, uint& value, const uint defvalue = 0) override;
|
||||
void Entry(const char* section, const char* var, bool& value, const bool defvalue = false) override;
|
||||
void Entry(const char* section, const char* var, float& value, const float defvalue = 0.0) override;
|
||||
void Entry(const char* section, const char* var, std::string& value, const std::string& default_value = std::string()) override;
|
||||
void Entry(const char* section, const char* var, SmallStringBase& value, std::string_view default_value = std::string_view()) override;
|
||||
|
||||
bool EntryBitBool(const char* section, const char* var, bool value, const bool defvalue = false) override;
|
||||
int EntryBitfield(const char* section, const char* var, int value, const int defvalue = 0) override;
|
||||
|
||||
protected:
|
||||
void _EnumEntry(const char* section, const char* var, int& value, const char* const* enumArray, int defvalue) override;
|
||||
};
|
||||
|
||||
class SettingsClearWrapper final : public SettingsWrapper
|
||||
{
|
||||
public:
|
||||
SettingsClearWrapper(SettingsInterface& si);
|
||||
|
||||
bool IsLoading() const override;
|
||||
bool IsSaving() const override;
|
||||
|
||||
void Entry(const char* section, const char* var, int& value, const int defvalue = 0) override;
|
||||
void Entry(const char* section, const char* var, uint& value, const uint defvalue = 0) override;
|
||||
void Entry(const char* section, const char* var, bool& value, const bool defvalue = false) override;
|
||||
void Entry(const char* section, const char* var, float& value, const float defvalue = 0.0) override;
|
||||
void Entry(const char* section, const char* var, std::string& value, const std::string& default_value = std::string()) override;
|
||||
void Entry(const char* section, const char* var, SmallStringBase& value, std::string_view default_value = std::string_view()) override;
|
||||
|
||||
bool EntryBitBool(const char* section, const char* var, bool value, const bool defvalue = false) override;
|
||||
int EntryBitfield(const char* section, const char* var, int value, const int defvalue = 0) override;
|
||||
|
||||
protected:
|
||||
void _EnumEntry(const char* section, const char* var, int& value, const char* const* enumArray, int defvalue) override;
|
||||
};
|
||||
|
||||
#define SettingsWrapSection(section) const char* CURRENT_SETTINGS_SECTION = section;
|
||||
#define SettingsWrapEntry(var) wrap.Entry(CURRENT_SETTINGS_SECTION, #var, var, var)
|
||||
#define SettingsWrapEntryEx(var, name) wrap.Entry(CURRENT_SETTINGS_SECTION, name, var, var)
|
||||
#define SettingsWrapBitfield(varname) varname = wrap.EntryBitfield(CURRENT_SETTINGS_SECTION, #varname, varname, varname)
|
||||
#define SettingsWrapBitBool(varname) varname = wrap.EntryBitBool(CURRENT_SETTINGS_SECTION, #varname, !!varname, varname)
|
||||
#define SettingsWrapBitfieldEx(varname, textname) varname = wrap.EntryBitfield(CURRENT_SETTINGS_SECTION, textname, varname, varname)
|
||||
#define SettingsWrapBitBoolEx(varname, textname) varname = wrap.EntryBitBool(CURRENT_SETTINGS_SECTION, textname, !!varname, varname)
|
||||
#define SettingsWrapEnumEx(varname, textname, names) wrap.EnumEntry(CURRENT_SETTINGS_SECTION, textname, varname, names, varname)
|
||||
#define SettingsWrapParsedEnum(varname, textname, parse_func, name_func) wrap.EnumEntry(CURRENT_SETTINGS_SECTION, textname, varname, parse_func, name_func, varname)
|
||||
#define SettingsWrapIntEnumEx(varname, textname) varname = static_cast<decltype(varname)>(wrap.EntryBitfield(CURRENT_SETTINGS_SECTION, textname, static_cast<int>(varname), static_cast<int>(varname)))
|
||||
|
||||
195
common/SingleRegisterTypes.h
Normal file
195
common/SingleRegisterTypes.h
Normal file
@@ -0,0 +1,195 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// r64 / r128 - Types that are guaranteed to fit in one register
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Note: Recompilers rely on some of these types and the registers they allocate to,
|
||||
// so be careful if you want to change them
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Pcsx2Defs.h"
|
||||
#include "Pcsx2Types.h"
|
||||
#include "VectorIntrin.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#if defined(_M_X86)
|
||||
|
||||
// Can't stick them in structs because it breaks calling convention things, yay
|
||||
using r128 = __m128i;
|
||||
|
||||
// Calling convention setting, yay
|
||||
#define RETURNS_R128 r128 __vectorcall
|
||||
#define TAKES_R128 __vectorcall
|
||||
|
||||
// And since we can't stick them in structs, we get lots of static methods, yay!
|
||||
[[maybe_unused]] __fi static r128 r128_load(const void* ptr)
|
||||
{
|
||||
return _mm_load_si128(reinterpret_cast<const r128*>(ptr));
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static void r128_store(void* ptr, r128 val)
|
||||
{
|
||||
return _mm_store_si128(reinterpret_cast<r128*>(ptr), val);
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static void r128_store_unaligned(void* ptr, r128 val)
|
||||
{
|
||||
return _mm_storeu_si128(reinterpret_cast<r128*>(ptr), val);
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static r128 r128_zero()
|
||||
{
|
||||
return _mm_setzero_si128();
|
||||
}
|
||||
|
||||
/// Expects that r64 came from r64-handling code, and not from a recompiler or something
|
||||
[[maybe_unused]] __fi static r128 r128_from_u64_dup(u64 val)
|
||||
{
|
||||
return _mm_set1_epi64x(val);
|
||||
}
|
||||
[[maybe_unused]] __fi static r128 r128_from_u64_zext(u64 val)
|
||||
{
|
||||
return _mm_set_epi64x(0, val);
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static r128 r128_from_u32_dup(u32 val)
|
||||
{
|
||||
return _mm_set1_epi32(val);
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static r128 r128_from_u32x4(u32 lo0, u32 lo1, u32 hi0, u32 hi1)
|
||||
{
|
||||
return _mm_setr_epi32(lo0, lo1, hi0, hi1);
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static r128 r128_from_u128(const u128& u)
|
||||
{
|
||||
return _mm_loadu_si128(reinterpret_cast<const __m128i*>(&u));
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static u32 r128_to_u32(r128 val)
|
||||
{
|
||||
return _mm_cvtsi128_si32(val);
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static u64 r128_to_u64(r128 val)
|
||||
{
|
||||
return _mm_cvtsi128_si64(val);
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static u128 r128_to_u128(r128 val)
|
||||
{
|
||||
alignas(16) u128 ret;
|
||||
_mm_store_si128(reinterpret_cast<r128*>(&ret), val);
|
||||
return ret;
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static void CopyQWC(void* dest, const void* src)
|
||||
{
|
||||
_mm_store_ps((float*)dest, _mm_load_ps((const float*)src));
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static void ZeroQWC(void* dest)
|
||||
{
|
||||
_mm_store_ps((float*)dest, _mm_setzero_ps());
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static void ZeroQWC(u128& dest)
|
||||
{
|
||||
_mm_store_ps((float*)&dest, _mm_setzero_ps());
|
||||
}
|
||||
|
||||
#elif defined(_M_ARM64)
|
||||
|
||||
using r128 = uint32x4_t;
|
||||
|
||||
#define RETURNS_R128 r128 __vectorcall
|
||||
#define TAKES_R128 __vectorcall
|
||||
|
||||
[[maybe_unused]] __fi static void CopyQWC(void* dest, const void* src)
|
||||
{
|
||||
vst1q_u8(static_cast<u8*>(dest), vld1q_u8(static_cast<const u8*>(src)));
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static void ZeroQWC(void* dest)
|
||||
{
|
||||
vst1q_u8(static_cast<u8*>(dest), vmovq_n_u8(0));
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static void ZeroQWC(u128& dest)
|
||||
{
|
||||
vst1q_u8(&dest._u8[0], vmovq_n_u8(0));
|
||||
}
|
||||
|
||||
|
||||
[[maybe_unused]] __fi static r128 r128_load(const void* ptr)
|
||||
{
|
||||
return vld1q_u32(reinterpret_cast<const uint32_t*>(ptr));
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static void r128_store(void* ptr, r128 value)
|
||||
{
|
||||
return vst1q_u32(reinterpret_cast<uint32_t*>(ptr), value);
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static void r128_store_unaligned(void* ptr, r128 value)
|
||||
{
|
||||
return vst1q_u32(reinterpret_cast<uint32_t*>(ptr), value);
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static r128 r128_zero()
|
||||
{
|
||||
return vmovq_n_u32(0);
|
||||
}
|
||||
|
||||
/// Expects that r64 came from r64-handling code, and not from a recompiler or something
|
||||
[[maybe_unused]] __fi static r128 r128_from_u64_dup(u64 val)
|
||||
{
|
||||
return vreinterpretq_u32_u64(vdupq_n_u64(val));
|
||||
}
|
||||
[[maybe_unused]] __fi static r128 r128_from_u64_zext(u64 val)
|
||||
{
|
||||
return vreinterpretq_u32_u64(vcombine_u64(vcreate_u64(val), vcreate_u64(0)));
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static r128 r128_from_u32_dup(u32 val)
|
||||
{
|
||||
return vdupq_n_u32(val);
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static r128 r128_from_u32x4(u32 lo0, u32 lo1, u32 hi0, u32 hi1)
|
||||
{
|
||||
const u32 values[4] = {lo0, lo1, hi0, hi1};
|
||||
return vld1q_u32(values);
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static r128 r128_from_u128(const u128& u)
|
||||
{
|
||||
return vld1q_u32(reinterpret_cast<const uint32_t*>(u._u32));
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static u32 r128_to_u32(r128 val)
|
||||
{
|
||||
return vgetq_lane_u32(val, 0);
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static u64 r128_to_u64(r128 val)
|
||||
{
|
||||
return vgetq_lane_u64(vreinterpretq_u64_u32(val), 0);
|
||||
}
|
||||
|
||||
[[maybe_unused]] __fi static u128 r128_to_u128(r128 val)
|
||||
{
|
||||
alignas(16) u128 ret;
|
||||
vst1q_u32(ret._u32, val);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#error Unknown architecture.
|
||||
|
||||
#endif
|
||||
843
common/SmallString.cpp
Normal file
843
common/SmallString.cpp
Normal file
@@ -0,0 +1,843 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "SmallString.h"
|
||||
#include "Assertions.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define CASE_COMPARE _stricmp
|
||||
#define CASE_N_COMPARE _strnicmp
|
||||
#else
|
||||
#define CASE_COMPARE strcasecmp
|
||||
#define CASE_N_COMPARE strncasecmp
|
||||
#endif
|
||||
|
||||
SmallStringBase::SmallStringBase() = default;
|
||||
|
||||
SmallStringBase::SmallStringBase(const SmallStringBase& copy)
|
||||
{
|
||||
assign(copy.m_buffer, copy.m_length);
|
||||
}
|
||||
|
||||
SmallStringBase::SmallStringBase(const char* str)
|
||||
{
|
||||
assign(str);
|
||||
}
|
||||
|
||||
SmallStringBase::SmallStringBase(const char* str, u32 count)
|
||||
{
|
||||
assign(str, count);
|
||||
}
|
||||
|
||||
SmallStringBase::SmallStringBase(SmallStringBase&& move)
|
||||
{
|
||||
assign(std::move(move));
|
||||
}
|
||||
|
||||
SmallStringBase::SmallStringBase(const std::string_view sv)
|
||||
{
|
||||
assign(sv);
|
||||
}
|
||||
|
||||
SmallStringBase::SmallStringBase(const std::string& str)
|
||||
{
|
||||
assign(str);
|
||||
}
|
||||
|
||||
SmallStringBase::~SmallStringBase()
|
||||
{
|
||||
if (m_on_heap)
|
||||
std::free(m_buffer);
|
||||
}
|
||||
|
||||
void SmallStringBase::reserve(u32 new_reserve)
|
||||
{
|
||||
const u32 real_reserve = new_reserve + 1;
|
||||
if (m_buffer_size >= real_reserve)
|
||||
return;
|
||||
|
||||
if (m_on_heap)
|
||||
{
|
||||
char* new_ptr = static_cast<char*>(std::realloc(m_buffer, real_reserve));
|
||||
if (!new_ptr)
|
||||
pxFailRel("Memory allocation failed.");
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::memset(new_ptr + m_length, 0, real_reserve - m_length);
|
||||
#endif
|
||||
m_buffer = new_ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
char* new_ptr = static_cast<char*>(std::malloc(real_reserve));
|
||||
if (!new_ptr)
|
||||
pxFailRel("Memory allocation failed.");
|
||||
|
||||
if (m_length > 0)
|
||||
std::memcpy(new_ptr, m_buffer, m_length);
|
||||
#ifdef _DEBUG
|
||||
std::memset(new_ptr + m_length, 0, real_reserve - m_length);
|
||||
#else
|
||||
new_ptr[m_length] = 0;
|
||||
#endif
|
||||
m_buffer = new_ptr;
|
||||
m_on_heap = true;
|
||||
}
|
||||
|
||||
m_buffer_size = new_reserve;
|
||||
}
|
||||
|
||||
void SmallStringBase::shrink_to_fit()
|
||||
{
|
||||
const u32 buffer_size = (m_length + 1);
|
||||
if (!m_on_heap || buffer_size == m_buffer_size)
|
||||
return;
|
||||
|
||||
if (m_length == 0)
|
||||
{
|
||||
std::free(m_buffer);
|
||||
m_buffer = nullptr;
|
||||
m_buffer_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
char* new_ptr = static_cast<char*>(std::realloc(m_buffer, buffer_size));
|
||||
if (!new_ptr)
|
||||
pxFailRel("Memory allocation failed.");
|
||||
|
||||
m_buffer = new_ptr;
|
||||
m_buffer_size = buffer_size;
|
||||
}
|
||||
|
||||
std::string_view SmallStringBase::view() const
|
||||
{
|
||||
return (m_length == 0) ? std::string_view() : std::string_view(m_buffer, m_length);
|
||||
}
|
||||
|
||||
SmallStringBase& SmallStringBase::operator=(SmallStringBase&& move)
|
||||
{
|
||||
assign(move);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SmallStringBase& SmallStringBase::operator=(const std::string_view str)
|
||||
{
|
||||
assign(str);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SmallStringBase& SmallStringBase::operator=(const std::string& str)
|
||||
{
|
||||
assign(str);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SmallStringBase& SmallStringBase::operator=(const char* str)
|
||||
{
|
||||
assign(str);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SmallStringBase& SmallStringBase::operator=(const SmallStringBase& copy)
|
||||
{
|
||||
assign(copy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void SmallStringBase::make_room_for(u32 space)
|
||||
{
|
||||
const u32 required_size = m_length + space + 1;
|
||||
if (m_buffer_size >= required_size)
|
||||
return;
|
||||
|
||||
reserve(std::max(required_size, m_buffer_size * 2));
|
||||
}
|
||||
|
||||
void SmallStringBase::append(const char* str, u32 length)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
make_room_for(length);
|
||||
|
||||
pxAssert((length + m_length) < m_buffer_size);
|
||||
|
||||
std::memcpy(m_buffer + m_length, str, length);
|
||||
m_length += length;
|
||||
m_buffer[m_length] = 0;
|
||||
}
|
||||
|
||||
void SmallStringBase::append_hex(const void* data, size_t len)
|
||||
{
|
||||
if (len == 0)
|
||||
return;
|
||||
|
||||
make_room_for(static_cast<u32>(len) * 4);
|
||||
const u8* bytes = static_cast<const u8*>(data);
|
||||
append_format("{:02X}", bytes[0]);
|
||||
for (size_t i = 1; i < len; i++)
|
||||
append_format(", {:02X}", bytes[i]);
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend(const char* str, u32 length)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
make_room_for(length);
|
||||
|
||||
pxAssert((length + m_length) < m_buffer_size);
|
||||
|
||||
std::memmove(m_buffer + length, m_buffer, m_length);
|
||||
std::memcpy(m_buffer, str, length);
|
||||
m_length += length;
|
||||
m_buffer[m_length] = 0;
|
||||
}
|
||||
|
||||
void SmallStringBase::append(char c)
|
||||
{
|
||||
append(&c, 1);
|
||||
}
|
||||
|
||||
void SmallStringBase::append(const SmallStringBase& str)
|
||||
{
|
||||
append(str.m_buffer, str.m_length);
|
||||
}
|
||||
|
||||
void SmallStringBase::append(const char* str)
|
||||
{
|
||||
append(str, static_cast<u32>(std::strlen(str)));
|
||||
}
|
||||
|
||||
void SmallStringBase::append(const std::string& str)
|
||||
{
|
||||
append(str.c_str(), static_cast<u32>(str.length()));
|
||||
}
|
||||
|
||||
void SmallStringBase::append(const std::string_view str)
|
||||
{
|
||||
append(str.data(), static_cast<u32>(str.length()));
|
||||
}
|
||||
|
||||
void SmallStringBase::append_sprintf(const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, format);
|
||||
append_vsprintf(format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SmallStringBase::append_vsprintf(const char* format, va_list ap)
|
||||
{
|
||||
// We have a 1KB byte buffer on the stack here. If this is too little, we'll grow it via the heap,
|
||||
// but 1KB should be enough for most strings.
|
||||
char stack_buffer[1024];
|
||||
char* heap_buffer = nullptr;
|
||||
char* buffer = stack_buffer;
|
||||
u32 buffer_size = static_cast<u32>(std::size(stack_buffer));
|
||||
u32 written;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
std::va_list ap_copy;
|
||||
va_copy(ap_copy, ap);
|
||||
const int ret = std::vsnprintf(buffer, buffer_size, format, ap_copy);
|
||||
va_end(ap_copy);
|
||||
if (ret < 0 || ((u32)ret >= (buffer_size - 1)))
|
||||
{
|
||||
buffer_size *= 2;
|
||||
buffer = heap_buffer = reinterpret_cast<char*>(std::realloc(heap_buffer, buffer_size));
|
||||
continue;
|
||||
}
|
||||
|
||||
written = static_cast<u32>(ret);
|
||||
break;
|
||||
}
|
||||
|
||||
append(buffer, written);
|
||||
|
||||
if (heap_buffer)
|
||||
std::free(heap_buffer);
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend(char c)
|
||||
{
|
||||
prepend(&c, 1);
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend(const SmallStringBase& str)
|
||||
{
|
||||
prepend(str.m_buffer, str.m_length);
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend(const char* str)
|
||||
{
|
||||
prepend(str, static_cast<u32>(std::strlen(str)));
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend(const std::string& str)
|
||||
{
|
||||
prepend(str.c_str(), static_cast<u32>(str.length()));
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend(const std::string_view str)
|
||||
{
|
||||
prepend(str.data(), static_cast<u32>(str.length()));
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend_sprintf(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
prepend_vsprintf(format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SmallStringBase::prepend_vsprintf(const char* format, va_list ArgPtr)
|
||||
{
|
||||
// We have a 1KB byte buffer on the stack here. If this is too little, we'll grow it via the heap,
|
||||
// but 1KB should be enough for most strings.
|
||||
char stack_buffer[1024];
|
||||
char* heap_buffer = NULL;
|
||||
char* buffer = stack_buffer;
|
||||
u32 buffer_size = static_cast<u32>(std::size(stack_buffer));
|
||||
u32 written;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int ret = std::vsnprintf(buffer, buffer_size, format, ArgPtr);
|
||||
if (ret < 0 || (static_cast<u32>(ret) >= (buffer_size - 1)))
|
||||
{
|
||||
buffer_size *= 2;
|
||||
buffer = heap_buffer = reinterpret_cast<char*>(std::realloc(heap_buffer, buffer_size));
|
||||
continue;
|
||||
}
|
||||
|
||||
written = static_cast<u32>(ret);
|
||||
break;
|
||||
}
|
||||
|
||||
prepend(buffer, written);
|
||||
|
||||
if (heap_buffer)
|
||||
std::free(heap_buffer);
|
||||
}
|
||||
|
||||
void SmallStringBase::insert(s32 offset, const char* str)
|
||||
{
|
||||
insert(offset, str, static_cast<u32>(std::strlen(str)));
|
||||
}
|
||||
|
||||
void SmallStringBase::insert(s32 offset, const SmallStringBase& str)
|
||||
{
|
||||
insert(offset, str, str.m_length);
|
||||
}
|
||||
|
||||
void SmallStringBase::insert(s32 offset, const char* str, u32 length)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
make_room_for(length);
|
||||
|
||||
// calc real offset
|
||||
u32 real_offset;
|
||||
if (offset < 0)
|
||||
real_offset = static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length) + offset));
|
||||
else
|
||||
real_offset = std::min(static_cast<u32>(offset), m_length);
|
||||
|
||||
// determine number of characters after offset
|
||||
pxAssert(real_offset <= m_length);
|
||||
const u32 chars_after_offset = m_length - real_offset;
|
||||
if (chars_after_offset > 0)
|
||||
std::memmove(m_buffer + offset + length, m_buffer + offset, chars_after_offset);
|
||||
|
||||
// insert the string
|
||||
std::memcpy(m_buffer + real_offset, str, length);
|
||||
m_length += length;
|
||||
|
||||
// ensure null termination
|
||||
m_buffer[m_length] = 0;
|
||||
}
|
||||
|
||||
void SmallStringBase::insert(s32 offset, const std::string& str)
|
||||
{
|
||||
insert(offset, str.c_str(), static_cast<u32>(str.size()));
|
||||
}
|
||||
|
||||
void SmallStringBase::insert(s32 offset, const std::string_view str)
|
||||
{
|
||||
insert(offset, str.data(), static_cast<u32>(str.size()));
|
||||
}
|
||||
|
||||
void SmallStringBase::sprintf(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
vsprintf(format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SmallStringBase::vsprintf(const char* format, va_list ap)
|
||||
{
|
||||
clear();
|
||||
append_vsprintf(format, ap);
|
||||
}
|
||||
|
||||
void SmallStringBase::assign(const SmallStringBase& copy)
|
||||
{
|
||||
assign(copy.c_str(), copy.length());
|
||||
}
|
||||
|
||||
void SmallStringBase::assign(const char* str)
|
||||
{
|
||||
assign(str, static_cast<u32>(std::strlen(str)));
|
||||
}
|
||||
|
||||
void SmallStringBase::assign(const char* str, u32 length)
|
||||
{
|
||||
clear();
|
||||
if (length > 0)
|
||||
append(str, length);
|
||||
}
|
||||
|
||||
void SmallStringBase::assign(SmallStringBase&& move)
|
||||
{
|
||||
if (move.m_on_heap)
|
||||
{
|
||||
if (m_on_heap)
|
||||
std::free(m_buffer);
|
||||
m_buffer = move.m_buffer;
|
||||
m_buffer_size = move.m_buffer_size;
|
||||
m_length = move.m_length;
|
||||
m_on_heap = true;
|
||||
move.m_buffer = nullptr;
|
||||
move.m_buffer_size = 0;
|
||||
move.m_length = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
assign(move.m_buffer, move.m_buffer_size);
|
||||
}
|
||||
}
|
||||
|
||||
void SmallStringBase::assign(const std::string& str)
|
||||
{
|
||||
clear();
|
||||
append(str.data(), static_cast<u32>(str.size()));
|
||||
}
|
||||
|
||||
void SmallStringBase::assign(const std::string_view str)
|
||||
{
|
||||
clear();
|
||||
append(str.data(), static_cast<u32>(str.size()));
|
||||
}
|
||||
|
||||
void SmallStringBase::vformat(fmt::string_view fmt, fmt::format_args args)
|
||||
{
|
||||
clear();
|
||||
fmt::vformat_to(std::back_inserter(*this), fmt, args);
|
||||
}
|
||||
|
||||
bool SmallStringBase::equals(const char* str) const
|
||||
{
|
||||
if (m_length == 0)
|
||||
return (std::strlen(str) == 0);
|
||||
else
|
||||
return (std::strcmp(m_buffer, str) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::equals(const SmallStringBase& str) const
|
||||
{
|
||||
return (m_length == str.m_length && (m_length == 0 || std::strcmp(m_buffer, str.m_buffer) == 0));
|
||||
}
|
||||
|
||||
bool SmallStringBase::equals(const std::string_view str) const
|
||||
{
|
||||
return (m_length == static_cast<u32>(str.length()) &&
|
||||
(m_length == 0 || std::memcmp(m_buffer, str.data(), m_length) == 0));
|
||||
}
|
||||
|
||||
bool SmallStringBase::equals(const std::string& str) const
|
||||
{
|
||||
return (m_length == static_cast<u32>(str.length()) &&
|
||||
(m_length == 0 || std::memcmp(m_buffer, str.data(), m_length) == 0));
|
||||
}
|
||||
|
||||
bool SmallStringBase::iequals(const char* otherText) const
|
||||
{
|
||||
if (m_length == 0)
|
||||
return (std::strlen(otherText) == 0);
|
||||
else
|
||||
return (CASE_COMPARE(m_buffer, otherText) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::iequals(const SmallStringBase& str) const
|
||||
{
|
||||
return (m_length == str.m_length && (m_length == 0 || std::strcmp(m_buffer, str.m_buffer) == 0));
|
||||
}
|
||||
|
||||
bool SmallStringBase::iequals(const std::string_view str) const
|
||||
{
|
||||
return (m_length == static_cast<u32>(str.length()) &&
|
||||
(m_length == 0 || CASE_N_COMPARE(m_buffer, str.data(), m_length) == 0));
|
||||
}
|
||||
|
||||
bool SmallStringBase::iequals(const std::string& str) const
|
||||
{
|
||||
return (m_length == static_cast<u32>(str.length()) &&
|
||||
(m_length == 0 || CASE_N_COMPARE(m_buffer, str.data(), m_length) == 0));
|
||||
}
|
||||
|
||||
int SmallStringBase::compare(const char* otherText) const
|
||||
{
|
||||
return compare(std::string_view(otherText));
|
||||
}
|
||||
|
||||
int SmallStringBase::compare(const SmallStringBase& str) const
|
||||
{
|
||||
if (m_length == 0)
|
||||
return (str.m_length == 0) ? 0 : -1;
|
||||
else if (str.m_length == 0)
|
||||
return 1;
|
||||
|
||||
const int res = std::strncmp(m_buffer, str.m_buffer, std::min(m_length, str.m_length));
|
||||
if (m_length == str.m_length || res != 0)
|
||||
return res;
|
||||
else
|
||||
return (m_length > str.m_length) ? 1 : -1;
|
||||
}
|
||||
|
||||
int SmallStringBase::compare(const std::string_view str) const
|
||||
{
|
||||
const u32 slength = static_cast<u32>(str.length());
|
||||
if (m_length == 0)
|
||||
return (slength == 0) ? 0 : -1;
|
||||
else if (slength == 0)
|
||||
return 1;
|
||||
|
||||
const int res = std::strncmp(m_buffer, str.data(), std::min(m_length, slength));
|
||||
if (m_length == slength || res != 0)
|
||||
return res;
|
||||
else
|
||||
return (m_length > slength) ? 1 : -1;
|
||||
}
|
||||
|
||||
int SmallStringBase::compare(const std::string& str) const
|
||||
{
|
||||
const u32 slength = static_cast<u32>(str.length());
|
||||
if (m_length == 0)
|
||||
return (slength == 0) ? 0 : -1;
|
||||
else if (slength == 0)
|
||||
return 1;
|
||||
|
||||
const int res = std::strncmp(m_buffer, str.data(), std::min(m_length, slength));
|
||||
if (m_length == slength || res != 0)
|
||||
return res;
|
||||
else
|
||||
return (m_length > slength) ? 1 : -1;
|
||||
}
|
||||
|
||||
int SmallStringBase::icompare(const char* otherText) const
|
||||
{
|
||||
return icompare(std::string_view(otherText));
|
||||
}
|
||||
|
||||
int SmallStringBase::icompare(const SmallStringBase& str) const
|
||||
{
|
||||
if (m_length == 0)
|
||||
return (str.m_length == 0) ? 0 : -1;
|
||||
else if (str.m_length == 0)
|
||||
return 1;
|
||||
|
||||
const int res = CASE_N_COMPARE(m_buffer, str.m_buffer, std::min(m_length, str.m_length));
|
||||
if (m_length == str.m_length || res != 0)
|
||||
return res;
|
||||
else
|
||||
return (m_length > str.m_length) ? 1 : -1;
|
||||
}
|
||||
|
||||
int SmallStringBase::icompare(const std::string_view str) const
|
||||
{
|
||||
const u32 slength = static_cast<u32>(str.length());
|
||||
if (m_length == 0)
|
||||
return (slength == 0) ? 0 : -1;
|
||||
else if (slength == 0)
|
||||
return 1;
|
||||
|
||||
const int res = CASE_N_COMPARE(m_buffer, str.data(), std::min(m_length, slength));
|
||||
if (m_length == slength || res != 0)
|
||||
return res;
|
||||
else
|
||||
return (m_length > slength) ? 1 : -1;
|
||||
}
|
||||
|
||||
int SmallStringBase::icompare(const std::string& str) const
|
||||
{
|
||||
const u32 slength = static_cast<u32>(str.length());
|
||||
if (m_length == 0)
|
||||
return (slength == 0) ? 0 : -1;
|
||||
else if (slength == 0)
|
||||
return 1;
|
||||
|
||||
const int res = CASE_N_COMPARE(m_buffer, str.data(), std::min(m_length, slength));
|
||||
if (m_length == slength || res != 0)
|
||||
return res;
|
||||
else
|
||||
return (m_length > slength) ? 1 : -1;
|
||||
}
|
||||
|
||||
bool SmallStringBase::starts_with(const char* str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = static_cast<u32>(std::strlen(str));
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
return (case_sensitive) ? (std::strncmp(str, m_buffer, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str, m_buffer, other_length) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::starts_with(const SmallStringBase& str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = str.m_length;
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
return (case_sensitive) ? (std::strncmp(str.m_buffer, m_buffer, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str.m_buffer, m_buffer, other_length) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::starts_with(const std::string_view str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = static_cast<u32>(str.length());
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
return (case_sensitive) ? (std::strncmp(str.data(), m_buffer, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str.data(), m_buffer, other_length) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::starts_with(const std::string& str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = static_cast<u32>(str.length());
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
return (case_sensitive) ? (std::strncmp(str.data(), m_buffer, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str.data(), m_buffer, other_length) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::ends_with(const char* str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = static_cast<u32>(std::strlen(str));
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
u32 start_offset = m_length - other_length;
|
||||
return (case_sensitive) ? (std::strncmp(str, m_buffer + start_offset, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str, m_buffer + start_offset, other_length) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::ends_with(const SmallStringBase& str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = str.m_length;
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
const u32 start_offset = m_length - other_length;
|
||||
return (case_sensitive) ? (std::strncmp(str.m_buffer, m_buffer + start_offset, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str.m_buffer, m_buffer + start_offset, other_length) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::ends_with(const std::string_view str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = static_cast<u32>(str.length());
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
const u32 start_offset = m_length - other_length;
|
||||
return (case_sensitive) ? (std::strncmp(str.data(), m_buffer + start_offset, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str.data(), m_buffer + start_offset, other_length) == 0);
|
||||
}
|
||||
|
||||
bool SmallStringBase::ends_with(const std::string& str, bool case_sensitive) const
|
||||
{
|
||||
const u32 other_length = static_cast<u32>(str.length());
|
||||
if (other_length > m_length)
|
||||
return false;
|
||||
|
||||
const u32 start_offset = m_length - other_length;
|
||||
return (case_sensitive) ? (std::strncmp(str.data(), m_buffer + start_offset, other_length) == 0) :
|
||||
(CASE_N_COMPARE(str.data(), m_buffer + start_offset, other_length) == 0);
|
||||
}
|
||||
|
||||
void SmallStringBase::clear()
|
||||
{
|
||||
// in debug, zero whole string, in release, zero only the first character
|
||||
#if _DEBUG
|
||||
std::memset(m_buffer, 0, m_buffer_size);
|
||||
#else
|
||||
m_buffer[0] = '\0';
|
||||
#endif
|
||||
m_length = 0;
|
||||
}
|
||||
|
||||
s32 SmallStringBase::find(char c, u32 offset) const
|
||||
{
|
||||
if (m_length == 0)
|
||||
return -1;
|
||||
|
||||
pxAssert(offset <= m_length);
|
||||
const char* at = std::strchr(m_buffer + offset, c);
|
||||
return at ? static_cast<s32>(at - m_buffer) : -1;
|
||||
}
|
||||
|
||||
s32 SmallStringBase::rfind(char c, u32 offset) const
|
||||
{
|
||||
if (m_length == 0)
|
||||
return -1;
|
||||
|
||||
pxAssert(offset <= m_length);
|
||||
const char* at = std::strrchr(m_buffer + offset, c);
|
||||
return at ? static_cast<s32>(at - m_buffer) : -1;
|
||||
}
|
||||
|
||||
s32 SmallStringBase::find(const char* str, u32 offset) const
|
||||
{
|
||||
if (m_length == 0)
|
||||
return -1;
|
||||
|
||||
pxAssert(offset <= m_length);
|
||||
const char* at = std::strstr(m_buffer + offset, str);
|
||||
return at ? static_cast<s32>(at - m_buffer) : -1;
|
||||
}
|
||||
|
||||
u32 SmallStringBase::count(char ch) const
|
||||
{
|
||||
const char* ptr = m_buffer;
|
||||
const char* end = ptr + m_length;
|
||||
u32 count = 0;
|
||||
while (ptr != end)
|
||||
count += static_cast<u32>(*(ptr++) == ch);
|
||||
return count;
|
||||
}
|
||||
|
||||
void SmallStringBase::resize(u32 new_size, char fill, bool shrink_if_smaller)
|
||||
{
|
||||
// if going larger, or we don't own the buffer, realloc
|
||||
if (new_size >= m_buffer_size)
|
||||
{
|
||||
reserve(new_size);
|
||||
|
||||
if (m_length < new_size)
|
||||
{
|
||||
std::memset(m_buffer + m_length, fill, m_buffer_size - m_length - 1);
|
||||
}
|
||||
|
||||
m_length = new_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// update length and terminator
|
||||
#if _DEBUG
|
||||
std::memset(m_buffer + new_size, 0, m_buffer_size - new_size);
|
||||
#else
|
||||
m_buffer[new_size] = 0;
|
||||
#endif
|
||||
m_length = new_size;
|
||||
|
||||
// shrink if requested
|
||||
if (shrink_if_smaller)
|
||||
shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
void SmallStringBase::update_size()
|
||||
{
|
||||
m_length = static_cast<u32>(std::strlen(m_buffer));
|
||||
}
|
||||
|
||||
std::string_view SmallStringBase::substr(s32 offset, s32 count) const
|
||||
{
|
||||
// calc real offset
|
||||
u32 real_offset;
|
||||
if (offset < 0)
|
||||
real_offset = static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length + offset)));
|
||||
else
|
||||
real_offset = std::min((u32)offset, m_length);
|
||||
|
||||
// calc real count
|
||||
u32 real_count;
|
||||
if (count < 0)
|
||||
{
|
||||
real_count =
|
||||
std::min(m_length - real_offset, static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length) + count)));
|
||||
}
|
||||
else
|
||||
{
|
||||
real_count = std::min(m_length - real_offset, static_cast<u32>(count));
|
||||
}
|
||||
|
||||
return (real_count > 0) ? std::string_view(m_buffer + real_offset, real_count) : std::string_view();
|
||||
}
|
||||
|
||||
void SmallStringBase::erase(s32 offset, s32 count)
|
||||
{
|
||||
// calc real offset
|
||||
u32 real_offset;
|
||||
if (offset < 0)
|
||||
real_offset = static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length + offset)));
|
||||
else
|
||||
real_offset = std::min((u32)offset, m_length);
|
||||
|
||||
// calc real count
|
||||
u32 real_count;
|
||||
if (count < 0)
|
||||
{
|
||||
real_count =
|
||||
std::min(m_length - real_offset, static_cast<u32>(std::max<s32>(0, static_cast<s32>(m_length) + count)));
|
||||
}
|
||||
else
|
||||
{
|
||||
real_count = std::min(m_length - real_offset, static_cast<u32>(count));
|
||||
}
|
||||
|
||||
// Fastpath: offset == 0, count < 0, wipe whole string.
|
||||
if (real_offset == 0 && real_count == m_length)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fastpath: offset >= 0, count < 0, wipe everything after offset + count
|
||||
if ((real_offset + real_count) == m_length)
|
||||
{
|
||||
m_length -= real_count;
|
||||
#ifdef _DEBUG
|
||||
std::memset(m_buffer + m_length, 0, m_buffer_size - m_length);
|
||||
#else
|
||||
m_buffer[m_length] = 0;
|
||||
#endif
|
||||
}
|
||||
// Slowpath: offset >= 0, count < length
|
||||
else
|
||||
{
|
||||
const u32 after_erase_block = m_length - real_offset - real_count;
|
||||
pxAssert(after_erase_block > 0);
|
||||
|
||||
std::memmove(m_buffer + offset, m_buffer + real_offset + real_count, after_erase_block);
|
||||
m_length = m_length - real_count;
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::memset(m_buffer + m_length, 0, m_buffer_size - m_length);
|
||||
#else
|
||||
m_buffer[m_length] = 0;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
426
common/SmallString.h
Normal file
426
common/SmallString.h
Normal file
@@ -0,0 +1,426 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Pcsx2Defs.h"
|
||||
|
||||
#include "fmt/base.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
//
|
||||
// SmallString
|
||||
// Lightweight string class which can be allocated on the stack, instead of with heap allocations.
|
||||
//
|
||||
class SmallStringBase
|
||||
{
|
||||
public:
|
||||
using value_type = char;
|
||||
|
||||
SmallStringBase();
|
||||
SmallStringBase(const char* str);
|
||||
SmallStringBase(const char* str, u32 length);
|
||||
SmallStringBase(const SmallStringBase& copy);
|
||||
SmallStringBase(SmallStringBase&& move);
|
||||
SmallStringBase(const std::string& str);
|
||||
SmallStringBase(const std::string_view sv);
|
||||
|
||||
// Destructor. Child classes may not have any destructors, as this is not virtual.
|
||||
~SmallStringBase();
|
||||
|
||||
// manual assignment
|
||||
void assign(const char* str);
|
||||
void assign(const char* str, u32 length);
|
||||
void assign(const std::string& copy);
|
||||
void assign(const std::string_view copy);
|
||||
void assign(const SmallStringBase& copy);
|
||||
void assign(SmallStringBase&& move);
|
||||
|
||||
// Ensures that we have space bytes free in the buffer.
|
||||
void make_room_for(u32 space);
|
||||
|
||||
// clears the contents of the string
|
||||
void clear();
|
||||
|
||||
// append a single character to this string
|
||||
void append(char c);
|
||||
|
||||
// append a string to this string
|
||||
void append(const char* appendText);
|
||||
void append(const char* str, u32 length);
|
||||
void append(const std::string& str);
|
||||
void append(const std::string_view str);
|
||||
void append(const SmallStringBase& str);
|
||||
|
||||
// append formatted string to this string
|
||||
void append_sprintf(const char* format, ...) /*printflike(2, 3)*/;
|
||||
void append_vsprintf(const char* format, va_list ap);
|
||||
|
||||
template <typename... T>
|
||||
void append_format(fmt::format_string<T...> fmt, T&&... args);
|
||||
|
||||
// append hex string
|
||||
void append_hex(const void* data, size_t len);
|
||||
|
||||
// append a single character to this string
|
||||
void prepend(char c);
|
||||
|
||||
// append a string to this string
|
||||
void prepend(const char* str);
|
||||
void prepend(const char* str, u32 length);
|
||||
void prepend(const std::string& str);
|
||||
void prepend(const std::string_view str);
|
||||
void prepend(const SmallStringBase& str);
|
||||
|
||||
// append formatted string to this string
|
||||
void prepend_sprintf(const char* format, ...) /*printflike(2, 3)*/;
|
||||
void prepend_vsprintf(const char* format, va_list ap);
|
||||
|
||||
template <typename... T>
|
||||
void prepend_format(fmt::format_string<T...> fmt, T&&... args);
|
||||
|
||||
// insert a string at the specified offset
|
||||
void insert(s32 offset, const char* str);
|
||||
void insert(s32 offset, const char* str, u32 length);
|
||||
void insert(s32 offset, const std::string& str);
|
||||
void insert(s32 offset, const std::string_view str);
|
||||
void insert(s32 offset, const SmallStringBase& str);
|
||||
|
||||
// set to formatted string
|
||||
void sprintf(const char* format, ...) /*printflike(2, 3)*/;
|
||||
void vsprintf(const char* format, va_list ap);
|
||||
|
||||
template <typename... T>
|
||||
void format(fmt::format_string<T...> fmt, T&&... args);
|
||||
|
||||
void vformat(fmt::string_view fmt, fmt::format_args args);
|
||||
|
||||
// compare one string to another
|
||||
bool equals(const char* str) const;
|
||||
bool equals(const SmallStringBase& str) const;
|
||||
bool equals(const std::string_view str) const;
|
||||
bool equals(const std::string& str) const;
|
||||
bool iequals(const char* str) const;
|
||||
bool iequals(const SmallStringBase& str) const;
|
||||
bool iequals(const std::string_view str) const;
|
||||
bool iequals(const std::string& str) const;
|
||||
|
||||
// numerical compares
|
||||
int compare(const char* str) const;
|
||||
int compare(const SmallStringBase& str) const;
|
||||
int compare(const std::string_view str) const;
|
||||
int compare(const std::string& str) const;
|
||||
int icompare(const char* str) const;
|
||||
int icompare(const SmallStringBase& str) const;
|
||||
int icompare(const std::string_view str) const;
|
||||
int icompare(const std::string& str) const;
|
||||
|
||||
// starts with / ends with
|
||||
bool starts_with(const char* str, bool case_sensitive = true) const;
|
||||
bool starts_with(const SmallStringBase& str, bool case_sensitive = true) const;
|
||||
bool starts_with(const std::string_view str, bool case_sensitive = true) const;
|
||||
bool starts_with(const std::string& str, bool case_sensitive = true) const;
|
||||
bool ends_with(const char* str, bool case_sensitive = true) const;
|
||||
bool ends_with(const SmallStringBase& str, bool case_sensitive = true) const;
|
||||
bool ends_with(const std::string_view str, bool case_sensitive = true) const;
|
||||
bool ends_with(const std::string& str, bool case_sensitive = true) const;
|
||||
|
||||
// searches for a character inside a string
|
||||
// rfind is the same except it starts at the end instead of the start
|
||||
// returns -1 if it is not found, otherwise the offset in the string
|
||||
s32 find(char c, u32 offset = 0) const;
|
||||
s32 rfind(char c, u32 offset = 0) const;
|
||||
|
||||
// searches for a string inside a string
|
||||
// rfind is the same except it starts at the end instead of the start
|
||||
// returns -1 if it is not found, otherwise the offset in the string
|
||||
s32 find(const char* str, u32 offset = 0) const;
|
||||
|
||||
// returns the number of instances of the specified character
|
||||
u32 count(char ch) const;
|
||||
|
||||
// removes characters from string
|
||||
void erase(s32 offset, s32 count = std::numeric_limits<s32>::max());
|
||||
|
||||
// alters the length of the string to be at least len bytes long
|
||||
void reserve(u32 new_reserve);
|
||||
|
||||
// Cuts characters off the string to reduce it to len bytes long.
|
||||
void resize(u32 new_size, char fill = ' ', bool shrink_if_smaller = false);
|
||||
|
||||
// updates the internal length counter when the string is externally modified
|
||||
void update_size();
|
||||
|
||||
// shrink the string to the minimum size possible
|
||||
void shrink_to_fit();
|
||||
|
||||
// gets the size of the string
|
||||
__fi u32 length() const { return m_length; }
|
||||
__fi bool empty() const { return (m_length == 0); }
|
||||
|
||||
// gets the maximum number of bytes we can write to the string, currently
|
||||
__fi u32 buffer_size() const { return m_buffer_size; }
|
||||
|
||||
// gets a constant pointer to the C string
|
||||
__fi const char* c_str() const { return m_buffer; }
|
||||
|
||||
// gets a writable char array, do not write more than reserve characters to it.
|
||||
__fi char* data() { return m_buffer; }
|
||||
|
||||
// returns the end of the string (pointer is past the last character)
|
||||
__fi const char* end_ptr() const { return m_buffer + m_length; }
|
||||
|
||||
// STL adapters
|
||||
__fi void push_back(value_type val) { append(val); }
|
||||
|
||||
// returns a string view for this string
|
||||
std::string_view view() const;
|
||||
|
||||
// returns a substring view for this string
|
||||
std::string_view substr(s32 offset, s32 count) const;
|
||||
|
||||
// accessor operators
|
||||
__fi operator const char*() const { return c_str(); }
|
||||
__fi operator char*() { return data(); }
|
||||
__fi operator std::string_view() const { return view(); }
|
||||
|
||||
// comparative operators
|
||||
__fi bool operator==(const char* str) const { return equals(str); }
|
||||
__fi bool operator==(const SmallStringBase& str) const { return equals(str); }
|
||||
__fi bool operator==(const std::string_view str) const { return equals(str); }
|
||||
__fi bool operator==(const std::string& str) const { return equals(str); }
|
||||
__fi bool operator!=(const char* str) const { return !equals(str); }
|
||||
__fi bool operator!=(const SmallStringBase& str) const { return !equals(str); }
|
||||
__fi bool operator!=(const std::string_view str) const { return !equals(str); }
|
||||
__fi bool operator!=(const std::string& str) const { return !equals(str); }
|
||||
__fi bool operator<(const char* str) const { return (compare(str) < 0); }
|
||||
__fi bool operator<(const SmallStringBase& str) const { return (compare(str) < 0); }
|
||||
__fi bool operator<(const std::string_view str) const { return (compare(str) < 0); }
|
||||
__fi bool operator<(const std::string& str) const { return (compare(str) < 0); }
|
||||
__fi bool operator>(const char* str) const { return (compare(str) > 0); }
|
||||
__fi bool operator>(const SmallStringBase& str) const { return (compare(str) > 0); }
|
||||
__fi bool operator>(const std::string_view str) const { return (compare(str) > 0); }
|
||||
__fi bool operator>(const std::string& str) const { return (compare(str) > 0); }
|
||||
|
||||
SmallStringBase& operator=(const SmallStringBase& copy);
|
||||
SmallStringBase& operator=(const char* str);
|
||||
SmallStringBase& operator=(const std::string& str);
|
||||
SmallStringBase& operator=(const std::string_view str);
|
||||
SmallStringBase& operator=(SmallStringBase&& move);
|
||||
|
||||
protected:
|
||||
// Pointer to memory where the string is located
|
||||
char* m_buffer = nullptr;
|
||||
|
||||
// Length of the string located in pBuffer (in characters)
|
||||
u32 m_length = 0;
|
||||
|
||||
// Size of the buffer pointed to by pBuffer
|
||||
u32 m_buffer_size = 0;
|
||||
|
||||
// True if the string is dynamically allocated on the heap.
|
||||
bool m_on_heap = false;
|
||||
};
|
||||
|
||||
// stack-allocated string
|
||||
template <u32 L>
|
||||
class SmallStackString : public SmallStringBase
|
||||
{
|
||||
public:
|
||||
__fi SmallStackString() { init(); }
|
||||
|
||||
__fi SmallStackString(const char* str)
|
||||
{
|
||||
init();
|
||||
assign(str);
|
||||
}
|
||||
|
||||
__fi SmallStackString(const char* str, u32 length)
|
||||
{
|
||||
init();
|
||||
assign(str, length);
|
||||
}
|
||||
|
||||
__fi SmallStackString(const SmallStringBase& copy)
|
||||
{
|
||||
init();
|
||||
assign(copy);
|
||||
}
|
||||
|
||||
__fi SmallStackString(SmallStringBase&& move)
|
||||
{
|
||||
init();
|
||||
assign(move);
|
||||
}
|
||||
|
||||
__fi SmallStackString(const SmallStackString& copy)
|
||||
{
|
||||
init();
|
||||
assign(copy);
|
||||
}
|
||||
|
||||
__fi SmallStackString(SmallStackString&& move)
|
||||
{
|
||||
init();
|
||||
assign(move);
|
||||
}
|
||||
|
||||
__fi SmallStackString(const std::string_view sv)
|
||||
{
|
||||
init();
|
||||
assign(sv);
|
||||
}
|
||||
|
||||
__fi SmallStackString& operator=(const SmallStringBase& copy)
|
||||
{
|
||||
assign(copy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi SmallStackString& operator=(SmallStringBase&& move)
|
||||
{
|
||||
assign(move);
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi SmallStackString& operator=(const SmallStackString& copy)
|
||||
{
|
||||
assign(copy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi SmallStackString& operator=(SmallStackString&& move)
|
||||
{
|
||||
assign(move);
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi SmallStackString& operator=(const std::string_view sv)
|
||||
{
|
||||
assign(sv);
|
||||
return *this;
|
||||
}
|
||||
|
||||
__fi SmallStackString& operator=(const char* str)
|
||||
{
|
||||
assign(str);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Override the fromstring method
|
||||
static SmallStackString from_sprintf(const char* format, ...) /*printflike(1, 2)*/;
|
||||
|
||||
template <typename... T>
|
||||
static SmallStackString from_format(fmt::format_string<T...> fmt, T&&... args);
|
||||
|
||||
static SmallStackString from_vformat(fmt::string_view fmt, fmt::format_args args);
|
||||
|
||||
private:
|
||||
char m_stack_buffer[L + 1];
|
||||
|
||||
__fi void init()
|
||||
{
|
||||
m_buffer = m_stack_buffer;
|
||||
m_buffer_size = L + 1;
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::memset(m_stack_buffer, 0, sizeof(m_stack_buffer));
|
||||
#else
|
||||
m_stack_buffer[0] = '\0';
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4459) // warning C4459: declaration of 'uint' hides global declaration
|
||||
#endif
|
||||
|
||||
template <u32 L>
|
||||
SmallStackString<L> SmallStackString<L>::from_sprintf(const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, format);
|
||||
|
||||
SmallStackString ret;
|
||||
ret.vsprintf(format, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <u32 L>
|
||||
template <typename... T>
|
||||
__fi SmallStackString<L> SmallStackString<L>::from_format(fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
SmallStackString<L> ret;
|
||||
fmt::vformat_to(std::back_inserter(ret), fmt, fmt::make_format_args(args...));
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <u32 L>
|
||||
__fi SmallStackString<L> SmallStackString<L>::from_vformat(fmt::string_view fmt, fmt::format_args args)
|
||||
{
|
||||
SmallStackString<L> ret;
|
||||
fmt::vformat_to(std::back_inserter(ret), fmt, args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// stack string types
|
||||
using TinyString = SmallStackString<64>;
|
||||
using SmallString = SmallStackString<256>;
|
||||
|
||||
template <typename... T>
|
||||
__fi void SmallStringBase::append_format(fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
fmt::vformat_to(std::back_inserter(*this), fmt, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
__fi void SmallStringBase::prepend_format(fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
TinyString str;
|
||||
fmt::vformat_to(std::back_inserter(str), fmt, fmt::make_format_args(args...));
|
||||
prepend(str);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
__fi void SmallStringBase::format(fmt::format_string<T...> fmt, T&&... args)
|
||||
{
|
||||
clear();
|
||||
fmt::vformat_to(std::back_inserter(*this), fmt, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#define MAKE_FORMATTER(type) \
|
||||
template <> \
|
||||
struct fmt::formatter<type> \
|
||||
{ \
|
||||
template <typename ParseContext> \
|
||||
constexpr auto parse(ParseContext& ctx) \
|
||||
{ \
|
||||
return ctx.begin(); \
|
||||
} \
|
||||
\
|
||||
template <typename FormatContext> \
|
||||
auto format(const type& str, FormatContext& ctx) const \
|
||||
{ \
|
||||
return fmt::format_to(ctx.out(), "{}", str.view()); \
|
||||
} \
|
||||
};
|
||||
|
||||
MAKE_FORMATTER(TinyString);
|
||||
MAKE_FORMATTER(SmallString);
|
||||
|
||||
#undef MAKE_FORMATTER
|
||||
1229
common/StackWalker.cpp
Normal file
1229
common/StackWalker.cpp
Normal file
File diff suppressed because it is too large
Load Diff
181
common/StackWalker.h
Normal file
181
common/StackWalker.h
Normal file
@@ -0,0 +1,181 @@
|
||||
/**********************************************************************
|
||||
*
|
||||
* StackWalker.h
|
||||
*
|
||||
*
|
||||
*
|
||||
* LICENSE (http://www.opensource.org/licenses/bsd-license.php)
|
||||
*
|
||||
* Copyright (c) 2005-2009, Jochen Kalmbach
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* Neither the name of Jochen Kalmbach nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* **********************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include "RedtapeWindows.h"
|
||||
|
||||
class StackWalkerInternal; // forward
|
||||
class StackWalker
|
||||
{
|
||||
public:
|
||||
typedef enum StackWalkOptions
|
||||
{
|
||||
// No addition info will be retrieved
|
||||
// (only the address is available)
|
||||
RetrieveNone = 0,
|
||||
|
||||
// Try to get the symbol-name
|
||||
RetrieveSymbol = 1,
|
||||
|
||||
// Try to get the line for this symbol
|
||||
RetrieveLine = 2,
|
||||
|
||||
// Try to retrieve the module-infos
|
||||
RetrieveModuleInfo = 4,
|
||||
|
||||
// Also retrieve the version for the DLL/EXE
|
||||
RetrieveFileVersion = 8,
|
||||
|
||||
// Contains all the above
|
||||
RetrieveVerbose = 0xF,
|
||||
|
||||
// Generate a "good" symbol-search-path
|
||||
SymBuildPath = 0x10,
|
||||
|
||||
// Also use the public Microsoft-Symbol-Server
|
||||
SymUseSymSrv = 0x20,
|
||||
|
||||
// Contains all the above "Sym"-options
|
||||
SymAll = 0x30,
|
||||
|
||||
// Contains all options (default)
|
||||
OptionsAll = 0x3F
|
||||
} StackWalkOptions;
|
||||
|
||||
StackWalker(int options = OptionsAll, // 'int' is by design, to combine the enum-flags
|
||||
LPCSTR szSymPath = NULL,
|
||||
DWORD dwProcessId = GetCurrentProcessId(),
|
||||
HANDLE hProcess = GetCurrentProcess());
|
||||
StackWalker(DWORD dwProcessId, HANDLE hProcess);
|
||||
StackWalker(const StackWalker&) = delete;
|
||||
virtual ~StackWalker();
|
||||
|
||||
static HMODULE LoadDbgHelpLibrary();
|
||||
|
||||
typedef BOOL(__stdcall* PReadProcessMemoryRoutine)(
|
||||
HANDLE hProcess,
|
||||
DWORD64 qwBaseAddress,
|
||||
PVOID lpBuffer,
|
||||
DWORD nSize,
|
||||
LPDWORD lpNumberOfBytesRead,
|
||||
LPVOID pUserData // optional data, which was passed in "ShowCallstack"
|
||||
);
|
||||
|
||||
BOOL LoadModules();
|
||||
|
||||
BOOL ShowCallstack(
|
||||
HANDLE hThread = GetCurrentThread(),
|
||||
const CONTEXT* context = NULL,
|
||||
PReadProcessMemoryRoutine readMemoryFunction = NULL,
|
||||
LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback
|
||||
);
|
||||
|
||||
BOOL ShowObject(LPVOID pObject);
|
||||
|
||||
#if _MSC_VER >= 1300
|
||||
// due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public"
|
||||
// in older compilers in order to use it... starting with VC7 we can declare it as "protected"
|
||||
protected:
|
||||
#endif
|
||||
enum
|
||||
{
|
||||
STACKWALK_MAX_NAMELEN = 1024
|
||||
}; // max name length for found symbols
|
||||
|
||||
protected:
|
||||
// Entry for each Callstack-Entry
|
||||
typedef struct CallstackEntry
|
||||
{
|
||||
DWORD64 offset; // if 0, we have no valid entry
|
||||
CHAR name[STACKWALK_MAX_NAMELEN];
|
||||
CHAR undName[STACKWALK_MAX_NAMELEN];
|
||||
CHAR undFullName[STACKWALK_MAX_NAMELEN];
|
||||
DWORD64 offsetFromSmybol;
|
||||
DWORD offsetFromLine;
|
||||
DWORD lineNumber;
|
||||
CHAR lineFileName[STACKWALK_MAX_NAMELEN];
|
||||
DWORD symType;
|
||||
LPCSTR symTypeString;
|
||||
CHAR moduleName[STACKWALK_MAX_NAMELEN];
|
||||
DWORD64 baseOfImage;
|
||||
CHAR loadedImageName[STACKWALK_MAX_NAMELEN];
|
||||
} CallstackEntry;
|
||||
|
||||
typedef enum CallstackEntryType
|
||||
{
|
||||
firstEntry,
|
||||
nextEntry,
|
||||
lastEntry
|
||||
} CallstackEntryType;
|
||||
|
||||
virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
|
||||
virtual void OnLoadModule(LPCSTR img,
|
||||
LPCSTR mod,
|
||||
DWORD64 baseAddr,
|
||||
DWORD size,
|
||||
DWORD result,
|
||||
LPCSTR symType,
|
||||
LPCSTR pdbName,
|
||||
ULONGLONG fileVersion);
|
||||
virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry& entry);
|
||||
virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr);
|
||||
virtual void OnOutput(LPCSTR szText);
|
||||
|
||||
StackWalkerInternal* m_sw;
|
||||
HANDLE m_hProcess;
|
||||
DWORD m_dwProcessId;
|
||||
BOOL m_modulesLoaded;
|
||||
LPSTR m_szSymPath;
|
||||
|
||||
int m_options;
|
||||
int m_MaxRecursionCount;
|
||||
|
||||
static BOOL __stdcall myReadProcMem(HANDLE hProcess,
|
||||
DWORD64 qwBaseAddress,
|
||||
PVOID lpBuffer,
|
||||
DWORD nSize,
|
||||
LPDWORD lpNumberOfBytesRead);
|
||||
|
||||
friend StackWalkerInternal;
|
||||
}; // class StackWalker
|
||||
|
||||
// The following is defined for x86 (XP and higher), x64 and IA64:
|
||||
#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \
|
||||
do \
|
||||
{ \
|
||||
memset(&c, 0, sizeof(CONTEXT)); \
|
||||
c.ContextFlags = contextFlags; \
|
||||
RtlCaptureContext(&c); \
|
||||
} while (0);
|
||||
544
common/StringUtil.cpp
Normal file
544
common/StringUtil.cpp
Normal file
@@ -0,0 +1,544 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "Assertions.h"
|
||||
#include "StringUtil.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <codecvt>
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "RedtapeWindows.h"
|
||||
#endif
|
||||
|
||||
namespace StringUtil
|
||||
{
|
||||
std::string StdStringFromFormat(const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, format);
|
||||
std::string ret = StdStringFromFormatV(format, ap);
|
||||
va_end(ap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string StdStringFromFormatV(const char* format, std::va_list ap)
|
||||
{
|
||||
std::va_list ap_copy;
|
||||
va_copy(ap_copy, ap);
|
||||
|
||||
#ifdef _WIN32
|
||||
int len = _vscprintf(format, ap_copy);
|
||||
#else
|
||||
int len = std::vsnprintf(nullptr, 0, format, ap_copy);
|
||||
#endif
|
||||
va_end(ap_copy);
|
||||
|
||||
std::string ret;
|
||||
|
||||
// If an encoding error occurs, len is -1. Which we definitely don't want to resize to.
|
||||
if (len > 0)
|
||||
{
|
||||
ret.resize(len);
|
||||
std::vsnprintf(ret.data(), ret.size() + 1, format, ap);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool WildcardMatch(const char* subject, const char* mask, bool case_sensitive /*= true*/)
|
||||
{
|
||||
if (case_sensitive)
|
||||
{
|
||||
const char* cp = nullptr;
|
||||
const char* mp = nullptr;
|
||||
|
||||
while ((*subject) && (*mask != '*'))
|
||||
{
|
||||
if ((*mask != '?') && (std::tolower(*mask) != std::tolower(*subject)))
|
||||
return false;
|
||||
|
||||
mask++;
|
||||
subject++;
|
||||
}
|
||||
|
||||
while (*subject)
|
||||
{
|
||||
if (*mask == '*')
|
||||
{
|
||||
if (*++mask == 0)
|
||||
return true;
|
||||
|
||||
mp = mask;
|
||||
cp = subject + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((*mask == '?') || (std::tolower(*mask) == std::tolower(*subject)))
|
||||
{
|
||||
mask++;
|
||||
subject++;
|
||||
}
|
||||
else
|
||||
{
|
||||
mask = mp;
|
||||
subject = cp++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (*mask == '*')
|
||||
{
|
||||
mask++;
|
||||
}
|
||||
|
||||
return *mask == 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* cp = nullptr;
|
||||
const char* mp = nullptr;
|
||||
|
||||
while ((*subject) && (*mask != '*'))
|
||||
{
|
||||
if ((*mask != *subject) && (*mask != '?'))
|
||||
return false;
|
||||
|
||||
mask++;
|
||||
subject++;
|
||||
}
|
||||
|
||||
while (*subject)
|
||||
{
|
||||
if (*mask == '*')
|
||||
{
|
||||
if (*++mask == 0)
|
||||
return true;
|
||||
|
||||
mp = mask;
|
||||
cp = subject + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((*mask == *subject) || (*mask == '?'))
|
||||
{
|
||||
mask++;
|
||||
subject++;
|
||||
}
|
||||
else
|
||||
{
|
||||
mask = mp;
|
||||
subject = cp++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (*mask == '*')
|
||||
{
|
||||
mask++;
|
||||
}
|
||||
|
||||
return *mask == 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t Strlcpy(char* dst, const char* src, std::size_t size)
|
||||
{
|
||||
std::size_t len = std::strlen(src);
|
||||
if (len < size)
|
||||
{
|
||||
std::memcpy(dst, src, len + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(dst, src, size - 1);
|
||||
dst[size - 1] = '\0';
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
std::size_t Strlcpy(char* dst, const std::string_view src, std::size_t size)
|
||||
{
|
||||
std::size_t len = src.length();
|
||||
if (len < size)
|
||||
{
|
||||
std::memcpy(dst, src.data(), len);
|
||||
dst[len] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(dst, src.data(), size - 1);
|
||||
dst[size - 1] = '\0';
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
std::optional<std::vector<u8>> DecodeHex(const std::string_view in)
|
||||
{
|
||||
std::vector<u8> data;
|
||||
data.reserve(in.size() / 2);
|
||||
|
||||
for (size_t i = 0; i < in.size() / 2; i++)
|
||||
{
|
||||
std::optional<u8> byte = StringUtil::FromChars<u8>(in.substr(i * 2, 2), 16);
|
||||
if (byte.has_value())
|
||||
data.push_back(*byte);
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return {data};
|
||||
}
|
||||
|
||||
std::string EncodeHex(const u8* data, int length)
|
||||
{
|
||||
std::stringstream ss;
|
||||
for (int i = 0; i < length; i++)
|
||||
ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(data[i]);
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string toLower(const std::string_view input)
|
||||
{
|
||||
std::string newStr;
|
||||
std::transform(input.begin(), input.end(), std::back_inserter(newStr),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
return newStr;
|
||||
}
|
||||
|
||||
std::string toUpper(const std::string_view input)
|
||||
{
|
||||
std::string newStr;
|
||||
std::transform(input.begin(), input.end(), std::back_inserter(newStr),
|
||||
[](unsigned char c) { return std::toupper(c); });
|
||||
return newStr;
|
||||
}
|
||||
|
||||
bool compareNoCase(const std::string_view str1, const std::string_view str2)
|
||||
{
|
||||
if (str1.length() != str2.length())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Strncasecmp(str1.data(), str2.data(), str1.length()) == 0;
|
||||
}
|
||||
|
||||
std::vector<std::string> splitOnNewLine(const std::string& str)
|
||||
{
|
||||
std::vector<std::string> lines;
|
||||
std::istringstream stream(str);
|
||||
std::string line;
|
||||
while (std::getline(stream, line))
|
||||
{
|
||||
lines.push_back(line);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
std::string_view StripWhitespace(const std::string_view str)
|
||||
{
|
||||
std::string_view::size_type start = 0;
|
||||
while (start < str.size() && std::isspace(str[start]))
|
||||
start++;
|
||||
if (start == str.size())
|
||||
return {};
|
||||
|
||||
std::string_view::size_type end = str.size() - 1;
|
||||
while (end > start && std::isspace(str[end]))
|
||||
end--;
|
||||
|
||||
return str.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
void StripWhitespace(std::string* str)
|
||||
{
|
||||
{
|
||||
const char* cstr = str->c_str();
|
||||
std::string_view::size_type start = 0;
|
||||
while (start < str->size() && std::isspace(cstr[start]))
|
||||
start++;
|
||||
if (start != 0)
|
||||
str->erase(0, start);
|
||||
}
|
||||
|
||||
{
|
||||
const char* cstr = str->c_str();
|
||||
std::string_view::size_type start = str->size();
|
||||
while (start > 0 && std::isspace(cstr[start - 1]))
|
||||
start--;
|
||||
if (start != str->size())
|
||||
str->erase(start);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string_view> SplitString(const std::string_view str, char delimiter, bool skip_empty /*= true*/)
|
||||
{
|
||||
std::vector<std::string_view> res;
|
||||
std::string_view::size_type last_pos = 0;
|
||||
std::string_view::size_type pos;
|
||||
while (last_pos < str.size() && (pos = str.find(delimiter, last_pos)) != std::string_view::npos)
|
||||
{
|
||||
std::string_view part(StripWhitespace(str.substr(last_pos, pos - last_pos)));
|
||||
if (!skip_empty || !part.empty())
|
||||
res.push_back(std::move(part));
|
||||
|
||||
last_pos = pos + 1;
|
||||
}
|
||||
|
||||
if (last_pos < str.size())
|
||||
{
|
||||
std::string_view part(StripWhitespace(str.substr(last_pos)));
|
||||
if (!skip_empty || !part.empty())
|
||||
res.push_back(std::move(part));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string ReplaceAll(const std::string_view subject, const std::string_view search, const std::string_view replacement)
|
||||
{
|
||||
std::string ret(subject);
|
||||
ReplaceAll(&ret, search, replacement);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ReplaceAll(std::string* subject, const std::string_view search, const std::string_view replacement)
|
||||
{
|
||||
if (!subject->empty())
|
||||
{
|
||||
std::string::size_type start_pos = 0;
|
||||
while ((start_pos = subject->find(search, start_pos)) != std::string::npos)
|
||||
{
|
||||
subject->replace(start_pos, search.length(), replacement);
|
||||
start_pos += replacement.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ParseAssignmentString(const std::string_view str, std::string_view* key, std::string_view* value)
|
||||
{
|
||||
const std::string_view::size_type pos = str.find('=');
|
||||
if (pos == std::string_view::npos)
|
||||
{
|
||||
*key = std::string_view();
|
||||
*value = std::string_view();
|
||||
return false;
|
||||
}
|
||||
|
||||
*key = StripWhitespace(str.substr(0, pos));
|
||||
if (pos != (str.size() - 1))
|
||||
*value = StripWhitespace(str.substr(pos + 1));
|
||||
else
|
||||
*value = std::string_view();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppendUTF16CharacterToUTF8(std::string& s, u16 ch)
|
||||
{
|
||||
if (ch & 0xf800)
|
||||
{
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0xe0 | static_cast<u8>(ch >> 12))));
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0x80 | static_cast<u8>(((ch >> 6) & 0x3f)))));
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0x80 | static_cast<u8>((ch & 0x3f)))));
|
||||
}
|
||||
else if (ch & 0xff80)
|
||||
{
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0xc0 | static_cast<u8>((ch >> 6)))));
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0x80 | static_cast<u8>((ch & 0x3f)))));
|
||||
}
|
||||
else
|
||||
{
|
||||
s.push_back(static_cast<char>(static_cast<u8>(ch)));
|
||||
}
|
||||
}
|
||||
|
||||
void EncodeAndAppendUTF8(std::string& s, char32_t ch)
|
||||
{
|
||||
if (ch <= 0x7F)
|
||||
{
|
||||
s.push_back(static_cast<char>(static_cast<u8>(ch)));
|
||||
}
|
||||
else if (ch <= 0x07FF)
|
||||
{
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0xc0 | static_cast<u8>((ch >> 6) & 0x1f))));
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0x80 | static_cast<u8>((ch & 0x3f)))));
|
||||
}
|
||||
else if (ch <= 0xFFFF)
|
||||
{
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0xe0 | static_cast<u8>(((ch >> 12) & 0x0f)))));
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0x80 | static_cast<u8>(((ch >> 6) & 0x3f)))));
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0x80 | static_cast<u8>((ch & 0x3f)))));
|
||||
}
|
||||
else if (ch <= 0x10FFFF)
|
||||
{
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0xf0 | static_cast<u8>(((ch >> 18) & 0x07)))));
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0x80 | static_cast<u8>(((ch >> 12) & 0x3f)))));
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0x80 | static_cast<u8>(((ch >> 6) & 0x3f)))));
|
||||
s.push_back(static_cast<char>(static_cast<u8>(0x80 | static_cast<u8>((ch & 0x3f)))));
|
||||
}
|
||||
else
|
||||
{
|
||||
s.push_back(static_cast<char>(0xefu));
|
||||
s.push_back(static_cast<char>(0xbfu));
|
||||
s.push_back(static_cast<char>(0xbdu));
|
||||
}
|
||||
}
|
||||
|
||||
size_t DecodeUTF8(const void* bytes, size_t length, char32_t* ch)
|
||||
{
|
||||
const u8* s = reinterpret_cast<const u8*>(bytes);
|
||||
if (s[0] < 0x80)
|
||||
{
|
||||
*ch = s[0];
|
||||
return 1;
|
||||
}
|
||||
else if ((s[0] & 0xe0) == 0xc0)
|
||||
{
|
||||
if (length < 2)
|
||||
goto invalid;
|
||||
|
||||
*ch = static_cast<char32_t>((static_cast<u32>(s[0] & 0x1f) << 6) | (static_cast<u32>(s[1] & 0x3f) << 0));
|
||||
return 2;
|
||||
}
|
||||
else if ((s[0] & 0xf0) == 0xe0)
|
||||
{
|
||||
if (length < 3)
|
||||
goto invalid;
|
||||
|
||||
*ch = static_cast<char32_t>((static_cast<u32>(s[0] & 0x0f) << 12) | (static_cast<u32>(s[1] & 0x3f) << 6) |
|
||||
(static_cast<u32>(s[2] & 0x3f) << 0));
|
||||
return 3;
|
||||
}
|
||||
else if ((s[0] & 0xf8) == 0xf0 && (s[0] <= 0xf4))
|
||||
{
|
||||
if (length < 4)
|
||||
goto invalid;
|
||||
|
||||
*ch = static_cast<char32_t>((static_cast<u32>(s[0] & 0x07) << 18) | (static_cast<u32>(s[1] & 0x3f) << 12) |
|
||||
(static_cast<u32>(s[2] & 0x3f) << 6) | (static_cast<u32>(s[3] & 0x3f) << 0));
|
||||
return 4;
|
||||
}
|
||||
|
||||
invalid:
|
||||
*ch = 0xFFFFFFFFu;
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t DecodeUTF8(const std::string_view str, size_t offset, char32_t* ch)
|
||||
{
|
||||
return DecodeUTF8(str.data() + offset, str.length() - offset, ch);
|
||||
}
|
||||
|
||||
size_t DecodeUTF8(const std::string& str, size_t offset, char32_t* ch)
|
||||
{
|
||||
return DecodeUTF8(str.data() + offset, str.length() - offset, ch);
|
||||
}
|
||||
|
||||
std::string Ellipsise(const std::string_view str, u32 max_length, const char* ellipsis /*= "..."*/)
|
||||
{
|
||||
std::string ret;
|
||||
ret.reserve(max_length);
|
||||
|
||||
const u32 str_length = static_cast<u32>(str.length());
|
||||
const u32 ellipsis_len = static_cast<u32>(std::strlen(ellipsis));
|
||||
pxAssert(ellipsis_len > 0 && ellipsis_len <= max_length);
|
||||
|
||||
if (str_length > max_length)
|
||||
{
|
||||
const u32 copy_size = std::min(str_length, max_length - ellipsis_len);
|
||||
if (copy_size > 0)
|
||||
ret.append(str.data(), copy_size);
|
||||
if (copy_size != str_length)
|
||||
ret.append(ellipsis);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.append(str);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void EllipsiseInPlace(std::string& str, u32 max_length, const char* ellipsis /*= "..."*/)
|
||||
{
|
||||
const u32 str_length = static_cast<u32>(str.length());
|
||||
const u32 ellipsis_len = static_cast<u32>(std::strlen(ellipsis));
|
||||
pxAssert(ellipsis_len > 0 && ellipsis_len <= max_length);
|
||||
|
||||
if (str_length > max_length)
|
||||
{
|
||||
const u32 keep_size = std::min(static_cast<u32>(str.length()), max_length - ellipsis_len);
|
||||
if (keep_size != str_length)
|
||||
str.erase(keep_size);
|
||||
|
||||
str.append(ellipsis);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
std::wstring UTF8StringToWideString(const std::string_view str)
|
||||
{
|
||||
std::wstring ret;
|
||||
if (!UTF8StringToWideString(ret, str))
|
||||
return {};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool UTF8StringToWideString(std::wstring& dest, const std::string_view str)
|
||||
{
|
||||
int wlen = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.length()), nullptr, 0);
|
||||
if (wlen < 0)
|
||||
return false;
|
||||
|
||||
dest.resize(wlen);
|
||||
if (wlen > 0 && MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.length()), dest.data(), wlen) < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string WideStringToUTF8String(const std::wstring_view& str)
|
||||
{
|
||||
std::string ret;
|
||||
if (!WideStringToUTF8String(ret, str))
|
||||
ret.clear();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool WideStringToUTF8String(std::string& dest, const std::wstring_view& str)
|
||||
{
|
||||
int mblen = WideCharToMultiByte(CP_UTF8, 0, str.data(), static_cast<int>(str.length()), nullptr, 0, nullptr, nullptr);
|
||||
if (mblen < 0)
|
||||
return false;
|
||||
|
||||
dest.resize(mblen);
|
||||
if (mblen > 0 && WideCharToMultiByte(CP_UTF8, 0, str.data(), static_cast<int>(str.length()), dest.data(), mblen,
|
||||
nullptr, nullptr) < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string U128ToString(const u128& u)
|
||||
{
|
||||
return fmt::format("0x{:08X}.{:08X}.{:08X}.{:08X}", u._u32[0], u._u32[1], u._u32[2], u._u32[3]);
|
||||
}
|
||||
|
||||
std::string& AppendU128ToString(const u128& u, std::string& s)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(s), "0x{:08X}.{:08X}.{:08X}.{:08X}", u._u32[0], u._u32[1], u._u32[2], u._u32[3]);
|
||||
return s;
|
||||
}
|
||||
} // namespace StringUtil
|
||||
345
common/StringUtil.h
Normal file
345
common/StringUtil.h
Normal file
@@ -0,0 +1,345 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include "Pcsx2Types.h"
|
||||
#include <algorithm>
|
||||
#include <charconv>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
// Work around us defining _M_ARM64 but fast_float thinking that it means MSVC.
|
||||
#if defined(_M_ARM64) && !defined(_WIN32)
|
||||
#define HAD_M_ARM64 _M_ARM64
|
||||
#undef _M_ARM64
|
||||
#endif
|
||||
#include "fast_float/fast_float.h"
|
||||
#if defined(HAD_M_ARM64) && !defined(_WIN32)
|
||||
#define _M_ARM64 HAD_M_ARM64
|
||||
#undef HAD_M_ARM64
|
||||
#endif
|
||||
|
||||
// Older versions of libstdc++ are missing support for from_chars() with floats, and was only recently
|
||||
// merged in libc++. So, just fall back to stringstream (yuck!) on everywhere except MSVC.
|
||||
#if !defined(_MSC_VER)
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
#ifdef __APPLE__
|
||||
#include <Availability.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace StringUtil
|
||||
{
|
||||
/// Constructs a std::string from a format string.
|
||||
#ifdef __GNUC__
|
||||
std::string StdStringFromFormat(const char* format, ...) __attribute__((format(printf, 1, 2)));
|
||||
#else
|
||||
std::string StdStringFromFormat(const char* format, ...);
|
||||
#endif
|
||||
std::string StdStringFromFormatV(const char* format, std::va_list ap);
|
||||
|
||||
/// Checks if a wildcard matches a search string.
|
||||
bool WildcardMatch(const char* subject, const char* mask, bool case_sensitive = true);
|
||||
|
||||
/// Safe version of strlcpy.
|
||||
std::size_t Strlcpy(char* dst, const char* src, std::size_t size);
|
||||
|
||||
/// Strlcpy from string_view.
|
||||
std::size_t Strlcpy(char* dst, const std::string_view src, std::size_t size);
|
||||
|
||||
/// Platform-independent strcasecmp
|
||||
static inline int Strcasecmp(const char* s1, const char* s2)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return _stricmp(s1, s2);
|
||||
#else
|
||||
return strcasecmp(s1, s2);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Platform-independent strcasecmp
|
||||
static inline int Strncasecmp(const char* s1, const char* s2, std::size_t n)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return _strnicmp(s1, s2, n);
|
||||
#else
|
||||
return strncasecmp(s1, s2, n);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Wrapper around std::from_chars
|
||||
template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
|
||||
inline std::optional<T> FromChars(const std::string_view str, int base = 10)
|
||||
{
|
||||
T value;
|
||||
|
||||
const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), value, base);
|
||||
if (result.ec != std::errc())
|
||||
return std::nullopt;
|
||||
|
||||
return value;
|
||||
}
|
||||
template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
|
||||
inline std::optional<T> FromChars(const std::string_view str, int base, std::string_view* endptr)
|
||||
{
|
||||
T value;
|
||||
|
||||
const char* ptr = str.data();
|
||||
const char* end = ptr + str.length();
|
||||
const std::from_chars_result result = std::from_chars(ptr, end, value, base);
|
||||
if (result.ec != std::errc())
|
||||
return std::nullopt;
|
||||
|
||||
if (endptr)
|
||||
*endptr = (result.ptr < end) ? std::string_view(result.ptr, end - result.ptr) : std::string_view();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
|
||||
inline std::optional<T> FromChars(const std::string_view str)
|
||||
{
|
||||
T value;
|
||||
|
||||
const fast_float::from_chars_result result = fast_float::from_chars(str.data(), str.data() + str.length(), value);
|
||||
if (result.ec != std::errc())
|
||||
return std::nullopt;
|
||||
|
||||
return value;
|
||||
}
|
||||
template <typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
|
||||
inline std::optional<T> FromChars(const std::string_view str, std::string_view* endptr)
|
||||
{
|
||||
T value;
|
||||
|
||||
const char* ptr = str.data();
|
||||
const char* end = ptr + str.length();
|
||||
const fast_float::from_chars_result result = fast_float::from_chars(ptr, end, value);
|
||||
if (result.ec != std::errc())
|
||||
return std::nullopt;
|
||||
|
||||
if (endptr)
|
||||
*endptr = (result.ptr < end) ? std::string_view(result.ptr, end - result.ptr) : std::string_view();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Wrapper around std::to_chars
|
||||
template <typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
|
||||
inline std::string ToChars(T value, int base = 10)
|
||||
{
|
||||
// to_chars() requires macOS 10.15+.
|
||||
#if !defined(__APPLE__) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15
|
||||
constexpr size_t MAX_SIZE = 32;
|
||||
char buf[MAX_SIZE];
|
||||
std::string ret;
|
||||
|
||||
const std::to_chars_result result = std::to_chars(buf, buf + MAX_SIZE, value, base);
|
||||
if (result.ec == std::errc())
|
||||
ret.append(buf, result.ptr - buf);
|
||||
|
||||
return ret;
|
||||
#else
|
||||
std::ostringstream ss;
|
||||
ss.imbue(std::locale::classic());
|
||||
ss << std::setbase(base) << value;
|
||||
return ss.str();
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
|
||||
inline std::string ToChars(T value)
|
||||
{
|
||||
// No to_chars() in older versions of libstdc++/libc++.
|
||||
#ifdef _MSC_VER
|
||||
constexpr size_t MAX_SIZE = 64;
|
||||
char buf[MAX_SIZE];
|
||||
std::string ret;
|
||||
const std::to_chars_result result = std::to_chars(buf, buf + MAX_SIZE, value);
|
||||
if (result.ec == std::errc())
|
||||
ret.append(buf, result.ptr - buf);
|
||||
return ret;
|
||||
#else
|
||||
std::ostringstream ss;
|
||||
ss.imbue(std::locale::classic());
|
||||
ss << value;
|
||||
return ss.str();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/// Explicit override for booleans
|
||||
template <>
|
||||
inline std::optional<bool> FromChars(const std::string_view str, int base)
|
||||
{
|
||||
if (Strncasecmp("true", str.data(), str.length()) == 0 || Strncasecmp("yes", str.data(), str.length()) == 0 ||
|
||||
Strncasecmp("on", str.data(), str.length()) == 0 || Strncasecmp("1", str.data(), str.length()) == 0 ||
|
||||
Strncasecmp("enabled", str.data(), str.length()) == 0 || Strncasecmp("1", str.data(), str.length()) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Strncasecmp("false", str.data(), str.length()) == 0 || Strncasecmp("no", str.data(), str.length()) == 0 ||
|
||||
Strncasecmp("off", str.data(), str.length()) == 0 || Strncasecmp("0", str.data(), str.length()) == 0 ||
|
||||
Strncasecmp("disabled", str.data(), str.length()) == 0 || Strncasecmp("0", str.data(), str.length()) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string ToChars(bool value, int base)
|
||||
{
|
||||
return std::string(value ? "true" : "false");
|
||||
}
|
||||
|
||||
/// Encode/decode hexadecimal byte buffers
|
||||
std::optional<std::vector<u8>> DecodeHex(const std::string_view str);
|
||||
std::string EncodeHex(const u8* data, int length);
|
||||
|
||||
/// StartsWith/EndsWith variants which aren't case sensitive.
|
||||
static inline bool StartsWithNoCase(const std::string_view str, const std::string_view prefix)
|
||||
{
|
||||
return (!str.empty() && Strncasecmp(str.data(), prefix.data(), prefix.length()) == 0);
|
||||
}
|
||||
static inline bool EndsWithNoCase(const std::string_view str, const std::string_view suffix)
|
||||
{
|
||||
const std::size_t suffix_length = suffix.length();
|
||||
return (str.length() >= suffix_length && Strncasecmp(str.data() + (str.length() - suffix_length), suffix.data(), suffix_length) == 0);
|
||||
}
|
||||
|
||||
/// Strip whitespace from the start/end of the string.
|
||||
std::string_view StripWhitespace(const std::string_view str);
|
||||
void StripWhitespace(std::string* str);
|
||||
|
||||
/// Splits a string based on a single character delimiter.
|
||||
std::vector<std::string_view> SplitString(const std::string_view str, char delimiter, bool skip_empty = true);
|
||||
|
||||
/// Joins a string together using the specified delimiter.
|
||||
template <typename T>
|
||||
static inline std::string JoinString(const T& start, const T& end, char delimiter)
|
||||
{
|
||||
std::string ret;
|
||||
for (auto it = start; it != end; ++it)
|
||||
{
|
||||
if (it != start)
|
||||
ret += delimiter;
|
||||
ret.append(*it);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
template <typename T>
|
||||
static inline std::string JoinString(const T& start, const T& end, const std::string_view delimiter)
|
||||
{
|
||||
std::string ret;
|
||||
for (auto it = start; it != end; ++it)
|
||||
{
|
||||
if (it != start)
|
||||
ret.append(delimiter);
|
||||
ret.append(*it);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Replaces all instances of search in subject with replacement.
|
||||
std::string ReplaceAll(const std::string_view subject, const std::string_view search, const std::string_view replacement);
|
||||
void ReplaceAll(std::string* subject, const std::string_view search, const std::string_view replacement);
|
||||
|
||||
/// Parses an assignment string (Key = Value) into its two components.
|
||||
bool ParseAssignmentString(const std::string_view str, std::string_view* key, std::string_view* value);
|
||||
|
||||
/// Appends a UTF-16/UTF-32 codepoint to a UTF-8 string.
|
||||
void AppendUTF16CharacterToUTF8(std::string& s, u16 ch);
|
||||
|
||||
/// Appends a UTF-16/UTF-32 codepoint to a UTF-8 string.
|
||||
void EncodeAndAppendUTF8(std::string& s, char32_t ch);
|
||||
|
||||
/// Decodes UTF-8 to a single codepoint, updating the position parameter.
|
||||
/// Returns the number of bytes the codepoint took in the original string.
|
||||
size_t DecodeUTF8(const void* bytes, size_t length, char32_t* ch);
|
||||
size_t DecodeUTF8(const std::string_view str, size_t offset, char32_t* ch);
|
||||
size_t DecodeUTF8(const std::string& str, size_t offset, char32_t* ch);
|
||||
|
||||
// Replaces the end of a string with ellipsis if it exceeds the specified length.
|
||||
std::string Ellipsise(const std::string_view str, u32 max_length, const char* ellipsis = "...");
|
||||
void EllipsiseInPlace(std::string& str, u32 max_length, const char* ellipsis = "...");
|
||||
|
||||
/// Strided memcpy/memcmp.
|
||||
static inline void StrideMemCpy(void* dst, std::size_t dst_stride, const void* src, std::size_t src_stride,
|
||||
std::size_t copy_size, std::size_t count)
|
||||
{
|
||||
if (src_stride == dst_stride && src_stride == copy_size)
|
||||
{
|
||||
std::memcpy(dst, src, src_stride * count);
|
||||
return;
|
||||
}
|
||||
|
||||
const u8* src_ptr = static_cast<const u8*>(src);
|
||||
u8* dst_ptr = static_cast<u8*>(dst);
|
||||
for (std::size_t i = 0; i < count; i++)
|
||||
{
|
||||
std::memcpy(dst_ptr, src_ptr, copy_size);
|
||||
src_ptr += src_stride;
|
||||
dst_ptr += dst_stride;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int StrideMemCmp(const void* p1, std::size_t p1_stride, const void* p2, std::size_t p2_stride,
|
||||
std::size_t copy_size, std::size_t count)
|
||||
{
|
||||
if (p1_stride == p2_stride && p1_stride == copy_size)
|
||||
return std::memcmp(p1, p2, p1_stride * count);
|
||||
|
||||
const u8* p1_ptr = static_cast<const u8*>(p1);
|
||||
const u8* p2_ptr = static_cast<const u8*>(p2);
|
||||
for (std::size_t i = 0; i < count; i++)
|
||||
{
|
||||
int result = std::memcmp(p1_ptr, p2_ptr, copy_size);
|
||||
if (result != 0)
|
||||
return result;
|
||||
p2_ptr += p2_stride;
|
||||
p1_ptr += p1_stride;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string toLower(const std::string_view str);
|
||||
std::string toUpper(const std::string_view str);
|
||||
bool compareNoCase(const std::string_view str1, const std::string_view str2);
|
||||
std::vector<std::string> splitOnNewLine(const std::string& str);
|
||||
|
||||
#ifdef _WIN32
|
||||
/// Converts the specified UTF-8 string to a wide string.
|
||||
std::wstring UTF8StringToWideString(const std::string_view str);
|
||||
bool UTF8StringToWideString(std::wstring& dest, const std::string_view str);
|
||||
|
||||
/// Converts the specified wide string to a UTF-8 string.
|
||||
std::string WideStringToUTF8String(const std::wstring_view& str);
|
||||
bool WideStringToUTF8String(std::string& dest, const std::wstring_view& str);
|
||||
#endif
|
||||
|
||||
/// Converts unsigned 128-bit data to string.
|
||||
std::string U128ToString(const u128& u);
|
||||
std::string& AppendU128ToString(const u128& u, std::string& s);
|
||||
|
||||
template <typename ContainerType>
|
||||
static inline bool ContainsSubString(const ContainerType& haystack, const std::string_view needle)
|
||||
{
|
||||
using ValueType = typename ContainerType::value_type;
|
||||
if (needle.empty())
|
||||
return std::empty(haystack);
|
||||
|
||||
return std::search(std::begin(haystack), std::end(haystack), reinterpret_cast<const ValueType*>(needle.data()),
|
||||
reinterpret_cast<const ValueType*>(needle.data() + needle.length())) != std::end(haystack);
|
||||
}
|
||||
} // namespace StringUtil
|
||||
1151
common/TextureDecompress.cpp
Normal file
1151
common/TextureDecompress.cpp
Normal file
File diff suppressed because it is too large
Load Diff
198
common/TextureDecompress.h
Normal file
198
common/TextureDecompress.h
Normal file
@@ -0,0 +1,198 @@
|
||||
// See TextureDecompress.cpp for license info.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
enum BC4Mode
|
||||
{
|
||||
BC4_UNORM = 0,
|
||||
BC4_SNORM = 1
|
||||
};
|
||||
|
||||
enum BC5Mode
|
||||
{
|
||||
BC5_UNORM = 0,
|
||||
BC5_SNORM = 1
|
||||
};
|
||||
|
||||
void DecompressBlockBC1(uint32_t x, uint32_t y, uint32_t stride,
|
||||
const uint8_t* blockStorage, unsigned char* image);
|
||||
void DecompressBlockBC2(uint32_t x, uint32_t y, uint32_t stride,
|
||||
const uint8_t* blockStorage, unsigned char* image);
|
||||
void DecompressBlockBC3(uint32_t x, uint32_t y, uint32_t stride,
|
||||
const uint8_t* blockStorage, unsigned char* image);
|
||||
void DecompressBlockBC4(uint32_t x, uint32_t y, uint32_t stride,
|
||||
enum BC4Mode mode, const uint8_t* blockStorage, unsigned char* image);
|
||||
void DecompressBlockBC5(uint32_t x, uint32_t y, uint32_t stride,
|
||||
enum BC5Mode mode, const uint8_t* blockStorage, unsigned char* image);
|
||||
|
||||
namespace bc7decomp
|
||||
{
|
||||
|
||||
enum eNoClamp { cNoClamp };
|
||||
|
||||
template <typename S> inline S clamp(S value, S low, S high) { return (value < low) ? low : ((value > high) ? high : value); }
|
||||
|
||||
class color_rgba
|
||||
{
|
||||
public:
|
||||
union
|
||||
{
|
||||
uint8_t m_comps[4];
|
||||
|
||||
struct
|
||||
{
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t a;
|
||||
};
|
||||
};
|
||||
|
||||
inline color_rgba() = default;
|
||||
|
||||
inline color_rgba(int y)
|
||||
{
|
||||
set(y);
|
||||
}
|
||||
|
||||
inline color_rgba(int y, int na)
|
||||
{
|
||||
set(y, na);
|
||||
}
|
||||
|
||||
inline color_rgba(int sr, int sg, int sb, int sa)
|
||||
{
|
||||
set(sr, sg, sb, sa);
|
||||
}
|
||||
|
||||
inline color_rgba(eNoClamp, int sr, int sg, int sb, int sa)
|
||||
{
|
||||
set_noclamp_rgba((uint8_t)sr, (uint8_t)sg, (uint8_t)sb, (uint8_t)sa);
|
||||
}
|
||||
|
||||
inline color_rgba& set_noclamp_y(int y)
|
||||
{
|
||||
m_comps[0] = (uint8_t)y;
|
||||
m_comps[1] = (uint8_t)y;
|
||||
m_comps[2] = (uint8_t)y;
|
||||
m_comps[3] = (uint8_t)255;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline color_rgba &set_noclamp_rgba(int sr, int sg, int sb, int sa)
|
||||
{
|
||||
m_comps[0] = (uint8_t)sr;
|
||||
m_comps[1] = (uint8_t)sg;
|
||||
m_comps[2] = (uint8_t)sb;
|
||||
m_comps[3] = (uint8_t)sa;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline color_rgba &set(int y)
|
||||
{
|
||||
m_comps[0] = static_cast<uint8_t>(clamp<int>(y, 0, 255));
|
||||
m_comps[1] = m_comps[0];
|
||||
m_comps[2] = m_comps[0];
|
||||
m_comps[3] = 255;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline color_rgba &set(int y, int na)
|
||||
{
|
||||
m_comps[0] = static_cast<uint8_t>(clamp<int>(y, 0, 255));
|
||||
m_comps[1] = m_comps[0];
|
||||
m_comps[2] = m_comps[0];
|
||||
m_comps[3] = static_cast<uint8_t>(clamp<int>(na, 0, 255));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline color_rgba &set(int sr, int sg, int sb, int sa)
|
||||
{
|
||||
m_comps[0] = static_cast<uint8_t>(clamp<int>(sr, 0, 255));
|
||||
m_comps[1] = static_cast<uint8_t>(clamp<int>(sg, 0, 255));
|
||||
m_comps[2] = static_cast<uint8_t>(clamp<int>(sb, 0, 255));
|
||||
m_comps[3] = static_cast<uint8_t>(clamp<int>(sa, 0, 255));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline color_rgba &set_rgb(int sr, int sg, int sb)
|
||||
{
|
||||
m_comps[0] = static_cast<uint8_t>(clamp<int>(sr, 0, 255));
|
||||
m_comps[1] = static_cast<uint8_t>(clamp<int>(sg, 0, 255));
|
||||
m_comps[2] = static_cast<uint8_t>(clamp<int>(sb, 0, 255));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline color_rgba &set_rgb(const color_rgba &other)
|
||||
{
|
||||
r = other.r;
|
||||
g = other.g;
|
||||
b = other.b;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline const uint8_t &operator[] (uint32_t index) const { assert(index < 4); return m_comps[index]; }
|
||||
inline uint8_t &operator[] (uint32_t index) { assert(index < 4); return m_comps[index]; }
|
||||
|
||||
inline void clear()
|
||||
{
|
||||
m_comps[0] = 0;
|
||||
m_comps[1] = 0;
|
||||
m_comps[2] = 0;
|
||||
m_comps[3] = 0;
|
||||
}
|
||||
|
||||
inline bool operator== (const color_rgba &rhs) const
|
||||
{
|
||||
if (m_comps[0] != rhs.m_comps[0]) return false;
|
||||
if (m_comps[1] != rhs.m_comps[1]) return false;
|
||||
if (m_comps[2] != rhs.m_comps[2]) return false;
|
||||
if (m_comps[3] != rhs.m_comps[3]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool operator!= (const color_rgba &rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
inline bool operator<(const color_rgba &rhs) const
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (m_comps[i] < rhs.m_comps[i])
|
||||
return true;
|
||||
else if (m_comps[i] != rhs.m_comps[i])
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline int get_601_luma() const { return (19595U * m_comps[0] + 38470U * m_comps[1] + 7471U * m_comps[2] + 32768U) >> 16U; }
|
||||
inline int get_709_luma() const { return (13938U * m_comps[0] + 46869U * m_comps[1] + 4729U * m_comps[2] + 32768U) >> 16U; }
|
||||
inline int get_luma(bool luma_601) const { return luma_601 ? get_601_luma() : get_709_luma(); }
|
||||
|
||||
static color_rgba comp_min(const color_rgba& a, const color_rgba& b) { return color_rgba(std::min(a[0], b[0]), std::min(a[1], b[1]), std::min(a[2], b[2]), std::min(a[3], b[3])); }
|
||||
static color_rgba comp_max(const color_rgba& a, const color_rgba& b) { return color_rgba(std::max(a[0], b[0]), std::max(a[1], b[1]), std::max(a[2], b[2]), std::max(a[3], b[3])); }
|
||||
};
|
||||
|
||||
static_assert(sizeof(color_rgba) == 4);
|
||||
|
||||
bool unpack_bc7(const void *pBlock, color_rgba *pPixels);
|
||||
|
||||
} // namespace bc7decomp
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
254
common/Threading.h
Normal file
254
common/Threading.h
Normal file
@@ -0,0 +1,254 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <mach/semaphore.h>
|
||||
#elif !defined(_WIN32)
|
||||
#include <semaphore.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
|
||||
namespace Threading
|
||||
{
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Platform Specific External APIs
|
||||
// --------------------------------------------------------------------------------------
|
||||
// The following set of documented functions have Linux/Win32 specific implementations,
|
||||
// which are found in WinThreads.cpp and LnxThreads.cpp
|
||||
|
||||
extern u64 GetThreadCpuTime();
|
||||
extern u64 GetThreadTicksPerSecond();
|
||||
|
||||
/// Set the name of the current thread
|
||||
extern void SetNameOfCurrentThread(const char* name);
|
||||
|
||||
// Releases a timeslice to other threads.
|
||||
extern void Timeslice();
|
||||
|
||||
// For use in spin/wait loops.
|
||||
extern void SpinWait();
|
||||
|
||||
// Optional implementation to enable hires thread/process scheduler for the operating system.
|
||||
// Needed by Windows, but might not be relevant to other platforms.
|
||||
extern void EnableHiresScheduler();
|
||||
extern void DisableHiresScheduler();
|
||||
|
||||
// sleeps the current thread for the given number of milliseconds.
|
||||
extern void Sleep(int ms);
|
||||
|
||||
// sleeps the current thread until the specified time point, or later.
|
||||
extern void SleepUntil(u64 ticks);
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// ThreadHandle
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Abstracts an OS's handle to a thread, closing the handle when necessary. Currently,
|
||||
// only used for getting the CPU time for a thread.
|
||||
//
|
||||
class ThreadHandle
|
||||
{
|
||||
public:
|
||||
ThreadHandle();
|
||||
ThreadHandle(ThreadHandle&& handle);
|
||||
ThreadHandle(const ThreadHandle& handle);
|
||||
~ThreadHandle();
|
||||
|
||||
/// Returns a new handle for the calling thread.
|
||||
static ThreadHandle GetForCallingThread();
|
||||
|
||||
ThreadHandle& operator=(ThreadHandle&& handle);
|
||||
ThreadHandle& operator=(const ThreadHandle& handle);
|
||||
|
||||
operator void*() const { return m_native_handle; }
|
||||
operator bool() const { return (m_native_handle != nullptr); }
|
||||
|
||||
/// Returns the amount of CPU time consumed by the thread, at the GetThreadTicksPerSecond() frequency.
|
||||
u64 GetCPUTime() const;
|
||||
|
||||
/// Sets the affinity for a thread to the specified processors.
|
||||
/// Obviously, only works up to 64 processors.
|
||||
bool SetAffinity(u64 processor_mask) const;
|
||||
|
||||
protected:
|
||||
void* m_native_handle = nullptr;
|
||||
|
||||
// We need the thread ID for affinity adjustments on Linux.
|
||||
#if defined(__linux__)
|
||||
unsigned int m_native_id = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Thread
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Abstracts a native thread in a lightweight manner. Provides more functionality than
|
||||
// std::thread (allowing stack size adjustments).
|
||||
//
|
||||
class Thread : public ThreadHandle
|
||||
{
|
||||
public:
|
||||
using EntryPoint = std::function<void()>;
|
||||
|
||||
Thread();
|
||||
Thread(Thread&& thread);
|
||||
Thread(const Thread&) = delete;
|
||||
Thread(EntryPoint func);
|
||||
~Thread();
|
||||
|
||||
ThreadHandle& operator=(Thread&& thread);
|
||||
ThreadHandle& operator=(const Thread& handle) = delete;
|
||||
|
||||
__fi bool Joinable() const { return (m_native_handle != nullptr); }
|
||||
__fi u32 GetStackSize() const { return m_stack_size; }
|
||||
|
||||
/// Sets the stack size for the thread. Do not call if the thread has already been started.
|
||||
void SetStackSize(u32 size);
|
||||
|
||||
bool Start(EntryPoint func);
|
||||
void Detach();
|
||||
void Join();
|
||||
|
||||
protected:
|
||||
#ifdef _WIN32
|
||||
static unsigned __stdcall ThreadProc(void* param);
|
||||
#else
|
||||
static void* ThreadProc(void* param);
|
||||
#endif
|
||||
|
||||
u32 m_stack_size = 0;
|
||||
};
|
||||
|
||||
/// A semaphore that may not have a fast userspace path
|
||||
/// (Used in other semaphore-based algorithms where the semaphore is just used for its thread sleep/wake ability)
|
||||
class KernelSemaphore
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
void* m_sema;
|
||||
#elif defined(__APPLE__)
|
||||
semaphore_t m_sema;
|
||||
#else
|
||||
sem_t m_sema;
|
||||
#endif
|
||||
public:
|
||||
KernelSemaphore();
|
||||
~KernelSemaphore();
|
||||
void Post();
|
||||
void Wait();
|
||||
bool TryWait();
|
||||
};
|
||||
|
||||
/// A semaphore for notifying a work-processing thread of new work in a (separate) queue
|
||||
///
|
||||
/// Usage:
|
||||
/// - Processing thread loops on `WaitForWork()` followed by processing all work in the queue
|
||||
/// - Threads adding work first add their work to the queue, then call `NotifyOfWork()`
|
||||
class WorkSema
|
||||
{
|
||||
/// Semaphore for sleeping the worker thread
|
||||
KernelSemaphore m_sema;
|
||||
/// Semaphore for sleeping thread waiting on worker queue empty
|
||||
KernelSemaphore m_empty_sema;
|
||||
/// Current state (see enum below)
|
||||
std::atomic<s32> m_state{0};
|
||||
|
||||
// Expected call frequency is NotifyOfWork > WaitForWork > WaitForEmpty
|
||||
// So optimize states for fast NotifyOfWork
|
||||
enum
|
||||
{
|
||||
/* Any <-2 state: STATE_DEAD: Thread has crashed and is awaiting revival */
|
||||
STATE_SPINNING = -2, ///< Worker thread is spinning waiting for work
|
||||
STATE_SLEEPING = -1, ///< Worker thread is sleeping on m_sema
|
||||
STATE_RUNNING_0 = 0, ///< Worker thread is processing work, but no work has been added since it last checked for new work
|
||||
/* Any >0 state: STATE_RUNNING_N: Worker thread is processing work, and work has been added since it last checked for new work */
|
||||
STATE_FLAG_WAITING_EMPTY = 1 << 30, ///< Flag to indicate that a thread is sleeping on m_empty_sema (can be applied to any STATE_RUNNING)
|
||||
};
|
||||
|
||||
bool IsDead(s32 state)
|
||||
{
|
||||
return state < STATE_SPINNING;
|
||||
}
|
||||
|
||||
bool IsReadyForSleep(s32 state)
|
||||
{
|
||||
s32 waiting_empty_cleared = state & (STATE_FLAG_WAITING_EMPTY - 1);
|
||||
return waiting_empty_cleared == STATE_RUNNING_0;
|
||||
}
|
||||
|
||||
s32 NextStateWaitForWork(s32 current)
|
||||
{
|
||||
s32 new_state = IsReadyForSleep(current) ? STATE_SLEEPING : STATE_RUNNING_0;
|
||||
return new_state | (current & STATE_FLAG_WAITING_EMPTY); // Preserve waiting empty flag for RUNNING_N → RUNNING_0
|
||||
}
|
||||
|
||||
public:
|
||||
/// Notify the worker thread that you've added new work to its queue
|
||||
void NotifyOfWork()
|
||||
{
|
||||
// State change:
|
||||
// DEAD: Stay in DEAD (starting DEAD state is INT_MIN so we can assume we won't flip over to anything else)
|
||||
// SPINNING: Change state to RUNNING. Thread will notice and process the new data
|
||||
// SLEEPING: Change state to RUNNING and wake worker. Thread will wake up and process the new data.
|
||||
// RUNNING_0: Change state to RUNNING_N.
|
||||
// RUNNING_N: Stay in RUNNING_N
|
||||
s32 old = m_state.fetch_add(2, std::memory_order_release);
|
||||
if (old == STATE_SLEEPING)
|
||||
m_sema.Post();
|
||||
}
|
||||
|
||||
/// Checks if there's any work in the queue
|
||||
bool CheckForWork();
|
||||
/// Wait for work to be added to the queue
|
||||
void WaitForWork();
|
||||
/// Wait for work to be added to the queue, spinning for a bit before sleeping the thread
|
||||
void WaitForWorkWithSpin();
|
||||
/// Wait for the worker thread to finish processing all entries in the queue or die
|
||||
/// Returns false if the thread is dead
|
||||
bool WaitForEmpty();
|
||||
/// Wait for the worker thread to finish processing all entries in the queue or die, spinning a bit before sleeping the thread
|
||||
/// Returns false if the thread is dead
|
||||
bool WaitForEmptyWithSpin();
|
||||
/// Called by the worker thread to notify others of its death
|
||||
/// Dead threads don't process work, and WaitForEmpty will return instantly even though there may be work in the queue
|
||||
void Kill();
|
||||
/// Reset the semaphore to the initial state
|
||||
/// Should be called by the worker thread if it restarts after dying
|
||||
void Reset();
|
||||
};
|
||||
|
||||
/// A semaphore that definitely has a fast userspace path
|
||||
class UserspaceSemaphore
|
||||
{
|
||||
KernelSemaphore m_sema;
|
||||
std::atomic<int32_t> m_counter{0};
|
||||
|
||||
public:
|
||||
UserspaceSemaphore() = default;
|
||||
~UserspaceSemaphore() = default;
|
||||
|
||||
void Post()
|
||||
{
|
||||
if (m_counter.fetch_add(1, std::memory_order_release) < 0)
|
||||
m_sema.Post();
|
||||
}
|
||||
|
||||
void Wait()
|
||||
{
|
||||
if (m_counter.fetch_sub(1, std::memory_order_acquire) <= 0)
|
||||
m_sema.Wait();
|
||||
}
|
||||
|
||||
bool TryWait()
|
||||
{
|
||||
int32_t counter = m_counter.load(std::memory_order_relaxed);
|
||||
while (counter > 0 && !m_counter.compare_exchange_weak(counter, counter - 1, std::memory_order_acquire, std::memory_order_relaxed))
|
||||
;
|
||||
return counter > 0;
|
||||
}
|
||||
};
|
||||
} // namespace Threading
|
||||
186
common/Timer.cpp
Normal file
186
common/Timer.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "Timer.h"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include "RedtapeWindows.h"
|
||||
#else
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
namespace Common
|
||||
{
|
||||
#ifdef _WIN32
|
||||
static double s_counter_frequency;
|
||||
static bool s_counter_initialized = false;
|
||||
|
||||
Timer::Value Timer::GetCurrentValue()
|
||||
{
|
||||
// even if this races, it should still result in the same value..
|
||||
if (!s_counter_initialized)
|
||||
{
|
||||
LARGE_INTEGER Freq;
|
||||
QueryPerformanceFrequency(&Freq);
|
||||
s_counter_frequency = static_cast<double>(Freq.QuadPart) / 1000000000.0;
|
||||
s_counter_initialized = true;
|
||||
}
|
||||
|
||||
Timer::Value ReturnValue;
|
||||
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&ReturnValue));
|
||||
return ReturnValue;
|
||||
}
|
||||
|
||||
double Timer::ConvertValueToNanoseconds(Timer::Value value)
|
||||
{
|
||||
return (static_cast<double>(value) / s_counter_frequency);
|
||||
}
|
||||
|
||||
double Timer::ConvertValueToMilliseconds(Timer::Value value)
|
||||
{
|
||||
return ((static_cast<double>(value) / s_counter_frequency) / 1000000.0);
|
||||
}
|
||||
|
||||
double Timer::ConvertValueToSeconds(Timer::Value value)
|
||||
{
|
||||
return ((static_cast<double>(value) / s_counter_frequency) / 1000000000.0);
|
||||
}
|
||||
|
||||
Timer::Value Timer::ConvertSecondsToValue(double s)
|
||||
{
|
||||
return static_cast<Value>((s * 1000000000.0) * s_counter_frequency);
|
||||
}
|
||||
|
||||
Timer::Value Timer::ConvertMillisecondsToValue(double ms)
|
||||
{
|
||||
return static_cast<Value>((ms * 1000000.0) * s_counter_frequency);
|
||||
}
|
||||
|
||||
Timer::Value Timer::ConvertNanosecondsToValue(double ns)
|
||||
{
|
||||
return static_cast<Value>(ns * s_counter_frequency);
|
||||
}
|
||||
#else
|
||||
Timer::Value Timer::GetCurrentValue()
|
||||
{
|
||||
struct timespec tv;
|
||||
clock_gettime(CLOCK_MONOTONIC, &tv);
|
||||
return ((Value)tv.tv_nsec + (Value)tv.tv_sec * 1000000000);
|
||||
}
|
||||
|
||||
double Timer::ConvertValueToNanoseconds(Timer::Value value)
|
||||
{
|
||||
return static_cast<double>(value);
|
||||
}
|
||||
|
||||
double Timer::ConvertValueToMilliseconds(Timer::Value value)
|
||||
{
|
||||
return (static_cast<double>(value) / 1000000.0);
|
||||
}
|
||||
|
||||
double Timer::ConvertValueToSeconds(Timer::Value value)
|
||||
{
|
||||
return (static_cast<double>(value) / 1000000000.0);
|
||||
}
|
||||
|
||||
Timer::Value Timer::ConvertSecondsToValue(double s)
|
||||
{
|
||||
return static_cast<Value>(s * 1000000000.0);
|
||||
}
|
||||
|
||||
Timer::Value Timer::ConvertMillisecondsToValue(double ms)
|
||||
{
|
||||
return static_cast<Value>(ms * 1000000.0);
|
||||
}
|
||||
|
||||
Timer::Value Timer::ConvertNanosecondsToValue(double ns)
|
||||
{
|
||||
return static_cast<Value>(ns);
|
||||
}
|
||||
#endif
|
||||
|
||||
Timer::Timer()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Timer::Reset()
|
||||
{
|
||||
m_tvStartValue = GetCurrentValue();
|
||||
}
|
||||
|
||||
double Timer::GetTimeSeconds() const
|
||||
{
|
||||
return ConvertValueToSeconds(GetCurrentValue() - m_tvStartValue);
|
||||
}
|
||||
|
||||
double Timer::GetTimeMilliseconds() const
|
||||
{
|
||||
return ConvertValueToMilliseconds(GetCurrentValue() - m_tvStartValue);
|
||||
}
|
||||
|
||||
double Timer::GetTimeNanoseconds() const
|
||||
{
|
||||
return ConvertValueToNanoseconds(GetCurrentValue() - m_tvStartValue);
|
||||
}
|
||||
|
||||
double Timer::GetTimeSecondsAndReset()
|
||||
{
|
||||
const Value value = GetCurrentValue();
|
||||
const double ret = ConvertValueToSeconds(value - m_tvStartValue);
|
||||
m_tvStartValue = value;
|
||||
return ret;
|
||||
}
|
||||
|
||||
double Timer::GetTimeMillisecondsAndReset()
|
||||
{
|
||||
const Value value = GetCurrentValue();
|
||||
const double ret = ConvertValueToMilliseconds(value - m_tvStartValue);
|
||||
m_tvStartValue = value;
|
||||
return ret;
|
||||
}
|
||||
|
||||
double Timer::GetTimeNanosecondsAndReset()
|
||||
{
|
||||
const Value value = GetCurrentValue();
|
||||
const double ret = ConvertValueToNanoseconds(value - m_tvStartValue);
|
||||
m_tvStartValue = value;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool Timer::ResetIfSecondsPassed(double s)
|
||||
{
|
||||
const Value value = GetCurrentValue();
|
||||
const double ret = ConvertValueToSeconds(value - m_tvStartValue);
|
||||
if (ret < s)
|
||||
return false;
|
||||
|
||||
m_tvStartValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Timer::ResetIfMillisecondsPassed(double s)
|
||||
{
|
||||
const Value value = GetCurrentValue();
|
||||
const double ret = ConvertValueToMilliseconds(value - m_tvStartValue);
|
||||
if (ret < s)
|
||||
return false;
|
||||
|
||||
m_tvStartValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Timer::ResetIfNanosecondsPassed(double s)
|
||||
{
|
||||
const Value value = GetCurrentValue();
|
||||
const double ret = ConvertValueToNanoseconds(value - m_tvStartValue);
|
||||
if (ret < s)
|
||||
return false;
|
||||
|
||||
m_tvStartValue = value;
|
||||
return true;
|
||||
}
|
||||
} // namespace Common
|
||||
44
common/Timer.h
Normal file
44
common/Timer.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Common
|
||||
{
|
||||
class Timer
|
||||
{
|
||||
public:
|
||||
using Value = std::uint64_t;
|
||||
|
||||
Timer();
|
||||
|
||||
static Value GetCurrentValue();
|
||||
static double ConvertValueToSeconds(Value value);
|
||||
static double ConvertValueToMilliseconds(Value value);
|
||||
static double ConvertValueToNanoseconds(Value value);
|
||||
static Value ConvertSecondsToValue(double s);
|
||||
static Value ConvertMillisecondsToValue(double s);
|
||||
static Value ConvertNanosecondsToValue(double ns);
|
||||
|
||||
void Reset();
|
||||
void ResetTo(Value value) { m_tvStartValue = value; }
|
||||
|
||||
Value GetStartValue() const { return m_tvStartValue; }
|
||||
|
||||
double GetTimeSeconds() const;
|
||||
double GetTimeMilliseconds() const;
|
||||
double GetTimeNanoseconds() const;
|
||||
|
||||
double GetTimeSecondsAndReset();
|
||||
double GetTimeMillisecondsAndReset();
|
||||
double GetTimeNanosecondsAndReset();
|
||||
|
||||
bool ResetIfSecondsPassed(double s);
|
||||
bool ResetIfMillisecondsPassed(double s);
|
||||
bool ResetIfNanosecondsPassed(double s);
|
||||
|
||||
private:
|
||||
Value m_tvStartValue;
|
||||
};
|
||||
} // namespace Common
|
||||
46
common/VectorIntrin.h
Normal file
46
common/VectorIntrin.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
// Includes appropriate intrinsic header based on platform.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(_M_X86)
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#if defined(__AVX2__)
|
||||
#define _M_SSE 0x501
|
||||
#elif defined(__AVX__)
|
||||
#define _M_SSE 0x500
|
||||
#elif defined(__SSE4_1__)
|
||||
#define _M_SSE 0x401
|
||||
#else
|
||||
#error PCSX2 requires compiling for at least SSE 4.1
|
||||
#endif
|
||||
|
||||
// Starting with AVX, processors have fast unaligned loads
|
||||
// Reduce code duplication by not compiling multiple versions
|
||||
#if _M_SSE >= 0x500
|
||||
#define FAST_UNALIGNED 1
|
||||
#else
|
||||
#define FAST_UNALIGNED 0
|
||||
#endif
|
||||
|
||||
#include <xmmintrin.h>
|
||||
#include <emmintrin.h>
|
||||
#include <tmmintrin.h>
|
||||
#include <smmintrin.h>
|
||||
#include <immintrin.h>
|
||||
|
||||
#elif defined(_M_ARM64)
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <stdlib.h> // alloca
|
||||
#else
|
||||
#include <malloc.h> // alloca
|
||||
#endif
|
||||
115
common/WAVWriter.cpp
Normal file
115
common/WAVWriter.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/WAVWriter.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/Console.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct WAV_HEADER
|
||||
{
|
||||
u32 chunk_id; // RIFF
|
||||
u32 chunk_size;
|
||||
u32 format; // WAVE
|
||||
|
||||
struct FormatChunk
|
||||
{
|
||||
u32 chunk_id; // "fmt "
|
||||
u32 chunk_size;
|
||||
u16 audio_format; // pcm = 1
|
||||
u16 num_channels;
|
||||
u32 sample_rate;
|
||||
u32 byte_rate;
|
||||
u16 block_align;
|
||||
u16 bits_per_sample;
|
||||
} fmt_chunk;
|
||||
|
||||
struct DataChunkHeader
|
||||
{
|
||||
u32 chunk_id; // "data "
|
||||
u32 chunk_size;
|
||||
} data_chunk_header;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
using namespace Common;
|
||||
|
||||
WAVWriter::WAVWriter() = default;
|
||||
|
||||
WAVWriter::~WAVWriter()
|
||||
{
|
||||
if (IsOpen())
|
||||
Close();
|
||||
}
|
||||
|
||||
bool WAVWriter::Open(const char* filename, u32 sample_rate, u32 num_channels)
|
||||
{
|
||||
if (IsOpen())
|
||||
Close();
|
||||
|
||||
m_file = FileSystem::OpenCFile(filename, "wb");
|
||||
if (!m_file)
|
||||
return false;
|
||||
|
||||
m_sample_rate = sample_rate;
|
||||
m_num_channels = num_channels;
|
||||
|
||||
if (!WriteHeader())
|
||||
{
|
||||
Console.Error("Failed to write header to file");
|
||||
m_sample_rate = 0;
|
||||
m_num_channels = 0;
|
||||
std::fclose(m_file);
|
||||
m_file = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WAVWriter::Close()
|
||||
{
|
||||
if (!IsOpen())
|
||||
return;
|
||||
|
||||
if (std::fseek(m_file, 0, SEEK_SET) != 0 || !WriteHeader())
|
||||
Console.Error("Failed to re-write header on file, file may be unplayable");
|
||||
|
||||
std::fclose(m_file);
|
||||
m_file = nullptr;
|
||||
m_sample_rate = 0;
|
||||
m_num_channels = 0;
|
||||
m_num_frames = 0;
|
||||
}
|
||||
|
||||
void WAVWriter::WriteFrames(const s16* samples, u32 num_frames)
|
||||
{
|
||||
const u32 num_frames_written =
|
||||
static_cast<u32>(std::fwrite(samples, sizeof(s16) * m_num_channels, num_frames, m_file));
|
||||
if (num_frames_written != num_frames)
|
||||
Console.Error("Only wrote %u of %u frames to output file", num_frames_written, num_frames);
|
||||
|
||||
m_num_frames += num_frames_written;
|
||||
}
|
||||
|
||||
bool WAVWriter::WriteHeader()
|
||||
{
|
||||
const u32 data_size = sizeof(SampleType) * m_num_channels * m_num_frames;
|
||||
|
||||
WAV_HEADER header = {};
|
||||
header.chunk_id = 0x46464952; // 0x52494646
|
||||
header.chunk_size = sizeof(WAV_HEADER) - 8 + data_size;
|
||||
header.format = 0x45564157; // 0x57415645
|
||||
header.fmt_chunk.chunk_id = 0x20746d66; // 0x666d7420
|
||||
header.fmt_chunk.chunk_size = sizeof(header.fmt_chunk) - 8;
|
||||
header.fmt_chunk.audio_format = 1;
|
||||
header.fmt_chunk.num_channels = static_cast<u16>(m_num_channels);
|
||||
header.fmt_chunk.sample_rate = m_sample_rate;
|
||||
header.fmt_chunk.byte_rate = m_sample_rate * m_num_channels * sizeof(SampleType);
|
||||
header.fmt_chunk.block_align = static_cast<u16>(m_num_channels * sizeof(SampleType));
|
||||
header.fmt_chunk.bits_per_sample = 16;
|
||||
header.data_chunk_header.chunk_id = 0x61746164; // 0x64617461
|
||||
header.data_chunk_header.chunk_size = data_size;
|
||||
|
||||
return (std::fwrite(&header, sizeof(header), 1, m_file) == 1);
|
||||
}
|
||||
36
common/WAVWriter.h
Normal file
36
common/WAVWriter.h
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include "common/Pcsx2Defs.h"
|
||||
#include <cstdio>
|
||||
|
||||
namespace Common
|
||||
{
|
||||
class WAVWriter
|
||||
{
|
||||
public:
|
||||
WAVWriter();
|
||||
~WAVWriter();
|
||||
|
||||
__fi u32 GetSampleRate() const { return m_sample_rate; }
|
||||
__fi u32 GetNumChannels() const { return m_num_channels; }
|
||||
__fi u32 GetNumFrames() const { return m_num_frames; }
|
||||
__fi bool IsOpen() const { return (m_file != nullptr); }
|
||||
|
||||
bool Open(const char* filename, u32 sample_rate, u32 num_channels);
|
||||
void Close();
|
||||
|
||||
void WriteFrames(const s16* samples, u32 num_frames);
|
||||
|
||||
private:
|
||||
using SampleType = s16;
|
||||
|
||||
bool WriteHeader();
|
||||
|
||||
std::FILE* m_file = nullptr;
|
||||
u32 m_sample_rate = 0;
|
||||
u32 m_num_channels = 0;
|
||||
u32 m_num_frames = 0;
|
||||
};
|
||||
} // namespace Common
|
||||
257
common/WindowInfo.cpp
Normal file
257
common/WindowInfo.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "WindowInfo.h"
|
||||
#include "Console.h"
|
||||
#include "Error.h"
|
||||
#include "HeapArray.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#include "RedtapeWindows.h"
|
||||
#include <dwmapi.h>
|
||||
|
||||
static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd)
|
||||
{
|
||||
// Partially based on Chromium ui/display/win/display_config_helper.cc.
|
||||
const HMONITOR monitor = MonitorFromWindow(hwnd, 0);
|
||||
if (!monitor) [[unlikely]]
|
||||
{
|
||||
ERROR_LOG("{}() failed: {}", "MonitorFromWindow", Error::CreateWin32(GetLastError()).GetDescription());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
MONITORINFOEXW mi = {};
|
||||
mi.cbSize = sizeof(mi);
|
||||
if (!GetMonitorInfoW(monitor, &mi))
|
||||
{
|
||||
ERROR_LOG("{}() failed: {}", "GetMonitorInfoW", Error::CreateWin32(GetLastError()).GetDescription());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
DynamicHeapArray<DISPLAYCONFIG_PATH_INFO> path_info;
|
||||
DynamicHeapArray<DISPLAYCONFIG_MODE_INFO> mode_info;
|
||||
|
||||
// I guess this could fail if it changes inbetween two calls... unlikely.
|
||||
for (;;)
|
||||
{
|
||||
UINT32 path_size = 0, mode_size = 0;
|
||||
LONG res = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_size, &mode_size);
|
||||
if (res != ERROR_SUCCESS)
|
||||
{
|
||||
ERROR_LOG("{}() failed: {}", "GetDisplayConfigBufferSizes", Error::CreateWin32(res).GetDescription());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
path_info.resize(path_size);
|
||||
mode_info.resize(mode_size);
|
||||
res =
|
||||
QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &path_size, path_info.data(), &mode_size, mode_info.data(), nullptr);
|
||||
if (res == ERROR_SUCCESS)
|
||||
break;
|
||||
if (res != ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
ERROR_LOG("{}() failed: {}", "QueryDisplayConfig", Error::CreateWin32(res).GetDescription());
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
for (const DISPLAYCONFIG_PATH_INFO& pi : path_info)
|
||||
{
|
||||
DISPLAYCONFIG_SOURCE_DEVICE_NAME sdn = {.header = {.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME,
|
||||
.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME),
|
||||
.adapterId = pi.sourceInfo.adapterId,
|
||||
.id = pi.sourceInfo.id}};
|
||||
LONG res = DisplayConfigGetDeviceInfo(&sdn.header);
|
||||
if (res != ERROR_SUCCESS)
|
||||
{
|
||||
ERROR_LOG("{}() failed: {}", "DisplayConfigGetDeviceInfo", Error::CreateWin32(res).GetDescription());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::wcscmp(sdn.viewGdiDeviceName, mi.szDevice) == 0)
|
||||
{
|
||||
// Found the monitor!
|
||||
return static_cast<float>(static_cast<double>(pi.targetInfo.refreshRate.Numerator) /
|
||||
static_cast<double>(pi.targetInfo.refreshRate.Denominator));
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<float> GetRefreshRateFromDWM(HWND hwnd)
|
||||
{
|
||||
BOOL composition_enabled;
|
||||
if (FAILED(DwmIsCompositionEnabled(&composition_enabled)))
|
||||
return std::nullopt;
|
||||
|
||||
DWM_TIMING_INFO ti = {};
|
||||
ti.cbSize = sizeof(ti);
|
||||
HRESULT hr = DwmGetCompositionTimingInfo(nullptr, &ti);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
if (ti.rateRefresh.uiNumerator == 0 || ti.rateRefresh.uiDenominator == 0)
|
||||
return std::nullopt;
|
||||
|
||||
return static_cast<float>(ti.rateRefresh.uiNumerator) / static_cast<float>(ti.rateRefresh.uiDenominator);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<float> GetRefreshRateFromMonitor(HWND hwnd)
|
||||
{
|
||||
HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||
if (!mon)
|
||||
return std::nullopt;
|
||||
|
||||
MONITORINFOEXW mi = {};
|
||||
mi.cbSize = sizeof(mi);
|
||||
if (GetMonitorInfoW(mon, &mi))
|
||||
{
|
||||
DEVMODEW dm = {};
|
||||
dm.dmSize = sizeof(dm);
|
||||
|
||||
// 0/1 are reserved for "defaults".
|
||||
if (EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm) && dm.dmDisplayFrequency > 1)
|
||||
return static_cast<float>(dm.dmDisplayFrequency);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi)
|
||||
{
|
||||
std::optional<float> ret;
|
||||
if (wi.type != Type::Win32 || !wi.window_handle)
|
||||
return ret;
|
||||
|
||||
// Try DWM first, then fall back to integer values.
|
||||
const HWND hwnd = static_cast<HWND>(wi.window_handle);
|
||||
ret = GetRefreshRateFromDisplayConfig(hwnd);
|
||||
if (!ret.has_value())
|
||||
{
|
||||
ret = GetRefreshRateFromDWM(hwnd);
|
||||
if (!ret.has_value())
|
||||
ret = GetRefreshRateFromMonitor(hwnd);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include "common/CocoaTools.h"
|
||||
|
||||
std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi)
|
||||
{
|
||||
if (wi.type == WindowInfo::Type::MacOS)
|
||||
return CocoaTools::GetViewRefreshRate(wi);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#if defined(X11_API)
|
||||
|
||||
#include "common/ScopedGuard.h"
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
static std::optional<float> GetRefreshRateFromXRandR(const WindowInfo& wi)
|
||||
{
|
||||
Display* display = static_cast<Display*>(wi.display_connection);
|
||||
Window window = static_cast<Window>(reinterpret_cast<uintptr_t>(wi.window_handle));
|
||||
if (!display || !window)
|
||||
return std::nullopt;
|
||||
|
||||
XRRScreenResources* res = XRRGetScreenResources(display, window);
|
||||
if (!res)
|
||||
{
|
||||
Console.Error("(GetRefreshRateFromXRandR) XRRGetScreenResources() failed");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ScopedGuard res_guard([res]() { XRRFreeScreenResources(res); });
|
||||
|
||||
int num_monitors;
|
||||
XRRMonitorInfo* mi = XRRGetMonitors(display, window, True, &num_monitors);
|
||||
if (num_monitors < 0)
|
||||
{
|
||||
Console.Error("(GetRefreshRateFromXRandR) XRRGetMonitors() failed");
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (num_monitors > 1)
|
||||
{
|
||||
Console.Warning("(GetRefreshRateFromXRandR) XRRGetMonitors() returned %d monitors, using first", num_monitors);
|
||||
}
|
||||
|
||||
ScopedGuard mi_guard([mi]() { XRRFreeMonitors(mi); });
|
||||
if (mi->noutput <= 0)
|
||||
{
|
||||
Console.Error("(GetRefreshRateFromXRandR) Monitor has no outputs");
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (mi->noutput > 1)
|
||||
{
|
||||
Console.Warning("(GetRefreshRateFromXRandR) Monitor has %d outputs, using first", mi->noutput);
|
||||
}
|
||||
|
||||
XRROutputInfo* oi = XRRGetOutputInfo(display, res, mi->outputs[0]);
|
||||
if (!oi)
|
||||
{
|
||||
Console.Error("(GetRefreshRateFromXRandR) XRRGetOutputInfo() failed");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ScopedGuard oi_guard([oi]() { XRRFreeOutputInfo(oi); });
|
||||
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(display, res, oi->crtc);
|
||||
if (!ci)
|
||||
{
|
||||
Console.Error("(GetRefreshRateFromXRandR) XRRGetCrtcInfo() failed");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ScopedGuard ci_guard([ci]() { XRRFreeCrtcInfo(ci); });
|
||||
|
||||
XRRModeInfo* mode = nullptr;
|
||||
for (int i = 0; i < res->nmode; i++)
|
||||
{
|
||||
if (res->modes[i].id == ci->mode)
|
||||
{
|
||||
mode = &res->modes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!mode)
|
||||
{
|
||||
Console.Error("(GetRefreshRateFromXRandR) Failed to look up mode %d (of %d)", static_cast<int>(ci->mode), res->nmode);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (mode->dotClock == 0 || mode->hTotal == 0 || mode->vTotal == 0)
|
||||
{
|
||||
Console.Error("(GetRefreshRateFromXRandR) Modeline is invalid: %ld/%d/%d", mode->dotClock, mode->hTotal, mode->vTotal);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return static_cast<float>(
|
||||
static_cast<double>(mode->dotClock) / (static_cast<double>(mode->hTotal) * static_cast<double>(mode->vTotal)));
|
||||
}
|
||||
|
||||
#endif // X11_API
|
||||
|
||||
std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi)
|
||||
{
|
||||
#if defined(X11_API)
|
||||
if (wi.type == WindowInfo::Type::X11)
|
||||
return GetRefreshRateFromXRandR(wi);
|
||||
#endif
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#endif
|
||||
47
common/WindowInfo.h
Normal file
47
common/WindowInfo.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include "Pcsx2Defs.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
/// Contains the information required to create a graphics context in a window.
|
||||
struct WindowInfo
|
||||
{
|
||||
enum class Type
|
||||
{
|
||||
Surfaceless,
|
||||
Win32,
|
||||
X11,
|
||||
Wayland,
|
||||
MacOS
|
||||
};
|
||||
|
||||
/// The type of the surface. Surfaceless indicates it will not be displayed on screen at all.
|
||||
Type type = Type::Surfaceless;
|
||||
|
||||
/// Connection to the display server. On most platforms except X11/Wayland, this is implicit and null.
|
||||
void* display_connection = nullptr;
|
||||
|
||||
/// Abstract handle to the window. This depends on the surface type.
|
||||
void* window_handle = nullptr;
|
||||
|
||||
/// For platforms where a separate surface/layer handle is needed, it is stored here (e.g. MacOS).
|
||||
void* surface_handle = nullptr;
|
||||
|
||||
/// Width of the surface in pixels.
|
||||
u32 surface_width = 0;
|
||||
|
||||
/// Height of the surface in pixels.
|
||||
u32 surface_height = 0;
|
||||
|
||||
/// DPI scale for the surface.
|
||||
float surface_scale = 1.0f;
|
||||
|
||||
/// Refresh rate of the surface, if available.
|
||||
float surface_refresh_rate = 0.0f;
|
||||
|
||||
/// Returns the host's refresh rate for the given window, if available.
|
||||
static std::optional<float> QueryRefreshRateForWindow(const WindowInfo& wi);
|
||||
};
|
||||
375
common/Windows/WinHostSys.cpp
Normal file
375
common/Windows/WinHostSys.cpp
Normal file
@@ -0,0 +1,375 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/HostSys.h"
|
||||
#include "common/AlignedMalloc.h"
|
||||
#include "common/Assertions.h"
|
||||
#include "common/BitUtils.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/Error.h"
|
||||
#include "common/RedtapeWindows.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
static DWORD ConvertToWinApi(const PageProtectionMode& mode)
|
||||
{
|
||||
DWORD winmode = PAGE_NOACCESS;
|
||||
|
||||
// Windows has some really bizarre memory protection enumeration that uses bitwise
|
||||
// numbering (like flags) but is in fact not a flag value. *Someone* from the early
|
||||
// microsoft days wasn't a very good coder, me thinks. --air
|
||||
|
||||
if (mode.CanExecute())
|
||||
{
|
||||
winmode = mode.CanWrite() ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ;
|
||||
}
|
||||
else if (mode.CanRead())
|
||||
{
|
||||
winmode = mode.CanWrite() ? PAGE_READWRITE : PAGE_READONLY;
|
||||
}
|
||||
|
||||
return winmode;
|
||||
}
|
||||
|
||||
void* HostSys::Mmap(void* base, size_t size, const PageProtectionMode& mode)
|
||||
{
|
||||
if (mode.IsNone())
|
||||
return nullptr;
|
||||
|
||||
return VirtualAlloc(base, size, MEM_RESERVE | MEM_COMMIT, ConvertToWinApi(mode));
|
||||
}
|
||||
|
||||
void HostSys::Munmap(void* base, size_t size)
|
||||
{
|
||||
if (!base)
|
||||
return;
|
||||
|
||||
VirtualFree((void*)base, 0, MEM_RELEASE);
|
||||
}
|
||||
|
||||
void HostSys::MemProtect(void* baseaddr, size_t size, const PageProtectionMode& mode)
|
||||
{
|
||||
pxAssert((size & (__pagesize - 1)) == 0);
|
||||
|
||||
DWORD OldProtect; // enjoy my uselessness, yo!
|
||||
if (!VirtualProtect(baseaddr, size, ConvertToWinApi(mode), &OldProtect))
|
||||
pxFail("VirtualProtect() failed");
|
||||
}
|
||||
|
||||
std::string HostSys::GetFileMappingName(const char* prefix)
|
||||
{
|
||||
const unsigned pid = GetCurrentProcessId();
|
||||
return fmt::format("{}_{}", prefix, pid);
|
||||
}
|
||||
|
||||
void* HostSys::CreateSharedMemory(const char* name, size_t size)
|
||||
{
|
||||
return static_cast<void*>(CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
|
||||
static_cast<DWORD>(size >> 32), static_cast<DWORD>(size), StringUtil::UTF8StringToWideString(name).c_str()));
|
||||
}
|
||||
|
||||
void HostSys::DestroySharedMemory(void* ptr)
|
||||
{
|
||||
CloseHandle(static_cast<HANDLE>(ptr));
|
||||
}
|
||||
|
||||
void* HostSys::MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, const PageProtectionMode& mode)
|
||||
{
|
||||
void* ret = MapViewOfFileEx(static_cast<HANDLE>(handle), FILE_MAP_READ | FILE_MAP_WRITE,
|
||||
static_cast<DWORD>(offset >> 32), static_cast<DWORD>(offset), size, baseaddr);
|
||||
if (!ret)
|
||||
return nullptr;
|
||||
|
||||
const DWORD prot = ConvertToWinApi(mode);
|
||||
if (prot != PAGE_READWRITE)
|
||||
{
|
||||
DWORD old_prot;
|
||||
if (!VirtualProtect(ret, size, prot, &old_prot))
|
||||
pxFail("Failed to protect memory mapping");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void HostSys::UnmapSharedMemory(void* baseaddr, size_t size)
|
||||
{
|
||||
if (!UnmapViewOfFile(baseaddr))
|
||||
pxFail("Failed to unmap shared memory");
|
||||
}
|
||||
|
||||
size_t HostSys::GetRuntimePageSize()
|
||||
{
|
||||
SYSTEM_INFO si = {};
|
||||
GetSystemInfo(&si);
|
||||
return si.dwPageSize;
|
||||
}
|
||||
|
||||
size_t HostSys::GetRuntimeCacheLineSize()
|
||||
{
|
||||
DWORD size = 0;
|
||||
if (!GetLogicalProcessorInformation(nullptr, &size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||||
return 0;
|
||||
|
||||
std::unique_ptr<SYSTEM_LOGICAL_PROCESSOR_INFORMATION[]> lpi =
|
||||
std::make_unique<SYSTEM_LOGICAL_PROCESSOR_INFORMATION[]>(
|
||||
(size + (sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) - 1)) / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION));
|
||||
if (!GetLogicalProcessorInformation(lpi.get(), &size))
|
||||
return 0;
|
||||
|
||||
u32 max_line_size = 0;
|
||||
for (u32 i = 0; i < size / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); i++)
|
||||
{
|
||||
if (lpi[i].Relationship == RelationCache)
|
||||
max_line_size = std::max<u32>(max_line_size, lpi[i].Cache.LineSize);
|
||||
}
|
||||
|
||||
return max_line_size;
|
||||
}
|
||||
|
||||
#ifdef _M_ARM64
|
||||
|
||||
void HostSys::FlushInstructionCache(void* address, u32 size)
|
||||
{
|
||||
::FlushInstructionCache(GetCurrentProcess(), address, size);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
SharedMemoryMappingArea::SharedMemoryMappingArea(u8* base_ptr, size_t size, size_t num_pages)
|
||||
: m_base_ptr(base_ptr)
|
||||
, m_size(size)
|
||||
, m_num_pages(num_pages)
|
||||
{
|
||||
m_placeholder_ranges.emplace(0, size);
|
||||
}
|
||||
|
||||
SharedMemoryMappingArea::~SharedMemoryMappingArea()
|
||||
{
|
||||
pxAssertRel(m_num_mappings == 0, "No mappings left");
|
||||
|
||||
// hopefully this will be okay, and we don't need to coalesce all the placeholders...
|
||||
if (!VirtualFreeEx(GetCurrentProcess(), m_base_ptr, 0, MEM_RELEASE))
|
||||
pxFailRel("Failed to release shared memory area");
|
||||
}
|
||||
|
||||
SharedMemoryMappingArea::PlaceholderMap::iterator SharedMemoryMappingArea::FindPlaceholder(size_t offset)
|
||||
{
|
||||
if (m_placeholder_ranges.empty())
|
||||
return m_placeholder_ranges.end();
|
||||
|
||||
// this will give us an iterator equal or after page
|
||||
auto it = m_placeholder_ranges.lower_bound(offset);
|
||||
if (it == m_placeholder_ranges.end())
|
||||
{
|
||||
// check the last page
|
||||
it = (++m_placeholder_ranges.rbegin()).base();
|
||||
}
|
||||
|
||||
// it's the one we found?
|
||||
if (offset >= it->first && offset < it->second)
|
||||
return it;
|
||||
|
||||
// otherwise try the one before
|
||||
if (it == m_placeholder_ranges.begin())
|
||||
return m_placeholder_ranges.end();
|
||||
|
||||
--it;
|
||||
if (offset >= it->first && offset < it->second)
|
||||
return it;
|
||||
else
|
||||
return m_placeholder_ranges.end();
|
||||
}
|
||||
|
||||
std::unique_ptr<SharedMemoryMappingArea> SharedMemoryMappingArea::Create(size_t size)
|
||||
{
|
||||
pxAssertRel(Common::IsAlignedPow2(size, __pagesize), "Size is page aligned");
|
||||
|
||||
void* alloc = VirtualAlloc2(GetCurrentProcess(), nullptr, size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0);
|
||||
if (!alloc)
|
||||
return nullptr;
|
||||
|
||||
return std::unique_ptr<SharedMemoryMappingArea>(new SharedMemoryMappingArea(static_cast<u8*>(alloc), size, size / __pagesize));
|
||||
}
|
||||
|
||||
u8* SharedMemoryMappingArea::Map(void* file_handle, size_t file_offset, void* map_base, size_t map_size, const PageProtectionMode& mode)
|
||||
{
|
||||
pxAssert(static_cast<u8*>(map_base) >= m_base_ptr && static_cast<u8*>(map_base) < (m_base_ptr + m_size));
|
||||
|
||||
const size_t map_offset = static_cast<u8*>(map_base) - m_base_ptr;
|
||||
pxAssert(Common::IsAlignedPow2(map_offset, __pagesize));
|
||||
pxAssert(Common::IsAlignedPow2(map_size, __pagesize));
|
||||
|
||||
// should be a placeholder. unless there's some other mapping we didn't free.
|
||||
PlaceholderMap::iterator phit = FindPlaceholder(map_offset);
|
||||
pxAssertMsg(phit != m_placeholder_ranges.end(), "Page we're mapping is a placeholder");
|
||||
pxAssertMsg(map_offset >= phit->first && map_offset < phit->second, "Page is in returned placeholder range");
|
||||
pxAssertMsg((map_offset + map_size) <= phit->second, "Page range is in returned placeholder range");
|
||||
|
||||
// do we need to split to the left? (i.e. is there a placeholder before this range)
|
||||
const size_t old_ph_end = phit->second;
|
||||
if (map_offset != phit->first)
|
||||
{
|
||||
phit->second = map_offset;
|
||||
|
||||
// split it (i.e. left..start and start..end are now separated)
|
||||
if (!VirtualFreeEx(GetCurrentProcess(), OffsetPointer(phit->first),
|
||||
(map_offset - phit->first), MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER))
|
||||
{
|
||||
pxFailRel("Failed to left split placeholder for map");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// start of the placeholder is getting used, we'll split it right below if there's anything left over
|
||||
m_placeholder_ranges.erase(phit);
|
||||
}
|
||||
|
||||
// do we need to split to the right? (i.e. is there a placeholder after this range)
|
||||
if ((map_offset + map_size) != old_ph_end)
|
||||
{
|
||||
// split out end..ph_end
|
||||
m_placeholder_ranges.emplace(map_offset + map_size, old_ph_end);
|
||||
|
||||
if (!VirtualFreeEx(GetCurrentProcess(), OffsetPointer(map_offset), map_size,
|
||||
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER))
|
||||
{
|
||||
pxFailRel("Failed to right split placeholder for map");
|
||||
}
|
||||
}
|
||||
|
||||
// actually do the mapping, replacing the placeholder on the range
|
||||
if (!MapViewOfFile3(static_cast<HANDLE>(file_handle), GetCurrentProcess(),
|
||||
map_base, file_offset, map_size, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0))
|
||||
{
|
||||
Console.Error("(SharedMemoryMappingArea) MapViewOfFile3() failed: %u", GetLastError());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const DWORD prot = ConvertToWinApi(mode);
|
||||
if (prot != PAGE_READWRITE)
|
||||
{
|
||||
DWORD old_prot;
|
||||
if (!VirtualProtect(map_base, map_size, prot, &old_prot))
|
||||
pxFail("Failed to protect memory mapping");
|
||||
}
|
||||
|
||||
m_num_mappings++;
|
||||
return static_cast<u8*>(map_base);
|
||||
}
|
||||
|
||||
bool SharedMemoryMappingArea::Unmap(void* map_base, size_t map_size)
|
||||
{
|
||||
pxAssert(static_cast<u8*>(map_base) >= m_base_ptr && static_cast<u8*>(map_base) < (m_base_ptr + m_size));
|
||||
|
||||
const size_t map_offset = static_cast<u8*>(map_base) - m_base_ptr;
|
||||
pxAssert(Common::IsAlignedPow2(map_offset, __pagesize));
|
||||
pxAssert(Common::IsAlignedPow2(map_size, __pagesize));
|
||||
|
||||
// unmap the specified range
|
||||
if (!UnmapViewOfFile2(GetCurrentProcess(), map_base, MEM_PRESERVE_PLACEHOLDER))
|
||||
{
|
||||
Console.Error("(SharedMemoryMappingArea) UnmapViewOfFile2() failed: %u", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// can we coalesce to the left?
|
||||
PlaceholderMap::iterator left_it = (map_offset > 0) ? FindPlaceholder(map_offset - 1) : m_placeholder_ranges.end();
|
||||
if (left_it != m_placeholder_ranges.end())
|
||||
{
|
||||
// the left placeholder should end at our start
|
||||
pxAssert(map_offset == left_it->second);
|
||||
left_it->second = map_offset + map_size;
|
||||
|
||||
// combine placeholders before and the range we're unmapping, i.e. to the left
|
||||
if (!VirtualFreeEx(GetCurrentProcess(), OffsetPointer(left_it->first),
|
||||
left_it->second - left_it->first, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS))
|
||||
{
|
||||
pxFail("Failed to coalesce placeholders left for unmap");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is a new placeholder
|
||||
left_it = m_placeholder_ranges.emplace(map_offset, map_offset + map_size).first;
|
||||
}
|
||||
|
||||
// can we coalesce to the right?
|
||||
PlaceholderMap::iterator right_it = ((map_offset + map_size) < m_size) ? FindPlaceholder(map_offset + map_size) : m_placeholder_ranges.end();
|
||||
if (right_it != m_placeholder_ranges.end())
|
||||
{
|
||||
// should start at our end
|
||||
pxAssert(right_it->first == (map_offset + map_size));
|
||||
left_it->second = right_it->second;
|
||||
m_placeholder_ranges.erase(right_it);
|
||||
|
||||
// combine our placeholder and the next, i.e. to the right
|
||||
if (!VirtualFreeEx(GetCurrentProcess(), OffsetPointer(left_it->first),
|
||||
left_it->second - left_it->first, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS))
|
||||
{
|
||||
pxFail("Failed to coalescae placeholders right for unmap");
|
||||
}
|
||||
}
|
||||
|
||||
m_num_mappings--;
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace PageFaultHandler
|
||||
{
|
||||
static LONG ExceptionHandler(PEXCEPTION_POINTERS exi);
|
||||
|
||||
static std::recursive_mutex s_exception_handler_mutex;
|
||||
static bool s_in_exception_handler = false;
|
||||
static bool s_installed = false;
|
||||
} // namespace PageFaultHandler
|
||||
|
||||
LONG PageFaultHandler::ExceptionHandler(PEXCEPTION_POINTERS exi)
|
||||
{
|
||||
// Executing the handler concurrently from multiple threads wouldn't go down well.
|
||||
std::unique_lock lock(s_exception_handler_mutex);
|
||||
|
||||
// Prevent recursive exception filtering.
|
||||
if (s_in_exception_handler)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
// Only interested in page faults.
|
||||
if (exi->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
#if defined(_M_X86)
|
||||
void* const exception_pc = reinterpret_cast<void*>(exi->ContextRecord->Rip);
|
||||
#elif defined(_M_ARM64)
|
||||
void* const exception_pc = reinterpret_cast<void*>(exi->ContextRecord->Pc);
|
||||
#else
|
||||
void* const exception_pc = nullptr;
|
||||
#endif
|
||||
|
||||
void* const exception_address = reinterpret_cast<void*>(exi->ExceptionRecord->ExceptionInformation[1]);
|
||||
const bool is_write = exi->ExceptionRecord->ExceptionInformation[0] == 1;
|
||||
|
||||
s_in_exception_handler = true;
|
||||
|
||||
const HandlerResult handled = HandlePageFault(exception_pc, exception_address, is_write);
|
||||
|
||||
s_in_exception_handler = false;
|
||||
|
||||
return (handled == HandlerResult::ContinueExecution) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
bool PageFaultHandler::Install(Error* error)
|
||||
{
|
||||
std::unique_lock lock(s_exception_handler_mutex);
|
||||
pxAssertRel(!s_installed, "Page fault handler has already been installed.");
|
||||
|
||||
PVOID handle = AddVectoredExceptionHandler(1, ExceptionHandler);
|
||||
if (!handle)
|
||||
{
|
||||
Error::SetWin32(error, "AddVectoredExceptionHandler() failed: ", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
s_installed = true;
|
||||
return true;
|
||||
}
|
||||
188
common/Windows/WinMisc.cpp
Normal file
188
common/Windows/WinMisc.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/Console.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/HostSys.h"
|
||||
#include "common/RedtapeWindows.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Threading.h"
|
||||
#include "common/WindowInfo.h"
|
||||
|
||||
#include <mmsystem.h>
|
||||
#include <timeapi.h>
|
||||
#include <VersionHelpers.h>
|
||||
|
||||
// If anything tries to read this as an initializer, we're in trouble.
|
||||
static const LARGE_INTEGER lfreq = []() {
|
||||
LARGE_INTEGER ret = {};
|
||||
QueryPerformanceFrequency(&ret);
|
||||
return ret;
|
||||
}();
|
||||
|
||||
// This gets leaked... oh well.
|
||||
static thread_local HANDLE s_sleep_timer;
|
||||
static thread_local bool s_sleep_timer_created = false;
|
||||
|
||||
static HANDLE GetSleepTimer()
|
||||
{
|
||||
if (s_sleep_timer_created)
|
||||
return s_sleep_timer;
|
||||
|
||||
s_sleep_timer_created = true;
|
||||
s_sleep_timer = CreateWaitableTimerEx(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
|
||||
if (!s_sleep_timer)
|
||||
s_sleep_timer = CreateWaitableTimer(nullptr, TRUE, nullptr);
|
||||
|
||||
return s_sleep_timer;
|
||||
}
|
||||
|
||||
u64 GetTickFrequency()
|
||||
{
|
||||
return lfreq.QuadPart;
|
||||
}
|
||||
|
||||
u64 GetCPUTicks()
|
||||
{
|
||||
LARGE_INTEGER count;
|
||||
QueryPerformanceCounter(&count);
|
||||
return count.QuadPart;
|
||||
}
|
||||
|
||||
u64 GetPhysicalMemory()
|
||||
{
|
||||
MEMORYSTATUSEX status;
|
||||
status.dwLength = sizeof(status);
|
||||
GlobalMemoryStatusEx(&status);
|
||||
return status.ullTotalPhys;
|
||||
}
|
||||
|
||||
u64 GetAvailablePhysicalMemory()
|
||||
{
|
||||
MEMORYSTATUSEX status;
|
||||
status.dwLength = sizeof(status);
|
||||
GlobalMemoryStatusEx(&status);
|
||||
return status.ullAvailPhys;
|
||||
}
|
||||
|
||||
// Calculates the Windows OS Version and processor architecture, and returns it as a
|
||||
// human-readable string. :)
|
||||
std::string GetOSVersionString()
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
SYSTEM_INFO si;
|
||||
GetNativeSystemInfo(&si);
|
||||
|
||||
if (IsWindows10OrGreater())
|
||||
{
|
||||
retval = "Microsoft ";
|
||||
retval += IsWindowsServer() ? "Windows Server 2016+" : "Windows 10+";
|
||||
|
||||
}
|
||||
else
|
||||
retval = "Unsupported Operating System!";
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool Common::InhibitScreensaver(bool inhibit)
|
||||
{
|
||||
EXECUTION_STATE flags = ES_CONTINUOUS;
|
||||
if (inhibit)
|
||||
flags |= ES_DISPLAY_REQUIRED;
|
||||
SetThreadExecutionState(flags);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Common::SetMousePosition(int x, int y)
|
||||
{
|
||||
SetCursorPos(x, y);
|
||||
}
|
||||
|
||||
/*
|
||||
static HHOOK mouseHook = nullptr;
|
||||
static std::function<void(int, int)> fnMouseMoveCb;
|
||||
LRESULT CALLBACK Mousecb(int nCode, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (nCode >= 0 && wParam == WM_MOUSEMOVE)
|
||||
{
|
||||
MSLLHOOKSTRUCT* mouse = (MSLLHOOKSTRUCT*)lParam;
|
||||
fnMouseMoveCb(mouse->pt.x, mouse->pt.y);
|
||||
}
|
||||
return CallNextHookEx(mouseHook, nCode, wParam, lParam);
|
||||
}
|
||||
*/
|
||||
|
||||
// This (and the above) works, but is not recommended on Windows and is only here for consistency.
|
||||
// Defer to using raw input instead.
|
||||
bool Common::AttachMousePositionCb(std::function<void(int, int)> cb)
|
||||
{
|
||||
/*
|
||||
if (mouseHook)
|
||||
Common::DetachMousePositionCb();
|
||||
|
||||
fnMouseMoveCb = cb;
|
||||
mouseHook = SetWindowsHookEx(WH_MOUSE_LL, Mousecb, GetModuleHandle(NULL), 0);
|
||||
if (!mouseHook)
|
||||
{
|
||||
Console.Warning("Failed to set mouse hook: %d", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(PCSX2_DEBUG) || defined(PCSX2_DEVBUILD)
|
||||
static bool warned = false;
|
||||
if (!warned)
|
||||
{
|
||||
Console.Warning("Mouse hooks are enabled, and this isn't a release build! Using a debugger, or loading symbols, _will_ stall the hook and cause global mouse lag.");
|
||||
warned = true;
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
void Common::DetachMousePositionCb()
|
||||
{
|
||||
/*
|
||||
UnhookWindowsHookEx(mouseHook);
|
||||
mouseHook = nullptr;
|
||||
*/
|
||||
}
|
||||
|
||||
bool Common::PlaySoundAsync(const char* path)
|
||||
{
|
||||
const std::wstring wpath = FileSystem::GetWin32Path(path);
|
||||
return PlaySoundW(wpath.c_str(), NULL, SND_ASYNC | SND_NODEFAULT);
|
||||
}
|
||||
|
||||
void Threading::Sleep(int ms)
|
||||
{
|
||||
::Sleep(ms);
|
||||
}
|
||||
|
||||
void Threading::SleepUntil(u64 ticks)
|
||||
{
|
||||
// This is definitely sub-optimal, but there's no way to sleep until a QPC timestamp on Win32.
|
||||
const s64 diff = static_cast<s64>(ticks - GetCPUTicks());
|
||||
if (diff <= 0)
|
||||
return;
|
||||
|
||||
const HANDLE hTimer = GetSleepTimer();
|
||||
if (!hTimer)
|
||||
return;
|
||||
|
||||
const u64 one_hundred_nanos_diff = (static_cast<u64>(diff) * 10000000ULL) / GetTickFrequency();
|
||||
if (one_hundred_nanos_diff == 0)
|
||||
return;
|
||||
|
||||
LARGE_INTEGER fti;
|
||||
fti.QuadPart = -static_cast<s64>(one_hundred_nanos_diff);
|
||||
|
||||
if (SetWaitableTimer(hTimer, &fti, 0, nullptr, nullptr, FALSE))
|
||||
{
|
||||
WaitForSingleObject(hTimer, INFINITE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
289
common/Windows/WinThreads.cpp
Normal file
289
common/Windows/WinThreads.cpp
Normal file
@@ -0,0 +1,289 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/Threading.h"
|
||||
#include "common/Assertions.h"
|
||||
#include "common/RedtapeWindows.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <mmsystem.h>
|
||||
#include <process.h>
|
||||
#include <timeapi.h>
|
||||
|
||||
__fi void Threading::Timeslice()
|
||||
{
|
||||
::Sleep(0);
|
||||
}
|
||||
|
||||
// For use in spin/wait loops, Acts as a hint to Intel CPUs and should, in theory
|
||||
// improve performance and reduce cpu power consumption.
|
||||
__fi void Threading::SpinWait()
|
||||
{
|
||||
#ifdef _M_X86
|
||||
_mm_pause();
|
||||
#else
|
||||
YieldProcessor();
|
||||
#endif
|
||||
}
|
||||
|
||||
__fi void Threading::EnableHiresScheduler()
|
||||
{
|
||||
// This improves accuracy of Sleep() by some amount, and only adds a negligible amount of
|
||||
// overhead on modern CPUs. Typically desktops are already set pretty low, but laptops in
|
||||
// particular may have a scheduler Period of 15 or 20ms to extend battery life.
|
||||
|
||||
// (note: this same trick is used by most multimedia software and games)
|
||||
|
||||
timeBeginPeriod(1);
|
||||
}
|
||||
|
||||
__fi void Threading::DisableHiresScheduler()
|
||||
{
|
||||
timeEndPeriod(1);
|
||||
}
|
||||
|
||||
Threading::ThreadHandle::ThreadHandle() = default;
|
||||
|
||||
Threading::ThreadHandle::ThreadHandle(const ThreadHandle& handle)
|
||||
{
|
||||
if (handle.m_native_handle)
|
||||
{
|
||||
HANDLE new_handle;
|
||||
if (DuplicateHandle(GetCurrentProcess(), (HANDLE)handle.m_native_handle,
|
||||
GetCurrentProcess(), &new_handle, THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, 0))
|
||||
{
|
||||
m_native_handle = (void*)new_handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Threading::ThreadHandle::ThreadHandle(ThreadHandle&& handle)
|
||||
: m_native_handle(handle.m_native_handle)
|
||||
{
|
||||
handle.m_native_handle = nullptr;
|
||||
}
|
||||
|
||||
|
||||
Threading::ThreadHandle::~ThreadHandle()
|
||||
{
|
||||
if (m_native_handle)
|
||||
CloseHandle(m_native_handle);
|
||||
}
|
||||
|
||||
Threading::ThreadHandle Threading::ThreadHandle::GetForCallingThread()
|
||||
{
|
||||
ThreadHandle ret;
|
||||
ret.m_native_handle = (void*)OpenThread(THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, GetCurrentThreadId());
|
||||
return ret;
|
||||
}
|
||||
|
||||
Threading::ThreadHandle& Threading::ThreadHandle::operator=(ThreadHandle&& handle)
|
||||
{
|
||||
if (m_native_handle)
|
||||
CloseHandle((HANDLE)m_native_handle);
|
||||
m_native_handle = handle.m_native_handle;
|
||||
handle.m_native_handle = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Threading::ThreadHandle& Threading::ThreadHandle::operator=(const ThreadHandle& handle)
|
||||
{
|
||||
if (m_native_handle)
|
||||
{
|
||||
CloseHandle((HANDLE)m_native_handle);
|
||||
m_native_handle = nullptr;
|
||||
}
|
||||
|
||||
HANDLE new_handle;
|
||||
if (DuplicateHandle(GetCurrentProcess(), (HANDLE)handle.m_native_handle,
|
||||
GetCurrentProcess(), &new_handle, THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, 0))
|
||||
{
|
||||
m_native_handle = (void*)new_handle;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
u64 Threading::ThreadHandle::GetCPUTime() const
|
||||
{
|
||||
#ifndef _M_ARM64
|
||||
u64 ret = 0;
|
||||
if (m_native_handle)
|
||||
QueryThreadCycleTime((HANDLE)m_native_handle, &ret);
|
||||
return ret;
|
||||
#else
|
||||
FILETIME user, kernel, unused;
|
||||
if (!GetThreadTimes((HANDLE)m_native_handle, &unused, &unused, &kernel, &user))
|
||||
return 0;
|
||||
|
||||
const u64 user_time = (static_cast<u64>(user.dwHighDateTime) << 32) | static_cast<u64>(user.dwLowDateTime);
|
||||
const u64 kernel_time = (static_cast<u64>(kernel.dwHighDateTime) << 32) | static_cast<u64>(kernel.dwLowDateTime);
|
||||
return user_time + kernel_time;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Threading::ThreadHandle::SetAffinity(u64 processor_mask) const
|
||||
{
|
||||
if (processor_mask == 0)
|
||||
processor_mask = ~processor_mask;
|
||||
|
||||
return (SetThreadAffinityMask(GetCurrentThread(), (DWORD_PTR)processor_mask) != 0 || GetLastError() != ERROR_SUCCESS);
|
||||
}
|
||||
|
||||
Threading::Thread::Thread() = default;
|
||||
|
||||
Threading::Thread::Thread(Thread&& thread)
|
||||
: ThreadHandle(thread)
|
||||
, m_stack_size(thread.m_stack_size)
|
||||
{
|
||||
thread.m_stack_size = 0;
|
||||
}
|
||||
|
||||
Threading::Thread::Thread(EntryPoint func)
|
||||
: ThreadHandle()
|
||||
{
|
||||
if (!Start(std::move(func)))
|
||||
pxFailRel("Failed to start implicitly started thread.");
|
||||
}
|
||||
|
||||
Threading::Thread::~Thread()
|
||||
{
|
||||
pxAssertRel(!m_native_handle, "Thread should be detached or joined at destruction");
|
||||
}
|
||||
|
||||
void Threading::Thread::SetStackSize(u32 size)
|
||||
{
|
||||
pxAssertRel(!m_native_handle, "Can't change the stack size on a started thread");
|
||||
m_stack_size = size;
|
||||
}
|
||||
|
||||
unsigned Threading::Thread::ThreadProc(void* param)
|
||||
{
|
||||
std::unique_ptr<EntryPoint> entry(static_cast<EntryPoint*>(param));
|
||||
(*entry.get())();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Threading::Thread::Start(EntryPoint func)
|
||||
{
|
||||
pxAssertRel(!m_native_handle, "Can't start an already-started thread");
|
||||
|
||||
std::unique_ptr<EntryPoint> func_clone(std::make_unique<EntryPoint>(std::move(func)));
|
||||
unsigned thread_id;
|
||||
m_native_handle = reinterpret_cast<void*>(_beginthreadex(nullptr, m_stack_size, ThreadProc, func_clone.get(), 0, &thread_id));
|
||||
if (!m_native_handle)
|
||||
return false;
|
||||
|
||||
// thread started, it'll release the memory
|
||||
func_clone.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Threading::Thread::Detach()
|
||||
{
|
||||
pxAssertRel(m_native_handle, "Can't detach without a thread");
|
||||
CloseHandle((HANDLE)m_native_handle);
|
||||
m_native_handle = nullptr;
|
||||
}
|
||||
|
||||
void Threading::Thread::Join()
|
||||
{
|
||||
pxAssertRel(m_native_handle, "Can't join without a thread");
|
||||
const DWORD res = WaitForSingleObject((HANDLE)m_native_handle, INFINITE);
|
||||
if (res != WAIT_OBJECT_0)
|
||||
pxFailRel("WaitForSingleObject() for thread join failed");
|
||||
|
||||
CloseHandle((HANDLE)m_native_handle);
|
||||
m_native_handle = nullptr;
|
||||
}
|
||||
|
||||
Threading::ThreadHandle& Threading::Thread::operator=(Thread&& thread)
|
||||
{
|
||||
ThreadHandle::operator=(thread);
|
||||
m_stack_size = thread.m_stack_size;
|
||||
thread.m_stack_size = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
u64 Threading::GetThreadCpuTime()
|
||||
{
|
||||
#ifndef _M_ARM64
|
||||
u64 ret = 0;
|
||||
QueryThreadCycleTime(GetCurrentThread(), &ret);
|
||||
return ret;
|
||||
#else
|
||||
FILETIME user, kernel, unused;
|
||||
if (!GetThreadTimes(GetCurrentThread(), &unused, &unused, &kernel, &user))
|
||||
return 0;
|
||||
|
||||
const u64 user_time = (static_cast<u64>(user.dwHighDateTime) << 32) | static_cast<u64>(user.dwLowDateTime);
|
||||
const u64 kernel_time = (static_cast<u64>(kernel.dwHighDateTime) << 32) | static_cast<u64>(kernel.dwLowDateTime);
|
||||
return user_time + kernel_time;
|
||||
#endif
|
||||
}
|
||||
|
||||
u64 Threading::GetThreadTicksPerSecond()
|
||||
{
|
||||
#ifndef _M_ARM64
|
||||
// On x86, despite what the MS documentation says, this basically appears to be rdtsc.
|
||||
// So, the frequency is our base clock speed (and stable regardless of power management).
|
||||
static u64 frequency = 0;
|
||||
if (frequency == 0) [[unlikely]]
|
||||
{
|
||||
HKEY key;
|
||||
LSTATUS res =
|
||||
RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key);
|
||||
if (res == ERROR_SUCCESS)
|
||||
{
|
||||
DWORD mhz;
|
||||
DWORD size = sizeof(mhz);
|
||||
res = RegQueryValueExW(key, L"~MHz", nullptr, nullptr, reinterpret_cast<LPBYTE>(&mhz), &size);
|
||||
if (res == ERROR_SUCCESS)
|
||||
frequency = static_cast<u64>(mhz) * static_cast<u64>(1000000);
|
||||
RegCloseKey(key);
|
||||
}
|
||||
}
|
||||
return frequency;
|
||||
#else
|
||||
return 10000000;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Threading::SetNameOfCurrentThread(const char* name)
|
||||
{
|
||||
// This feature needs Windows headers and MSVC's SEH support:
|
||||
|
||||
#if defined(_WIN32) && defined(_MSC_VER)
|
||||
|
||||
// This code sample was borrowed form some obscure MSDN article.
|
||||
// In a rare bout of sanity, it's an actual Microsoft-published hack
|
||||
// that actually works!
|
||||
|
||||
static const int MS_VC_EXCEPTION = 0x406D1388;
|
||||
|
||||
#pragma pack(push, 8)
|
||||
struct THREADNAME_INFO
|
||||
{
|
||||
DWORD dwType; // Must be 0x1000.
|
||||
LPCSTR szName; // Pointer to name (in user addr space).
|
||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
THREADNAME_INFO info;
|
||||
info.dwType = 0x1000;
|
||||
info.szName = name;
|
||||
info.dwThreadID = GetCurrentThreadId();
|
||||
info.dwFlags = 0;
|
||||
|
||||
__try
|
||||
{
|
||||
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
40
common/WrappedMemCopy.h
Normal file
40
common/WrappedMemCopy.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
|
||||
[[maybe_unused]]
|
||||
__ri static void MemCopy_WrappedDest(const u128* src, u128* destBase, uint& destStart, uint destSize, uint len)
|
||||
{
|
||||
uint endpos = destStart + len;
|
||||
if (endpos < destSize)
|
||||
{
|
||||
memcpy(&destBase[destStart], src, len * 16);
|
||||
destStart += len;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint firstcopylen = destSize - destStart;
|
||||
memcpy(&destBase[destStart], src, firstcopylen * 16);
|
||||
destStart = endpos % destSize;
|
||||
memcpy(destBase, src + firstcopylen, destStart * 16);
|
||||
}
|
||||
}
|
||||
|
||||
[[maybe_unused]]
|
||||
__ri static void MemCopy_WrappedSrc(const u128* srcBase, uint& srcStart, uint srcSize, u128* dest, uint len)
|
||||
{
|
||||
uint endpos = srcStart + len;
|
||||
if (endpos < srcSize)
|
||||
{
|
||||
memcpy(dest, &srcBase[srcStart], len * 16);
|
||||
srcStart += len;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint firstcopylen = srcSize - srcStart;
|
||||
memcpy(dest, &srcBase[srcStart], firstcopylen * 16);
|
||||
srcStart = endpos % srcSize;
|
||||
memcpy(dest + firstcopylen, srcBase, srcStart * 16);
|
||||
}
|
||||
}
|
||||
142
common/ZipHelpers.h
Normal file
142
common/ZipHelpers.h
Normal file
@@ -0,0 +1,142 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "zip.h"
|
||||
|
||||
#include "Console.h"
|
||||
|
||||
static inline std::unique_ptr<zip_t, void (*)(zip_t*)> zip_open_managed(const char* filename, int flags, zip_error_t* ze)
|
||||
{
|
||||
zip_source_t* zs = zip_source_file_create(filename, 0, 0, ze);
|
||||
zip_t* zip = nullptr;
|
||||
if (zs && !(zip = zip_open_from_source(zs, flags, ze)))
|
||||
{
|
||||
// have to clean up source
|
||||
zip_source_free(zs);
|
||||
}
|
||||
|
||||
return std::unique_ptr<zip_t, void (*)(zip_t*)>(zip, [](zip_t* zf) {
|
||||
if (!zf)
|
||||
return;
|
||||
|
||||
int err = zip_close(zf);
|
||||
if (err != 0)
|
||||
{
|
||||
Console.Error("Failed to close zip file: %d", err);
|
||||
zip_discard(zf);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static inline std::unique_ptr<zip_t, void (*)(zip_t*)> zip_open_buffer_managed(const void* buffer, size_t size, int flags, int freep, zip_error_t* ze)
|
||||
{
|
||||
zip_source_t* zs = zip_source_buffer_create(buffer, size, freep, ze);
|
||||
zip_t* zip = nullptr;
|
||||
if (zs && !(zip = zip_open_from_source(zs, flags, ze)))
|
||||
{
|
||||
// have to clean up source
|
||||
zip_source_free(zs);
|
||||
}
|
||||
|
||||
return std::unique_ptr<zip_t, void (*)(zip_t*)>(zip, [](zip_t* zf) {
|
||||
if (!zf)
|
||||
return;
|
||||
|
||||
int err = zip_close(zf);
|
||||
if (err != 0)
|
||||
{
|
||||
Console.Error("Failed to close zip file: %d", err);
|
||||
zip_discard(zf);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static inline std::unique_ptr<zip_file_t, int (*)(zip_file_t*)> zip_fopen_managed(zip_t* zip, const char* filename, zip_flags_t flags)
|
||||
{
|
||||
return std::unique_ptr<zip_file_t, int (*)(zip_file_t*)>(zip_fopen(zip, filename, flags), zip_fclose);
|
||||
}
|
||||
|
||||
static inline std::unique_ptr<zip_file_t, int (*)(zip_file_t*)> zip_fopen_index_managed(zip_t* zip, zip_uint64_t index, zip_flags_t flags)
|
||||
{
|
||||
return std::unique_ptr<zip_file_t, int (*)(zip_file_t*)>(zip_fopen_index(zip, index, flags), zip_fclose);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline std::optional<T> ReadFileInZipToContainer(zip_t* zip, const char* name)
|
||||
{
|
||||
std::optional<T> ret;
|
||||
const zip_int64_t file_index = zip_name_locate(zip, name, ZIP_FL_NOCASE);
|
||||
if (file_index >= 0)
|
||||
{
|
||||
zip_stat_t zst;
|
||||
if (zip_stat_index(zip, file_index, ZIP_FL_NOCASE, &zst) == 0)
|
||||
{
|
||||
zip_file_t* zf = zip_fopen_index(zip, file_index, ZIP_FL_NOCASE);
|
||||
if (zf)
|
||||
{
|
||||
ret = T();
|
||||
ret->resize(static_cast<size_t>(zst.size));
|
||||
if (zip_fread(zf, ret->data(), ret->size()) != static_cast<zip_int64_t>(ret->size()))
|
||||
{
|
||||
ret.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
static inline std::optional<T> ReadFileInZipToContainer(zip_file_t* file, u32 chunk_size = 4096)
|
||||
{
|
||||
std::optional<T> ret = T();
|
||||
for (;;)
|
||||
{
|
||||
const size_t pos = ret->size();
|
||||
ret->resize(pos + chunk_size);
|
||||
const s64 read = zip_fread(file, ret->data() + pos, chunk_size);
|
||||
if (read < 0)
|
||||
{
|
||||
// read error
|
||||
ret.reset();
|
||||
break;
|
||||
}
|
||||
|
||||
// if less than chunk size, we're EOF
|
||||
if (read != static_cast<s64>(chunk_size))
|
||||
{
|
||||
ret->resize(pos + static_cast<size_t>(read));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static inline std::optional<std::string> ReadFileInZipToString(zip_t* zip, const char* name)
|
||||
{
|
||||
return ReadFileInZipToContainer<std::string>(zip, name);
|
||||
}
|
||||
|
||||
static inline std::optional<std::string> ReadFileInZipToString(zip_file_t* file, u32 chunk_size = 4096)
|
||||
{
|
||||
return ReadFileInZipToContainer<std::string>(file, chunk_size);
|
||||
}
|
||||
|
||||
static inline std::optional<std::vector<u8>> ReadBinaryFileInZip(zip_t* zip, const char* name)
|
||||
{
|
||||
return ReadFileInZipToContainer<std::vector<u8>>(zip, name);
|
||||
}
|
||||
|
||||
static inline std::optional<std::vector<u8>> ReadBinaryFileInZip(zip_file_t* file, u32 chunk_size = 4096)
|
||||
{
|
||||
return ReadFileInZipToContainer<std::vector<u8>>(file, chunk_size);
|
||||
}
|
||||
210
common/boost_spsc_queue.hpp
Normal file
210
common/boost_spsc_queue.hpp
Normal file
@@ -0,0 +1,210 @@
|
||||
// This version is a stripped down version of boost/lockfree/spsc_queue.hpp boost_spsc_queue.hpp
|
||||
// Rational
|
||||
// * Performance is better on linux than the standard std::queue
|
||||
// * Performance in the same on windows
|
||||
// => 100-200MB of dependency feel rather unfriendly
|
||||
|
||||
// Potential optimization
|
||||
// * plug condition variable into the queue directly to avoid redundant m_count
|
||||
|
||||
// * Restore boost optimization
|
||||
// => unlikely or replace it with a % (if size is 2^n)
|
||||
|
||||
|
||||
// lock-free single-producer/single-consumer ringbuffer
|
||||
// this algorithm is implemented in various projects (linux kernel)
|
||||
//
|
||||
// Copyright (C) 2009-2013 Tim Blechmann
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See
|
||||
// accompanying file LICENSE_1_0.txt or copy at
|
||||
// http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// Boost Software License - Version 1.0 - August 17th, 2003
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person or organization
|
||||
// obtaining a copy of the software and accompanying documentation covered by
|
||||
// this license (the "Software") to use, reproduce, display, distribute,
|
||||
// execute, and transmit the Software, and to prepare derivative works of the
|
||||
// Software, and to permit third-parties to whom the Software is furnished to
|
||||
// do so, all subject to the following:
|
||||
//
|
||||
// The copyright notices in the Software and this entire statement, including
|
||||
// the above license grant, this restriction and the following disclaimer,
|
||||
// must be included in all copies of the Software, in whole or in part, and
|
||||
// all derivative works of the Software, unless such copies or derivative
|
||||
// works are solely in the form of machine-executable object code generated by
|
||||
// a source language processor.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include <atomic>
|
||||
#include "AlignedMalloc.h"
|
||||
#include "Pcsx2Defs.h"
|
||||
|
||||
template <typename T, size_t max_size>
|
||||
class ringbuffer_base
|
||||
{
|
||||
static const int padding_size = __cachelinesize - sizeof(size_t);
|
||||
|
||||
std::atomic<size_t> write_index_;
|
||||
char padding1[padding_size]; /* force read_index and write_index to different cache lines */
|
||||
std::atomic<size_t> read_index_;
|
||||
char padding2[padding_size]; /* force read_index and pending_pop_read_index to different cache lines */
|
||||
|
||||
size_t pending_pop_read_index;
|
||||
|
||||
T *buffer;
|
||||
|
||||
ringbuffer_base(ringbuffer_base const &) = delete;
|
||||
ringbuffer_base(ringbuffer_base &&) = delete;
|
||||
const ringbuffer_base& operator=( const ringbuffer_base& ) = delete;
|
||||
|
||||
public:
|
||||
ringbuffer_base(void):
|
||||
write_index_(0), read_index_(0), pending_pop_read_index(0)
|
||||
{
|
||||
// Use dynamically allocation here with no T object dependency
|
||||
// Otherwise the ringbuffer_base destructor will call the destructor
|
||||
// of T which crash if T is a (invalid) shared_ptr.
|
||||
//
|
||||
// Note another solution will be to create a char buffer as union of T
|
||||
buffer = (T*)_aligned_malloc(sizeof(T)*max_size, 32);
|
||||
}
|
||||
|
||||
~ringbuffer_base(void) {
|
||||
// destroy all remaining items
|
||||
T out;
|
||||
while (pop(out)) {};
|
||||
|
||||
_aligned_free(buffer);
|
||||
}
|
||||
|
||||
|
||||
static size_t next_index(size_t arg)
|
||||
{
|
||||
size_t ret = arg + 1;
|
||||
#if 0
|
||||
// Initial boost code
|
||||
while (unlikely(ret >= max_size))
|
||||
ret -= max_size;
|
||||
#else
|
||||
ret %= max_size;
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool push(T const & t)
|
||||
{
|
||||
const size_t write_index = write_index_.load(std::memory_order_relaxed); // only written from push thread
|
||||
const size_t next = next_index(write_index);
|
||||
|
||||
if (next == read_index_.load(std::memory_order_acquire))
|
||||
return false; /* ringbuffer is full */
|
||||
|
||||
new (buffer + write_index) T(t); // copy-construct
|
||||
|
||||
write_index_.store(next, std::memory_order_release);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pop (T & ret)
|
||||
{
|
||||
const size_t write_index = write_index_.load(std::memory_order_acquire);
|
||||
const size_t read_index = read_index_.load(std::memory_order_relaxed); // only written from pop thread
|
||||
if (empty(write_index, read_index))
|
||||
return false;
|
||||
|
||||
ret = buffer[read_index];
|
||||
buffer[read_index].~T();
|
||||
|
||||
size_t next = next_index(read_index);
|
||||
read_index_.store(next, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
T& front()
|
||||
{
|
||||
pending_pop_read_index = read_index_.load(std::memory_order_relaxed); // only written from pop thread
|
||||
|
||||
return buffer[pending_pop_read_index];
|
||||
}
|
||||
|
||||
void pop()
|
||||
{
|
||||
buffer[pending_pop_read_index].~T();
|
||||
|
||||
size_t next = next_index(pending_pop_read_index);
|
||||
read_index_.store(next, std::memory_order_release);
|
||||
}
|
||||
|
||||
template <typename Functor>
|
||||
bool consume_one(Functor & f)
|
||||
{
|
||||
const size_t write_index = write_index_.load(std::memory_order_acquire);
|
||||
const size_t read_index = read_index_.load(std::memory_order_relaxed); // only written from pop thread
|
||||
if (empty(write_index, read_index))
|
||||
return false;
|
||||
|
||||
f(buffer[read_index]);
|
||||
buffer[read_index].~T();
|
||||
|
||||
size_t next = next_index(read_index);
|
||||
read_index_.store(next, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
/** reset the ringbuffer
|
||||
*
|
||||
* \note Not thread-safe
|
||||
* */
|
||||
void reset(void)
|
||||
{
|
||||
write_index_.store(0, std::memory_order_relaxed);
|
||||
read_index_.store(0, std::memory_order_release);
|
||||
}
|
||||
|
||||
/** Check if the ringbuffer is empty
|
||||
*
|
||||
* \return true, if the ringbuffer is empty, false otherwise
|
||||
* \note Due to the concurrent nature of the ringbuffer the result may be inaccurate.
|
||||
* */
|
||||
bool empty(void)
|
||||
{
|
||||
return empty(write_index_.load(std::memory_order_relaxed), read_index_.load(std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
/**
|
||||
* \return true, if implementation is lock-free.
|
||||
*
|
||||
* */
|
||||
bool is_lock_free(void) const
|
||||
{
|
||||
return write_index_.is_lock_free() && read_index_.is_lock_free();
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
const size_t write_index = write_index_.load(std::memory_order_relaxed);
|
||||
const size_t read_index = read_index_.load(std::memory_order_relaxed);
|
||||
if (read_index > write_index) {
|
||||
return (write_index + max_size) - read_index;
|
||||
} else {
|
||||
return write_index - read_index;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool empty(size_t write_index, size_t read_index)
|
||||
{
|
||||
return write_index == read_index;
|
||||
}
|
||||
};
|
||||
194
common/common.vcxproj
Normal file
194
common/common.vcxproj
Normal file
@@ -0,0 +1,194 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(SolutionDir)common\vsprops\BaseProjectConfig.props" />
|
||||
<Import Project="$(SolutionDir)common\vsprops\WinSDK.props" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{4639972E-424E-4E13-8B07-CA403C481346}</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<PlatformToolset Condition="!$(Configuration.Contains(Clang))">$(DefaultPlatformToolset)</PlatformToolset>
|
||||
<PlatformToolset Condition="$(Configuration.Contains(Clang))">ClangCL</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<WholeProgramOptimization Condition="$(Configuration.Contains(Release))">true</WholeProgramOptimization>
|
||||
<UseDebugLibraries Condition="$(Configuration.Contains(Debug))">true</UseDebugLibraries>
|
||||
<UseDebugLibraries Condition="!$(Configuration.Contains(Debug))">false</UseDebugLibraries>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="vsprops\common.props" />
|
||||
<Import Condition="$(Configuration.Contains(Debug))" Project="vsprops\CodeGen_Debug.props" />
|
||||
<Import Condition="$(Configuration.Contains(Devel))" Project="vsprops\CodeGen_Devel.props" />
|
||||
<Import Condition="$(Configuration.Contains(Release))" Project="vsprops\CodeGen_Release.props" />
|
||||
<Import Condition="!$(Configuration.Contains(Release))" Project="vsprops\IncrementalLinking.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fast_float\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fmt\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\jpgd</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<ForcedIncludeFiles>PrecompiledHeader.h</ForcedIncludeFiles>
|
||||
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
|
||||
<ObjectFileName>$(IntDir)%(RelativeDir)</ObjectFileName>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AlignedMalloc.cpp" />
|
||||
<ClCompile Include="Assertions.cpp" />
|
||||
<ClCompile Include="Console.cpp" />
|
||||
<ClCompile Include="CrashHandler.cpp" />
|
||||
<ClCompile Include="DynamicLibrary.cpp" />
|
||||
<ClCompile Include="Error.cpp" />
|
||||
<ClCompile Include="FastJmp.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'!='ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FileSystem.cpp" />
|
||||
<ClCompile Include="Image.cpp" />
|
||||
<ClCompile Include="HTTPDownloader.cpp" />
|
||||
<ClCompile Include="HTTPDownloaderCurl.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HTTPDownloaderWinHTTP.cpp" />
|
||||
<ClCompile Include="MD5Digest.cpp" />
|
||||
<ClCompile Include="MemorySettingsInterface.cpp" />
|
||||
<ClCompile Include="ProgressCallback.cpp" />
|
||||
<ClCompile Include="ReadbackSpinManager.cpp" />
|
||||
<ClCompile Include="SmallString.cpp" />
|
||||
<ClCompile Include="StackWalker.cpp" />
|
||||
<ClCompile Include="StringUtil.cpp" />
|
||||
<ClCompile Include="SettingsWrapper.cpp" />
|
||||
<ClCompile Include="TextureDecompress.cpp" />
|
||||
<ClCompile Include="Timer.cpp" />
|
||||
<ClCompile Include="WAVWriter.cpp" />
|
||||
<ClCompile Include="WindowInfo.cpp" />
|
||||
<ClCompile Include="Perf.cpp" />
|
||||
<ClCompile Include="PrecompiledHeader.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Windows\WinHostSys.cpp" />
|
||||
<ClCompile Include="Windows\WinMisc.cpp" />
|
||||
<ClCompile Include="Windows\WinThreads.cpp" />
|
||||
<ClCompile Include="HostSys.cpp" />
|
||||
<ClCompile Include="Semaphore.cpp" />
|
||||
<ClCompile Include="emitter\avx.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\bmi.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\fpu.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\groups.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\jmp.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\legacy.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\legacy_sse.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\movs.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\simd.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\x86emitter.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MASM Include="FastJmp.asm">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'!='x64'">true</ExcludedFromBuild>
|
||||
</MASM>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="BitUtils.h" />
|
||||
<ClInclude Include="AlignedMalloc.h" />
|
||||
<ClInclude Include="ByteSwap.h" />
|
||||
<ClInclude Include="CrashHandler.h" />
|
||||
<ClInclude Include="DynamicLibrary.h" />
|
||||
<ClInclude Include="Easing.h" />
|
||||
<ClInclude Include="boost_spsc_queue.hpp" />
|
||||
<ClInclude Include="Error.h" />
|
||||
<ClInclude Include="FastJmp.h" />
|
||||
<ClInclude Include="FileSystem.h" />
|
||||
<ClInclude Include="FPControl.h" />
|
||||
<ClInclude Include="HashCombine.h" />
|
||||
<ClInclude Include="HeapArray.h" />
|
||||
<ClInclude Include="HeterogeneousContainers.h" />
|
||||
<ClInclude Include="Image.h" />
|
||||
<ClInclude Include="SingleRegisterTypes.h" />
|
||||
<ClInclude Include="VectorIntrin.h" />
|
||||
<ClInclude Include="LRUCache.h" />
|
||||
<ClInclude Include="HTTPDownloader.h" />
|
||||
<ClInclude Include="HTTPDownloaderCurl.h">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HTTPDownloaderWinHTTP.h" />
|
||||
<ClInclude Include="MD5Digest.h" />
|
||||
<ClInclude Include="MemorySettingsInterface.h" />
|
||||
<ClInclude Include="ProgressCallback.h" />
|
||||
<ClInclude Include="ScopedGuard.h" />
|
||||
<ClInclude Include="SmallString.h" />
|
||||
<ClInclude Include="StackWalker.h" />
|
||||
<ClInclude Include="StringUtil.h" />
|
||||
<ClInclude Include="SettingsInterface.h" />
|
||||
<ClInclude Include="SettingsWrapper.h" />
|
||||
<ClInclude Include="Assertions.h" />
|
||||
<ClInclude Include="Console.h" />
|
||||
<ClInclude Include="HostSys.h" />
|
||||
<ClInclude Include="Path.h" />
|
||||
<ClInclude Include="PrecompiledHeader.h" />
|
||||
<ClInclude Include="ReadbackSpinManager.h" />
|
||||
<ClInclude Include="RedtapeWindows.h" />
|
||||
<ClInclude Include="TextureDecompress.h" />
|
||||
<ClInclude Include="Timer.h" />
|
||||
<ClInclude Include="WAVWriter.h" />
|
||||
<ClInclude Include="WindowInfo.h" />
|
||||
<ClInclude Include="Threading.h" />
|
||||
<ClInclude Include="emitter\implement\avx.h" />
|
||||
<ClInclude Include="emitter\implement\bmi.h" />
|
||||
<ClInclude Include="emitter\instructions.h" />
|
||||
<ClInclude Include="emitter\internal.h" />
|
||||
<ClInclude Include="emitter\legacy_instructions.h" />
|
||||
<ClInclude Include="emitter\legacy_internal.h" />
|
||||
<ClInclude Include="emitter\legacy_types.h" />
|
||||
<ClInclude Include="emitter\x86emitter.h" />
|
||||
<ClInclude Include="emitter\x86types.h" />
|
||||
<ClInclude Include="emitter\implement\dwshift.h" />
|
||||
<ClInclude Include="emitter\implement\group1.h" />
|
||||
<ClInclude Include="emitter\implement\group2.h" />
|
||||
<ClInclude Include="emitter\implement\group3.h" />
|
||||
<ClInclude Include="emitter\implement\helpers.h" />
|
||||
<ClInclude Include="emitter\implement\incdec.h" />
|
||||
<ClInclude Include="emitter\implement\jmpcall.h" />
|
||||
<ClInclude Include="emitter\implement\movs.h" />
|
||||
<ClInclude Include="emitter\implement\test.h" />
|
||||
<ClInclude Include="emitter\implement\xchg.h" />
|
||||
<ClInclude Include="emitter\implement\simd_arithmetic.h" />
|
||||
<ClInclude Include="emitter\implement\simd_comparisons.h" />
|
||||
<ClInclude Include="emitter\implement\simd_helpers.h" />
|
||||
<ClInclude Include="emitter\implement\simd_moremovs.h" />
|
||||
<ClInclude Include="emitter\implement\simd_shufflepack.h" />
|
||||
<ClInclude Include="WrappedMemCopy.h" />
|
||||
<ClInclude Include="ZipHelpers.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
352
common/common.vcxproj.filters
Normal file
352
common/common.vcxproj.filters
Normal file
@@ -0,0 +1,352 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AlignedMalloc.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\bmi.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Console.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\fpu.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\groups.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\jmp.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\legacy.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\legacy_sse.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\avx.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\movs.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HostSys.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Perf.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PrecompiledHeader.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ReadbackSpinManager.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Semaphore.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\simd.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Windows\WinHostSys.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Windows\WinMisc.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Windows\WinThreads.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="emitter\x86emitter.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FastJmp.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="StringUtil.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SettingsWrapper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WindowInfo.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="StringUtil.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Timer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProgressCallback.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MD5Digest.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="StackWalker.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CrashHandler.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Image.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HTTPDownloader.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HTTPDownloaderCurl.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HTTPDownloaderWinHTTP.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MemorySettingsInterface.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DynamicLibrary.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WAVWriter.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FileSystem.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Error.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Assertions.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TextureDecompress.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SmallString.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AlignedMalloc.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Assertions.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\bmi.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="boost_spsc_queue.hpp">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Console.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\dwshift.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HostSys.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\group1.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\group2.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\group3.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\helpers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\incdec.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\instructions.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\internal.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\jmpcall.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\legacy_instructions.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\legacy_internal.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\legacy_types.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Path.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PrecompiledHeader.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ReadbackSpinManager.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="RedtapeWindows.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\simd_arithmetic.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\simd_comparisons.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\simd_helpers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\simd_moremovs.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\simd_shufflepack.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\movs.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\avx.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Threading.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\test.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\x86emitter.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\x86types.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="emitter\implement\xchg.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FastJmp.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="StringUtil.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SettingsInterface.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SettingsWrapper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="BitUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WindowInfo.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="StringUtil.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ScopedGuard.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Timer.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ProgressCallback.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ScopedGuard.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HashCombine.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MD5Digest.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ZipHelpers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CrashHandler.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="StackWalker.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="LRUCache.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Image.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Easing.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HTTPDownloader.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HTTPDownloaderCurl.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HTTPDownloaderWinHTTP.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HeterogeneousContainers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MemorySettingsInterface.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HeapArray.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DynamicLibrary.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WAVWriter.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="FileSystem.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ByteSwap.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WrappedMemCopy.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Error.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TextureDecompress.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SmallString.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="VectorIntrin.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SingleRegisterTypes.h" />
|
||||
<ClInclude Include="FPControl.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{2ee889b9-3f2a-4524-a936-962552d20487}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{eef579af-e6a8-4d3b-a88e-c0e4cad9e5d8}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MASM Include="FastJmp.asm">
|
||||
<Filter>Source Files</Filter>
|
||||
</MASM>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
172
common/emitter/avx.cpp
Normal file
172
common/emitter/avx.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/emitter/internal.h"
|
||||
|
||||
// warning: suggest braces around initialization of subobject [-Wmissing-braces]
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wmissing-braces"
|
||||
#endif
|
||||
|
||||
namespace x86Emitter
|
||||
{
|
||||
const xImplAVX_Move xVMOVAPS = {0x00, 0x28, 0x29};
|
||||
const xImplAVX_Move xVMOVUPS = {0x00, 0x10, 0x11};
|
||||
|
||||
const xImplAVX_ArithFloat xVADD = {
|
||||
{0x00, 0x58}, // VADDPS
|
||||
{0x66, 0x58}, // VADDPD
|
||||
{0xF3, 0x58}, // VADDSS
|
||||
{0xF2, 0x58}, // VADDSD
|
||||
};
|
||||
const xImplAVX_ArithFloat xVSUB = {
|
||||
{0x00, 0x5C}, // VSUBPS
|
||||
{0x66, 0x5C}, // VSUBPD
|
||||
{0xF3, 0x5C}, // VSUBSS
|
||||
{0xF2, 0x5C}, // VSUBSD
|
||||
};
|
||||
const xImplAVX_ArithFloat xVMUL = {
|
||||
{0x00, 0x59}, // VMULPS
|
||||
{0x66, 0x59}, // VMULPD
|
||||
{0xF3, 0x59}, // VMULSS
|
||||
{0xF2, 0x59}, // VMULSD
|
||||
};
|
||||
const xImplAVX_ArithFloat xVDIV = {
|
||||
{0x00, 0x5E}, // VDIVPS
|
||||
{0x66, 0x5E}, // VDIVPD
|
||||
{0xF3, 0x5E}, // VDIVSS
|
||||
{0xF2, 0x5E}, // VDIVSD
|
||||
};
|
||||
const xImplAVX_CmpFloat xVCMP = {
|
||||
{SSE2_Equal},
|
||||
{SSE2_Less},
|
||||
{SSE2_LessOrEqual},
|
||||
{SSE2_Unordered},
|
||||
{SSE2_NotEqual},
|
||||
{SSE2_NotLess},
|
||||
{SSE2_NotLessOrEqual},
|
||||
{SSE2_Ordered},
|
||||
};
|
||||
const xImplAVX_ThreeArgYMM xVPAND = {0x66, 0xDB};
|
||||
const xImplAVX_ThreeArgYMM xVPANDN = {0x66, 0xDF};
|
||||
const xImplAVX_ThreeArgYMM xVPOR = {0x66, 0xEB};
|
||||
const xImplAVX_ThreeArgYMM xVPXOR = {0x66, 0xEF};
|
||||
const xImplAVX_CmpInt xVPCMP = {
|
||||
{0x66, 0x74}, // VPCMPEQB
|
||||
{0x66, 0x75}, // VPCMPEQW
|
||||
{0x66, 0x76}, // VPCMPEQD
|
||||
{0x66, 0x64}, // VPCMPGTB
|
||||
{0x66, 0x65}, // VPCMPGTW
|
||||
{0x66, 0x66}, // VPCMPGTD
|
||||
};
|
||||
|
||||
void xVPMOVMSKB(const xRegister32& to, const xRegisterSSE& from)
|
||||
{
|
||||
xOpWriteC5(0x66, 0xd7, to, xRegister32(), from);
|
||||
}
|
||||
|
||||
void xVMOVMSKPS(const xRegister32& to, const xRegisterSSE& from)
|
||||
{
|
||||
xOpWriteC5(0x00, 0x50, to, xRegister32(), from);
|
||||
}
|
||||
|
||||
void xVMOVMSKPD(const xRegister32& to, const xRegisterSSE& from)
|
||||
{
|
||||
xOpWriteC5(0x66, 0x50, to, xRegister32(), from);
|
||||
}
|
||||
|
||||
void xVZEROUPPER()
|
||||
{
|
||||
// rather than dealing with nonexistant operands..
|
||||
xWrite8(0xc5);
|
||||
xWrite8(0xf8);
|
||||
xWrite8(0x77);
|
||||
}
|
||||
|
||||
void xImplAVX_Move::operator()(const xRegisterSSE& to, const xRegisterSSE& from) const
|
||||
{
|
||||
if (to != from)
|
||||
xOpWriteC5(Prefix, LoadOpcode, to, xRegisterSSE(), from);
|
||||
}
|
||||
|
||||
void xImplAVX_Move::operator()(const xRegisterSSE& to, const xIndirectVoid& from) const
|
||||
{
|
||||
xOpWriteC5(Prefix, LoadOpcode, to, xRegisterSSE(), from);
|
||||
}
|
||||
|
||||
void xImplAVX_Move::operator()(const xIndirectVoid& to, const xRegisterSSE& from) const
|
||||
{
|
||||
xOpWriteC5(Prefix, StoreOpcode, from, xRegisterSSE(), to);
|
||||
}
|
||||
|
||||
void xImplAVX_ThreeArg::operator()(const xRegisterSSE& to, const xRegisterSSE& from1, const xRegisterSSE& from2) const
|
||||
{
|
||||
pxAssert(!to.IsWideSIMD() && !from1.IsWideSIMD() && !from2.IsWideSIMD());
|
||||
xOpWriteC5(Prefix, Opcode, to, from1, from2);
|
||||
}
|
||||
|
||||
void xImplAVX_ThreeArg::operator()(const xRegisterSSE& to, const xRegisterSSE& from1, const xIndirectVoid& from2) const
|
||||
{
|
||||
pxAssert(!to.IsWideSIMD() && !from1.IsWideSIMD());
|
||||
xOpWriteC5(Prefix, Opcode, to, from1, from2);
|
||||
}
|
||||
|
||||
void xImplAVX_ThreeArgYMM::operator()(const xRegisterSSE& to, const xRegisterSSE& from1, const xRegisterSSE& from2) const
|
||||
{
|
||||
xOpWriteC5(Prefix, Opcode, to, from1, from2);
|
||||
}
|
||||
|
||||
void xImplAVX_ThreeArgYMM::operator()(const xRegisterSSE& to, const xRegisterSSE& from1, const xIndirectVoid& from2) const
|
||||
{
|
||||
xOpWriteC5(Prefix, Opcode, to, from1, from2);
|
||||
}
|
||||
|
||||
void xImplAVX_CmpFloatHelper::PS(const xRegisterSSE& to, const xRegisterSSE& from1, const xRegisterSSE& from2) const
|
||||
{
|
||||
xOpWriteC5(0x00, 0xC2, to, from1, from2);
|
||||
xWrite8(static_cast<u8>(CType));
|
||||
}
|
||||
|
||||
void xImplAVX_CmpFloatHelper::PS(const xRegisterSSE& to, const xRegisterSSE& from1, const xIndirectVoid& from2) const
|
||||
{
|
||||
xOpWriteC5(0x00, 0xC2, to, from1, from2);
|
||||
xWrite8(static_cast<u8>(CType));
|
||||
}
|
||||
|
||||
void xImplAVX_CmpFloatHelper::PD(const xRegisterSSE& to, const xRegisterSSE& from1, const xIndirectVoid& from2) const
|
||||
{
|
||||
xOpWriteC5(0x66, 0xC2, to, from1, from2);
|
||||
xWrite8(static_cast<u8>(CType));
|
||||
}
|
||||
|
||||
void xImplAVX_CmpFloatHelper::PD(const xRegisterSSE& to, const xRegisterSSE& from1, const xRegisterSSE& from2) const
|
||||
{
|
||||
xOpWriteC5(0x66, 0xC2, to, from1, from2);
|
||||
xWrite8(static_cast<u8>(CType));
|
||||
}
|
||||
|
||||
void xImplAVX_CmpFloatHelper::SS(const xRegisterSSE& to, const xRegisterSSE& from1, const xRegisterSSE& from2) const
|
||||
{
|
||||
xOpWriteC5(0xF3, 0xC2, to, from1, from2);
|
||||
xWrite8(static_cast<u8>(CType));
|
||||
}
|
||||
|
||||
void xImplAVX_CmpFloatHelper::SS(const xRegisterSSE& to, const xRegisterSSE& from1, const xIndirectVoid& from2) const
|
||||
{
|
||||
xOpWriteC5(0xF3, 0xC2, to, from1, from2);
|
||||
xWrite8(static_cast<u8>(CType));
|
||||
}
|
||||
|
||||
void xImplAVX_CmpFloatHelper::SD(const xRegisterSSE& to, const xRegisterSSE& from1, const xIndirectVoid& from2) const
|
||||
{
|
||||
xOpWriteC5(0xF2, 0xC2, to, from1, from2);
|
||||
xWrite8(static_cast<u8>(CType));
|
||||
}
|
||||
|
||||
void xImplAVX_CmpFloatHelper::SD(const xRegisterSSE& to, const xRegisterSSE& from1, const xRegisterSSE& from2) const
|
||||
{
|
||||
xOpWriteC5(0xF2, 0xC2, to, from1, from2);
|
||||
xWrite8(static_cast<u8>(CType));
|
||||
}
|
||||
} // namespace x86Emitter
|
||||
22
common/emitter/bmi.cpp
Normal file
22
common/emitter/bmi.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/emitter/internal.h"
|
||||
|
||||
namespace x86Emitter
|
||||
{
|
||||
|
||||
const xImplBMI_RVM xMULX = {0xF2, 0x38, 0xF6};
|
||||
const xImplBMI_RVM xPDEP = {0xF2, 0x38, 0xF5};
|
||||
const xImplBMI_RVM xPEXT = {0xF3, 0x38, 0xF5};
|
||||
const xImplBMI_RVM xANDN_S = {0x00, 0x38, 0xF2};
|
||||
|
||||
void xImplBMI_RVM::operator()(const xRegisterInt& to, const xRegisterInt& from1, const xRegisterInt& from2) const
|
||||
{
|
||||
xOpWriteC4(Prefix, MbPrefix, Opcode, to, from1, from2);
|
||||
}
|
||||
void xImplBMI_RVM::operator()(const xRegisterInt& to, const xRegisterInt& from1, const xIndirectVoid& from2) const
|
||||
{
|
||||
xOpWriteC4(Prefix, MbPrefix, Opcode, to, from1, from2);
|
||||
}
|
||||
} // namespace x86Emitter
|
||||
60
common/emitter/fpu.cpp
Normal file
60
common/emitter/fpu.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "common/emitter/legacy_internal.h"
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// FPU instructions
|
||||
//------------------------------------------------------------------
|
||||
/* fld m32 to fpu reg stack */
|
||||
emitterT void FLD32(u32 from)
|
||||
{
|
||||
xWrite8(0xD9);
|
||||
ModRM(0, 0x0, DISP32);
|
||||
xWrite32(MEMADDR(from, 4));
|
||||
}
|
||||
|
||||
// fld st(i)
|
||||
emitterT void FLD(int st) { xWrite16(0xc0d9 + (st << 8)); }
|
||||
emitterT void FLD1() { xWrite16(0xe8d9); }
|
||||
emitterT void FLDL2E() { xWrite16(0xead9); }
|
||||
|
||||
/* fstp m32 from fpu reg stack */
|
||||
emitterT void FSTP32(u32 to)
|
||||
{
|
||||
xWrite8(0xD9);
|
||||
ModRM(0, 0x3, DISP32);
|
||||
xWrite32(MEMADDR(to, 4));
|
||||
}
|
||||
|
||||
// fstp st(i)
|
||||
emitterT void FSTP(int st) { xWrite16(0xd8dd + (st << 8)); }
|
||||
|
||||
emitterT void FRNDINT() { xWrite16(0xfcd9); }
|
||||
emitterT void FXCH(int st) { xWrite16(0xc8d9 + (st << 8)); }
|
||||
emitterT void F2XM1() { xWrite16(0xf0d9); }
|
||||
emitterT void FSCALE() { xWrite16(0xfdd9); }
|
||||
emitterT void FPATAN(void) { xWrite16(0xf3d9); }
|
||||
emitterT void FSIN(void) { xWrite16(0xfed9); }
|
||||
|
||||
/* fadd ST(0) to fpu reg stack ST(src) */
|
||||
emitterT void FADD320toR(x86IntRegType src)
|
||||
{
|
||||
xWrite8(0xDC);
|
||||
xWrite8(0xC0 + src);
|
||||
}
|
||||
|
||||
/* fsub ST(src) to fpu reg stack ST(0) */
|
||||
emitterT void FSUB32Rto0(x86IntRegType src)
|
||||
{
|
||||
xWrite8(0xD8);
|
||||
xWrite8(0xE0 + src);
|
||||
}
|
||||
|
||||
/* fmul m32 to fpu reg stack */
|
||||
emitterT void FMUL32(u32 from)
|
||||
{
|
||||
xWrite8(0xD8);
|
||||
ModRM(0, 0x1, DISP32);
|
||||
xWrite32(MEMADDR(from, 4));
|
||||
}
|
||||
259
common/emitter/groups.cpp
Normal file
259
common/emitter/groups.cpp
Normal file
@@ -0,0 +1,259 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
/*
|
||||
* ix86 core v0.9.1
|
||||
*
|
||||
* Original Authors (v0.6.2 and prior):
|
||||
* linuzappz <linuzappz@pcsx.net>
|
||||
* alexey silinov
|
||||
* goldfinger
|
||||
* zerofrog(@gmail.com)
|
||||
*
|
||||
* Authors of v0.9.1:
|
||||
* Jake.Stine(@gmail.com)
|
||||
* cottonvibes(@gmail.com)
|
||||
* sudonim(1@gmail.com)
|
||||
*/
|
||||
|
||||
#include "common/emitter/internal.h"
|
||||
#include "common/emitter/implement/helpers.h"
|
||||
|
||||
namespace x86Emitter
|
||||
{
|
||||
|
||||
// =====================================================================================================
|
||||
// Group 1 Instructions - ADD, SUB, ADC, etc.
|
||||
// =====================================================================================================
|
||||
|
||||
// Note on "[Indirect],Imm" forms : use int as the source operand since it's "reasonably inert" from a
|
||||
// compiler perspective. (using uint tends to make the compiler try and fail to match signed immediates
|
||||
// with one of the other overloads).
|
||||
static void _g1_IndirectImm(G1Type InstType, const xIndirect64orLess& sibdest, int imm)
|
||||
{
|
||||
if (sibdest.Is8BitOp())
|
||||
{
|
||||
xOpWrite(sibdest.GetPrefix16(), 0x80, InstType, sibdest, 1);
|
||||
|
||||
xWrite<s8>(imm);
|
||||
}
|
||||
else
|
||||
{
|
||||
u8 opcode = is_s8(imm) ? 0x83 : 0x81;
|
||||
xOpWrite(sibdest.GetPrefix16(), opcode, InstType, sibdest, is_s8(imm) ? 1 : sibdest.GetImmSize());
|
||||
|
||||
if (is_s8(imm))
|
||||
xWrite<s8>(imm);
|
||||
else
|
||||
sibdest.xWriteImm(imm);
|
||||
}
|
||||
}
|
||||
|
||||
void _g1_EmitOp(G1Type InstType, const xRegisterInt& to, const xRegisterInt& from)
|
||||
{
|
||||
pxAssert(to.GetOperandSize() == from.GetOperandSize());
|
||||
|
||||
u8 opcode = (to.Is8BitOp() ? 0 : 1) | (InstType << 3);
|
||||
xOpWrite(to.GetPrefix16(), opcode, from, to);
|
||||
}
|
||||
|
||||
static void _g1_EmitOp(G1Type InstType, const xIndirectVoid& sibdest, const xRegisterInt& from)
|
||||
{
|
||||
u8 opcode = (from.Is8BitOp() ? 0 : 1) | (InstType << 3);
|
||||
xOpWrite(from.GetPrefix16(), opcode, from, sibdest);
|
||||
}
|
||||
|
||||
static void _g1_EmitOp(G1Type InstType, const xRegisterInt& to, const xIndirectVoid& sibsrc)
|
||||
{
|
||||
u8 opcode = (to.Is8BitOp() ? 2 : 3) | (InstType << 3);
|
||||
xOpWrite(to.GetPrefix16(), opcode, to, sibsrc);
|
||||
}
|
||||
|
||||
static void _g1_EmitOp(G1Type InstType, const xRegisterInt& to, int imm)
|
||||
{
|
||||
if (!to.Is8BitOp() && is_s8(imm))
|
||||
{
|
||||
xOpWrite(to.GetPrefix16(), 0x83, InstType, to);
|
||||
xWrite<s8>(imm);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (to.IsAccumulator())
|
||||
{
|
||||
u8 opcode = (to.Is8BitOp() ? 4 : 5) | (InstType << 3);
|
||||
xOpAccWrite(to.GetPrefix16(), opcode, InstType, to);
|
||||
}
|
||||
else
|
||||
{
|
||||
u8 opcode = to.Is8BitOp() ? 0x80 : 0x81;
|
||||
xOpWrite(to.GetPrefix16(), opcode, InstType, to);
|
||||
}
|
||||
to.xWriteImm(imm);
|
||||
}
|
||||
}
|
||||
|
||||
#define ImplementGroup1(g1type, insttype) \
|
||||
void g1type::operator()(const xRegisterInt& to, const xRegisterInt& from) const { _g1_EmitOp(insttype, to, from); } \
|
||||
void g1type::operator()(const xIndirectVoid& to, const xRegisterInt& from) const { _g1_EmitOp(insttype, to, from); } \
|
||||
void g1type::operator()(const xRegisterInt& to, const xIndirectVoid& from) const { _g1_EmitOp(insttype, to, from); } \
|
||||
void g1type::operator()(const xRegisterInt& to, int imm) const { _g1_EmitOp(insttype, to, imm); } \
|
||||
void g1type::operator()(const xIndirect64orLess& sibdest, int imm) const { _g1_IndirectImm(insttype, sibdest, imm); }
|
||||
|
||||
ImplementGroup1(xImpl_Group1, InstType)
|
||||
ImplementGroup1(xImpl_G1Logic, InstType)
|
||||
ImplementGroup1(xImpl_G1Arith, InstType)
|
||||
ImplementGroup1(xImpl_G1Compare, G1Type_CMP)
|
||||
|
||||
const xImpl_G1Logic xAND = {G1Type_AND, {0x00, 0x54}, {0x66, 0x54}};
|
||||
const xImpl_G1Logic xOR = {G1Type_OR, {0x00, 0x56}, {0x66, 0x56}};
|
||||
const xImpl_G1Logic xXOR = {G1Type_XOR, {0x00, 0x57}, {0x66, 0x57}};
|
||||
|
||||
const xImpl_G1Arith xADD = {G1Type_ADD, {0x00, 0x58}, {0x66, 0x58}, {0xf3, 0x58}, {0xf2, 0x58}};
|
||||
const xImpl_G1Arith xSUB = {G1Type_SUB, {0x00, 0x5c}, {0x66, 0x5c}, {0xf3, 0x5c}, {0xf2, 0x5c}};
|
||||
const xImpl_G1Compare xCMP = {{0x00, 0xc2}, {0x66, 0xc2}, {0xf3, 0xc2}, {0xf2, 0xc2}};
|
||||
|
||||
const xImpl_Group1 xADC = {G1Type_ADC};
|
||||
const xImpl_Group1 xSBB = {G1Type_SBB};
|
||||
|
||||
// =====================================================================================================
|
||||
// Group 2 Instructions - SHR, SHL, etc.
|
||||
// =====================================================================================================
|
||||
|
||||
void xImpl_Group2::operator()(const xRegisterInt& to, const xRegisterCL& /* from */) const
|
||||
{
|
||||
xOpWrite(to.GetPrefix16(), to.Is8BitOp() ? 0xd2 : 0xd3, InstType, to);
|
||||
}
|
||||
|
||||
void xImpl_Group2::operator()(const xRegisterInt& to, u8 imm) const
|
||||
{
|
||||
if (imm == 0)
|
||||
return;
|
||||
|
||||
if (imm == 1)
|
||||
{
|
||||
// special encoding of 1's
|
||||
xOpWrite(to.GetPrefix16(), to.Is8BitOp() ? 0xd0 : 0xd1, InstType, to);
|
||||
}
|
||||
else
|
||||
{
|
||||
xOpWrite(to.GetPrefix16(), to.Is8BitOp() ? 0xc0 : 0xc1, InstType, to);
|
||||
xWrite8(imm);
|
||||
}
|
||||
}
|
||||
|
||||
void xImpl_Group2::operator()(const xIndirect64orLess& sibdest, const xRegisterCL& /* from */) const
|
||||
{
|
||||
xOpWrite(sibdest.GetPrefix16(), sibdest.Is8BitOp() ? 0xd2 : 0xd3, InstType, sibdest);
|
||||
}
|
||||
|
||||
void xImpl_Group2::operator()(const xIndirect64orLess& sibdest, u8 imm) const
|
||||
{
|
||||
if (imm == 0)
|
||||
return;
|
||||
|
||||
if (imm == 1)
|
||||
{
|
||||
// special encoding of 1's
|
||||
xOpWrite(sibdest.GetPrefix16(), sibdest.Is8BitOp() ? 0xd0 : 0xd1, InstType, sibdest);
|
||||
}
|
||||
else
|
||||
{
|
||||
xOpWrite(sibdest.GetPrefix16(), sibdest.Is8BitOp() ? 0xc0 : 0xc1, InstType, sibdest, 1);
|
||||
xWrite8(imm);
|
||||
}
|
||||
}
|
||||
|
||||
const xImpl_Group2 xROL = {G2Type_ROL};
|
||||
const xImpl_Group2 xROR = {G2Type_ROR};
|
||||
const xImpl_Group2 xRCL = {G2Type_RCL};
|
||||
const xImpl_Group2 xRCR = {G2Type_RCR};
|
||||
const xImpl_Group2 xSHL = {G2Type_SHL};
|
||||
const xImpl_Group2 xSHR = {G2Type_SHR};
|
||||
const xImpl_Group2 xSAR = {G2Type_SAR};
|
||||
|
||||
|
||||
// =====================================================================================================
|
||||
// Group 3 Instructions - NOT, NEG, MUL, DIV
|
||||
// =====================================================================================================
|
||||
|
||||
static void _g3_EmitOp(G3Type InstType, const xRegisterInt& from)
|
||||
{
|
||||
xOpWrite(from.GetPrefix16(), from.Is8BitOp() ? 0xf6 : 0xf7, InstType, from);
|
||||
}
|
||||
|
||||
static void _g3_EmitOp(G3Type InstType, const xIndirect64orLess& from)
|
||||
{
|
||||
xOpWrite(from.GetPrefix16(), from.Is8BitOp() ? 0xf6 : 0xf7, InstType, from);
|
||||
}
|
||||
|
||||
void xImpl_Group3::operator()(const xRegisterInt& from) const { _g3_EmitOp(InstType, from); }
|
||||
void xImpl_Group3::operator()(const xIndirect64orLess& from) const { _g3_EmitOp(InstType, from); }
|
||||
|
||||
void xImpl_iDiv::operator()(const xRegisterInt& from) const { _g3_EmitOp(G3Type_iDIV, from); }
|
||||
void xImpl_iDiv::operator()(const xIndirect64orLess& from) const { _g3_EmitOp(G3Type_iDIV, from); }
|
||||
|
||||
template <typename SrcType>
|
||||
static void _imul_ImmStyle(const xRegisterInt& param1, const SrcType& param2, int imm)
|
||||
{
|
||||
pxAssert(param1.GetOperandSize() == param2.GetOperandSize());
|
||||
|
||||
xOpWrite0F(param1.GetPrefix16(), is_s8(imm) ? 0x6b : 0x69, param1, param2, is_s8(imm) ? 1 : param1.GetImmSize());
|
||||
|
||||
if (is_s8(imm))
|
||||
xWrite8((u8)imm);
|
||||
else
|
||||
param1.xWriteImm(imm);
|
||||
}
|
||||
|
||||
void xImpl_iMul::operator()(const xRegisterInt& from) const { _g3_EmitOp(G3Type_iMUL, from); }
|
||||
void xImpl_iMul::operator()(const xIndirect64orLess& from) const { _g3_EmitOp(G3Type_iMUL, from); }
|
||||
|
||||
void xImpl_iMul::operator()(const xRegister32& to, const xRegister32& from) const { xOpWrite0F(0xaf, to, from); }
|
||||
void xImpl_iMul::operator()(const xRegister32& to, const xIndirectVoid& src) const { xOpWrite0F(0xaf, to, src); }
|
||||
void xImpl_iMul::operator()(const xRegister16& to, const xRegister16& from) const { xOpWrite0F(0x66, 0xaf, to, from); }
|
||||
void xImpl_iMul::operator()(const xRegister16& to, const xIndirectVoid& src) const { xOpWrite0F(0x66, 0xaf, to, src); }
|
||||
|
||||
void xImpl_iMul::operator()(const xRegister32& to, const xRegister32& from, s32 imm) const { _imul_ImmStyle(to, from, imm); }
|
||||
void xImpl_iMul::operator()(const xRegister32& to, const xIndirectVoid& from, s32 imm) const { _imul_ImmStyle(to, from, imm); }
|
||||
void xImpl_iMul::operator()(const xRegister16& to, const xRegister16& from, s16 imm) const { _imul_ImmStyle(to, from, imm); }
|
||||
void xImpl_iMul::operator()(const xRegister16& to, const xIndirectVoid& from, s16 imm) const { _imul_ImmStyle(to, from, imm); }
|
||||
|
||||
const xImpl_Group3 xNOT = {G3Type_NOT};
|
||||
const xImpl_Group3 xNEG = {G3Type_NEG};
|
||||
const xImpl_Group3 xUMUL = {G3Type_MUL};
|
||||
const xImpl_Group3 xUDIV = {G3Type_DIV};
|
||||
|
||||
const xImpl_iDiv xDIV = {{0x00, 0x5e}, {0x66, 0x5e}, {0xf3, 0x5e}, {0xf2, 0x5e}};
|
||||
const xImpl_iMul xMUL = {{0x00, 0x59}, {0x66, 0x59}, {0xf3, 0x59}, {0xf2, 0x59}};
|
||||
|
||||
// =====================================================================================================
|
||||
// Group 8 Instructions
|
||||
// =====================================================================================================
|
||||
|
||||
void xImpl_Group8::operator()(const xRegister16or32or64& bitbase, const xRegister16or32or64& bitoffset) const
|
||||
{
|
||||
pxAssert(bitbase->GetOperandSize() == bitoffset->GetOperandSize());
|
||||
xOpWrite0F(bitbase->GetPrefix16(), 0xa3 | (InstType << 3), bitbase, bitoffset);
|
||||
}
|
||||
void xImpl_Group8::operator()(const xIndirect64& bitbase, u8 bitoffset) const { xOpWrite0F(0xba, InstType, bitbase, bitoffset); }
|
||||
void xImpl_Group8::operator()(const xIndirect32& bitbase, u8 bitoffset) const { xOpWrite0F(0xba, InstType, bitbase, bitoffset); }
|
||||
void xImpl_Group8::operator()(const xIndirect16& bitbase, u8 bitoffset) const { xOpWrite0F(0x66, 0xba, InstType, bitbase, bitoffset); }
|
||||
|
||||
void xImpl_Group8::operator()(const xRegister16or32or64& bitbase, u8 bitoffset) const
|
||||
{
|
||||
xOpWrite0F(bitbase->GetPrefix16(), 0xba, InstType, bitbase, bitoffset);
|
||||
}
|
||||
|
||||
void xImpl_Group8::operator()(const xIndirectVoid& bitbase, const xRegister16or32or64& bitoffset) const
|
||||
{
|
||||
xOpWrite0F(bitoffset->GetPrefix16(), 0xa3 | (InstType << 3), bitoffset, bitbase);
|
||||
}
|
||||
|
||||
const xImpl_Group8 xBT = {G8Type_BT};
|
||||
const xImpl_Group8 xBTR = {G8Type_BTR};
|
||||
const xImpl_Group8 xBTS = {G8Type_BTS};
|
||||
const xImpl_Group8 xBTC = {G8Type_BTC};
|
||||
|
||||
|
||||
|
||||
} // End namespace x86Emitter
|
||||
101
common/emitter/implement/avx.h
Normal file
101
common/emitter/implement/avx.h
Normal file
@@ -0,0 +1,101 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace x86Emitter
|
||||
{
|
||||
struct xImplAVX_Move
|
||||
{
|
||||
u8 Prefix;
|
||||
u8 LoadOpcode;
|
||||
u8 StoreOpcode;
|
||||
|
||||
void operator()(const xRegisterSSE& to, const xRegisterSSE& from) const;
|
||||
void operator()(const xRegisterSSE& to, const xIndirectVoid& from) const;
|
||||
void operator()(const xIndirectVoid& to, const xRegisterSSE& from) const;
|
||||
};
|
||||
|
||||
struct xImplAVX_ThreeArg
|
||||
{
|
||||
u8 Prefix;
|
||||
u8 Opcode;
|
||||
|
||||
void operator()(const xRegisterSSE& to, const xRegisterSSE& from1, const xRegisterSSE& from2) const;
|
||||
void operator()(const xRegisterSSE& to, const xRegisterSSE& from1, const xIndirectVoid& from2) const;
|
||||
};
|
||||
|
||||
struct xImplAVX_ThreeArgYMM : xImplAVX_ThreeArg
|
||||
{
|
||||
void operator()(const xRegisterSSE& to, const xRegisterSSE& from1, const xRegisterSSE& from2) const;
|
||||
void operator()(const xRegisterSSE& to, const xRegisterSSE& from1, const xIndirectVoid& from2) const;
|
||||
};
|
||||
|
||||
struct xImplAVX_ArithFloat
|
||||
{
|
||||
xImplAVX_ThreeArgYMM PS;
|
||||
xImplAVX_ThreeArgYMM PD;
|
||||
xImplAVX_ThreeArg SS;
|
||||
xImplAVX_ThreeArg SD;
|
||||
};
|
||||
|
||||
struct xImplAVX_CmpFloatHelper
|
||||
{
|
||||
SSE2_ComparisonType CType;
|
||||
|
||||
void PS(const xRegisterSSE& to, const xRegisterSSE& from1, const xRegisterSSE& from2) const;
|
||||
void PS(const xRegisterSSE& to, const xRegisterSSE& from1, const xIndirectVoid& from2) const;
|
||||
void PD(const xRegisterSSE& to, const xRegisterSSE& from1, const xRegisterSSE& from2) const;
|
||||
void PD(const xRegisterSSE& to, const xRegisterSSE& from1, const xIndirectVoid& from2) const;
|
||||
|
||||
void SS(const xRegisterSSE& to, const xRegisterSSE& from1, const xRegisterSSE& from2) const;
|
||||
void SS(const xRegisterSSE& to, const xRegisterSSE& from1, const xIndirectVoid& from2) const;
|
||||
void SD(const xRegisterSSE& to, const xRegisterSSE& from1, const xRegisterSSE& from2) const;
|
||||
void SD(const xRegisterSSE& to, const xRegisterSSE& from1, const xIndirectVoid& from2) const;
|
||||
};
|
||||
|
||||
struct xImplAVX_CmpFloat
|
||||
{
|
||||
xImplAVX_CmpFloatHelper EQ;
|
||||
xImplAVX_CmpFloatHelper LT;
|
||||
xImplAVX_CmpFloatHelper LE;
|
||||
xImplAVX_CmpFloatHelper UO;
|
||||
xImplAVX_CmpFloatHelper NE;
|
||||
xImplAVX_CmpFloatHelper GE;
|
||||
xImplAVX_CmpFloatHelper GT;
|
||||
xImplAVX_CmpFloatHelper OR;
|
||||
};
|
||||
|
||||
struct xImplAVX_CmpInt
|
||||
{
|
||||
// Compare packed bytes for equality.
|
||||
// If a data element in dest is equal to the corresponding date element src, the
|
||||
// corresponding data element in dest is set to all 1s; otherwise, it is set to all 0s.
|
||||
const xImplAVX_ThreeArgYMM EQB;
|
||||
|
||||
// Compare packed words for equality.
|
||||
// If a data element in dest is equal to the corresponding date element src, the
|
||||
// corresponding data element in dest is set to all 1s; otherwise, it is set to all 0s.
|
||||
const xImplAVX_ThreeArgYMM EQW;
|
||||
|
||||
// Compare packed doublewords [32-bits] for equality.
|
||||
// If a data element in dest is equal to the corresponding date element src, the
|
||||
// corresponding data element in dest is set to all 1s; otherwise, it is set to all 0s.
|
||||
const xImplAVX_ThreeArgYMM EQD;
|
||||
|
||||
// Compare packed signed bytes for greater than.
|
||||
// If a data element in dest is greater than the corresponding date element src, the
|
||||
// corresponding data element in dest is set to all 1s; otherwise, it is set to all 0s.
|
||||
const xImplAVX_ThreeArgYMM GTB;
|
||||
|
||||
// Compare packed signed words for greater than.
|
||||
// If a data element in dest is greater than the corresponding date element src, the
|
||||
// corresponding data element in dest is set to all 1s; otherwise, it is set to all 0s.
|
||||
const xImplAVX_ThreeArgYMM GTW;
|
||||
|
||||
// Compare packed signed doublewords [32-bits] for greater than.
|
||||
// If a data element in dest is greater than the corresponding date element src, the
|
||||
// corresponding data element in dest is set to all 1s; otherwise, it is set to all 0s.
|
||||
const xImplAVX_ThreeArgYMM GTD;
|
||||
};
|
||||
} // namespace x86Emitter
|
||||
49
common/emitter/implement/bmi.h
Normal file
49
common/emitter/implement/bmi.h
Normal file
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
// Implement BMI1/BMI2 instruction set
|
||||
|
||||
namespace x86Emitter
|
||||
{
|
||||
|
||||
struct xImplBMI_RVM
|
||||
{
|
||||
u8 Prefix;
|
||||
u8 MbPrefix;
|
||||
u8 Opcode;
|
||||
|
||||
// RVM
|
||||
// MULX Unsigned multiply without affecting flags, and arbitrary destination registers
|
||||
// PDEP Parallel bits deposit
|
||||
// PEXT Parallel bits extract
|
||||
// ANDN Logical and not ~x & y
|
||||
void operator()(const xRegisterInt& to, const xRegisterInt& from1, const xRegisterInt& from2) const;
|
||||
void operator()(const xRegisterInt& to, const xRegisterInt& from1, const xIndirectVoid& from2) const;
|
||||
|
||||
#if 0
|
||||
// RMV
|
||||
// BEXTR Bit field extract (with register) (src >> start) & ((1 << len)-1)[9]
|
||||
// BZHI Zero high bits starting with specified bit position
|
||||
// SARX Shift arithmetic right without affecting flags
|
||||
// SHRX Shift logical right without affecting flags
|
||||
// SHLX Shift logical left without affecting flags
|
||||
// FIXME: WARNING same as above but V and M are inverted
|
||||
//void operator()( const xRegisterInt& to, const xRegisterInt& from1, const xRegisterInt& from2) const;
|
||||
//void operator()( const xRegisterInt& to, const xIndirectVoid& from1, const xRegisterInt& from2) const;
|
||||
|
||||
// VM
|
||||
// BLSI Extract lowest set isolated bit x & -x
|
||||
// BLSMSK Get mask up to lowest set bit x ^ (x - 1)
|
||||
// BLSR Reset lowest set bit x & (x - 1)
|
||||
void operator()( const xRegisterInt& to, const xRegisterInt& from) const;
|
||||
void operator()( const xRegisterInt& to, const xIndirectVoid& from) const;
|
||||
|
||||
// RMI
|
||||
//RORX Rotate right logical without affecting flags
|
||||
void operator()( const xRegisterInt& to, const xRegisterInt& from, u8 imm) const;
|
||||
void operator()( const xRegisterInt& to, const xIndirectVoid& from, u8 imm) const;
|
||||
#endif
|
||||
};
|
||||
} // namespace x86Emitter
|
||||
32
common/emitter/implement/dwshift.h
Normal file
32
common/emitter/implement/dwshift.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace x86Emitter
|
||||
{
|
||||
|
||||
// Implementations here cover SHLD and SHRD.
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// xImpl_DowrdShift
|
||||
// --------------------------------------------------------------------------------------
|
||||
// I use explicit method declarations here instead of templates, in order to provide
|
||||
// *only* 32 and 16 bit register operand forms (8 bit registers are not valid in SHLD/SHRD).
|
||||
//
|
||||
// Optimization Note: Imm shifts by 0 are ignore (no code generated). This is a safe optimization
|
||||
// because shifts by 0 do *not* affect flags status (intel docs cited).
|
||||
//
|
||||
struct xImpl_DwordShift
|
||||
{
|
||||
u16 OpcodeBase;
|
||||
|
||||
void operator()(const xRegister16or32or64& to, const xRegister16or32or64& from, const xRegisterCL& clreg) const;
|
||||
|
||||
void operator()(const xRegister16or32or64& to, const xRegister16or32or64& from, u8 shiftcnt) const;
|
||||
|
||||
void operator()(const xIndirectVoid& dest, const xRegister16or32or64& from, const xRegisterCL& clreg) const;
|
||||
void operator()(const xIndirectVoid& dest, const xRegister16or32or64& from, u8 shiftcnt) const;
|
||||
};
|
||||
|
||||
} // End namespace x86Emitter
|
||||
131
common/emitter/implement/group1.h
Normal file
131
common/emitter/implement/group1.h
Normal file
@@ -0,0 +1,131 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace x86Emitter
|
||||
{
|
||||
|
||||
enum G1Type
|
||||
{
|
||||
G1Type_ADD = 0,
|
||||
G1Type_OR,
|
||||
G1Type_ADC,
|
||||
G1Type_SBB,
|
||||
G1Type_AND,
|
||||
G1Type_SUB,
|
||||
G1Type_XOR,
|
||||
G1Type_CMP
|
||||
};
|
||||
|
||||
extern void _g1_EmitOp(G1Type InstType, const xRegisterInt& to, const xRegisterInt& from);
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// xImpl_Group1
|
||||
// --------------------------------------------------------------------------------------
|
||||
struct xImpl_Group1
|
||||
{
|
||||
G1Type InstType;
|
||||
|
||||
void operator()(const xRegisterInt& to, const xRegisterInt& from) const;
|
||||
|
||||
void operator()(const xIndirectVoid& to, const xRegisterInt& from) const;
|
||||
void operator()(const xRegisterInt& to, const xIndirectVoid& from) const;
|
||||
void operator()(const xRegisterInt& to, int imm) const;
|
||||
void operator()(const xIndirect64orLess& to, int imm) const;
|
||||
|
||||
#if 0
|
||||
// ------------------------------------------------------------------------
|
||||
template< typename T > __noinline void operator()( const ModSibBase& to, const xImmReg<T>& immOrReg ) const
|
||||
{
|
||||
_DoI_helpermess( *this, to, immOrReg );
|
||||
}
|
||||
|
||||
template< typename T > __noinline void operator()( const xDirectOrIndirect<T>& to, const xImmReg<T>& immOrReg ) const
|
||||
{
|
||||
_DoI_helpermess( *this, to, immOrReg );
|
||||
}
|
||||
|
||||
template< typename T > __noinline void operator()( const xDirectOrIndirect<T>& to, int imm ) const
|
||||
{
|
||||
_DoI_helpermess( *this, to, imm );
|
||||
}
|
||||
|
||||
template< typename T > __noinline void operator()( const xDirectOrIndirect<T>& to, const xDirectOrIndirect<T>& from ) const
|
||||
{
|
||||
_DoI_helpermess( *this, to, from );
|
||||
}
|
||||
|
||||
// FIXME : Make this struct to 8, 16, and 32 bit registers
|
||||
template< typename T > __noinline void operator()( const xRegisterBase& to, const xDirectOrIndirect<T>& from ) const
|
||||
{
|
||||
_DoI_helpermess( *this, xDirectOrIndirect<T>( to ), from );
|
||||
}
|
||||
|
||||
// FIXME : Make this struct to 8, 16, and 32 bit registers
|
||||
template< typename T > __noinline void operator()( const xDirectOrIndirect<T>& to, const xRegisterBase& from ) const
|
||||
{
|
||||
_DoI_helpermess( *this, to, xDirectOrIndirect<T>( from ) );
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// This class combines x86 with SSE/SSE2 logic operations (ADD, OR, and NOT).
|
||||
// Note: ANDN [AndNot] is handled below separately.
|
||||
//
|
||||
struct xImpl_G1Logic
|
||||
{
|
||||
G1Type InstType;
|
||||
|
||||
void operator()(const xRegisterInt& to, const xRegisterInt& from) const;
|
||||
|
||||
void operator()(const xIndirectVoid& to, const xRegisterInt& from) const;
|
||||
void operator()(const xRegisterInt& to, const xIndirectVoid& from) const;
|
||||
void operator()(const xRegisterInt& to, int imm) const;
|
||||
|
||||
void operator()(const xIndirect64orLess& to, int imm) const;
|
||||
|
||||
xImplSimd_DestRegSSE PS; // packed single precision
|
||||
xImplSimd_DestRegSSE PD; // packed double precision
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// This class combines x86 with SSE/SSE2 arithmetic operations (ADD/SUB).
|
||||
//
|
||||
struct xImpl_G1Arith
|
||||
{
|
||||
G1Type InstType;
|
||||
|
||||
void operator()(const xRegisterInt& to, const xRegisterInt& from) const;
|
||||
|
||||
void operator()(const xIndirectVoid& to, const xRegisterInt& from) const;
|
||||
void operator()(const xRegisterInt& to, const xIndirectVoid& from) const;
|
||||
void operator()(const xRegisterInt& to, int imm) const;
|
||||
|
||||
void operator()(const xIndirect64orLess& to, int imm) const;
|
||||
|
||||
xImplSimd_DestRegSSE PS; // packed single precision
|
||||
xImplSimd_DestRegSSE PD; // packed double precision
|
||||
xImplSimd_DestRegSSE SS; // scalar single precision
|
||||
xImplSimd_DestRegSSE SD; // scalar double precision
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
struct xImpl_G1Compare
|
||||
{
|
||||
void operator()(const xRegisterInt& to, const xRegisterInt& from) const;
|
||||
|
||||
void operator()(const xIndirectVoid& to, const xRegisterInt& from) const;
|
||||
void operator()(const xRegisterInt& to, const xIndirectVoid& from) const;
|
||||
void operator()(const xRegisterInt& to, int imm) const;
|
||||
|
||||
void operator()(const xIndirect64orLess& to, int imm) const;
|
||||
|
||||
xImplSimd_DestSSE_CmpImm PS;
|
||||
xImplSimd_DestSSE_CmpImm PD;
|
||||
xImplSimd_DestSSE_CmpImm SS;
|
||||
xImplSimd_DestSSE_CmpImm SD;
|
||||
};
|
||||
|
||||
} // End namespace x86Emitter
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user