mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-27 17:31:31 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 213a9a7813 | |||
| 48ba1f3f24 | |||
| d59fcf01bf | |||
| b8456394f1 | |||
| a467dd1ba6 | |||
| c156f4760f | |||
| 13f11ebf49 | |||
| 6065e9aa09 | |||
| 8ed0ed5828 | |||
| 33067af283 | |||
| d0a054270e | |||
| 2a3507c2b9 | |||
| f71f43561d | |||
| ecbfad4193 | |||
| d76b2b5d26 | |||
| cd9527072d | |||
| ffdaf7369a | |||
| 5113f503d1 | |||
| a5f1c2bcb0 | |||
| 2e432c9d17 | |||
| 6637810fe6 | |||
| 8118557c17 | |||
| 1925726b96 | |||
| acf7deea95 | |||
| 84fdbbaaa1 | |||
| 638663b28e | |||
| df838a57fd | |||
| b2b73ecb62 | |||
| 2b7b44c3e4 |
+18
-2
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh -e
|
#!/bin/sh -e
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
NUM_JOBS=$(nproc 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)
|
NUM_JOBS=$(nproc 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)
|
||||||
@@ -29,6 +29,7 @@ Options:
|
|||||||
-b, --build-type <TYPE> Build type (variable: TYPE)
|
-b, --build-type <TYPE> Build type (variable: TYPE)
|
||||||
Valid values are: Release, RelWithDebInfo, Debug
|
Valid values are: Release, RelWithDebInfo, Debug
|
||||||
Default: Debug
|
Default: Debug
|
||||||
|
-n, --nightly Create a nightly build.
|
||||||
|
|
||||||
Extra arguments are passed to CMake (e.g. -DCMAKE_OPTION_NAME=VALUE)
|
Extra arguments are passed to CMake (e.g. -DCMAKE_OPTION_NAME=VALUE)
|
||||||
Set the CCACHE variable to "true" to enable build caching.
|
Set the CCACHE variable to "true" to enable build caching.
|
||||||
@@ -61,6 +62,7 @@ while true; do
|
|||||||
-r|--release) DEVEL=false ;;
|
-r|--release) DEVEL=false ;;
|
||||||
-t|--target) target "$2"; shift ;;
|
-t|--target) target "$2"; shift ;;
|
||||||
-b|--build-type) type "$2"; shift ;;
|
-b|--build-type) type "$2"; shift ;;
|
||||||
|
-n|--nightly) NIGHTLY=true ;;
|
||||||
-h|--help) usage ;;
|
-h|--help) usage ;;
|
||||||
*) break ;;
|
*) break ;;
|
||||||
esac
|
esac
|
||||||
@@ -101,7 +103,20 @@ cd src/android
|
|||||||
chmod +x ./gradlew
|
chmod +x ./gradlew
|
||||||
|
|
||||||
set -- "$@" -DUSE_CCACHE="${CCACHE}"
|
set -- "$@" -DUSE_CCACHE="${CCACHE}"
|
||||||
[ "$DEVEL" != "true" ] && set -- "$@" -DENABLE_UPDATE_CHECKER=ON
|
|
||||||
|
nightly() {
|
||||||
|
[ "$NIGHTLY" = "true" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
if nightly || [ "$DEVEL" != "true" ]; then
|
||||||
|
set -- "$@" -DENABLE_UPDATE_CHECKER=ON
|
||||||
|
fi
|
||||||
|
|
||||||
|
if nightly; then
|
||||||
|
NIGHTLY=true
|
||||||
|
else
|
||||||
|
NIGHTLY=false
|
||||||
|
fi
|
||||||
|
|
||||||
echo "-- building..."
|
echo "-- building..."
|
||||||
|
|
||||||
@@ -110,6 +125,7 @@ echo "-- building..."
|
|||||||
-Dorg.gradle.parallel="${CCACHE}" \
|
-Dorg.gradle.parallel="${CCACHE}" \
|
||||||
-Dorg.gradle.workers.max="${NUM_JOBS}" \
|
-Dorg.gradle.workers.max="${NUM_JOBS}" \
|
||||||
-PYUZU_ANDROID_ARGS="$*" \
|
-PYUZU_ANDROID_ARGS="$*" \
|
||||||
|
-Pnightly="$NIGHTLY" \
|
||||||
--info
|
--info
|
||||||
|
|
||||||
if [ -n "${ANDROID_KEYSTORE_B64}" ]; then
|
if [ -n "${ANDROID_KEYSTORE_B64}" ]; then
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
--- a/libs/context/CMakeLists.txt 2025-09-08 00:42:31.303651800 -0400
|
|
||||||
+++ b/libs/context/CMakeLists.txt 2025-09-08 00:42:40.592184300 -0400
|
|
||||||
@@ -146,7 +146,7 @@
|
|
||||||
set(ASM_LANGUAGE ASM)
|
|
||||||
endif()
|
|
||||||
elseif(BOOST_CONTEXT_ASSEMBLER STREQUAL armasm)
|
|
||||||
- set(ASM_LANGUAGE ASM_ARMASM)
|
|
||||||
+ set(ASM_LANGUAGE ASM_MARMASM)
|
|
||||||
else()
|
|
||||||
set(ASM_LANGUAGE ASM_MASM)
|
|
||||||
endif()
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
diff --git a/libs/context/CMakeLists.txt b/libs/context/CMakeLists.txt
|
|
||||||
index 8210f65..0e59dd7 100644
|
|
||||||
--- a/libs/context/CMakeLists.txt
|
|
||||||
+++ b/libs/context/CMakeLists.txt
|
|
||||||
@@ -186,7 +186,8 @@ if(BOOST_CONTEXT_IMPLEMENTATION STREQUAL "fcontext")
|
|
||||||
set_property(SOURCE ${ASM_SOURCES} APPEND PROPERTY COMPILE_OPTIONS "/safeseh")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
- else() # masm
|
|
||||||
+ # armasm doesn't support most of these options
|
|
||||||
+ elseif(NOT BOOST_CONTEXT_ASSEMBLER STREQUAL armasm) # masm
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|
||||||
set_property(SOURCE ${ASM_SOURCES} APPEND PROPERTY COMPILE_OPTIONS "-x" "assembler-with-cpp")
|
|
||||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
From ce992811fe8eb5ea7ad37e5b255bfecb0c313928 Mon Sep 17 00:00:00 2001
|
||||||
|
From: crueter <crueter@crueter.xyz>
|
||||||
|
Date: Sun, 7 Sep 2025 23:43:57 -0400
|
||||||
|
Subject: [PATCH] [algorithm] fix missing declaration error
|
||||||
|
|
||||||
|
Projects with restrictive error options won't compile without this
|
||||||
|
|
||||||
|
Signed-off-by: crueter <crueter@crueter.xyz>
|
||||||
|
---
|
||||||
|
include/jwt/algorithm.hpp | 2 ++
|
||||||
|
1 file changed, 2 insertions(+)
|
||||||
|
|
||||||
|
diff --git a/include/jwt/algorithm.hpp b/include/jwt/algorithm.hpp
|
||||||
|
index 0e3b843..35347fe 100644
|
||||||
|
--- a/include/jwt/algorithm.hpp
|
||||||
|
+++ b/include/jwt/algorithm.hpp
|
||||||
|
@@ -63,6 +63,8 @@ using sign_func_t = sign_result_t (*) (const jwt::string_view key,
|
||||||
|
using verify_func_t = verify_result_t (*) (const jwt::string_view key,
|
||||||
|
const jwt::string_view head,
|
||||||
|
const jwt::string_view jwt_sign);
|
||||||
|
+
|
||||||
|
+verify_result_t is_secret_a_public_key(const jwt::string_view secret);
|
||||||
|
|
||||||
|
namespace algo {
|
||||||
|
|
||||||
@@ -1,52 +1,62 @@
|
|||||||
From e1a946ffb79022d38351a0623f819a5419965c3e Mon Sep 17 00:00:00 2001
|
From 436fc1978c78edd085d99b33275b24be0ac96aa0 Mon Sep 17 00:00:00 2001
|
||||||
From: crueter <crueter@eden-emu.dev>
|
From: crueter <crueter@eden-emu.dev>
|
||||||
Date: Fri, 24 Oct 2025 23:41:09 -0700
|
Date: Sun, 1 Feb 2026 16:21:10 -0500
|
||||||
Subject: [PATCH] [build] Fix MinGW missing GetAddrInfoExCancel definition
|
Subject: [PATCH] Fix build on MinGW
|
||||||
|
|
||||||
MinGW does not define GetAddrInfoExCancel in its wstcpi whatever header,
|
MinGW doesn't define GetAddrInfoExCancel.
|
||||||
so to get around this we can just load it with GetProcAddress et al.
|
|
||||||
|
|
||||||
Signed-off-by: crueter <crueter@eden-emu.dev>
|
Signed-off-by: crueter <crueter@eden-emu.dev>
|
||||||
---
|
---
|
||||||
httplib.h | 14 ++++++++++++--
|
httplib.h | 18 ++++++++++++++++--
|
||||||
1 file changed, 12 insertions(+), 2 deletions(-)
|
1 file changed, 16 insertions(+), 2 deletions(-)
|
||||||
|
|
||||||
diff --git a/httplib.h b/httplib.h
|
diff --git a/httplib.h b/httplib.h
|
||||||
index e15ba44..90a76dc 100644
|
index ec8d2a2..5f9a510 100644
|
||||||
--- a/httplib.h
|
--- a/httplib.h
|
||||||
+++ b/httplib.h
|
+++ b/httplib.h
|
||||||
@@ -203,11 +203,13 @@
|
@@ -203,14 +203,17 @@
|
||||||
#error Sorry, Visual Studio versions prior to 2015 are not supported
|
#error Sorry, Visual Studio versions prior to 2015 are not supported
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
-#pragma comment(lib, "ws2_32.lib")
|
-#pragma comment(lib, "ws2_32.lib")
|
||||||
-
|
-
|
||||||
|
#ifndef _SSIZE_T_DEFINED
|
||||||
using ssize_t = __int64;
|
using ssize_t = __int64;
|
||||||
|
#define _SSIZE_T_DEFINED
|
||||||
|
#endif
|
||||||
#endif // _MSC_VER
|
#endif // _MSC_VER
|
||||||
|
|
||||||
+#if defined(_MSC_VER) || defined(__MINGW32__)
|
+#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||||
+#pragma comment(lib, "ws2_32.lib")
|
+#pragma comment(lib, "ws2_32.lib")
|
||||||
+#endif
|
+#endif
|
||||||
|
+
|
||||||
+
|
+
|
||||||
#ifndef S_ISREG
|
#ifndef S_ISREG
|
||||||
#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
|
#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
|
||||||
#endif // S_ISREG
|
#endif // S_ISREG
|
||||||
@@ -3557,7 +3559,15 @@ inline int getaddrinfo_with_timeout(const char *node, const char *service,
|
@@ -4528,7 +4531,17 @@ inline int getaddrinfo_with_timeout(const char *node, const char *service,
|
||||||
auto wait_result =
|
auto wait_result =
|
||||||
::WaitForSingleObject(event, static_cast<DWORD>(timeout_sec * 1000));
|
::WaitForSingleObject(event, static_cast<DWORD>(timeout_sec * 1000));
|
||||||
if (wait_result == WAIT_TIMEOUT) {
|
if (wait_result == WAIT_TIMEOUT) {
|
||||||
+#ifdef __MINGW32__
|
+#ifdef __MINGW32__
|
||||||
+ typedef INT (WSAAPI *PFN_GETADDRINFOEXCANCEL)(HANDLE *CancelHandle);
|
+ typedef INT(WSAAPI * PFN_GETADDRINFOEXCANCEL)(HANDLE * CancelHandle);
|
||||||
+ auto wsdll = LoadLibraryW((wchar_t*) "ws2_32.lib");
|
+ auto wsdll = LoadLibraryW((wchar_t *)"ws2_32.lib");
|
||||||
+ PFN_GETADDRINFOEXCANCEL GetAddrInfoExCancel = (PFN_GETADDRINFOEXCANCEL) GetProcAddress(wsdll, "GetAddrInfoExCancel");
|
+ PFN_GETADDRINFOEXCANCEL GetAddrInfoExCancel =
|
||||||
|
+ (PFN_GETADDRINFOEXCANCEL)GetProcAddress(wsdll, "GetAddrInfoExCancel");
|
||||||
+
|
+
|
||||||
+ if (cancel_handle) { GetAddrInfoExCancel(&cancel_handle); }
|
+ if (cancel_handle) { GetAddrInfoExCancel(&cancel_handle); }
|
||||||
+#else
|
+#else
|
||||||
if (cancel_handle) { ::GetAddrInfoExCancel(&cancel_handle); }
|
if (cancel_handle) { ::GetAddrInfoExCancel(&cancel_handle); }
|
||||||
+#endif
|
+#endif
|
||||||
|
+
|
||||||
::CloseHandle(event);
|
::CloseHandle(event);
|
||||||
return EAI_AGAIN;
|
return EAI_AGAIN;
|
||||||
}
|
}
|
||||||
--
|
@@ -13952,3 +13965,4 @@ inline SSL_CTX *Client::ssl_context() const {
|
||||||
2.51.0
|
} // namespace httplib
|
||||||
|
|
||||||
|
#endif // CPPHTTPLIB_HTTPLIB_H
|
||||||
|
+
|
||||||
|
--
|
||||||
|
2.51.2
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
From cc15da16e533b2a801934eab2dfeaf3c3949a1dc Mon Sep 17 00:00:00 2001
|
||||||
|
From: crueter <crueter@eden-emu.dev>
|
||||||
|
Date: Mon, 8 Sep 2025 12:28:55 -0400
|
||||||
|
Subject: [PATCH] [cmake] disable NEON runtime check on clang-cl
|
||||||
|
|
||||||
|
When enabling runtime NEON checking for clang-cl, the linker would error out with `undefined symbol: __emit`, since clang doesn't actually implement this instruction. Therefore it makes sense to disable the runtime check by default on this platform, until either this is fixed or a clang-cl compatible intrinsic check is added (I don't have enough knowledge of MSVC to do this)
|
||||||
|
---
|
||||||
|
cmake/OpusConfig.cmake | 7 ++++++-
|
||||||
|
1 file changed, 6 insertions(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/cmake/OpusConfig.cmake b/cmake/OpusConfig.cmake
|
||||||
|
index e9319fbad..d0f459e88 100644
|
||||||
|
--- a/cmake/OpusConfig.cmake
|
||||||
|
+++ b/cmake/OpusConfig.cmake
|
||||||
|
@@ -71,7 +71,12 @@ elseif(OPUS_CPU_ARM AND NOT OPUS_DISABLE_INTRINSICS)
|
||||||
|
opus_detect_neon(COMPILER_SUPPORT_NEON)
|
||||||
|
if(COMPILER_SUPPORT_NEON)
|
||||||
|
option(OPUS_USE_NEON "Option to enable NEON" ON)
|
||||||
|
- option(OPUS_MAY_HAVE_NEON "Does runtime check for neon support" ON)
|
||||||
|
+ if (MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||||
|
+ set(NEON_RUNTIME_CHECK_DEFAULT OFF)
|
||||||
|
+ else()
|
||||||
|
+ set(NEON_RUNTIME_CHECK_DEFAULT ON)
|
||||||
|
+ endif()
|
||||||
|
+ option(OPUS_MAY_HAVE_NEON "Does runtime check for neon support" ${NEON_RUNTIME_CHECK_DEFAULT})
|
||||||
|
option(OPUS_PRESUME_NEON "Assume target CPU has NEON support" OFF)
|
||||||
|
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
|
||||||
|
set(OPUS_PRESUME_NEON ON)
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
From bf455b67b4eaa446ffae5d25410b141b7b1b1082 Mon Sep 17 00:00:00 2001
|
||||||
|
From: crueter <crueter@eden-emu.dev>
|
||||||
|
Date: Mon, 8 Sep 2025 12:08:20 -0400
|
||||||
|
Subject: [PATCH] [cmake] `OPUS_INSTALL` option; only default install if root
|
||||||
|
project
|
||||||
|
|
||||||
|
Signed-off-by: crueter <crueter@eden-emu.dev>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 112 ++++++++++++++++++++++++++++---------------------
|
||||||
|
1 file changed, 64 insertions(+), 48 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index fcf034b19..08b5e16f8 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -4,6 +4,13 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||||
|
include(OpusPackageVersion)
|
||||||
|
get_package_version(PACKAGE_VERSION PROJECT_VERSION)
|
||||||
|
|
||||||
|
+# root project detection
|
||||||
|
+if(DEFINED PROJECT_NAME)
|
||||||
|
+ set(root_project OFF)
|
||||||
|
+else()
|
||||||
|
+ set(root_project ON)
|
||||||
|
+endif()
|
||||||
|
+
|
||||||
|
project(Opus LANGUAGES C VERSION ${PROJECT_VERSION})
|
||||||
|
|
||||||
|
include(OpusFunctions)
|
||||||
|
@@ -83,12 +90,16 @@ set(OPUS_DNN_FLOAT_DEBUG_HELP_STR "Run DNN computations as float for debugging p
|
||||||
|
option(OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR} OFF)
|
||||||
|
add_feature_info(OPUS_DNN_FLOAT_DEBUG OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR})
|
||||||
|
|
||||||
|
+set(OPUS_INSTALL_HELP_STR "Install Opus targets")
|
||||||
|
+option(OPUS_INSTALL ${OPUS_INSTALL_HELP_STR} ${root_project})
|
||||||
|
+add_feature_info(OPUS_INSTALL OPUS_INSTALL ${OPUS_INSTALL_HELP_STR})
|
||||||
|
+
|
||||||
|
set(OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR "install pkg-config module.")
|
||||||
|
-option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ON)
|
||||||
|
+option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ${OPUS_INSTALL})
|
||||||
|
add_feature_info(OPUS_INSTALL_PKG_CONFIG_MODULE OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR})
|
||||||
|
|
||||||
|
set(OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR "install CMake package config module.")
|
||||||
|
-option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ON)
|
||||||
|
+option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ${OPUS_INSTALL})
|
||||||
|
add_feature_info(OPUS_INSTALL_CMAKE_CONFIG_MODULE OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR})
|
||||||
|
|
||||||
|
set(OPUS_DRED_HELP_STR "enable DRED.")
|
||||||
|
@@ -613,53 +624,58 @@ if(OPUS_BUILD_FRAMEWORK)
|
||||||
|
OUTPUT_NAME Opus)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
-install(TARGETS opus
|
||||||
|
- EXPORT OpusTargets
|
||||||
|
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
- FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}
|
||||||
|
- PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/opus)
|
||||||
|
-
|
||||||
|
-if(OPUS_INSTALL_PKG_CONFIG_MODULE)
|
||||||
|
- set(prefix ${CMAKE_INSTALL_PREFIX})
|
||||||
|
- set(exec_prefix ${CMAKE_INSTALL_PREFIX})
|
||||||
|
- set(libdir ${CMAKE_INSTALL_FULL_LIBDIR})
|
||||||
|
- set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR})
|
||||||
|
- set(VERSION ${PACKAGE_VERSION})
|
||||||
|
- if(HAVE_LIBM)
|
||||||
|
- set(LIBM "-lm")
|
||||||
|
+if (OPUS_INSTALL)
|
||||||
|
+ install(TARGETS opus
|
||||||
|
+ EXPORT OpusTargets
|
||||||
|
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
+ FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}
|
||||||
|
+ PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/opus)
|
||||||
|
+
|
||||||
|
+ if(OPUS_INSTALL_PKG_CONFIG_MODULE)
|
||||||
|
+ set(prefix ${CMAKE_INSTALL_PREFIX})
|
||||||
|
+ set(exec_prefix ${CMAKE_INSTALL_PREFIX})
|
||||||
|
+ set(libdir ${CMAKE_INSTALL_FULL_LIBDIR})
|
||||||
|
+ set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR})
|
||||||
|
+ set(VERSION ${PACKAGE_VERSION})
|
||||||
|
+ if(HAVE_LIBM)
|
||||||
|
+ set(LIBM "-lm")
|
||||||
|
+ endif()
|
||||||
|
+ configure_file(opus.pc.in opus.pc)
|
||||||
|
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/opus.pc
|
||||||
|
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||||
|
+ endif()
|
||||||
|
+
|
||||||
|
+ if(OPUS_INSTALL_CMAKE_CONFIG_MODULE)
|
||||||
|
+ set(CPACK_GENERATOR TGZ)
|
||||||
|
+ include(CPack)
|
||||||
|
+ set(CMAKE_INSTALL_PACKAGEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
||||||
|
+ install(EXPORT OpusTargets
|
||||||
|
+ NAMESPACE Opus::
|
||||||
|
+ DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
||||||
|
+
|
||||||
|
+ include(CMakePackageConfigHelpers)
|
||||||
|
+
|
||||||
|
+ set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
|
||||||
|
+ configure_package_config_file(
|
||||||
|
+ ${PROJECT_SOURCE_DIR}/cmake/OpusConfig.cmake.in
|
||||||
|
+ OpusConfig.cmake
|
||||||
|
+ INSTALL_DESTINATION
|
||||||
|
+ ${CMAKE_INSTALL_PACKAGEDIR}
|
||||||
|
+ PATH_VARS
|
||||||
|
+ INCLUDE_INSTALL_DIR
|
||||||
|
+ INSTALL_PREFIX
|
||||||
|
+ ${CMAKE_INSTALL_PREFIX})
|
||||||
|
+
|
||||||
|
+ write_basic_package_version_file(OpusConfigVersion.cmake
|
||||||
|
+ VERSION ${PROJECT_VERSION}
|
||||||
|
+ COMPATIBILITY SameMajorVersion)
|
||||||
|
+
|
||||||
|
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpusConfig.cmake
|
||||||
|
+ ${CMAKE_CURRENT_BINARY_DIR}/OpusConfigVersion.cmake
|
||||||
|
+ DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
||||||
|
endif()
|
||||||
|
- configure_file(opus.pc.in opus.pc)
|
||||||
|
- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/opus.pc
|
||||||
|
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||||
|
-endif()
|
||||||
|
-
|
||||||
|
-if(OPUS_INSTALL_CMAKE_CONFIG_MODULE)
|
||||||
|
- set(CPACK_GENERATOR TGZ)
|
||||||
|
- include(CPack)
|
||||||
|
- set(CMAKE_INSTALL_PACKAGEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
||||||
|
- install(EXPORT OpusTargets
|
||||||
|
- NAMESPACE Opus::
|
||||||
|
- DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
||||||
|
-
|
||||||
|
- include(CMakePackageConfigHelpers)
|
||||||
|
-
|
||||||
|
- set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
|
||||||
|
- configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/OpusConfig.cmake.in
|
||||||
|
- OpusConfig.cmake
|
||||||
|
- INSTALL_DESTINATION
|
||||||
|
- ${CMAKE_INSTALL_PACKAGEDIR}
|
||||||
|
- PATH_VARS
|
||||||
|
- INCLUDE_INSTALL_DIR
|
||||||
|
- INSTALL_PREFIX
|
||||||
|
- ${CMAKE_INSTALL_PREFIX})
|
||||||
|
- write_basic_package_version_file(OpusConfigVersion.cmake
|
||||||
|
- VERSION ${PROJECT_VERSION}
|
||||||
|
- COMPATIBILITY SameMajorVersion)
|
||||||
|
- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpusConfig.cmake
|
||||||
|
- ${CMAKE_CURRENT_BINARY_DIR}/OpusConfigVersion.cmake
|
||||||
|
- DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(OPUS_BUILD_PROGRAMS)
|
||||||
+11
-1
@@ -178,7 +178,9 @@ endif()
|
|||||||
|
|
||||||
# Disable Warnings as Errors for MSVC
|
# Disable Warnings as Errors for MSVC
|
||||||
if (MSVC AND NOT CXX_CLANG)
|
if (MSVC AND NOT CXX_CLANG)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3 /WX-")
|
# This was dripping into spirv, being overriden, and causing cl flag override warning
|
||||||
|
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3 /WX-")
|
||||||
|
set(CMAKE_CXX_FLAGS_INIT "${CMAKE_CXX_FLAGS_INIT} /W3 /WX-")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Set bundled sdl2/qt as dependent options.
|
# Set bundled sdl2/qt as dependent options.
|
||||||
@@ -227,6 +229,8 @@ option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android"
|
|||||||
|
|
||||||
option(YUZU_LEGACY "Apply patches that improve compatibility with older GPUs (e.g. Snapdragon 865) at the cost of performance" OFF)
|
option(YUZU_LEGACY "Apply patches that improve compatibility with older GPUs (e.g. Snapdragon 865) at the cost of performance" OFF)
|
||||||
|
|
||||||
|
option(NIGHTLY_BUILD "Use Nightly qualifiers in the update checker and build metadata" OFF)
|
||||||
|
|
||||||
cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF)
|
cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF)
|
||||||
cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF)
|
cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF)
|
||||||
|
|
||||||
@@ -585,6 +589,7 @@ if (NOT YUZU_STATIC_ROOM)
|
|||||||
find_package(sirit)
|
find_package(sirit)
|
||||||
find_package(gamemode)
|
find_package(gamemode)
|
||||||
find_package(mcl)
|
find_package(mcl)
|
||||||
|
find_package(frozen)
|
||||||
|
|
||||||
if (ARCHITECTURE_riscv64)
|
if (ARCHITECTURE_riscv64)
|
||||||
find_package(biscuit)
|
find_package(biscuit)
|
||||||
@@ -691,6 +696,11 @@ if (ENABLE_QT)
|
|||||||
set(QT_MAJOR_VERSION 6)
|
set(QT_MAJOR_VERSION 6)
|
||||||
# Qt6 sets cxx_std_17 and we need to undo that
|
# Qt6 sets cxx_std_17 and we need to undo that
|
||||||
set_target_properties(Qt6::Platform PROPERTIES INTERFACE_COMPILE_FEATURES "")
|
set_target_properties(Qt6::Platform PROPERTIES INTERFACE_COMPILE_FEATURES "")
|
||||||
|
|
||||||
|
## Qt Externals ##
|
||||||
|
|
||||||
|
# QuaZip
|
||||||
|
AddJsonPackage(quazip)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT YUZU_STATIC_ROOM AND NOT (YUZU_USE_BUNDLED_FFMPEG OR YUZU_USE_EXTERNAL_FFMPEG))
|
if (NOT YUZU_STATIC_ROOM AND NOT (YUZU_USE_BUNDLED_FFMPEG OR YUZU_USE_EXTERNAL_FFMPEG))
|
||||||
|
|||||||
+192
-125
@@ -41,6 +41,11 @@ function(cpm_utils_message level name message)
|
|||||||
message(${level} "[CPMUtil] ${name}: ${message}")
|
message(${level} "[CPMUtil] ${name}: ${message}")
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
# propagate a variable to parent scope
|
||||||
|
macro(Propagate var)
|
||||||
|
set(${var} ${${var}} PARENT_SCOPE)
|
||||||
|
endmacro()
|
||||||
|
|
||||||
function(array_to_list array length out)
|
function(array_to_list array length out)
|
||||||
math(EXPR range "${length} - 1")
|
math(EXPR range "${length} - 1")
|
||||||
|
|
||||||
@@ -72,6 +77,159 @@ function(get_json_element object out member default)
|
|||||||
set("${out}" "${outvar}" PARENT_SCOPE)
|
set("${out}" "${outvar}" PARENT_SCOPE)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
# Determine whether or not a package has a viable system candidate.
|
||||||
|
function(SystemPackageViable JSON_NAME)
|
||||||
|
string(JSON object GET "${CPMFILE_CONTENT}" "${JSON_NAME}")
|
||||||
|
|
||||||
|
parse_object(${object})
|
||||||
|
|
||||||
|
string(REPLACE " " ";" find_args "${find_args}")
|
||||||
|
find_package(${package} ${version} ${find_args} QUIET NO_POLICY_SCOPE)
|
||||||
|
|
||||||
|
set(${pkg}_VIABLE ${${package}_FOUND} PARENT_SCOPE)
|
||||||
|
set(${pkg}_PACKAGE ${package} PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Add several packages such that if one is bundled,
|
||||||
|
# all the rest must also be bundled.
|
||||||
|
function(AddDependentPackages)
|
||||||
|
set(_some_system OFF)
|
||||||
|
set(_some_bundled OFF)
|
||||||
|
|
||||||
|
foreach(pkg ${ARGN})
|
||||||
|
SystemPackageViable(${pkg})
|
||||||
|
|
||||||
|
if (${pkg}_VIABLE)
|
||||||
|
set(_some_system ON)
|
||||||
|
list(APPEND _system_pkgs ${${pkg}_PACKAGE})
|
||||||
|
else()
|
||||||
|
set(_some_bundled ON)
|
||||||
|
list(APPEND _bundled_pkgs ${${pkg}_PACKAGE})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if (_some_system AND _some_bundled)
|
||||||
|
foreach(pkg ${ARGN})
|
||||||
|
list(APPEND package_names ${${pkg}_PACKAGE})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
string(REPLACE ";" ", " package_names "${package_names}")
|
||||||
|
string(REPLACE ";" ", " bundled_names "${_bundled_pkgs}")
|
||||||
|
foreach(sys ${_system_pkgs})
|
||||||
|
list(APPEND system_names ${sys}_FORCE_BUNDLED)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
string(REPLACE ";" ", " system_names "${system_names}")
|
||||||
|
|
||||||
|
message(FATAL_ERROR "Partial dependency installation detected "
|
||||||
|
"for the following packages:\n${package_names}\n"
|
||||||
|
"You can solve this in one of two ways:\n"
|
||||||
|
"1. Install the following packages to your system if available:"
|
||||||
|
"\n\t${bundled_names}\n"
|
||||||
|
"2. Set the following variables to ON:"
|
||||||
|
"\n\t${system_names}\n"
|
||||||
|
"This may also be caused by a version mismatch, "
|
||||||
|
"such as one package being newer than the other.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(pkg ${ARGN})
|
||||||
|
AddJsonPackage(${pkg})
|
||||||
|
endforeach()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# json util
|
||||||
|
macro(parse_object object)
|
||||||
|
get_json_element("${object}" package package ${JSON_NAME})
|
||||||
|
get_json_element("${object}" repo repo "")
|
||||||
|
get_json_element("${object}" ci ci OFF)
|
||||||
|
get_json_element("${object}" version version "")
|
||||||
|
|
||||||
|
if(ci)
|
||||||
|
get_json_element("${object}" name name "${JSON_NAME}")
|
||||||
|
get_json_element("${object}" extension extension "tar.zst")
|
||||||
|
get_json_element("${object}" min_version min_version "")
|
||||||
|
get_json_element("${object}" raw_disabled disabled_platforms "")
|
||||||
|
|
||||||
|
if(raw_disabled)
|
||||||
|
array_to_list("${raw_disabled}"
|
||||||
|
${raw_disabled_LENGTH} disabled_platforms)
|
||||||
|
else()
|
||||||
|
set(disabled_platforms "")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
get_json_element("${object}" hash hash "")
|
||||||
|
get_json_element("${object}" hash_suffix hash_suffix "")
|
||||||
|
get_json_element("${object}" sha sha "")
|
||||||
|
get_json_element("${object}" url url "")
|
||||||
|
get_json_element("${object}" key key "")
|
||||||
|
get_json_element("${object}" tag tag "")
|
||||||
|
get_json_element("${object}" artifact artifact "")
|
||||||
|
get_json_element("${object}" git_version git_version "")
|
||||||
|
get_json_element("${object}" git_host git_host "")
|
||||||
|
get_json_element("${object}" source_subdir source_subdir "")
|
||||||
|
get_json_element("${object}" bundled bundled "unset")
|
||||||
|
get_json_element("${object}" find_args find_args "")
|
||||||
|
get_json_element("${object}" raw_patches patches "")
|
||||||
|
|
||||||
|
# okay here comes the fun part: REPLACEMENTS!
|
||||||
|
# first: tag gets %VERSION% replaced if applicable,
|
||||||
|
# with either git_version (preferred) or version
|
||||||
|
# second: artifact gets %VERSION% and %TAG% replaced
|
||||||
|
# accordingly (same rules for VERSION)
|
||||||
|
|
||||||
|
if(git_version)
|
||||||
|
set(version_replace ${git_version})
|
||||||
|
else()
|
||||||
|
set(version_replace ${version})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# TODO(crueter): fmt module for cmake
|
||||||
|
if(tag)
|
||||||
|
string(REPLACE "%VERSION%" "${version_replace}" tag ${tag})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(artifact)
|
||||||
|
string(REPLACE "%VERSION%" "${version_replace}"
|
||||||
|
artifact ${artifact})
|
||||||
|
string(REPLACE "%TAG%" "${tag}" artifact ${artifact})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# format patchdir
|
||||||
|
if(raw_patches)
|
||||||
|
math(EXPR range "${raw_patches_LENGTH} - 1")
|
||||||
|
|
||||||
|
foreach(IDX RANGE ${range})
|
||||||
|
string(JSON _patch GET "${raw_patches}" "${IDX}")
|
||||||
|
|
||||||
|
set(full_patch
|
||||||
|
"${PROJECT_SOURCE_DIR}/.patch/${JSON_NAME}/${_patch}")
|
||||||
|
if(NOT EXISTS ${full_patch})
|
||||||
|
cpm_utils_message(FATAL_ERROR ${JSON_NAME}
|
||||||
|
"specifies patch ${full_patch} which does not exist")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(APPEND patches "${full_patch}")
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
# end format patchdir
|
||||||
|
|
||||||
|
# options
|
||||||
|
get_json_element("${object}" raw_options options "")
|
||||||
|
|
||||||
|
if(raw_options)
|
||||||
|
array_to_list("${raw_options}" ${raw_options_LENGTH} options)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(options ${options} ${JSON_OPTIONS})
|
||||||
|
# end options
|
||||||
|
|
||||||
|
# system/bundled
|
||||||
|
if(bundled STREQUAL "unset" AND DEFINED JSON_BUNDLED_PACKAGE)
|
||||||
|
set(bundled ${JSON_BUNDLED_PACKAGE})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
# The preferred usage
|
# The preferred usage
|
||||||
function(AddJsonPackage)
|
function(AddJsonPackage)
|
||||||
set(oneValueArgs
|
set(oneValueArgs
|
||||||
@@ -80,7 +238,8 @@ function(AddJsonPackage)
|
|||||||
# these are overrides that can be generated at runtime,
|
# these are overrides that can be generated at runtime,
|
||||||
# so can be defined separately from the json
|
# so can be defined separately from the json
|
||||||
DOWNLOAD_ONLY
|
DOWNLOAD_ONLY
|
||||||
BUNDLED_PACKAGE)
|
BUNDLED_PACKAGE
|
||||||
|
FORCE_BUNDLED_PACKAGE)
|
||||||
|
|
||||||
set(multiValueArgs OPTIONS)
|
set(multiValueArgs OPTIONS)
|
||||||
|
|
||||||
@@ -111,24 +270,9 @@ function(AddJsonPackage)
|
|||||||
cpm_utils_message(FATAL_ERROR ${JSON_NAME} "Not found in cpmfile")
|
cpm_utils_message(FATAL_ERROR ${JSON_NAME} "Not found in cpmfile")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
get_json_element("${object}" package package ${JSON_NAME})
|
parse_object(${object})
|
||||||
get_json_element("${object}" repo repo "")
|
|
||||||
get_json_element("${object}" ci ci OFF)
|
|
||||||
get_json_element("${object}" version version "")
|
|
||||||
|
|
||||||
if(ci)
|
if(ci)
|
||||||
get_json_element("${object}" name name "${JSON_NAME}")
|
|
||||||
get_json_element("${object}" extension extension "tar.zst")
|
|
||||||
get_json_element("${object}" min_version min_version "")
|
|
||||||
get_json_element("${object}" raw_disabled disabled_platforms "")
|
|
||||||
|
|
||||||
if(raw_disabled)
|
|
||||||
array_to_list("${raw_disabled}"
|
|
||||||
${raw_disabled_LENGTH} disabled_platforms)
|
|
||||||
else()
|
|
||||||
set(disabled_platforms "")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
AddCIPackage(
|
AddCIPackage(
|
||||||
VERSION ${version}
|
VERSION ${version}
|
||||||
NAME ${name}
|
NAME ${name}
|
||||||
@@ -138,116 +282,38 @@ function(AddJsonPackage)
|
|||||||
MIN_VERSION ${min_version}
|
MIN_VERSION ${min_version}
|
||||||
DISABLED_PLATFORMS ${disabled_platforms})
|
DISABLED_PLATFORMS ${disabled_platforms})
|
||||||
|
|
||||||
# pass stuff to parent scope
|
|
||||||
set(${package}_ADDED "${${package}_ADDED}"
|
|
||||||
PARENT_SCOPE)
|
|
||||||
set(${package}_SOURCE_DIR "${${package}_SOURCE_DIR}"
|
|
||||||
PARENT_SCOPE)
|
|
||||||
set(${package}_BINARY_DIR "${${package}_BINARY_DIR}"
|
|
||||||
PARENT_SCOPE)
|
|
||||||
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
get_json_element("${object}" hash hash "")
|
|
||||||
get_json_element("${object}" hash_suffix hash_suffix "")
|
|
||||||
get_json_element("${object}" sha sha "")
|
|
||||||
get_json_element("${object}" url url "")
|
|
||||||
get_json_element("${object}" key key "")
|
|
||||||
get_json_element("${object}" tag tag "")
|
|
||||||
get_json_element("${object}" artifact artifact "")
|
|
||||||
get_json_element("${object}" git_version git_version "")
|
|
||||||
get_json_element("${object}" git_host git_host "")
|
|
||||||
get_json_element("${object}" source_subdir source_subdir "")
|
|
||||||
get_json_element("${object}" bundled bundled "unset")
|
|
||||||
get_json_element("${object}" find_args find_args "")
|
|
||||||
get_json_element("${object}" raw_patches patches "")
|
|
||||||
|
|
||||||
# okay here comes the fun part: REPLACEMENTS!
|
|
||||||
# first: tag gets %VERSION% replaced if applicable,
|
|
||||||
# with either git_version (preferred) or version
|
|
||||||
# second: artifact gets %VERSION% and %TAG% replaced
|
|
||||||
# accordingly (same rules for VERSION)
|
|
||||||
|
|
||||||
if(git_version)
|
|
||||||
set(version_replace ${git_version})
|
|
||||||
else()
|
else()
|
||||||
set(version_replace ${version})
|
if (NOT DEFINED JSON_FORCE_BUNDLED_PACKAGE)
|
||||||
|
set(JSON_FORCE_BUNDLED_PACKAGE OFF)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
AddPackage(
|
||||||
|
NAME "${package}"
|
||||||
|
VERSION "${version}"
|
||||||
|
URL "${url}"
|
||||||
|
HASH "${hash}"
|
||||||
|
HASH_SUFFIX "${hash_suffix}"
|
||||||
|
SHA "${sha}"
|
||||||
|
REPO "${repo}"
|
||||||
|
KEY "${key}"
|
||||||
|
PATCHES "${patches}"
|
||||||
|
OPTIONS "${options}"
|
||||||
|
FIND_PACKAGE_ARGUMENTS "${find_args}"
|
||||||
|
BUNDLED_PACKAGE "${bundled}"
|
||||||
|
FORCE_BUNDLED_PACKAGE "${JSON_FORCE_BUNDLED_PACKAGE}"
|
||||||
|
SOURCE_SUBDIR "${source_subdir}"
|
||||||
|
|
||||||
|
GIT_VERSION ${git_version}
|
||||||
|
GIT_HOST ${git_host}
|
||||||
|
|
||||||
|
ARTIFACT ${artifact}
|
||||||
|
TAG ${tag})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# TODO(crueter): fmt module for cmake
|
|
||||||
if(tag)
|
|
||||||
string(REPLACE "%VERSION%" "${version_replace}" tag ${tag})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(artifact)
|
|
||||||
string(REPLACE "%VERSION%" "${version_replace}" artifact ${artifact})
|
|
||||||
string(REPLACE "%TAG%" "${tag}" artifact ${artifact})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# format patchdir
|
|
||||||
if(raw_patches)
|
|
||||||
math(EXPR range "${raw_patches_LENGTH} - 1")
|
|
||||||
|
|
||||||
foreach(IDX RANGE ${range})
|
|
||||||
string(JSON _patch GET "${raw_patches}" "${IDX}")
|
|
||||||
|
|
||||||
set(full_patch
|
|
||||||
"${PROJECT_SOURCE_DIR}/.patch/${JSON_NAME}/${_patch}")
|
|
||||||
if(NOT EXISTS ${full_patch})
|
|
||||||
cpm_utils_message(FATAL_ERROR ${JSON_NAME}
|
|
||||||
"specifies patch ${full_patch} which does not exist")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
list(APPEND patches "${full_patch}")
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
# end format patchdir
|
|
||||||
|
|
||||||
# options
|
|
||||||
get_json_element("${object}" raw_options options "")
|
|
||||||
|
|
||||||
if(raw_options)
|
|
||||||
array_to_list("${raw_options}" ${raw_options_LENGTH} options)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(options ${options} ${JSON_OPTIONS})
|
|
||||||
# end options
|
|
||||||
|
|
||||||
# system/bundled
|
|
||||||
if(bundled STREQUAL "unset" AND DEFINED JSON_BUNDLED_PACKAGE)
|
|
||||||
set(bundled ${JSON_BUNDLED_PACKAGE})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
AddPackage(
|
|
||||||
NAME "${package}"
|
|
||||||
VERSION "${version}"
|
|
||||||
URL "${url}"
|
|
||||||
HASH "${hash}"
|
|
||||||
HASH_SUFFIX "${hash_suffix}"
|
|
||||||
SHA "${sha}"
|
|
||||||
REPO "${repo}"
|
|
||||||
KEY "${key}"
|
|
||||||
PATCHES "${patches}"
|
|
||||||
OPTIONS "${options}"
|
|
||||||
FIND_PACKAGE_ARGUMENTS "${find_args}"
|
|
||||||
BUNDLED_PACKAGE "${bundled}"
|
|
||||||
SOURCE_SUBDIR "${source_subdir}"
|
|
||||||
|
|
||||||
GIT_VERSION ${git_version}
|
|
||||||
GIT_HOST ${git_host}
|
|
||||||
|
|
||||||
ARTIFACT ${artifact}
|
|
||||||
TAG ${tag})
|
|
||||||
|
|
||||||
# pass stuff to parent scope
|
# pass stuff to parent scope
|
||||||
set(${package}_ADDED "${${package}_ADDED}"
|
Propagate(${package}_ADDED)
|
||||||
PARENT_SCOPE)
|
Propagate(${package}_SOURCE_DIR)
|
||||||
set(${package}_SOURCE_DIR "${${package}_SOURCE_DIR}"
|
Propagate(${package}_BINARY_DIR)
|
||||||
PARENT_SCOPE)
|
|
||||||
set(${package}_BINARY_DIR "${${package}_BINARY_DIR}"
|
|
||||||
PARENT_SCOPE)
|
|
||||||
|
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
function(AddPackage)
|
function(AddPackage)
|
||||||
@@ -343,7 +409,7 @@ function(AddPackage)
|
|||||||
|
|
||||||
if(DEFINED PKG_ARGS_ARTIFACT)
|
if(DEFINED PKG_ARGS_ARTIFACT)
|
||||||
set(pkg_url
|
set(pkg_url
|
||||||
${pkg_git_url}/releases/download/${PKG_ARGS_TAG}/${PKG_ARGS_ARTIFACT})
|
"${pkg_git_url}/releases/download/${PKG_ARGS_TAG}/${PKG_ARGS_ARTIFACT}")
|
||||||
else()
|
else()
|
||||||
set(pkg_url
|
set(pkg_url
|
||||||
${pkg_git_url}/archive/refs/tags/${PKG_ARGS_TAG}.tar.gz)
|
${pkg_git_url}/archive/refs/tags/${PKG_ARGS_TAG}.tar.gz)
|
||||||
@@ -625,7 +691,8 @@ function(AddCIPackage)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (DEFINED pkgname AND NOT "${pkgname}" IN_LIST DISABLED_PLATFORMS)
|
if (DEFINED pkgname AND NOT "${pkgname}" IN_LIST DISABLED_PLATFORMS)
|
||||||
set(ARTIFACT "${ARTIFACT_NAME}-${pkgname}-${ARTIFACT_VERSION}.${ARTIFACT_EXT}")
|
set(ARTIFACT
|
||||||
|
"${ARTIFACT_NAME}-${pkgname}-${ARTIFACT_VERSION}.${ARTIFACT_EXT}")
|
||||||
|
|
||||||
AddPackage(
|
AddPackage(
|
||||||
NAME ${ARTIFACT_PACKAGE}
|
NAME ${ARTIFACT_PACKAGE}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: 2019 yuzu Emulator Project
|
# SPDX-FileCopyrightText: 2019 yuzu Emulator Project
|
||||||
@@ -15,27 +15,40 @@ endfunction()
|
|||||||
get_timestamp(BUILD_DATE)
|
get_timestamp(BUILD_DATE)
|
||||||
|
|
||||||
if (DEFINED GIT_RELEASE)
|
if (DEFINED GIT_RELEASE)
|
||||||
set(BUILD_VERSION "${GIT_TAG}")
|
set(BUILD_VERSION "${GIT_TAG}")
|
||||||
set(GIT_REFSPEC "${GIT_RELEASE}")
|
set(GIT_REFSPEC "${GIT_RELEASE}")
|
||||||
set(IS_DEV_BUILD false)
|
set(IS_DEV_BUILD false)
|
||||||
else()
|
else()
|
||||||
string(SUBSTRING ${GIT_COMMIT} 0 10 BUILD_VERSION)
|
string(SUBSTRING ${GIT_COMMIT} 0 10 BUILD_VERSION)
|
||||||
set(BUILD_VERSION "${BUILD_VERSION}-${GIT_REFSPEC}")
|
set(BUILD_VERSION "${BUILD_VERSION}-${GIT_REFSPEC}")
|
||||||
set(IS_DEV_BUILD true)
|
set(IS_DEV_BUILD true)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (NIGHTLY_BUILD)
|
||||||
|
set(IS_NIGHTLY_BUILD true)
|
||||||
|
else()
|
||||||
|
set(IS_NIGHTLY_BUILD false)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(GIT_DESC ${BUILD_VERSION})
|
set(GIT_DESC ${BUILD_VERSION})
|
||||||
|
|
||||||
# Generate cpp with Git revision from template
|
# Generate cpp with Git revision from template
|
||||||
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
|
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
|
||||||
set(REPO_NAME "Eden")
|
|
||||||
set(BUILD_ID ${GIT_REFSPEC})
|
|
||||||
set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
|
|
||||||
set(CXX_COMPILER "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
|
|
||||||
|
|
||||||
# Auto-updater metadata! Must somewhat mirror GitHub API endpoint
|
# Auto-updater metadata! Must somewhat mirror GitHub API endpoint
|
||||||
set(BUILD_AUTO_UPDATE_WEBSITE "https://github.com")
|
set(BUILD_AUTO_UPDATE_WEBSITE "https://github.com")
|
||||||
set(BUILD_AUTO_UPDATE_API "http://api.github.com")
|
set(BUILD_AUTO_UPDATE_API "http://api.github.com")
|
||||||
set(BUILD_AUTO_UPDATE_REPO "eden-emulator/Releases")
|
|
||||||
|
if (NIGHTLY_BUILD)
|
||||||
|
set(BUILD_AUTO_UPDATE_REPO "Eden-CI/Nightly")
|
||||||
|
set(REPO_NAME "Eden Nightly")
|
||||||
|
else()
|
||||||
|
set(BUILD_AUTO_UPDATE_REPO "eden-emulator/Releases")
|
||||||
|
set(REPO_NAME "Eden")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(BUILD_ID ${GIT_REFSPEC})
|
||||||
|
set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
|
||||||
|
set(CXX_COMPILER "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
|
||||||
|
|
||||||
configure_file(scm_rev.cpp.in scm_rev.cpp @ONLY)
|
configure_file(scm_rev.cpp.in scm_rev.cpp @ONLY)
|
||||||
|
|||||||
+25
-10
@@ -12,14 +12,12 @@
|
|||||||
"repo": "boostorg/boost",
|
"repo": "boostorg/boost",
|
||||||
"tag": "boost-%VERSION%",
|
"tag": "boost-%VERSION%",
|
||||||
"artifact": "%TAG%-cmake.tar.xz",
|
"artifact": "%TAG%-cmake.tar.xz",
|
||||||
"hash": "4fb7f6fde92762305aad8754d7643cd918dd1f3f67e104e9ab385b18c73178d72a17321354eb203b790b6702f2cf6d725a5d6e2dfbc63b1e35f9eb59fb42ece9",
|
"hash": "6ae6e94664fe7f2fb01976b59b276ac5df8085c7503fa829d810fbfe495960cfec44fa2c36e2cb23480bc19c956ed199d4952b02639a00a6c07625d4e7130c2d",
|
||||||
"git_version": "1.89.0",
|
"git_version": "1.90.0",
|
||||||
"version": "1.57",
|
"version": "1.57",
|
||||||
"find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem",
|
"find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem",
|
||||||
"patches": [
|
"patches": [
|
||||||
"0001-clang-cl.patch",
|
"0001-clang-cl.patch"
|
||||||
"0002-use-marmasm.patch",
|
|
||||||
"0003-armasm-options.patch"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
@@ -48,9 +46,9 @@
|
|||||||
"package": "ZLIB",
|
"package": "ZLIB",
|
||||||
"repo": "madler/zlib",
|
"repo": "madler/zlib",
|
||||||
"tag": "v%VERSION%",
|
"tag": "v%VERSION%",
|
||||||
"hash": "8c9642495bafd6fad4ab9fb67f09b268c69ff9af0f4f20cf15dfc18852ff1f312bd8ca41de761b3f8d8e90e77d79f2ccacd3d4c5b19e475ecf09d021fdfe9088",
|
"hash": "06eaa3a1eaaeb31f461a2283b03a91ed8eb2406e62cd97ea1c69836324909edeecd93edd03ff0bf593d9dde223e3376149134c5b1fe2e8688c258cadf8cd60ff",
|
||||||
"version": "1.2",
|
"version": "1.2",
|
||||||
"git_version": "1.3.1",
|
"git_version": "1.3.1.2",
|
||||||
"options": [
|
"options": [
|
||||||
"ZLIB_BUILD_SHARED OFF",
|
"ZLIB_BUILD_SHARED OFF",
|
||||||
"ZLIB_INSTALL OFF"
|
"ZLIB_INSTALL OFF"
|
||||||
@@ -69,13 +67,17 @@
|
|||||||
},
|
},
|
||||||
"opus": {
|
"opus": {
|
||||||
"package": "Opus",
|
"package": "Opus",
|
||||||
"repo": "crueter/opus",
|
"repo": "xiph/opus",
|
||||||
"sha": "ab19c44fad",
|
"sha": "a3f0ec02b3",
|
||||||
"hash": "d632e8f83c5d3245db404bcb637113f9860bf16331498ba2c8e77979d1febee6b52d8b1da448e7d54eeac373e912cd55e3e300fc6c242244923323280dc43fbe",
|
"hash": "9506147b0de35befda8633ff272981cc2575c860874791bd455b752f797fd7dbd1079f0ba42ccdd7bb1fe6773fa5e84b3d75667c2883dd1fb2d0e4a5fa4f8387",
|
||||||
"version": "1.3",
|
"version": "1.3",
|
||||||
"find_args": "MODULE",
|
"find_args": "MODULE",
|
||||||
"options": [
|
"options": [
|
||||||
"OPUS_PRESUME_NEON ON"
|
"OPUS_PRESUME_NEON ON"
|
||||||
|
],
|
||||||
|
"patches": [
|
||||||
|
"0001-disable-clang-runtime-neon.patch",
|
||||||
|
"0002-no-install.patch"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"boost_headers": {
|
"boost_headers": {
|
||||||
@@ -99,5 +101,18 @@
|
|||||||
"git_version": "1.4.335.0",
|
"git_version": "1.4.335.0",
|
||||||
"artifact": "android-binaries-%VERSION%.zip",
|
"artifact": "android-binaries-%VERSION%.zip",
|
||||||
"hash": "48167c4a17736301bd08f9290f41830443e1f18cce8ad867fc6f289b49e18b40e93c9850b377951af82f51b5b6d7313aa6a884fc5df79f5ce3df82696c1c1244"
|
"hash": "48167c4a17736301bd08f9290f41830443e1f18cce8ad867fc6f289b49e18b40e93c9850b377951af82f51b5b6d7313aa6a884fc5df79f5ce3df82696c1c1244"
|
||||||
|
},
|
||||||
|
"quazip": {
|
||||||
|
"package": "QuaZip-Qt6",
|
||||||
|
"repo": "stachenov/quazip",
|
||||||
|
"sha": "2e95c9001b",
|
||||||
|
"hash": "609c240c7f029ac26a37d8fbab51bc16284e05e128b78b9b9c0e95d083538c36047a67d682759ac990e4adb0eeb90f04f1ea7fe2253bbda7e7e3bcce32e53dd8",
|
||||||
|
"version": "1.3",
|
||||||
|
"git_version": "1.5",
|
||||||
|
"options": [
|
||||||
|
"QUAZIP_QT_MAJOR_VERSION 6",
|
||||||
|
"QUAZIP_INSTALL OFF",
|
||||||
|
"QUAZIP_ENABLE_QTEXTCODEC OFF"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
src/web_service @AleksandrPopovich
|
src/web_service @AleksandrPopovich
|
||||||
src/dynarmic @Lizzie
|
src/dynarmic @Lizzie
|
||||||
src/core @Lizzie @Maufeat @PavelBARABANOV @MrPurple666 @JPikachu
|
src/core @Lizzie @Maufeat @PavelBARABANOV @MrPurple666 @JPikachu
|
||||||
src/core/hle @Maufeat @PavelBARABANOV @SDK-Chan
|
src/core/hle @Maufeat @PavelBARABANOV
|
||||||
src/core/arm @Lizzie @MrPurple666
|
src/core/arm @Lizzie @MrPurple666
|
||||||
src/*_room @AleksandrPopovich
|
src/*_room @AleksandrPopovich
|
||||||
src/video_core @CamilleLaVey @MaranBr @Wildcard @weakboson
|
src/video_core @CamilleLaVey @MaranBr @Wildcard @weakboson
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# AddDependentPackage
|
||||||
|
|
||||||
|
Use `AddDependentPackage` when you have multiple packages that are required to all be from the system, OR bundled. This is useful in cases where e.g. versions must absolutely match.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
Versioning must be handled by the package itself.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Vulkan
|
||||||
|
|
||||||
|
`cpmfile.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"vulkan-headers": {
|
||||||
|
"repo": "KhronosGroup/Vulkan-Headers",
|
||||||
|
"package": "VulkanHeaders",
|
||||||
|
"version": "1.4.317",
|
||||||
|
"hash": "26e0ad8fa34ab65a91ca62ddc54cc4410d209a94f64f2817dcdb8061dc621539a4262eab6387e9b9aa421db3dbf2cf8e2a4b041b696d0d03746bae1f25191272",
|
||||||
|
"git_version": "1.4.342",
|
||||||
|
"tag": "v%VERSION%"
|
||||||
|
},
|
||||||
|
"vulkan-utility-libraries": {
|
||||||
|
"repo": "KhronosGroup/Vulkan-Utility-Libraries",
|
||||||
|
"package": "VulkanUtilityLibraries",
|
||||||
|
"hash": "8147370f964fd82c315d6bb89adeda30186098427bf3efaa641d36282d42a263f31e96e4586bfd7ae0410ff015379c19aa4512ba160630444d3d8553afd1ec14",
|
||||||
|
"git_version": "1.4.342",
|
||||||
|
"tag": "v%VERSION%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`CMakeLists.txt`:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
AddDependentPackages(vulkan-headers vulkan-utility-libraries)
|
||||||
|
```
|
||||||
|
|
||||||
|
If Vulkan Headers are installed, but NOT Vulkan Utility Libraries, then CPMUtil will throw an error.
|
||||||
@@ -31,6 +31,10 @@ The core of CPMUtil is the [`AddPackage`](./AddPackage.md) function. [`AddPackag
|
|||||||
|
|
||||||
[`AddJsonPackage`](./AddJsonPackage.md) is the recommended method of usage for CPMUtil.
|
[`AddJsonPackage`](./AddJsonPackage.md) is the recommended method of usage for CPMUtil.
|
||||||
|
|
||||||
|
## AddDependentPackage
|
||||||
|
|
||||||
|
[`AddDependentPackage`](./AddDependentPackage.md) allows you to add multiple packages such that all of them must be from the system OR bundled.
|
||||||
|
|
||||||
## AddQt
|
## AddQt
|
||||||
|
|
||||||
[`AddQt`](./AddQt.md) adds a specific version of Qt to your project.
|
[`AddQt`](./AddQt.md) adds a specific version of Qt to your project.
|
||||||
|
|||||||
Vendored
+4
-6
@@ -83,13 +83,11 @@ endif()
|
|||||||
# mcl
|
# mcl
|
||||||
AddJsonPackage(mcl)
|
AddJsonPackage(mcl)
|
||||||
|
|
||||||
# VulkanUtilityHeaders - pulls in headers and utility libs
|
# Vulkan stuff
|
||||||
AddJsonPackage(vulkan-utility-headers)
|
AddDependentPackages(vulkan-headers vulkan-utility-libraries)
|
||||||
|
|
||||||
# small hack
|
# frozen
|
||||||
if (NOT VulkanUtilityLibraries_ADDED)
|
AddJsonPackage(frozen)
|
||||||
find_package(VulkanHeaders 1.3.274 REQUIRED)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# DiscordRPC
|
# DiscordRPC
|
||||||
if (USE_DISCORD_PRESENCE)
|
if (USE_DISCORD_PRESENCE)
|
||||||
|
|||||||
Vendored
+32
-17
@@ -28,8 +28,8 @@
|
|||||||
"httplib": {
|
"httplib": {
|
||||||
"repo": "yhirose/cpp-httplib",
|
"repo": "yhirose/cpp-httplib",
|
||||||
"tag": "v%VERSION%",
|
"tag": "v%VERSION%",
|
||||||
"hash": "e7a8877d489c97669a8ee536e1498575be921e558ed947253013fe6b67a49d4569eedd01f543caa70183b92d8ac0e8687d662a70d880954412e387317008a239",
|
"hash": "a229e24cca4afe78e5c0aa2e482f15108ac34101fd8dbd927365f15e8c37dec4de38c5277d635017d692a5b320e1b929f8bfcc076f52b8e4dcdab8fe53bfdf2e",
|
||||||
"git_version": "0.28.0",
|
"git_version": "0.30.1",
|
||||||
"find_args": "MODULE GLOBAL",
|
"find_args": "MODULE GLOBAL",
|
||||||
"patches": [
|
"patches": [
|
||||||
"0001-mingw.patch"
|
"0001-mingw.patch"
|
||||||
@@ -37,12 +37,15 @@
|
|||||||
},
|
},
|
||||||
"cpp-jwt": {
|
"cpp-jwt": {
|
||||||
"version": "1.4",
|
"version": "1.4",
|
||||||
"repo": "crueter/cpp-jwt",
|
"repo": "arun11299/cpp-jwt",
|
||||||
"sha": "9eaea6328f",
|
"sha": "7f24eb4c32",
|
||||||
"hash": "35b0b2bfb143585c7b2bd6dc6ca7df5ae5c6e2681000b2ebca077b0ac4bc1e6b6afbe1ce8e47f6d2edad12fcc6404f677acc2ad205661d819b8821ce6f4823fd",
|
"hash": "d11cbd5ddb3197b4c5ca15679bcd76a49963e7b530b7dd132db91e042925efa20dfb2c24ccfbe7ef82a7012af80deff0f72ee25851312ae80381a462df8534b8",
|
||||||
"find_args": "CONFIG",
|
"find_args": "CONFIG",
|
||||||
"options": [
|
"options": [
|
||||||
"CPP_JWT_USE_VENDORED_NLOHMANN_JSON OFF"
|
"CPP_JWT_USE_VENDORED_NLOHMANN_JSON OFF"
|
||||||
|
],
|
||||||
|
"patches": [
|
||||||
|
"0001-fix-missing-decl.patch"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"xbyak_sun": {
|
"xbyak_sun": {
|
||||||
@@ -115,18 +118,9 @@
|
|||||||
"git_version": "1.3.18",
|
"git_version": "1.3.18",
|
||||||
"find_args": "MODULE"
|
"find_args": "MODULE"
|
||||||
},
|
},
|
||||||
"vulkan-utility-headers": {
|
|
||||||
"package": "VulkanUtilityLibraries",
|
|
||||||
"repo": "scripts/VulkanUtilityHeaders",
|
|
||||||
"tag": "%VERSION%",
|
|
||||||
"git_version": "1.4.335",
|
|
||||||
"artifact": "VulkanUtilityHeaders.tar.zst",
|
|
||||||
"git_host": "git.crueter.xyz",
|
|
||||||
"hash": "16dac0e6586702580c4279e4cd37ffe3cf909c93eb31b5069da7af36436d47b270a9cbaac953bb66c22ed12ed67ffa096688599267f307dfb62be1bc09f79833"
|
|
||||||
},
|
|
||||||
"spirv-tools": {
|
"spirv-tools": {
|
||||||
"package": "SPIRV-Tools",
|
"package": "SPIRV-Tools",
|
||||||
"repo": "crueter/SPIRV-Tools",
|
"repo": "KhronosGroup/SPIRV-Tools",
|
||||||
"sha": "0a7e28689a",
|
"sha": "0a7e28689a",
|
||||||
"hash": "eadfcceb82f4b414528d99962335e4f806101168474028f3cf7691ac40c37f323decf2a42c525e2d5bfa6f14ff132d6c5cf9b87c151490efad01f5e13ade1520",
|
"hash": "eadfcceb82f4b414528d99962335e4f806101168474028f3cf7691ac40c37f323decf2a42c525e2d5bfa6f14ff132d6c5cf9b87c151490efad01f5e13ade1520",
|
||||||
"git_version": "2025.4",
|
"git_version": "2025.4",
|
||||||
@@ -171,9 +165,9 @@
|
|||||||
"package": "Catch2",
|
"package": "Catch2",
|
||||||
"repo": "catchorg/Catch2",
|
"repo": "catchorg/Catch2",
|
||||||
"tag": "v%VERSION%",
|
"tag": "v%VERSION%",
|
||||||
"hash": "a95495142f915d6e9c2a23e80fe360343e9097680066a2f9d3037a070ba5f81ee5559a0407cc9e972dc2afae325873f1fc7ea07a64012c0f01aac6e549f03e3f",
|
"hash": "acb3f463a7404d6a3bce52e474075cdadf9bb241d93feaf147c182d756e5a2f8bd412f4658ca186d15ab8fed36fc587d79ec311f55642d8e4ded16df9e213656",
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"git_version": "3.11.0",
|
"git_version": "3.12.0",
|
||||||
"patches": [
|
"patches": [
|
||||||
"0001-solaris-isnan-fix.patch"
|
"0001-solaris-isnan-fix.patch"
|
||||||
]
|
]
|
||||||
@@ -278,5 +272,26 @@
|
|||||||
"tag": "%VERSION%",
|
"tag": "%VERSION%",
|
||||||
"hash": "dc37a189a44ce8b5c988ca550582431a6c7eadfd3c6e709bee6277116ee803e714333e85c9e6cbb5c69346a14d6f2cc7ed96e8aa09cc5fb8a89f945059651db6",
|
"hash": "dc37a189a44ce8b5c988ca550582431a6c7eadfd3c6e709bee6277116ee803e714333e85c9e6cbb5c69346a14d6f2cc7ed96e8aa09cc5fb8a89f945059651db6",
|
||||||
"version": "121125"
|
"version": "121125"
|
||||||
|
},
|
||||||
|
"vulkan-headers": {
|
||||||
|
"repo": "KhronosGroup/Vulkan-Headers",
|
||||||
|
"package": "VulkanHeaders",
|
||||||
|
"version": "1.4.317",
|
||||||
|
"hash": "26e0ad8fa34ab65a91ca62ddc54cc4410d209a94f64f2817dcdb8061dc621539a4262eab6387e9b9aa421db3dbf2cf8e2a4b041b696d0d03746bae1f25191272",
|
||||||
|
"git_version": "1.4.342",
|
||||||
|
"tag": "v%VERSION%"
|
||||||
|
},
|
||||||
|
"vulkan-utility-libraries": {
|
||||||
|
"repo": "KhronosGroup/Vulkan-Utility-Libraries",
|
||||||
|
"package": "VulkanUtilityLibraries",
|
||||||
|
"hash": "8147370f964fd82c315d6bb89adeda30186098427bf3efaa641d36282d42a263f31e96e4586bfd7ae0410ff015379c19aa4512ba160630444d3d8553afd1ec14",
|
||||||
|
"git_version": "1.4.342",
|
||||||
|
"tag": "v%VERSION%"
|
||||||
|
},
|
||||||
|
"frozen": {
|
||||||
|
"package": "frozen",
|
||||||
|
"repo": "serge-sans-paille/frozen",
|
||||||
|
"sha": "61dce5ae18",
|
||||||
|
"hash": "b8dfe741c82bc178dfc9749d4ab5a130cee718d9ee7b71d9b547cf5f7f23027ed0152ad250012a8546399fcc1e12187efc68d89d6731256c4d2df7d04eef8d5c"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ if (YUZU_STATIC_BUILD)
|
|||||||
add_compile_definitions(QT_STATICPLUGIN)
|
add_compile_definitions(QT_STATICPLUGIN)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (NIGHTLY_BUILD)
|
||||||
|
add_compile_definitions(NIGHTLY_BUILD)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Set compilation flags
|
# Set compilation flags
|
||||||
if (MSVC AND NOT CXX_CLANG)
|
if (MSVC AND NOT CXX_CLANG)
|
||||||
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
|
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
// import android.annotation.SuppressLint
|
// import android.annotation.SuppressLint
|
||||||
|
import com.android.build.gradle.api.ApplicationVariant
|
||||||
import kotlin.collections.setOf
|
import kotlin.collections.setOf
|
||||||
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
|
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
|
||||||
import com.github.triplet.gradle.androidpublisher.ReleaseStatus
|
import com.github.triplet.gradle.androidpublisher.ReleaseStatus
|
||||||
@@ -37,6 +38,9 @@ android {
|
|||||||
compileSdkVersion = "android-36"
|
compileSdkVersion = "android-36"
|
||||||
ndkVersion = "28.2.13676358"
|
ndkVersion = "28.2.13676358"
|
||||||
|
|
||||||
|
val isNightly =
|
||||||
|
providers.gradleProperty("nightly").orNull?.toBooleanStrictOrNull() ?: false
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
}
|
}
|
||||||
@@ -71,6 +75,7 @@ android {
|
|||||||
val extraCMakeArgs =
|
val extraCMakeArgs =
|
||||||
(project.findProperty("YUZU_ANDROID_ARGS") as String?)?.split("\\s+".toRegex())
|
(project.findProperty("YUZU_ANDROID_ARGS") as String?)?.split("\\s+".toRegex())
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
|
|
||||||
arguments.addAll(
|
arguments.addAll(
|
||||||
listOf(
|
listOf(
|
||||||
"-DENABLE_QT=0", // Don't use QT
|
"-DENABLE_QT=0", // Don't use QT
|
||||||
@@ -89,6 +94,13 @@ android {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (isNightly) {
|
||||||
|
arguments.addAll(listOf(
|
||||||
|
"-DENABLE_UPDATE_CHECKER=ON",
|
||||||
|
"-DNIGHTLY_BUILD=ON",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
abiFilters("arm64-v8a")
|
abiFilters("arm64-v8a")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +137,12 @@ android {
|
|||||||
signingConfigs.getByName("default")
|
signingConfigs.getByName("default")
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestPlaceholders += mapOf("appNameSuffix" to "")
|
if (isNightly) {
|
||||||
|
applicationIdSuffix = ".nightly"
|
||||||
|
manifestPlaceholders += mapOf("appNameSuffix" to " Nightly")
|
||||||
|
} else {
|
||||||
|
manifestPlaceholders += mapOf("appNameSuffix" to "")
|
||||||
|
}
|
||||||
|
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
isDebuggable = false
|
isDebuggable = false
|
||||||
@@ -239,6 +256,15 @@ android {
|
|||||||
path = file("${edenDir}/CMakeLists.txt")
|
path = file("${edenDir}/CMakeLists.txt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
productFlavors.all {
|
||||||
|
val currentName = manifestPlaceholders["appNameBase"] as? String ?: "Eden"
|
||||||
|
val suffix = if (isNightly) " Nightly" else ""
|
||||||
|
|
||||||
|
// apply nightly suffix I/A
|
||||||
|
resValue("string", "app_name_suffixed", "$currentName$suffix")
|
||||||
|
resValue("string", "app_name", "Eden$suffix")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
idea {
|
idea {
|
||||||
@@ -258,7 +284,7 @@ tasks.register<Delete>("ktlintReset", fun Delete.() {
|
|||||||
val showFormatHelp = {
|
val showFormatHelp = {
|
||||||
logger.lifecycle(
|
logger.lifecycle(
|
||||||
"If this check fails, please try running \"gradlew ktlintFormat\" for automatic " +
|
"If this check fails, please try running \"gradlew ktlintFormat\" for automatic " +
|
||||||
"codestyle fixes"
|
"codestyle fixes"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
tasks.getByPath("ktlintKotlinScriptCheck").doFirst { showFormatHelp.invoke() }
|
tasks.getByPath("ktlintKotlinScriptCheck").doFirst { showFormatHelp.invoke() }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
@@ -218,7 +218,7 @@ object NativeLibrary {
|
|||||||
/**
|
/**
|
||||||
* Checks for available updates.
|
* Checks for available updates.
|
||||||
*/
|
*/
|
||||||
external fun checkForUpdate(): String?
|
external fun checkForUpdate(): Array<String>?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the URL to the release page
|
* Return the URL to the release page
|
||||||
@@ -228,13 +228,18 @@ object NativeLibrary {
|
|||||||
/**
|
/**
|
||||||
* Return the URL to download the APK for the given version
|
* Return the URL to download the APK for the given version
|
||||||
*/
|
*/
|
||||||
external fun getUpdateApkUrl(version: String, packageId: String): String
|
external fun getUpdateApkUrl(tag: String, artifact: String, packageId: String): String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the update checker is enabled through CMAKE options.
|
* Returns whether the update checker is enabled through CMAKE options.
|
||||||
*/
|
*/
|
||||||
external fun isUpdateCheckerEnabled(): Boolean
|
external fun isUpdateCheckerEnabled(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not this is a nightly build.
|
||||||
|
*/
|
||||||
|
external fun isNightlyBuild(): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the build version generated by CMake (BUILD_VERSION).
|
* Returns the build version generated by CMake (BUILD_VERSION).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,14 +22,6 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||||
|
|
||||||
class QuickSettings(val emulationFragment: EmulationFragment) {
|
class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||||
// Kinda a crappy workaround to get a title from setting keys
|
|
||||||
// Idk how to do this witthout hardcoding every single one
|
|
||||||
private fun getSettingTitle(settingKey: String): String {
|
|
||||||
return settingKey.replace("_", " ").split(" ")
|
|
||||||
.joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun saveSettings() {
|
private fun saveSettings() {
|
||||||
if (emulationFragment.shouldUseCustom) {
|
if (emulationFragment.shouldUseCustom) {
|
||||||
NativeConfig.savePerGameConfig()
|
NativeConfig.savePerGameConfig()
|
||||||
@@ -60,6 +52,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
|||||||
// settings
|
// settings
|
||||||
|
|
||||||
fun addIntSetting(
|
fun addIntSetting(
|
||||||
|
name: Int,
|
||||||
container: ViewGroup,
|
container: ViewGroup,
|
||||||
setting: IntSetting,
|
setting: IntSetting,
|
||||||
namesArrayId: Int,
|
namesArrayId: Int,
|
||||||
@@ -73,7 +66,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
|||||||
val expandIcon = itemView.findViewById<android.widget.ImageView>(R.id.expand_icon)
|
val expandIcon = itemView.findViewById<android.widget.ImageView>(R.id.expand_icon)
|
||||||
val radioGroup = itemView.findViewById<RadioGroup>(R.id.radio_group)
|
val radioGroup = itemView.findViewById<RadioGroup>(R.id.radio_group)
|
||||||
|
|
||||||
titleView.text = getSettingTitle(setting.key)
|
titleView.text = YuzuApplication.appContext.getString(name)
|
||||||
|
|
||||||
val names = emulationFragment.resources.getStringArray(namesArrayId)
|
val names = emulationFragment.resources.getStringArray(namesArrayId)
|
||||||
val values = emulationFragment.resources.getIntArray(valuesArrayId)
|
val values = emulationFragment.resources.getIntArray(valuesArrayId)
|
||||||
@@ -115,6 +108,8 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addBooleanSetting(
|
fun addBooleanSetting(
|
||||||
|
name: Int,
|
||||||
|
|
||||||
container: ViewGroup,
|
container: ViewGroup,
|
||||||
setting: BooleanSetting
|
setting: BooleanSetting
|
||||||
) {
|
) {
|
||||||
@@ -125,7 +120,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
|||||||
val titleView = itemView.findViewById<TextView>(R.id.switch_title)
|
val titleView = itemView.findViewById<TextView>(R.id.switch_title)
|
||||||
val switchView = itemView.findViewById<com.google.android.material.materialswitch.MaterialSwitch>(R.id.setting_switch)
|
val switchView = itemView.findViewById<com.google.android.material.materialswitch.MaterialSwitch>(R.id.setting_switch)
|
||||||
|
|
||||||
titleView.text = getSettingTitle(setting.key)
|
titleView.text = YuzuApplication.appContext.getString(name)
|
||||||
switchContainer.visibility = View.VISIBLE
|
switchContainer.visibility = View.VISIBLE
|
||||||
switchView.isChecked = setting.getBoolean()
|
switchView.isChecked = setting.getBoolean()
|
||||||
|
|
||||||
@@ -141,6 +136,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addSliderSetting(
|
fun addSliderSetting(
|
||||||
|
name: Int,
|
||||||
container: ViewGroup,
|
container: ViewGroup,
|
||||||
setting: AbstractSetting,
|
setting: AbstractSetting,
|
||||||
minValue: Int = 0,
|
minValue: Int = 0,
|
||||||
@@ -156,7 +152,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
|||||||
val slider = itemView.findViewById<com.google.android.material.slider.Slider>(R.id.setting_slider)
|
val slider = itemView.findViewById<com.google.android.material.slider.Slider>(R.id.setting_slider)
|
||||||
|
|
||||||
|
|
||||||
titleView.text = getSettingTitle(setting.key)
|
titleView.text = YuzuApplication.appContext.getString(name)
|
||||||
sliderContainer.visibility = View.VISIBLE
|
sliderContainer.visibility = View.VISIBLE
|
||||||
|
|
||||||
slider.valueFrom = minValue.toFloat()
|
slider.valueFrom = minValue.toFloat()
|
||||||
|
|||||||
+13
-1
@@ -24,6 +24,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
RENDERER_FORCE_MAX_CLOCK("force_max_clock"),
|
RENDERER_FORCE_MAX_CLOCK("force_max_clock"),
|
||||||
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
|
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
|
||||||
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
||||||
|
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
|
||||||
SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
|
SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
|
||||||
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
|
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
|
||||||
RENDERER_DEBUG("debug"),
|
RENDERER_DEBUG("debug"),
|
||||||
@@ -36,6 +37,9 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
USE_CUSTOM_RTC("custom_rtc_enabled"),
|
USE_CUSTOM_RTC("custom_rtc_enabled"),
|
||||||
BLACK_BACKGROUNDS("black_backgrounds"),
|
BLACK_BACKGROUNDS("black_backgrounds"),
|
||||||
|
|
||||||
|
ENABLE_FOLDER_BUTTON("enable_folder_button"),
|
||||||
|
ENABLE_QLAUNCH_BUTTON("enable_qlaunch_button"),
|
||||||
|
|
||||||
ENABLE_UPDATE_CHECKS("enable_update_checks"),
|
ENABLE_UPDATE_CHECKS("enable_update_checks"),
|
||||||
JOYSTICK_REL_CENTER("joystick_rel_center"),
|
JOYSTICK_REL_CENTER("joystick_rel_center"),
|
||||||
DPAD_SLIDE("dpad_slide"),
|
DPAD_SLIDE("dpad_slide"),
|
||||||
@@ -72,8 +76,16 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
USE_LRU_CACHE("use_lru_cache"),
|
USE_LRU_CACHE("use_lru_cache"),
|
||||||
|
|
||||||
DONT_SHOW_DRIVER_SHADER_WARNING("dont_show_driver_shader_warning"),
|
DONT_SHOW_DRIVER_SHADER_WARNING("dont_show_driver_shader_warning"),
|
||||||
ENABLE_OVERLAY("enable_overlay");
|
ENABLE_OVERLAY("enable_overlay"),
|
||||||
|
|
||||||
|
// GPU Logging
|
||||||
|
GPU_LOGGING_ENABLED("gpu_logging_enabled"),
|
||||||
|
GPU_LOG_VULKAN_CALLS("gpu_log_vulkan_calls"),
|
||||||
|
GPU_LOG_SHADER_DUMPS("gpu_log_shader_dumps"),
|
||||||
|
GPU_LOG_MEMORY_TRACKING("gpu_log_memory_tracking"),
|
||||||
|
GPU_LOG_DRIVER_DEBUG("gpu_log_driver_debug"),
|
||||||
|
|
||||||
|
ENABLE_QUICK_SETTINGS("enable_quick_settings");
|
||||||
|
|
||||||
// external fun isFrameSkippingEnabled(): Boolean
|
// external fun isFrameSkippingEnabled(): Boolean
|
||||||
external fun isFrameInterpolationEnabled(): Boolean
|
external fun isFrameInterpolationEnabled(): Boolean
|
||||||
|
|||||||
+3
-2
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
@@ -9,7 +9,8 @@ package org.yuzu.yuzu_emu.features.settings.model
|
|||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
enum class ByteSetting(override val key: String) : AbstractByteSetting {
|
enum class ByteSetting(override val key: String) : AbstractByteSetting {
|
||||||
AUDIO_VOLUME("volume"),;
|
AUDIO_VOLUME("volume"),
|
||||||
|
GPU_LOG_LEVEL("gpu_log_level");
|
||||||
|
|
||||||
override fun getByte(needsGlobal: Boolean): Byte = NativeConfig.getByte(key, needsGlobal)
|
override fun getByte(needsGlobal: Boolean): Byte = NativeConfig.getByte(key, needsGlobal)
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -67,7 +67,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
|||||||
MY_PAGE_APPLET("my_page_applet_mode"),
|
MY_PAGE_APPLET("my_page_applet_mode"),
|
||||||
INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide"),
|
INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide"),
|
||||||
OVERLAY_GRID_SIZE("overlay_grid_size"),
|
OVERLAY_GRID_SIZE("overlay_grid_size"),
|
||||||
DEBUG_KNOBS("debug_knobs")
|
DEBUG_KNOBS("debug_knobs"),
|
||||||
|
GPU_LOG_RING_BUFFER_SIZE("gpu_log_ring_buffer_size")
|
||||||
;
|
;
|
||||||
|
|
||||||
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
|
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
|
||||||
|
|||||||
+89
@@ -60,6 +60,11 @@ abstract class SettingsItem(
|
|||||||
return NativeInput.getStyleIndex(0) != NpadStyleIndex.Handheld
|
return NativeInput.getStyleIndex(0) != NpadStyleIndex.Handheld
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Can't edit enable_qlaunch_button if firmware is not available
|
||||||
|
if (setting.key == BooleanSetting.ENABLE_QLAUNCH_BUTTON.key) {
|
||||||
|
return NativeLibrary.isFirmwareAvailable()
|
||||||
|
}
|
||||||
|
|
||||||
// Can't edit settings that aren't saveable in per-game config even if they are switchable
|
// Can't edit settings that aren't saveable in per-game config even if they are switchable
|
||||||
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
|
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
|
||||||
return false
|
return false
|
||||||
@@ -740,6 +745,13 @@ abstract class SettingsItem(
|
|||||||
descriptionId = R.string.renderer_reactive_flushing_description
|
descriptionId = R.string.renderer_reactive_flushing_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.ENABLE_BUFFER_HISTORY,
|
||||||
|
titleId = R.string.enable_buffer_history,
|
||||||
|
descriptionId = R.string.enable_buffer_history_description
|
||||||
|
)
|
||||||
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.SYNC_MEMORY_OPERATIONS,
|
BooleanSetting.SYNC_MEMORY_OPERATIONS,
|
||||||
@@ -794,6 +806,27 @@ abstract class SettingsItem(
|
|||||||
descriptionId = R.string.enable_update_checks_description,
|
descriptionId = R.string.enable_update_checks_description,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.ENABLE_QUICK_SETTINGS,
|
||||||
|
titleId = R.string.enable_quick_settings,
|
||||||
|
descriptionId = R.string.enable_quick_settings_description,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.ENABLE_FOLDER_BUTTON,
|
||||||
|
titleId = R.string.enable_folder_button,
|
||||||
|
descriptionId = R.string.enable_folder_button_description,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.ENABLE_QLAUNCH_BUTTON,
|
||||||
|
titleId = R.string.enable_qlaunch_button,
|
||||||
|
descriptionId = R.string.enable_qlaunch_button_description,
|
||||||
|
)
|
||||||
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.APP_LANGUAGE,
|
IntSetting.APP_LANGUAGE,
|
||||||
@@ -836,6 +869,62 @@ abstract class SettingsItem(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GPU Logging settings
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.GPU_LOGGING_ENABLED,
|
||||||
|
titleId = R.string.gpu_logging_enabled,
|
||||||
|
descriptionId = R.string.gpu_logging_enabled_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
ByteSetting.GPU_LOG_LEVEL,
|
||||||
|
titleId = R.string.gpu_log_level,
|
||||||
|
descriptionId = R.string.gpu_log_level_description,
|
||||||
|
choicesId = R.array.gpuLogLevelEntries,
|
||||||
|
valuesId = R.array.gpuLogLevelValues
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.GPU_LOG_VULKAN_CALLS,
|
||||||
|
titleId = R.string.gpu_log_vulkan_calls,
|
||||||
|
descriptionId = R.string.gpu_log_vulkan_calls_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.GPU_LOG_SHADER_DUMPS,
|
||||||
|
titleId = R.string.gpu_log_shader_dumps,
|
||||||
|
descriptionId = R.string.gpu_log_shader_dumps_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.GPU_LOG_MEMORY_TRACKING,
|
||||||
|
titleId = R.string.gpu_log_memory_tracking,
|
||||||
|
descriptionId = R.string.gpu_log_memory_tracking_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.GPU_LOG_DRIVER_DEBUG,
|
||||||
|
titleId = R.string.gpu_log_driver_debug,
|
||||||
|
descriptionId = R.string.gpu_log_driver_debug_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SpinBoxSetting(
|
||||||
|
IntSetting.GPU_LOG_RING_BUFFER_SIZE,
|
||||||
|
titleId = R.string.gpu_log_ring_buffer_size,
|
||||||
|
descriptionId = R.string.gpu_log_ring_buffer_size_description,
|
||||||
|
valueHint = R.string.gpu_log_ring_buffer_size_hint,
|
||||||
|
min = 64,
|
||||||
|
max = 4096
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val fastmem = object : AbstractBooleanSetting {
|
val fastmem = object : AbstractBooleanSetting {
|
||||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||||
BooleanSetting.FASTMEM.getBoolean() &&
|
BooleanSetting.FASTMEM.getBoolean() &&
|
||||||
|
|||||||
+19
@@ -275,6 +275,7 @@ class SettingsFragmentPresenter(
|
|||||||
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
||||||
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
||||||
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
|
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
|
||||||
|
add(BooleanSetting.ENABLE_BUFFER_HISTORY.key)
|
||||||
|
|
||||||
add(HeaderSetting(R.string.hacks))
|
add(HeaderSetting(R.string.hacks))
|
||||||
|
|
||||||
@@ -1074,6 +1075,8 @@ class SettingsFragmentPresenter(
|
|||||||
add(BooleanSetting.ENABLE_UPDATE_CHECKS.key)
|
add(BooleanSetting.ENABLE_UPDATE_CHECKS.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add(BooleanSetting.ENABLE_QUICK_SETTINGS.key)
|
||||||
|
|
||||||
add(HeaderSetting(R.string.theme_and_color))
|
add(HeaderSetting(R.string.theme_and_color))
|
||||||
|
|
||||||
|
|
||||||
@@ -1191,6 +1194,13 @@ class SettingsFragmentPresenter(
|
|||||||
descriptionId = R.string.use_black_backgrounds_description
|
descriptionId = R.string.use_black_backgrounds_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add(HeaderSetting(R.string.buttons))
|
||||||
|
add(BooleanSetting.ENABLE_FOLDER_BUTTON.key)
|
||||||
|
add(BooleanSetting.ENABLE_QLAUNCH_BUTTON.key)
|
||||||
|
if (!NativeLibrary.isFirmwareAvailable()) {
|
||||||
|
BooleanSetting.ENABLE_QLAUNCH_BUTTON.setBoolean(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1220,6 +1230,15 @@ class SettingsFragmentPresenter(
|
|||||||
add(HeaderSetting(R.string.general))
|
add(HeaderSetting(R.string.general))
|
||||||
|
|
||||||
add(IntSetting.DEBUG_KNOBS.key)
|
add(IntSetting.DEBUG_KNOBS.key)
|
||||||
|
|
||||||
|
add(HeaderSetting(R.string.gpu_logging_header))
|
||||||
|
add(BooleanSetting.GPU_LOGGING_ENABLED.key)
|
||||||
|
add(ByteSetting.GPU_LOG_LEVEL.key)
|
||||||
|
add(BooleanSetting.GPU_LOG_VULKAN_CALLS.key)
|
||||||
|
add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key)
|
||||||
|
add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key)
|
||||||
|
add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key)
|
||||||
|
add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -690,8 +690,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
})
|
})
|
||||||
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||||
|
|
||||||
|
if (!BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean()) {
|
||||||
|
binding.drawerLayout.setDrawerLockMode(
|
||||||
|
DrawerLayout.LOCK_MODE_LOCKED_CLOSED,
|
||||||
|
binding.quickSettingsSheet
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
updateGameTitle()
|
updateGameTitle()
|
||||||
|
|
||||||
|
binding.inGameMenu.menu.findItem(R.id.menu_quick_settings)?.isVisible =
|
||||||
|
BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean()
|
||||||
|
|
||||||
binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply {
|
binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply {
|
||||||
val lockMode = IntSetting.LOCK_DRAWER.getInt()
|
val lockMode = IntSetting.LOCK_DRAWER.getInt()
|
||||||
val titleId = if (lockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
|
val titleId = if (lockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
|
||||||
@@ -749,10 +759,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_quick_settings -> {
|
if (BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean())
|
||||||
openQuickSettingsMenu()
|
R.id.menu_quick_settings else 0 -> {
|
||||||
true
|
openQuickSettingsMenu()
|
||||||
}
|
true
|
||||||
|
}
|
||||||
|
|
||||||
R.id.menu_settings_per_game -> {
|
R.id.menu_settings_per_game -> {
|
||||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||||
@@ -1045,11 +1056,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
quickSettings.addBooleanSetting(
|
quickSettings.addBooleanSetting(
|
||||||
|
R.string.frame_limit_enable,
|
||||||
container,
|
container,
|
||||||
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
|
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
|
||||||
)
|
)
|
||||||
|
|
||||||
quickSettings.addSliderSetting(
|
quickSettings.addSliderSetting(
|
||||||
|
R.string.frame_limit_slider,
|
||||||
container,
|
container,
|
||||||
ShortSetting.RENDERER_SPEED_LIMIT,
|
ShortSetting.RENDERER_SPEED_LIMIT,
|
||||||
minValue = 0,
|
minValue = 0,
|
||||||
@@ -1058,6 +1071,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
)
|
)
|
||||||
|
|
||||||
quickSettings.addBooleanSetting(
|
quickSettings.addBooleanSetting(
|
||||||
|
R.string.use_docked_mode,
|
||||||
container,
|
container,
|
||||||
BooleanSetting.USE_DOCKED_MODE,
|
BooleanSetting.USE_DOCKED_MODE,
|
||||||
)
|
)
|
||||||
@@ -1065,6 +1079,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
quickSettings.addDivider(container)
|
quickSettings.addDivider(container)
|
||||||
|
|
||||||
quickSettings.addIntSetting(
|
quickSettings.addIntSetting(
|
||||||
|
R.string.renderer_accuracy,
|
||||||
container,
|
container,
|
||||||
IntSetting.RENDERER_ACCURACY,
|
IntSetting.RENDERER_ACCURACY,
|
||||||
R.array.rendererAccuracyNames,
|
R.array.rendererAccuracyNames,
|
||||||
@@ -1073,6 +1088,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
|
|
||||||
|
|
||||||
quickSettings.addIntSetting(
|
quickSettings.addIntSetting(
|
||||||
|
R.string.renderer_scaling_filter,
|
||||||
container,
|
container,
|
||||||
IntSetting.RENDERER_SCALING_FILTER,
|
IntSetting.RENDERER_SCALING_FILTER,
|
||||||
R.array.rendererScalingFilterNames,
|
R.array.rendererScalingFilterNames,
|
||||||
@@ -1080,6 +1096,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
)
|
)
|
||||||
|
|
||||||
quickSettings.addSliderSetting(
|
quickSettings.addSliderSetting(
|
||||||
|
R.string.fsr_sharpness,
|
||||||
container,
|
container,
|
||||||
IntSetting.FSR_SHARPENING_SLIDER,
|
IntSetting.FSR_SHARPENING_SLIDER,
|
||||||
minValue = 0,
|
minValue = 0,
|
||||||
@@ -1088,6 +1105,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
)
|
)
|
||||||
|
|
||||||
quickSettings.addIntSetting(
|
quickSettings.addIntSetting(
|
||||||
|
R.string.renderer_anti_aliasing,
|
||||||
container,
|
container,
|
||||||
IntSetting.RENDERER_ANTI_ALIASING,
|
IntSetting.RENDERER_ANTI_ALIASING,
|
||||||
R.array.rendererAntiAliasingNames,
|
R.array.rendererAntiAliasingNames,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
@@ -222,6 +222,14 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
{ shareLog() }
|
{ shareLog() }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
add(
|
||||||
|
HomeSetting(
|
||||||
|
R.string.share_gpu_log,
|
||||||
|
R.string.share_gpu_log_description,
|
||||||
|
R.drawable.ic_log,
|
||||||
|
{ shareGpuLog() }
|
||||||
|
)
|
||||||
|
)
|
||||||
add(
|
add(
|
||||||
HomeSetting(
|
HomeSetting(
|
||||||
R.string.open_user_folder,
|
R.string.open_user_folder,
|
||||||
@@ -408,6 +416,40 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun shareGpuLog() {
|
||||||
|
val currentLog = DocumentFile.fromSingleUri(
|
||||||
|
mainActivity,
|
||||||
|
DocumentsContract.buildDocumentUri(
|
||||||
|
DocumentProvider.AUTHORITY,
|
||||||
|
"${DocumentProvider.ROOT_ID}/log/eden_gpu.log"
|
||||||
|
)
|
||||||
|
)!!
|
||||||
|
val oldLog = DocumentFile.fromSingleUri(
|
||||||
|
mainActivity,
|
||||||
|
DocumentsContract.buildDocumentUri(
|
||||||
|
DocumentProvider.AUTHORITY,
|
||||||
|
"${DocumentProvider.ROOT_ID}/log/eden_gpu.log.old.txt"
|
||||||
|
)
|
||||||
|
)!!
|
||||||
|
|
||||||
|
val intent = Intent(Intent.ACTION_SEND)
|
||||||
|
.setDataAndType(currentLog.uri, FileUtil.TEXT_PLAIN)
|
||||||
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
if (!Log.gameLaunched && oldLog.exists()) {
|
||||||
|
intent.putExtra(Intent.EXTRA_STREAM, oldLog.uri)
|
||||||
|
startActivity(Intent.createChooser(intent, getText(R.string.share_gpu_log)))
|
||||||
|
} else if (currentLog.exists()) {
|
||||||
|
intent.putExtra(Intent.EXTRA_STREAM, currentLog.uri)
|
||||||
|
startActivity(Intent.createChooser(intent, getText(R.string.share_gpu_log)))
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
requireContext(),
|
||||||
|
getText(R.string.share_gpu_log_missing),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setInsets() =
|
private fun setInsets() =
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.ui
|
package org.yuzu.yuzu_emu.ui
|
||||||
@@ -13,6 +13,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
@@ -27,10 +28,14 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||||
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
|
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||||
|
import org.yuzu.yuzu_emu.model.AppletInfo
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||||
@@ -173,10 +178,16 @@ class GamesFragment : Fragment() {
|
|||||||
|
|
||||||
setupTopView()
|
setupTopView()
|
||||||
|
|
||||||
|
updateButtonsVisibility()
|
||||||
|
|
||||||
binding.addDirectory.setOnClickListener {
|
binding.addDirectory.setOnClickListener {
|
||||||
getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
|
getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.launchQlaunch?.setOnClickListener {
|
||||||
|
launchQLaunch()
|
||||||
|
}
|
||||||
|
|
||||||
setInsets()
|
setInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,6 +456,47 @@ class GamesFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun launchQLaunch() {
|
||||||
|
try {
|
||||||
|
val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.QLaunch.entryId)
|
||||||
|
if (appletPath.isEmpty()) {
|
||||||
|
Toast.makeText(
|
||||||
|
requireContext(),
|
||||||
|
R.string.applets_error_applet,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeLibrary.setCurrentAppletId(AppletInfo.QLaunch.appletId)
|
||||||
|
|
||||||
|
val qlaunchGame = Game(
|
||||||
|
title = getString(R.string.qlaunch_applet),
|
||||||
|
path = appletPath
|
||||||
|
)
|
||||||
|
|
||||||
|
val action = HomeNavigationDirections.actionGlobalEmulationActivity(qlaunchGame)
|
||||||
|
findNavController().navigate(action)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(
|
||||||
|
requireContext(),
|
||||||
|
"Failed to launch QLaunch: ${e.message}",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateButtonsVisibility() {
|
||||||
|
val showQLaunch = BooleanSetting.ENABLE_QLAUNCH_BUTTON.getBoolean()
|
||||||
|
val showFolder = BooleanSetting.ENABLE_FOLDER_BUTTON.getBoolean()
|
||||||
|
val isFirmwareAvailable = NativeLibrary.isFirmwareAvailable()
|
||||||
|
|
||||||
|
val shouldShowQLaunch = showQLaunch && isFirmwareAvailable
|
||||||
|
binding.launchQlaunch.visibility = if (shouldShowQLaunch) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
|
binding.addDirectory.visibility = if (showFolder) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
private fun setInsets() =
|
private fun setInsets() =
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(
|
ViewCompat.setOnApplyWindowInsetsListener(
|
||||||
binding.root
|
binding.root
|
||||||
@@ -498,6 +550,13 @@ class GamesFragment : Fragment() {
|
|||||||
mlpFab.rightMargin = rightInset + fabPadding
|
mlpFab.rightMargin = rightInset + fabPadding
|
||||||
binding.addDirectory.layoutParams = mlpFab
|
binding.addDirectory.layoutParams = mlpFab
|
||||||
|
|
||||||
|
binding.launchQlaunch?.let { qlaunchButton ->
|
||||||
|
val mlpQLaunch = qlaunchButton.layoutParams as ViewGroup.MarginLayoutParams
|
||||||
|
mlpQLaunch.leftMargin = leftInset + fabPadding
|
||||||
|
mlpQLaunch.bottomMargin = barInsets.bottom + fabPadding
|
||||||
|
qlaunchButton.layoutParams = mlpQLaunch
|
||||||
|
}
|
||||||
|
|
||||||
val navInsets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
|
val navInsets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
|
||||||
val gestureInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
|
val gestureInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
|
||||||
val bottomInset = maxOf(navInsets.bottom, gestureInsets.bottom, cutoutInsets.bottom)
|
val bottomInset = maxOf(navInsets.bottom, gestureInsets.bottom, cutoutInsets.bottom)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.ui.main
|
package org.yuzu.yuzu_emu.ui.main
|
||||||
@@ -183,18 +183,26 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
val latestVersion = NativeLibrary.checkForUpdate()
|
val latestVersion = NativeLibrary.checkForUpdate()
|
||||||
if (latestVersion != null) {
|
if (latestVersion != null) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
showUpdateDialog(latestVersion)
|
val tag: String = latestVersion[0]
|
||||||
|
val name: String = latestVersion[1]
|
||||||
|
showUpdateDialog(tag, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showUpdateDialog(version: String) {
|
private fun showUpdateDialog(tag: String, name: String) {
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.update_available)
|
.setTitle(R.string.update_available)
|
||||||
.setMessage(getString(R.string.update_available_description, version))
|
.setMessage(getString(R.string.update_available_description, name))
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
downloadAndInstallUpdate(version)
|
var artifact = tag
|
||||||
|
// Nightly builds have a slightly different format
|
||||||
|
if (NativeLibrary.isNightlyBuild()) {
|
||||||
|
val splitTag = tag.split('.')
|
||||||
|
artifact = splitTag.subList(1, splitTag.size - 1).joinToString(".")
|
||||||
|
}
|
||||||
|
downloadAndInstallUpdate(tag, artifact)
|
||||||
}
|
}
|
||||||
.setNeutralButton(R.string.cancel) { dialog, _ ->
|
.setNeutralButton(R.string.cancel) { dialog, _ ->
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
@@ -207,11 +215,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun downloadAndInstallUpdate(version: String) {
|
private fun downloadAndInstallUpdate(version: String, artifact: String) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val packageId = applicationContext.packageName
|
val packageId = applicationContext.packageName
|
||||||
val apkUrl = NativeLibrary.getUpdateApkUrl(version, packageId)
|
val apkUrl = NativeLibrary.getUpdateApkUrl(version, artifact, packageId)
|
||||||
val apkFile = File(cacheDir, "update-$version.apk")
|
val apkFile = File(cacheDir, "update-$artifact.apk")
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
showDownloadProgressDialog()
|
showDownloadProgressDialog()
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ namespace AndroidSettings {
|
|||||||
Settings::Setting<s32> app_language{linkage, 0, "app_language", Settings::Category::Android};
|
Settings::Setting<s32> app_language{linkage, 0, "app_language", Settings::Category::Android};
|
||||||
Settings::Setting<bool> enable_update_checks{linkage, true, "enable_update_checks",
|
Settings::Setting<bool> enable_update_checks{linkage, true, "enable_update_checks",
|
||||||
Settings::Category::Android};
|
Settings::Category::Android};
|
||||||
|
Settings::Setting<bool> enable_folder_button{linkage, true, "enable_folder_button",
|
||||||
|
Settings::Category::Android};
|
||||||
|
Settings::Setting<bool> enable_qlaunch_button{linkage, false, "enable_qlaunch_button",
|
||||||
|
Settings::Category::Android};
|
||||||
|
|
||||||
// Input/performance overlay settings
|
// Input/performance overlay settings
|
||||||
std::vector<OverlayControlData> overlay_control_data;
|
std::vector<OverlayControlData> overlay_control_data;
|
||||||
@@ -198,9 +202,14 @@ namespace AndroidSettings {
|
|||||||
Settings::Specialization::Default, true, true,
|
Settings::Specialization::Default, true, true,
|
||||||
&show_soc_overlay};
|
&show_soc_overlay};
|
||||||
|
|
||||||
|
// MISC
|
||||||
Settings::Setting<bool> dont_show_driver_shader_warning{linkage, false,
|
Settings::Setting<bool> dont_show_driver_shader_warning{linkage, false,
|
||||||
"dont_show_driver_shader_warning",
|
"dont_show_driver_shader_warning",
|
||||||
Settings::Category::Android, Settings::Specialization::Default, true, true};
|
Settings::Category::Android, Settings::Specialization::Default, true, true};
|
||||||
|
Settings::Setting<bool> enable_quick_settings{linkage, true,
|
||||||
|
"enable_quick_settings",
|
||||||
|
Settings::Category::Android, Settings::Specialization::Default, true,
|
||||||
|
false};
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Values values;
|
extern Values values;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
@@ -1595,7 +1595,6 @@ JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_updatePowerState(
|
|||||||
g_has_battery.store(hasBattery, std::memory_order_relaxed);
|
g_has_battery.store(hasBattery, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return #ifdef ENABLE_UPDATE_CHECKER
|
|
||||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isUpdateCheckerEnabled(
|
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isUpdateCheckerEnabled(
|
||||||
JNIEnv* env,
|
JNIEnv* env,
|
||||||
jobject obj) {
|
jobject obj) {
|
||||||
@@ -1606,22 +1605,39 @@ JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isUpdateChecker
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isNightlyBuild(
|
||||||
|
JNIEnv* env,
|
||||||
|
jobject obj) {
|
||||||
|
#ifdef NIGHTLY_BUILD
|
||||||
|
return JNI_TRUE;
|
||||||
|
#else
|
||||||
|
return JNI_FALSE;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_UPDATE_CHECKER
|
#ifdef ENABLE_UPDATE_CHECKER
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
|
|
||||||
|
JNIEXPORT jobjectArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
|
||||||
JNIEnv* env,
|
JNIEnv* env,
|
||||||
jobject obj) {
|
jobject obj) {
|
||||||
const bool is_prerelease = ((strstr(Common::g_build_version, "pre-alpha") != nullptr) ||
|
std::optional<UpdateChecker::Update> release = UpdateChecker::GetUpdate();
|
||||||
(strstr(Common::g_build_version, "alpha") != nullptr) ||
|
if (!release) return nullptr;
|
||||||
(strstr(Common::g_build_version, "beta") != nullptr) ||
|
|
||||||
(strstr(Common::g_build_version, "rc") != nullptr));
|
|
||||||
const std::optional<std::string> latest_release_tag =
|
|
||||||
UpdateChecker::GetLatestRelease(is_prerelease);
|
|
||||||
|
|
||||||
if (latest_release_tag && latest_release_tag.value() != Common::g_build_version) {
|
const std::string tag = release->tag;
|
||||||
return env->NewStringUTF(latest_release_tag.value().c_str());
|
const std::string name = release->name;
|
||||||
}
|
|
||||||
return nullptr;
|
jobjectArray result = env->NewObjectArray(2, env->FindClass("java/lang/String"), nullptr);
|
||||||
|
|
||||||
|
const jstring jtag = env->NewStringUTF(tag.c_str());
|
||||||
|
const jstring jname = env->NewStringUTF(name.c_str());
|
||||||
|
|
||||||
|
env->SetObjectArrayElement(result, 0, jtag);
|
||||||
|
env->SetObjectArrayElement(result, 1, jname);
|
||||||
|
env->DeleteLocalRef(jtag);
|
||||||
|
env->DeleteLocalRef(jname);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
|
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
|
||||||
@@ -1640,9 +1656,11 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
|
|||||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
|
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
|
||||||
JNIEnv* env,
|
JNIEnv* env,
|
||||||
jobject obj,
|
jobject obj,
|
||||||
jstring version,
|
jstring tag,
|
||||||
|
jstring artifact,
|
||||||
jstring packageId) {
|
jstring packageId) {
|
||||||
const char* version_str = env->GetStringUTFChars(version, nullptr);
|
const char* version_str = env->GetStringUTFChars(tag, nullptr);
|
||||||
|
const char* artifact_str = env->GetStringUTFChars(artifact, nullptr);
|
||||||
const char* package_id_str = env->GetStringUTFChars(packageId, nullptr);
|
const char* package_id_str = env->GetStringUTFChars(packageId, nullptr);
|
||||||
|
|
||||||
std::string variant;
|
std::string variant;
|
||||||
@@ -1653,7 +1671,11 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
|
|||||||
} else if (package_id.find("com.miHoYo.Yuanshen") != std::string::npos) {
|
} else if (package_id.find("com.miHoYo.Yuanshen") != std::string::npos) {
|
||||||
variant = "optimized";
|
variant = "optimized";
|
||||||
} else {
|
} else {
|
||||||
|
#ifdef ARCHITECTURE_arm64
|
||||||
variant = "standard";
|
variant = "standard";
|
||||||
|
#else
|
||||||
|
variant = "chromeos";
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", version_str, variant);
|
const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", version_str, variant);
|
||||||
@@ -1663,7 +1685,7 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
|
|||||||
version_str,
|
version_str,
|
||||||
apk_filename);
|
apk_filename);
|
||||||
|
|
||||||
env->ReleaseStringUTFChars(version, version_str);
|
env->ReleaseStringUTFChars(tag, version_str);
|
||||||
env->ReleaseStringUTFChars(packageId, package_id_str);
|
env->ReleaseStringUTFChars(packageId, package_id_str);
|
||||||
return env->NewStringUTF(url.c_str());
|
return env->NewStringUTF(url.c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <common/fs/path_util.h>
|
|
||||||
|
|
||||||
#include "android_config.h"
|
#include "android_config.h"
|
||||||
#include "android_settings.h"
|
#include "android_settings.h"
|
||||||
#include "common/android/android_common.h"
|
#include "common/android/android_common.h"
|
||||||
#include "common/android/id_cache.h"
|
#include "common/android/id_cache.h"
|
||||||
|
#include "common/fs/path_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "frontend_common/config.h"
|
#include "frontend_common/config.h"
|
||||||
|
#include "frontend_common/settings_generator.h"
|
||||||
#include "native.h"
|
#include "native.h"
|
||||||
|
|
||||||
std::unique_ptr<AndroidConfig> global_config;
|
std::unique_ptr<AndroidConfig> global_config;
|
||||||
@@ -37,6 +38,7 @@ extern "C" {
|
|||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
global_config = std::make_unique<AndroidConfig>();
|
global_config = std::make_unique<AndroidConfig>();
|
||||||
|
FrontendCommon::GenerateSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
|
|||||||
@@ -220,6 +220,23 @@
|
|||||||
android:textColor="?attr/colorOnPrimary"
|
android:textColor="?attr/colorOnPrimary"
|
||||||
app:backgroundTint="?attr/colorPrimary"
|
app:backgroundTint="?attr/colorPrimary"
|
||||||
app:iconTint="?attr/colorOnPrimary"
|
app:iconTint="?attr/colorOnPrimary"
|
||||||
|
app:rippleColor="#99FFFFFF"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
android:id="@+id/launch_qlaunch"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:contentDescription="Launch QLaunch"
|
||||||
|
android:text="@string/qlaunch_applet"
|
||||||
|
app:icon="@drawable/ic_home"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:textColor="?attr/colorOnPrimary"
|
||||||
|
app:backgroundTint="?attr/colorPrimary"
|
||||||
|
app:iconTint="?attr/colorOnPrimary"
|
||||||
|
app:rippleColor="#99FFFFFF"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -214,6 +214,22 @@
|
|||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
android:id="@+id/launch_qlaunch"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:contentDescription="Launch QLaunch"
|
||||||
|
android:text="@string/qlaunch_applet"
|
||||||
|
app:icon="@drawable/ic_home"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:textColor="?attr/colorOnPrimary"
|
||||||
|
app:backgroundTint="?attr/colorPrimary"
|
||||||
|
app:iconTint="?attr/colorOnPrimary"
|
||||||
|
app:rippleColor="#99FFFFFF"
|
||||||
|
/>
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/add_directory"
|
android:id="@+id/add_directory"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -227,6 +243,7 @@
|
|||||||
android:textColor="?attr/colorOnPrimary"
|
android:textColor="?attr/colorOnPrimary"
|
||||||
app:backgroundTint="?attr/colorPrimary"
|
app:backgroundTint="?attr/colorPrimary"
|
||||||
app:iconTint="?attr/colorOnPrimary"
|
app:iconTint="?attr/colorOnPrimary"
|
||||||
|
app:rippleColor="#99FFFFFF"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -631,4 +631,21 @@
|
|||||||
<item>@string/error_keys_invalid_filename</item>
|
<item>@string/error_keys_invalid_filename</item>
|
||||||
<item>@string/error_keys_failed_init</item>
|
<item>@string/error_keys_failed_init</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<!-- GPU Logging Arrays -->
|
||||||
|
<string-array name="gpuLogLevelEntries">
|
||||||
|
<item>Off</item>
|
||||||
|
<item>Errors Only</item>
|
||||||
|
<item>Standard</item>
|
||||||
|
<item>Verbose</item>
|
||||||
|
<item>All</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<integer-array name="gpuLogLevelValues">
|
||||||
|
<item>0</item>
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
<item>3</item>
|
||||||
|
<item>4</item>
|
||||||
|
</integer-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -325,6 +325,9 @@
|
|||||||
<string name="share_log">Share debug logs</string>
|
<string name="share_log">Share debug logs</string>
|
||||||
<string name="share_log_description">Share Eden\'s log file to debug issues</string>
|
<string name="share_log_description">Share Eden\'s log file to debug issues</string>
|
||||||
<string name="share_log_missing">No log file found</string>
|
<string name="share_log_missing">No log file found</string>
|
||||||
|
<string name="share_gpu_log">Share GPU logs</string>
|
||||||
|
<string name="share_gpu_log_description">Share Eden\'s GPU log file to debug graphics issues</string>
|
||||||
|
<string name="share_gpu_log_missing">No GPU log file found</string>
|
||||||
<string name="install_game_content">Install game content</string>
|
<string name="install_game_content">Install game content</string>
|
||||||
<string name="install_game_content_description">Install game updates or DLC</string>
|
<string name="install_game_content_description">Install game updates or DLC</string>
|
||||||
<string name="installing_game_content">Installing content…</string>
|
<string name="installing_game_content">Installing content…</string>
|
||||||
@@ -490,6 +493,8 @@
|
|||||||
<string name="renderer_force_max_clock_description">Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied).</string>
|
<string name="renderer_force_max_clock_description">Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied).</string>
|
||||||
<string name="renderer_reactive_flushing">Use reactive flushing</string>
|
<string name="renderer_reactive_flushing">Use reactive flushing</string>
|
||||||
<string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string>
|
<string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string>
|
||||||
|
<string name="enable_buffer_history">Enable buffer history</string>
|
||||||
|
<string name="enable_buffer_history_description">Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games.</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="hacks">Hacks</string>
|
<string name="hacks">Hacks</string>
|
||||||
@@ -552,6 +557,24 @@
|
|||||||
<string name="flush_by_line">Flush debug logs by line</string>
|
<string name="flush_by_line">Flush debug logs by line</string>
|
||||||
<string name="flush_by_line_description">Flushes debugging logs on each line written, making debugging easier in cases of crashing or freezing.</string>
|
<string name="flush_by_line_description">Flushes debugging logs on each line written, making debugging easier in cases of crashing or freezing.</string>
|
||||||
|
|
||||||
|
<!-- GPU Logging strings -->
|
||||||
|
<string name="gpu_logging_header">GPU Logging</string>
|
||||||
|
<string name="gpu_logging_enabled">Enable GPU Logging</string>
|
||||||
|
<string name="gpu_logging_enabled_description">Log GPU operations to eden_gpu.log for debugging Adreno drivers</string>
|
||||||
|
<string name="gpu_log_level">Log Level</string>
|
||||||
|
<string name="gpu_log_level_description">Detail level for GPU logs (higher = more detail, more overhead)</string>
|
||||||
|
<string name="gpu_log_vulkan_calls">Log Vulkan API Calls</string>
|
||||||
|
<string name="gpu_log_vulkan_calls_description">Track all Vulkan API calls in ring buffer</string>
|
||||||
|
<string name="gpu_log_shader_dumps">Dump Shaders</string>
|
||||||
|
<string name="gpu_log_shader_dumps_description">Save compiled shader SPIR-V to files</string>
|
||||||
|
<string name="gpu_log_memory_tracking">Track GPU Memory</string>
|
||||||
|
<string name="gpu_log_memory_tracking_description">Monitor GPU memory allocations and deallocations</string>
|
||||||
|
<string name="gpu_log_driver_debug">Driver Debug Info</string>
|
||||||
|
<string name="gpu_log_driver_debug_description">Capture driver-specific debug information (Turnip breadcrumbs, etc.)</string>
|
||||||
|
<string name="gpu_log_ring_buffer_size">Ring Buffer Size</string>
|
||||||
|
<string name="gpu_log_ring_buffer_size_description">Number of recent Vulkan calls to track (default: 512)</string>
|
||||||
|
<string name="gpu_log_ring_buffer_size_hint">64 to 4096 entries</string>
|
||||||
|
|
||||||
<string name="general">General</string>
|
<string name="general">General</string>
|
||||||
|
|
||||||
<!-- Audio settings strings -->
|
<!-- Audio settings strings -->
|
||||||
@@ -715,6 +738,8 @@
|
|||||||
<string name="preferences_graphics">Graphics</string>
|
<string name="preferences_graphics">Graphics</string>
|
||||||
<string name="preferences_graphics_description">Accuracy level, resolution, shader cache</string>
|
<string name="preferences_graphics_description">Accuracy level, resolution, shader cache</string>
|
||||||
<string name="quick_settings">Quick Settings</string>
|
<string name="quick_settings">Quick Settings</string>
|
||||||
|
<string name="enable_quick_settings">Enable Quick Settings</string>
|
||||||
|
<string name="enable_quick_settings_description">Allow access to quick settings menu via swipe and menu button</string>
|
||||||
<string name="preferences_audio">Audio</string>
|
<string name="preferences_audio">Audio</string>
|
||||||
<string name="preferences_audio_description">Output engine, volume</string>
|
<string name="preferences_audio_description">Output engine, volume</string>
|
||||||
<string name="preferences_controls">Controls</string>
|
<string name="preferences_controls">Controls</string>
|
||||||
@@ -1140,6 +1165,12 @@
|
|||||||
<string name="use_black_backgrounds">Black backgrounds</string>
|
<string name="use_black_backgrounds">Black backgrounds</string>
|
||||||
<string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string>
|
<string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string>
|
||||||
|
|
||||||
|
<!-- Buttons -->
|
||||||
|
<string name="enable_folder_button">Folder</string>
|
||||||
|
<string name="enable_folder_button_description">Show the button to add game folders</string>
|
||||||
|
<string name="enable_qlaunch_button">QLaunch</string>
|
||||||
|
<string name="enable_qlaunch_button_description">Show the button to launch QLaunch</string>
|
||||||
|
|
||||||
<!-- App Language -->
|
<!-- App Language -->
|
||||||
<string name="app_language">App Language</string>
|
<string name="app_language">App Language</string>
|
||||||
<string name="app_language_description">Change the language of the app interface</string>
|
<string name="app_language_description">Change the language of the app interface</string>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#define BUILD_AUTO_UPDATE_WEBSITE "@BUILD_AUTO_UPDATE_WEBSITE@"
|
#define BUILD_AUTO_UPDATE_WEBSITE "@BUILD_AUTO_UPDATE_WEBSITE@"
|
||||||
#define BUILD_AUTO_UPDATE_API "@BUILD_AUTO_UPDATE_API@"
|
#define BUILD_AUTO_UPDATE_API "@BUILD_AUTO_UPDATE_API@"
|
||||||
#define BUILD_AUTO_UPDATE_REPO "@BUILD_AUTO_UPDATE_REPO@"
|
#define BUILD_AUTO_UPDATE_REPO "@BUILD_AUTO_UPDATE_REPO@"
|
||||||
|
#define IS_NIGHTLY_BUILD @IS_NIGHTLY_BUILD@
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
@@ -35,7 +36,9 @@ constexpr const char g_build_id[] = BUILD_ID;
|
|||||||
constexpr const char g_title_bar_format_idle[] = TITLE_BAR_FORMAT_IDLE;
|
constexpr const char g_title_bar_format_idle[] = TITLE_BAR_FORMAT_IDLE;
|
||||||
constexpr const char g_title_bar_format_running[] = TITLE_BAR_FORMAT_RUNNING;
|
constexpr const char g_title_bar_format_running[] = TITLE_BAR_FORMAT_RUNNING;
|
||||||
constexpr const char g_compiler_id[] = COMPILER_ID;
|
constexpr const char g_compiler_id[] = COMPILER_ID;
|
||||||
|
|
||||||
constexpr const bool g_is_dev_build = IS_DEV_BUILD;
|
constexpr const bool g_is_dev_build = IS_DEV_BUILD;
|
||||||
|
constexpr const bool g_is_nightly_build = IS_NIGHTLY_BUILD;
|
||||||
|
|
||||||
constexpr const char g_build_auto_update_website[] = BUILD_AUTO_UPDATE_WEBSITE;
|
constexpr const char g_build_auto_update_website[] = BUILD_AUTO_UPDATE_WEBSITE;
|
||||||
constexpr const char g_build_auto_update_api[] = BUILD_AUTO_UPDATE_API;
|
constexpr const char g_build_auto_update_api[] = BUILD_AUTO_UPDATE_API;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||||
@@ -20,7 +20,10 @@ extern const char g_title_bar_format_idle[];
|
|||||||
extern const char g_title_bar_format_running[];
|
extern const char g_title_bar_format_running[];
|
||||||
extern const char g_shader_cache_version[];
|
extern const char g_shader_cache_version[];
|
||||||
extern const char g_compiler_id[];
|
extern const char g_compiler_id[];
|
||||||
|
|
||||||
extern const bool g_is_dev_build;
|
extern const bool g_is_dev_build;
|
||||||
|
extern const bool g_is_nightly_build;
|
||||||
|
|
||||||
extern const char g_build_auto_update_website[];
|
extern const char g_build_auto_update_website[];
|
||||||
extern const char g_build_auto_update_api[];
|
extern const char g_build_auto_update_api[];
|
||||||
extern const char g_build_auto_update_repo[];
|
extern const char g_build_auto_update_repo[];
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ SWITCHABLE(CpuBackend, true);
|
|||||||
SWITCHABLE(CpuAccuracy, true);
|
SWITCHABLE(CpuAccuracy, true);
|
||||||
SWITCHABLE(FullscreenMode, true);
|
SWITCHABLE(FullscreenMode, true);
|
||||||
SWITCHABLE(GpuAccuracy, true);
|
SWITCHABLE(GpuAccuracy, true);
|
||||||
|
SWITCHABLE(GpuLogLevel, true);
|
||||||
SWITCHABLE(Language, true);
|
SWITCHABLE(Language, true);
|
||||||
SWITCHABLE(MemoryLayout, true);
|
SWITCHABLE(MemoryLayout, true);
|
||||||
SWITCHABLE(NvdecEmulation, false);
|
SWITCHABLE(NvdecEmulation, false);
|
||||||
|
|||||||
+21
-1
@@ -481,6 +481,14 @@ struct Values {
|
|||||||
SwitchableSetting<bool> barrier_feedback_loops{linkage, true, "barrier_feedback_loops",
|
SwitchableSetting<bool> barrier_feedback_loops{linkage, true, "barrier_feedback_loops",
|
||||||
Category::RendererAdvanced};
|
Category::RendererAdvanced};
|
||||||
|
|
||||||
|
SwitchableSetting<bool> enable_buffer_history{linkage,
|
||||||
|
false,
|
||||||
|
"enable_buffer_history",
|
||||||
|
Category::RendererAdvanced,
|
||||||
|
Specialization::Default,
|
||||||
|
true,
|
||||||
|
true};
|
||||||
|
|
||||||
// Renderer Hacks //
|
// Renderer Hacks //
|
||||||
SwitchableSetting<GpuOverclock> fast_gpu_time{linkage,
|
SwitchableSetting<GpuOverclock> fast_gpu_time{linkage,
|
||||||
GpuOverclock::Medium,
|
GpuOverclock::Medium,
|
||||||
@@ -738,6 +746,18 @@ struct Values {
|
|||||||
Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
|
Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
|
||||||
Setting<bool> disable_web_applet{linkage, true, "disable_web_applet", Category::Debugging};
|
Setting<bool> disable_web_applet{linkage, true, "disable_web_applet", Category::Debugging};
|
||||||
|
|
||||||
|
// GPU Logging
|
||||||
|
Setting<bool> gpu_logging_enabled{linkage, false, "gpu_logging_enabled", Category::Debugging};
|
||||||
|
SwitchableSetting<GpuLogLevel> gpu_log_level{linkage, GpuLogLevel::Standard, "gpu_log_level",
|
||||||
|
Category::Debugging};
|
||||||
|
Setting<bool> gpu_log_vulkan_calls{linkage, true, "gpu_log_vulkan_calls", Category::Debugging};
|
||||||
|
Setting<bool> gpu_log_shader_dumps{linkage, false, "gpu_log_shader_dumps", Category::Debugging};
|
||||||
|
Setting<bool> gpu_log_memory_tracking{linkage, true, "gpu_log_memory_tracking",
|
||||||
|
Category::Debugging};
|
||||||
|
Setting<bool> gpu_log_driver_debug{linkage, true, "gpu_log_driver_debug", Category::Debugging};
|
||||||
|
Setting<s32> gpu_log_ring_buffer_size{linkage, 512, "gpu_log_ring_buffer_size",
|
||||||
|
Category::Debugging};
|
||||||
|
|
||||||
SwitchableSetting<u16, true> debug_knobs{linkage,
|
SwitchableSetting<u16, true> debug_knobs{linkage,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@@ -766,7 +786,7 @@ struct Values {
|
|||||||
Category::WebService};
|
Category::WebService};
|
||||||
Setting<std::string> eden_username{linkage, "Eden", "eden_username",
|
Setting<std::string> eden_username{linkage, "Eden", "eden_username",
|
||||||
Category::WebService};
|
Category::WebService};
|
||||||
Setting<std::string> eden_token{linkage, "njausoolxygtpvraofqunuufhmupriifnpfggjxefntlyglr",
|
Setting<std::string> eden_token{linkage, "",
|
||||||
"eden_token", Category::WebService};
|
"eden_token", Category::WebService};
|
||||||
|
|
||||||
// Add-Ons
|
// Add-Ons
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project
|
||||||
@@ -154,6 +154,7 @@ ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High)
|
|||||||
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
|
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
|
||||||
ENUM(TemperatureUnits, Celsius, Fahrenheit)
|
ENUM(TemperatureUnits, Celsius, Fahrenheit)
|
||||||
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
||||||
|
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
|
||||||
|
|
||||||
template <typename Type>
|
template <typename Type>
|
||||||
inline std::string_view CanonicalizeEnum(Type id) {
|
inline std::string_view CanonicalizeEnum(Type id) {
|
||||||
|
|||||||
+38
-17
@@ -1,13 +1,15 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "common/error.h"
|
#include "common/error.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/assert.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include <mach/mach.h>
|
#include <mach/mach.h>
|
||||||
@@ -18,6 +20,8 @@
|
|||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#else
|
#else
|
||||||
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||||
|
#include <sys/cpuset.h>
|
||||||
|
#include <sys/_cpuset.h>
|
||||||
#include <pthread_np.h>
|
#include <pthread_np.h>
|
||||||
#endif
|
#endif
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
@@ -28,7 +32,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __FreeBSD__
|
#ifdef __FreeBSD__
|
||||||
#define cpu_set_t cpuset_t
|
# define cpu_set_t cpuset_t
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
@@ -77,22 +81,14 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetCurrentThreadName(const char* name) {
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
|
// Sets the debugger-visible name of the current thread.
|
||||||
// Sets the debugger-visible name of the current thread.
|
if (auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription"); pf)
|
||||||
void SetCurrentThreadName(const char* name) {
|
|
||||||
static auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription");
|
|
||||||
if (pf)
|
|
||||||
pf(GetCurrentThread(), UTF8ToUTF16W(name).data()); // Windows 10+
|
pf(GetCurrentThread(), UTF8ToUTF16W(name).data()); // Windows 10+
|
||||||
}
|
else
|
||||||
|
; // No-op
|
||||||
#else // !MSVC_VER, so must be POSIX threads
|
#elif defined(__APPLE__)
|
||||||
|
|
||||||
// MinGW with the POSIX threading model does not support pthread_setname_np
|
|
||||||
void SetCurrentThreadName(const char* name) {
|
|
||||||
// See for reference
|
|
||||||
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
|
|
||||||
#ifdef __APPLE__
|
|
||||||
pthread_setname_np(name);
|
pthread_setname_np(name);
|
||||||
#elif defined(__HAIKU__)
|
#elif defined(__HAIKU__)
|
||||||
rename_thread(find_thread(NULL), name);
|
rename_thread(find_thread(NULL), name);
|
||||||
@@ -112,13 +108,38 @@ void SetCurrentThreadName(const char* name) {
|
|||||||
pthread_setname_np(pthread_self(), buf);
|
pthread_setname_np(pthread_self(), buf);
|
||||||
}
|
}
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
// mingw stub
|
// MinGW with the POSIX threading model does not support pthread_setname_np
|
||||||
|
// See for reference
|
||||||
|
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
|
||||||
(void)name;
|
(void)name;
|
||||||
#else
|
#else
|
||||||
pthread_setname_np(pthread_self(), name);
|
pthread_setname_np(pthread_self(), name);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PinCurrentThreadToPerformanceCore(size_t core_id) {
|
||||||
|
ASSERT(core_id < 4);
|
||||||
|
// If we set a flag for a CPU that doesn't exist, the thread may not be allowed to
|
||||||
|
// run in ANY processor!
|
||||||
|
auto const total_cores = std::thread::hardware_concurrency();
|
||||||
|
if (core_id < total_cores) {
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
cpu_set_t set;
|
||||||
|
CPU_ZERO(&set);
|
||||||
|
CPU_SET(core_id, &set);
|
||||||
|
sched_setaffinity(pthread_self(), sizeof(set), &set);
|
||||||
|
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||||
|
cpu_set_t set;
|
||||||
|
CPU_ZERO(&set);
|
||||||
|
CPU_SET(core_id, &set);
|
||||||
|
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
DWORD set = 1UL << core_id;
|
||||||
|
SetThreadAffinityMask(GetCurrentThread(), set);
|
||||||
|
#else
|
||||||
|
// No pin functionality implemented
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||||
@@ -106,7 +106,7 @@ enum class ThreadPriority : u32 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
||||||
|
|
||||||
void SetCurrentThreadName(const char* name);
|
void SetCurrentThreadName(const char* name);
|
||||||
|
void PinCurrentThreadToPerformanceCore(size_t core_id);
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|||||||
+11
-12
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "common/fiber.h"
|
#include "common/fiber.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
|
#include "common/settings.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
#include "core/cpu_manager.h"
|
#include "core/cpu_manager.h"
|
||||||
@@ -25,11 +26,8 @@ CpuManager::~CpuManager() = default;
|
|||||||
void CpuManager::Initialize() {
|
void CpuManager::Initialize() {
|
||||||
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
|
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
|
||||||
gpu_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
|
gpu_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
|
||||||
|
for (std::size_t core = 0; core < num_cores; core++)
|
||||||
for (std::size_t core = 0; core < num_cores; core++) {
|
core_data[core].host_thread = std::jthread([this, core](std::stop_token token) { RunThread(token, core); });
|
||||||
core_data[core].host_thread =
|
|
||||||
std::jthread([this, core](std::stop_token token) { RunThread(token, core); });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CpuManager::Shutdown() {
|
void CpuManager::Shutdown() {
|
||||||
@@ -188,14 +186,15 @@ void CpuManager::ShutdownThread() {
|
|||||||
void CpuManager::RunThread(std::stop_token token, std::size_t core) {
|
void CpuManager::RunThread(std::stop_token token, std::size_t core) {
|
||||||
/// Initialization
|
/// Initialization
|
||||||
system.RegisterCoreThread(core);
|
system.RegisterCoreThread(core);
|
||||||
std::string name;
|
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
|
||||||
if (is_multicore) {
|
|
||||||
name = "CPUCore_" + std::to_string(core);
|
|
||||||
} else {
|
|
||||||
name = "CPUThread";
|
|
||||||
}
|
|
||||||
Common::SetCurrentThreadName(name.c_str());
|
Common::SetCurrentThreadName(name.c_str());
|
||||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
// Aimed specifically for Snapdragon 8 Elite devices
|
||||||
|
// This kills performance on desktop, but boosts perf for UMA devices
|
||||||
|
// like the S8E. Mediatek and Mali likely won't suffer.
|
||||||
|
Common::PinCurrentThreadToPerformanceCore(core);
|
||||||
|
#endif
|
||||||
auto& data = core_data[core];
|
auto& data = core_data[core];
|
||||||
data.host_context = Common::Fiber::ThreadToFiber();
|
data.host_context = Common::Fiber::ThreadToFiber();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
@@ -144,7 +144,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
|
|||||||
{617, nullptr, "UnregisterExternalKey"},
|
{617, nullptr, "UnregisterExternalKey"},
|
||||||
{620, nullptr, "SetSdCardEncryptionSeed"},
|
{620, nullptr, "SetSdCardEncryptionSeed"},
|
||||||
{630, nullptr, "SetSdCardAccessibility"},
|
{630, nullptr, "SetSdCardAccessibility"},
|
||||||
{631, nullptr, "IsSdCardAccessible"},
|
{631, D<&FSP_SRV::IsSdCardAccessible>, "IsSdCardAccessible"},
|
||||||
{640, nullptr, "IsSignedSystemPartitionOnSdCardValid"},
|
{640, nullptr, "IsSignedSystemPartitionOnSdCardValid"},
|
||||||
{700, nullptr, "OpenAccessFailureResolver"},
|
{700, nullptr, "OpenAccessFailureResolver"},
|
||||||
{701, nullptr, "GetAccessFailureDetectionEvent"},
|
{701, nullptr, "GetAccessFailureDetectionEvent"},
|
||||||
@@ -524,6 +524,14 @@ Result FSP_SRV::OpenDataStorageWithProgramIndex(OutInterface<IStorage> out_inter
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result FSP_SRV::IsSdCardAccessible(Out<bool> out_is_accessible) {
|
||||||
|
LOG_DEBUG(Service_FS, "(STUBBED) called");
|
||||||
|
|
||||||
|
*out_is_accessible = true;
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
Result FSP_SRV::DisableAutoSaveDataCreation() {
|
Result FSP_SRV::DisableAutoSaveDataCreation() {
|
||||||
LOG_DEBUG(Service_FS, "called");
|
LOG_DEBUG(Service_FS, "called");
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
@@ -101,6 +101,7 @@ private:
|
|||||||
Result OpenPatchDataStorageByCurrentProcess(OutInterface<IStorage> out_interface,
|
Result OpenPatchDataStorageByCurrentProcess(OutInterface<IStorage> out_interface,
|
||||||
FileSys::StorageId storage_id, u64 title_id);
|
FileSys::StorageId storage_id, u64 title_id);
|
||||||
Result OpenDataStorageWithProgramIndex(OutInterface<IStorage> out_interface, u8 program_index);
|
Result OpenDataStorageWithProgramIndex(OutInterface<IStorage> out_interface, u8 program_index);
|
||||||
|
Result IsSdCardAccessible(Out<bool> out_is_accessible);
|
||||||
Result DisableAutoSaveDataCreation();
|
Result DisableAutoSaveDataCreation();
|
||||||
Result SetGlobalAccessLogMode(AccessLogMode access_log_mode_);
|
Result SetGlobalAccessLogMode(AccessLogMode access_log_mode_);
|
||||||
Result GetGlobalAccessLogMode(Out<AccessLogMode> out_access_log_mode);
|
Result GetGlobalAccessLogMode(Out<AccessLogMode> out_access_log_mode);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -99,11 +99,6 @@ Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer,
|
|||||||
slots[slot].acquire_called = true;
|
slots[slot].acquire_called = true;
|
||||||
slots[slot].needs_cleanup_on_release = false;
|
slots[slot].needs_cleanup_on_release = false;
|
||||||
slots[slot].buffer_state = BufferState::Acquired;
|
slots[slot].buffer_state = BufferState::Acquired;
|
||||||
|
|
||||||
// TODO: for now, avoid resetting the fence, so that when we next return this
|
|
||||||
// slot to the producer, it will wait for the fence to pass. We should fix this
|
|
||||||
// by properly waiting for the fence in the BufferItemConsumer.
|
|
||||||
// slots[slot].fence = Fence::NoFence();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr to
|
// If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr to
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -17,6 +17,39 @@ BufferQueueCore::BufferQueueCore() = default;
|
|||||||
|
|
||||||
BufferQueueCore::~BufferQueueCore() = default;
|
BufferQueueCore::~BufferQueueCore() = default;
|
||||||
|
|
||||||
|
void BufferQueueCore::PushHistory(u64 frame_number, s64 queue_time, s64 presentation_time, BufferState state) {
|
||||||
|
std::lock_guard lk(buffer_history_mutex);
|
||||||
|
|
||||||
|
auto it = buffer_history_map.find(frame_number);
|
||||||
|
if (it != buffer_history_map.end()) {
|
||||||
|
it->second.state = state;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_history_map.emplace(frame_number, BufferHistoryInfo{
|
||||||
|
frame_number,
|
||||||
|
queue_time,
|
||||||
|
presentation_time,
|
||||||
|
state
|
||||||
|
});
|
||||||
|
buffer_history_order.push_back(frame_number);
|
||||||
|
|
||||||
|
if (buffer_history_order.size() > BUFFER_HISTORY_SIZE) {
|
||||||
|
u64 oldest_frame = buffer_history_order.front();
|
||||||
|
buffer_history_order.pop_front();
|
||||||
|
buffer_history_map.erase(oldest_frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BufferQueueCore::UpdateHistory(u64 frame_number, BufferState state) {
|
||||||
|
std::lock_guard lk(buffer_history_mutex);
|
||||||
|
|
||||||
|
auto it = buffer_history_map.find(frame_number);
|
||||||
|
if (it != buffer_history_map.end()) {
|
||||||
|
it->second.state = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BufferQueueCore::SignalDequeueCondition() {
|
void BufferQueueCore::SignalDequeueCondition() {
|
||||||
dequeue_possible.store(true);
|
dequeue_possible.store(true);
|
||||||
dequeue_condition.notify_all();
|
dequeue_condition.notify_all();
|
||||||
@@ -30,7 +63,6 @@ bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk)
|
|||||||
}
|
}
|
||||||
|
|
||||||
s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
|
s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
|
||||||
// If DequeueBuffer is allowed to error out, we don't have to add an extra buffer.
|
|
||||||
if (!use_async_buffer) {
|
if (!use_async_buffer) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -55,8 +87,6 @@ s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
|
|||||||
return override_max_buffer_count;
|
return override_max_buffer_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any buffers that are dequeued by the producer or sitting in the queue waiting to be consumed
|
|
||||||
// need to have their slots preserved.
|
|
||||||
for (s32 slot = max_buffer_count; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
|
for (s32 slot = max_buffer_count; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
|
||||||
const auto state = slots[slot].buffer_state;
|
const auto state = slots[slot].buffer_state;
|
||||||
if (state == BufferState::Queued || state == BufferState::Dequeued) {
|
if (state == BufferState::Queued || state == BufferState::Dequeued) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -10,20 +10,31 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <deque>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "core/hle/service/nvnflinger/buffer_item.h"
|
#include "core/hle/service/nvnflinger/buffer_item.h"
|
||||||
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
|
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
|
||||||
|
#include "core/hle/service/nvnflinger/buffer_slot.h"
|
||||||
#include "core/hle/service/nvnflinger/pixel_format.h"
|
#include "core/hle/service/nvnflinger/pixel_format.h"
|
||||||
#include "core/hle/service/nvnflinger/status.h"
|
#include "core/hle/service/nvnflinger/status.h"
|
||||||
#include "core/hle/service/nvnflinger/window.h"
|
#include "core/hle/service/nvnflinger/window.h"
|
||||||
|
|
||||||
namespace Service::android {
|
namespace Service::android {
|
||||||
|
|
||||||
|
struct BufferHistoryInfo {
|
||||||
|
u64 frame_number{};
|
||||||
|
s64 queue_time{};
|
||||||
|
s64 presentation_time{};
|
||||||
|
BufferState state{};
|
||||||
|
};
|
||||||
|
|
||||||
class IConsumerListener;
|
class IConsumerListener;
|
||||||
class IProducerListener;
|
class IProducerListener;
|
||||||
|
|
||||||
@@ -33,10 +44,14 @@ class BufferQueueCore final {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr s32 INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT;
|
static constexpr s32 INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT;
|
||||||
|
static constexpr u32 BUFFER_HISTORY_SIZE = 8;
|
||||||
|
|
||||||
BufferQueueCore();
|
BufferQueueCore();
|
||||||
~BufferQueueCore();
|
~BufferQueueCore();
|
||||||
|
|
||||||
|
void PushHistory(u64 frame_number, s64 queue_time, s64 presentation_time, BufferState state);
|
||||||
|
void UpdateHistory(u64 frame_number, BufferState state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SignalDequeueCondition();
|
void SignalDequeueCondition();
|
||||||
bool WaitForDequeueCondition(std::unique_lock<std::mutex>& lk);
|
bool WaitForDequeueCondition(std::unique_lock<std::mutex>& lk);
|
||||||
@@ -72,6 +87,11 @@ private:
|
|||||||
const s32 max_acquired_buffer_count{}; // This is always zero on HOS
|
const s32 max_acquired_buffer_count{}; // This is always zero on HOS
|
||||||
bool buffer_has_been_queued{};
|
bool buffer_has_been_queued{};
|
||||||
u64 frame_counter{};
|
u64 frame_counter{};
|
||||||
|
|
||||||
|
std::unordered_map<u64, BufferHistoryInfo> buffer_history_map{};
|
||||||
|
mutable std::mutex buffer_history_mutex{};
|
||||||
|
std::deque<u64> buffer_history_order;
|
||||||
|
|
||||||
u32 transform_hint{};
|
u32 transform_hint{};
|
||||||
bool is_allocating{};
|
bool is_allocating{};
|
||||||
mutable std::condition_variable_any is_allocating_condition;
|
mutable std::condition_variable_any is_allocating_condition;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/settings.h"
|
||||||
#include "core/hle/kernel/k_event.h"
|
#include "core/hle/kernel/k_event.h"
|
||||||
#include "core/hle/kernel/k_readable_event.h"
|
#include "core/hle/kernel/k_readable_event.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
@@ -26,7 +27,7 @@ BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext&
|
|||||||
std::shared_ptr<BufferQueueCore> buffer_queue_core_,
|
std::shared_ptr<BufferQueueCore> buffer_queue_core_,
|
||||||
Service::Nvidia::NvCore::NvMap& nvmap_)
|
Service::Nvidia::NvCore::NvMap& nvmap_)
|
||||||
: service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots),
|
: service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots),
|
||||||
nvmap(nvmap_) {
|
clock{Common::CreateOptimalClock()}, nvmap(nvmap_) {
|
||||||
buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
|
buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,8 +429,7 @@ Status BufferQueueProducer::AttachBuffer(s32* out_slot,
|
|||||||
return return_flags;
|
return return_flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, QueueBufferOutput* output) {
|
||||||
QueueBufferOutput* output) {
|
|
||||||
s64 timestamp{};
|
s64 timestamp{};
|
||||||
bool is_auto_timestamp{};
|
bool is_auto_timestamp{};
|
||||||
Common::Rectangle<s32> crop;
|
Common::Rectangle<s32> crop;
|
||||||
@@ -440,8 +440,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
|||||||
s32 swap_interval{};
|
s32 swap_interval{};
|
||||||
Fence fence{};
|
Fence fence{};
|
||||||
|
|
||||||
input.Deflate(×tamp, &is_auto_timestamp, &crop, &scaling_mode, &transform,
|
input.Deflate(×tamp, &is_auto_timestamp, &crop, &scaling_mode, &transform, &sticky_transform_, &async, &swap_interval, &fence);
|
||||||
&sticky_transform_, &async, &swap_interval, &fence);
|
|
||||||
|
|
||||||
switch (scaling_mode) {
|
switch (scaling_mode) {
|
||||||
case NativeWindowScalingMode::Freeze:
|
case NativeWindowScalingMode::Freeze:
|
||||||
@@ -455,10 +454,9 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
|||||||
return Status::BadValue;
|
return Status::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<IConsumerListener> frame_available_listener;
|
|
||||||
std::shared_ptr<IConsumerListener> frame_replaced_listener;
|
|
||||||
s32 callback_ticket{};
|
|
||||||
BufferItem item;
|
BufferItem item;
|
||||||
|
std::shared_ptr<IConsumerListener> listener_available;
|
||||||
|
std::shared_ptr<IConsumerListener> listener_replaced;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::scoped_lock lock{core->mutex};
|
std::scoped_lock lock{core->mutex};
|
||||||
@@ -469,127 +467,82 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
|
const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
|
||||||
if (async && core->override_max_buffer_count) {
|
|
||||||
if (core->override_max_buffer_count < max_buffer_count) {
|
|
||||||
LOG_ERROR(Service_Nvnflinger, "async mode is invalid with "
|
|
||||||
"buffer count override");
|
|
||||||
return Status::BadValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slot < 0 || slot >= max_buffer_count) {
|
if (slot < 0 || slot >= max_buffer_count) {
|
||||||
LOG_ERROR(Service_Nvnflinger, "slot index {} out of range [0, {})", slot,
|
LOG_ERROR(Service_Nvnflinger, "slot {} out of range [0, {})", slot, max_buffer_count);
|
||||||
max_buffer_count);
|
|
||||||
return Status::BadValue;
|
return Status::BadValue;
|
||||||
} else if (slots[slot].buffer_state != BufferState::Dequeued) {
|
}
|
||||||
LOG_ERROR(Service_Nvnflinger,
|
if (slots[slot].buffer_state != BufferState::Dequeued) {
|
||||||
"slot {} is not owned by the producer "
|
LOG_ERROR(Service_Nvnflinger, "slot {} is not owned by producer", slot);
|
||||||
"(state = {})",
|
|
||||||
slot, slots[slot].buffer_state);
|
|
||||||
return Status::BadValue;
|
return Status::BadValue;
|
||||||
} else if (!slots[slot].request_buffer_called) {
|
}
|
||||||
LOG_ERROR(Service_Nvnflinger,
|
if (!slots[slot].request_buffer_called) {
|
||||||
"slot {} was queued without requesting "
|
LOG_ERROR(Service_Nvnflinger, "slot {} was queued without request", slot);
|
||||||
"a buffer",
|
|
||||||
slot);
|
|
||||||
return Status::BadValue;
|
return Status::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DEBUG(Service_Nvnflinger,
|
|
||||||
"slot={} frame={} time={} crop=[{},{},{},{}] transform={} scale={}", slot,
|
|
||||||
core->frame_counter + 1, timestamp, crop.Left(), crop.Top(), crop.Right(),
|
|
||||||
crop.Bottom(), transform, scaling_mode);
|
|
||||||
|
|
||||||
const std::shared_ptr<GraphicBuffer>& graphic_buffer(slots[slot].graphic_buffer);
|
|
||||||
Common::Rectangle<s32> buffer_rect(graphic_buffer->Width(), graphic_buffer->Height());
|
|
||||||
Common::Rectangle<s32> cropped_rect;
|
|
||||||
[[maybe_unused]] const bool unused = crop.Intersect(buffer_rect, &cropped_rect);
|
|
||||||
|
|
||||||
if (cropped_rect != crop) {
|
|
||||||
LOG_ERROR(Service_Nvnflinger, "crop rect is not contained within the buffer in slot {}",
|
|
||||||
slot);
|
|
||||||
return Status::BadValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
slots[slot].fence = fence;
|
|
||||||
slots[slot].buffer_state = BufferState::Queued;
|
|
||||||
++core->frame_counter;
|
++core->frame_counter;
|
||||||
|
slots[slot].buffer_state = BufferState::Queued;
|
||||||
slots[slot].frame_number = core->frame_counter;
|
slots[slot].frame_number = core->frame_counter;
|
||||||
|
slots[slot].queue_time = timestamp;
|
||||||
|
slots[slot].presentation_time = clock->GetTimeNS().count();
|
||||||
|
slots[slot].fence = fence;
|
||||||
|
|
||||||
item.acquire_called = slots[slot].acquire_called;
|
item.slot = slot;
|
||||||
item.graphic_buffer = slots[slot].graphic_buffer;
|
item.graphic_buffer = slots[slot].graphic_buffer;
|
||||||
item.crop = crop;
|
item.frame_number = core->frame_counter;
|
||||||
item.transform = transform & ~NativeWindowTransform::InverseDisplay;
|
|
||||||
item.transform_to_display_inverse =
|
|
||||||
(transform & NativeWindowTransform::InverseDisplay) != NativeWindowTransform::None;
|
|
||||||
item.scaling_mode = static_cast<u32>(scaling_mode);
|
|
||||||
item.timestamp = timestamp;
|
item.timestamp = timestamp;
|
||||||
item.is_auto_timestamp = is_auto_timestamp;
|
item.is_auto_timestamp = is_auto_timestamp;
|
||||||
item.frame_number = core->frame_counter;
|
item.crop = crop;
|
||||||
item.slot = slot;
|
item.transform = transform & ~NativeWindowTransform::InverseDisplay;
|
||||||
|
item.transform_to_display_inverse = (transform & NativeWindowTransform::InverseDisplay) != NativeWindowTransform::None;
|
||||||
|
item.scaling_mode = static_cast<u32>(scaling_mode);
|
||||||
item.fence = fence;
|
item.fence = fence;
|
||||||
item.is_droppable = core->dequeue_buffer_cannot_block || async;
|
item.is_droppable = core->dequeue_buffer_cannot_block || async;
|
||||||
item.swap_interval = swap_interval;
|
item.swap_interval = swap_interval;
|
||||||
|
item.acquire_called = slots[slot].acquire_called;
|
||||||
|
|
||||||
sticky_transform = sticky_transform_;
|
sticky_transform = sticky_transform_;
|
||||||
|
|
||||||
if (core->queue.empty()) {
|
if (core->queue.empty()) {
|
||||||
// When the queue is empty, we can simply queue this buffer
|
|
||||||
core->queue.push_back(item);
|
core->queue.push_back(item);
|
||||||
frame_available_listener = core->consumer_listener;
|
listener_available = core->consumer_listener;
|
||||||
} else {
|
} else {
|
||||||
// When the queue is not empty, we need to look at the front buffer
|
auto front = core->queue.begin();
|
||||||
// state to see if we need to replace it
|
if (front->is_droppable && core->StillTracking(*front)) {
|
||||||
auto front(core->queue.begin());
|
slots[front->slot].buffer_state = BufferState::Free;
|
||||||
|
if (Settings::values.enable_buffer_history.GetValue()) {
|
||||||
|
core->UpdateHistory(front->frame_number, BufferState::Free);
|
||||||
|
}
|
||||||
|
slots[front->slot].frame_number = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (front->is_droppable) {
|
if (front->is_droppable) {
|
||||||
// If the front queued buffer is still being tracked, we first
|
|
||||||
// mark it as freed
|
|
||||||
if (core->StillTracking(*front)) {
|
|
||||||
slots[front->slot].buffer_state = BufferState::Free;
|
|
||||||
// Reset the frame number of the freed buffer so that it is the first in line to
|
|
||||||
// be dequeued again
|
|
||||||
slots[front->slot].frame_number = 0;
|
|
||||||
}
|
|
||||||
// Overwrite the droppable buffer with the incoming one
|
|
||||||
*front = item;
|
*front = item;
|
||||||
frame_replaced_listener = core->consumer_listener;
|
listener_replaced = core->consumer_listener;
|
||||||
} else {
|
} else {
|
||||||
core->queue.push_back(item);
|
core->queue.push_back(item);
|
||||||
frame_available_listener = core->consumer_listener;
|
listener_available = core->consumer_listener;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Settings::values.enable_buffer_history.GetValue()) {
|
||||||
|
core->PushHistory(core->frame_counter, slots[slot].queue_time, slots[slot].presentation_time, BufferState::Queued);
|
||||||
|
}
|
||||||
|
|
||||||
core->buffer_has_been_queued = true;
|
core->buffer_has_been_queued = true;
|
||||||
core->SignalDequeueCondition();
|
core->SignalDequeueCondition();
|
||||||
output->Inflate(core->default_width, core->default_height, core->transform_hint,
|
|
||||||
static_cast<u32>(core->queue.size()));
|
|
||||||
|
|
||||||
// Take a ticket for the callback functions
|
output->Inflate(core->default_width, core->default_height, core->transform_hint, static_cast<u32>(core->queue.size()));
|
||||||
callback_ticket = next_callback_ticket++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't send the GraphicBuffer through the callback, and don't send the slot number, since the
|
|
||||||
// consumer shouldn't need it
|
|
||||||
item.graphic_buffer.reset();
|
item.graphic_buffer.reset();
|
||||||
item.slot = BufferItem::INVALID_BUFFER_SLOT;
|
item.slot = BufferItem::INVALID_BUFFER_SLOT;
|
||||||
|
|
||||||
// Call back without the main BufferQueue lock held, but with the callback lock held so we can
|
if (listener_available) {
|
||||||
// ensure that callbacks occur in order
|
listener_available->OnFrameAvailable(item);
|
||||||
{
|
} else if (listener_replaced) {
|
||||||
std::scoped_lock lock{callback_mutex};
|
listener_replaced->OnFrameReplaced(item);
|
||||||
while (callback_ticket != current_callback_ticket) {
|
|
||||||
callback_condition.wait(callback_mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame_available_listener != nullptr) {
|
|
||||||
frame_available_listener->OnFrameAvailable(item);
|
|
||||||
} else if (frame_replaced_listener != nullptr) {
|
|
||||||
frame_replaced_listener->OnFrameReplaced(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
++current_callback_ticket;
|
|
||||||
callback_condition.notify_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Status::NoError;
|
return Status::NoError;
|
||||||
@@ -810,6 +763,10 @@ Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot,
|
|||||||
return Status::NoError;
|
return Status::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kernel::KReadableEvent* BufferQueueProducer::GetNativeHandle(u32 type_id) {
|
||||||
|
return &buffer_wait_event->GetReadableEvent();
|
||||||
|
}
|
||||||
|
|
||||||
void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
|
void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
|
||||||
std::span<u8> parcel_reply, u32 flags) {
|
std::span<u8> parcel_reply, u32 flags) {
|
||||||
// Values used by BnGraphicBufferProducer onTransact
|
// Values used by BnGraphicBufferProducer onTransact
|
||||||
@@ -929,9 +886,42 @@ void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
|
|||||||
status = SetBufferCount(buffer_count);
|
status = SetBufferCount(buffer_count);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TransactionId::GetBufferHistory:
|
case TransactionId::GetBufferHistory: {
|
||||||
LOG_DEBUG(Service_Nvnflinger, "(STUBBED) called, transaction=GetBufferHistory");
|
if (!Settings::values.enable_buffer_history.GetValue()) {
|
||||||
|
LOG_DEBUG(Service_Nvnflinger, "(STUBBED) called");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_Nvnflinger, "called, transaction=GetBufferHistory");
|
||||||
|
|
||||||
|
const s32 request = parcel_in.Read<s32>();
|
||||||
|
if (request <= 0) {
|
||||||
|
parcel_out.Write(Status::BadValue);
|
||||||
|
parcel_out.Write<s32>(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BufferHistoryInfo> snapshot;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::scoped_lock lk(core->buffer_history_mutex);
|
||||||
|
for (auto& [frame, info] : core->buffer_history_map) {
|
||||||
|
snapshot.push_back(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(snapshot.begin(), snapshot.end(), [](auto& a, auto& b){
|
||||||
|
return a.frame_number > b.frame_number;
|
||||||
|
});
|
||||||
|
|
||||||
|
const s32 limit = std::min(request, (s32)snapshot.size());
|
||||||
|
parcel_out.Write(Status::NoError);
|
||||||
|
parcel_out.Write<s32>(limit);
|
||||||
|
for (s32 i = 0; i < limit; ++i) {
|
||||||
|
parcel_out.Write(snapshot[i]);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
ASSERT_MSG(false, "Unimplemented TransactionId {}", code);
|
ASSERT_MSG(false, "Unimplemented TransactionId {}", code);
|
||||||
break;
|
break;
|
||||||
@@ -944,8 +934,4 @@ void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
|
|||||||
(std::min)(parcel_reply.size(), serialized.size()));
|
(std::min)(parcel_reply.size(), serialized.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Kernel::KReadableEvent* BufferQueueProducer::GetNativeHandle(u32 type_id) {
|
|
||||||
return &buffer_wait_event->GetReadableEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Service::android
|
} // namespace Service::android
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
|
#include "common/wall_clock.h"
|
||||||
#include "core/hle/service/nvdrv/nvdata.h"
|
#include "core/hle/service/nvdrv/nvdata.h"
|
||||||
#include "core/hle/service/nvnflinger/binder.h"
|
#include "core/hle/service/nvnflinger/binder.h"
|
||||||
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
|
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
|
||||||
@@ -88,6 +89,7 @@ private:
|
|||||||
s32 next_callback_ticket{};
|
s32 next_callback_ticket{};
|
||||||
s32 current_callback_ticket{};
|
s32 current_callback_ticket{};
|
||||||
std::condition_variable_any callback_condition;
|
std::condition_variable_any callback_condition;
|
||||||
|
std::unique_ptr<Common::WallClock> clock;
|
||||||
|
|
||||||
Service::Nvidia::NvCore::NvMap& nvmap;
|
Service::Nvidia::NvCore::NvMap& nvmap;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -37,6 +37,7 @@ struct BufferSlot final {
|
|||||||
bool needs_cleanup_on_release{};
|
bool needs_cleanup_on_release{};
|
||||||
bool attached_by_consumer{};
|
bool attached_by_consumer{};
|
||||||
bool is_preallocated{};
|
bool is_preallocated{};
|
||||||
|
s64 queue_time{}, presentation_time{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::android
|
} // namespace Service::android
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -218,7 +218,7 @@ EmittedBlockInfo EmitArm64(oaknut::CodeGenerator& code, IR::Block block, const E
|
|||||||
code.l(pass);
|
code.l(pass);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto iter = block.begin(); iter != block.end(); ++iter) {
|
for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) {
|
||||||
IR::Inst* inst = &*iter;
|
IR::Inst* inst = &*iter;
|
||||||
|
|
||||||
switch (inst->GetOpcode()) {
|
switch (inst->GetOpcode()) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -582,11 +582,9 @@ void EmitIR<IR::Opcode::A32BXWritePC>(oaknut::CodeGenerator& code, EmitContext&
|
|||||||
|
|
||||||
template<>
|
template<>
|
||||||
void EmitIR<IR::Opcode::A32UpdateUpperLocationDescriptor>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst*) {
|
void EmitIR<IR::Opcode::A32UpdateUpperLocationDescriptor>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst*) {
|
||||||
for (auto& inst : ctx.block) {
|
for (auto& inst : ctx.block.instructions)
|
||||||
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC) {
|
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
|
||||||
EmitSetUpperLocationDescriptor(code, ctx, ctx.block.EndLocation(), ctx.block.Location());
|
EmitSetUpperLocationDescriptor(code, ctx, ctx.block.EndLocation(), ctx.block.Location());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -193,7 +193,6 @@ void RegAlloc::PrepareForCall(std::optional<Argument::copyable_reference> arg0,
|
|||||||
|
|
||||||
void RegAlloc::DefineAsExisting(IR::Inst* inst, Argument& arg) {
|
void RegAlloc::DefineAsExisting(IR::Inst* inst, Argument& arg) {
|
||||||
defined_insts.insert(inst);
|
defined_insts.insert(inst);
|
||||||
|
|
||||||
ASSERT(!ValueLocation(inst));
|
ASSERT(!ValueLocation(inst));
|
||||||
|
|
||||||
if (arg.value.IsImmediate()) {
|
if (arg.value.IsImmediate()) {
|
||||||
@@ -208,7 +207,6 @@ void RegAlloc::DefineAsExisting(IR::Inst* inst, Argument& arg) {
|
|||||||
|
|
||||||
void RegAlloc::DefineAsRegister(IR::Inst* inst, oaknut::Reg reg) {
|
void RegAlloc::DefineAsRegister(IR::Inst* inst, oaknut::Reg reg) {
|
||||||
defined_insts.insert(inst);
|
defined_insts.insert(inst);
|
||||||
|
|
||||||
ASSERT(!ValueLocation(inst));
|
ASSERT(!ValueLocation(inst));
|
||||||
auto& info = reg.is_vector() ? fprs[reg.index()] : gprs[reg.index()];
|
auto& info = reg.is_vector() ? fprs[reg.index()] : gprs[reg.index()];
|
||||||
ASSERT(info.IsCompletelyEmpty());
|
ASSERT(info.IsCompletelyEmpty());
|
||||||
@@ -375,7 +373,6 @@ int RegAlloc::RealizeReadImpl(const IR::Value& value) {
|
|||||||
template<HostLoc::Kind kind>
|
template<HostLoc::Kind kind>
|
||||||
int RegAlloc::RealizeWriteImpl(const IR::Inst* value) {
|
int RegAlloc::RealizeWriteImpl(const IR::Inst* value) {
|
||||||
defined_insts.insert(value);
|
defined_insts.insert(value);
|
||||||
|
|
||||||
ASSERT(!ValueLocation(value));
|
ASSERT(!ValueLocation(value));
|
||||||
|
|
||||||
if constexpr (kind == HostLoc::Kind::Gpr) {
|
if constexpr (kind == HostLoc::Kind::Gpr) {
|
||||||
@@ -400,7 +397,6 @@ int RegAlloc::RealizeWriteImpl(const IR::Inst* value) {
|
|||||||
template<HostLoc::Kind kind>
|
template<HostLoc::Kind kind>
|
||||||
int RegAlloc::RealizeReadWriteImpl(const IR::Value& read_value, const IR::Inst* write_value) {
|
int RegAlloc::RealizeReadWriteImpl(const IR::Value& read_value, const IR::Inst* write_value) {
|
||||||
defined_insts.insert(write_value);
|
defined_insts.insert(write_value);
|
||||||
|
|
||||||
// TODO: Move elimination
|
// TODO: Move elimination
|
||||||
|
|
||||||
const int write_loc = RealizeWriteImpl<kind>(write_value);
|
const int write_loc = RealizeWriteImpl<kind>(write_value);
|
||||||
@@ -464,7 +460,6 @@ void RegAlloc::SpillFpr(int index) {
|
|||||||
|
|
||||||
void RegAlloc::ReadWriteFlags(Argument& read, IR::Inst* write) {
|
void RegAlloc::ReadWriteFlags(Argument& read, IR::Inst* write) {
|
||||||
defined_insts.insert(write);
|
defined_insts.insert(write);
|
||||||
|
|
||||||
const auto current_location = ValueLocation(read.value.GetInst());
|
const auto current_location = ValueLocation(read.value.GetInst());
|
||||||
ASSERT(current_location);
|
ASSERT(current_location);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -302,17 +302,12 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
friend struct Argument;
|
friend struct Argument;
|
||||||
template<typename>
|
template<typename> friend struct RAReg;
|
||||||
friend struct RAReg;
|
|
||||||
|
|
||||||
template<HostLoc::Kind kind>
|
template<HostLoc::Kind kind> int GenerateImmediate(const IR::Value& value);
|
||||||
int GenerateImmediate(const IR::Value& value);
|
template<HostLoc::Kind kind> int RealizeReadImpl(const IR::Value& value);
|
||||||
template<HostLoc::Kind kind>
|
template<HostLoc::Kind kind> int RealizeWriteImpl(const IR::Inst* value);
|
||||||
int RealizeReadImpl(const IR::Value& value);
|
template<HostLoc::Kind kind> int RealizeReadWriteImpl(const IR::Value& read_value, const IR::Inst* write_value);
|
||||||
template<HostLoc::Kind kind>
|
|
||||||
int RealizeWriteImpl(const IR::Inst* value);
|
|
||||||
template<HostLoc::Kind kind>
|
|
||||||
int RealizeReadWriteImpl(const IR::Value& read_value, const IR::Inst* write_value);
|
|
||||||
|
|
||||||
int AllocateRegister(const std::array<HostLocInfo, 32>& regs, const std::vector<int>& order) const;
|
int AllocateRegister(const std::array<HostLocInfo, 32>& regs, const std::vector<int>& order) const;
|
||||||
void SpillGpr(int index);
|
void SpillGpr(int index);
|
||||||
@@ -337,7 +332,6 @@ private:
|
|||||||
std::array<HostLocInfo, SpillCount> spills;
|
std::array<HostLocInfo, SpillCount> spills;
|
||||||
|
|
||||||
mutable std::mt19937 rand_gen;
|
mutable std::mt19937 rand_gen;
|
||||||
|
|
||||||
ankerl::unordered_dense::set<const IR::Inst*> defined_insts;
|
ankerl::unordered_dense::set<const IR::Inst*> defined_insts;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -15,26 +15,24 @@
|
|||||||
|
|
||||||
namespace Dynarmic::Backend {
|
namespace Dynarmic::Backend {
|
||||||
|
|
||||||
template<typename ProgramCounterType>
|
template<typename P>
|
||||||
void BlockRangeInformation<ProgramCounterType>::AddRange(boost::icl::discrete_interval<ProgramCounterType> range, IR::LocationDescriptor location) {
|
void BlockRangeInformation<P>::AddRange(boost::icl::discrete_interval<P> range, IR::LocationDescriptor location) {
|
||||||
block_ranges.add(std::make_pair(range, std::set<IR::LocationDescriptor>{location}));
|
block_ranges.add(std::make_pair(range, ankerl::unordered_dense::set<IR::LocationDescriptor>{location}));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename ProgramCounterType>
|
template<typename P>
|
||||||
void BlockRangeInformation<ProgramCounterType>::ClearCache() {
|
void BlockRangeInformation<P>::ClearCache() {
|
||||||
block_ranges.clear();
|
block_ranges.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename ProgramCounterType>
|
template<typename P>
|
||||||
ankerl::unordered_dense::set<IR::LocationDescriptor> BlockRangeInformation<ProgramCounterType>::InvalidateRanges(const boost::icl::interval_set<ProgramCounterType>& ranges) {
|
ankerl::unordered_dense::set<IR::LocationDescriptor> BlockRangeInformation<P>::InvalidateRanges(const boost::icl::interval_set<P>& ranges) {
|
||||||
ankerl::unordered_dense::set<IR::LocationDescriptor> erase_locations;
|
ankerl::unordered_dense::set<IR::LocationDescriptor> erase_locations;
|
||||||
for (auto invalidate_interval : ranges) {
|
for (auto invalidate_interval : ranges) {
|
||||||
auto pair = block_ranges.equal_range(invalidate_interval);
|
auto pair = block_ranges.equal_range(invalidate_interval);
|
||||||
for (auto it = pair.first; it != pair.second; ++it) {
|
for (auto it = pair.first; it != pair.second; ++it)
|
||||||
for (const auto& descriptor : it->second) {
|
for (const auto& descriptor : it->second)
|
||||||
erase_locations.insert(descriptor);
|
erase_locations.insert(descriptor);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// TODO: EFFICIENCY: Remove ranges that are to be erased.
|
// TODO: EFFICIENCY: Remove ranges that are to be erased.
|
||||||
return erase_locations;
|
return erase_locations;
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
* Copyright (c) 2018 MerryMage
|
* Copyright (c) 2018 MerryMage
|
||||||
* SPDX-License-Identifier: 0BSD
|
* SPDX-License-Identifier: 0BSD
|
||||||
@@ -15,15 +18,13 @@
|
|||||||
|
|
||||||
namespace Dynarmic::Backend {
|
namespace Dynarmic::Backend {
|
||||||
|
|
||||||
template<typename ProgramCounterType>
|
template<typename P>
|
||||||
class BlockRangeInformation {
|
class BlockRangeInformation {
|
||||||
public:
|
public:
|
||||||
void AddRange(boost::icl::discrete_interval<ProgramCounterType> range, IR::LocationDescriptor location);
|
void AddRange(boost::icl::discrete_interval<P> range, IR::LocationDescriptor location);
|
||||||
void ClearCache();
|
void ClearCache();
|
||||||
ankerl::unordered_dense::set<IR::LocationDescriptor> InvalidateRanges(const boost::icl::interval_set<ProgramCounterType>& ranges);
|
ankerl::unordered_dense::set<IR::LocationDescriptor> InvalidateRanges(const boost::icl::interval_set<P>& ranges);
|
||||||
|
boost::icl::interval_map<P, ankerl::unordered_dense::set<IR::LocationDescriptor>> block_ranges;
|
||||||
private:
|
|
||||||
boost::icl::interval_map<ProgramCounterType, std::set<IR::LocationDescriptor>> block_ranges;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Dynarmic::Backend
|
} // namespace Dynarmic::Backend
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -122,7 +122,7 @@ EmittedBlockInfo EmitRV64(biscuit::Assembler& as, IR::Block block, const EmitCon
|
|||||||
|
|
||||||
ebi.entry_point = reinterpret_cast<CodePtr>(as.GetCursorPointer());
|
ebi.entry_point = reinterpret_cast<CodePtr>(as.GetCursorPointer());
|
||||||
|
|
||||||
for (auto iter = block.begin(); iter != block.end(); ++iter) {
|
for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) {
|
||||||
IR::Inst* inst = &*iter;
|
IR::Inst* inst = &*iter;
|
||||||
|
|
||||||
switch (inst->GetOpcode()) {
|
switch (inst->GetOpcode()) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -123,7 +123,7 @@ A32EmitX64::BlockDescriptor A32EmitX64::Emit(IR::Block& block) {
|
|||||||
|
|
||||||
EmitCondPrelude(ctx);
|
EmitCondPrelude(ctx);
|
||||||
|
|
||||||
for (auto iter = block.begin(); iter != block.end(); ++iter) [[likely]] {
|
for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) [[likely]] {
|
||||||
auto* inst = &*iter;
|
auto* inst = &*iter;
|
||||||
// Call the relevant Emit* member function.
|
// Call the relevant Emit* member function.
|
||||||
switch (inst->GetOpcode()) {
|
switch (inst->GetOpcode()) {
|
||||||
@@ -727,11 +727,9 @@ void A32EmitX64::EmitA32BXWritePC(A32EmitContext& ctx, IR::Inst* inst) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void A32EmitX64::EmitA32UpdateUpperLocationDescriptor(A32EmitContext& ctx, IR::Inst*) {
|
void A32EmitX64::EmitA32UpdateUpperLocationDescriptor(A32EmitContext& ctx, IR::Inst*) {
|
||||||
for (auto& inst : ctx.block) {
|
for (auto& inst : ctx.block.instructions)
|
||||||
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC) {
|
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
|
||||||
EmitSetUpperLocationDescriptor(ctx.EndLocation(), ctx.Location());
|
EmitSetUpperLocationDescriptor(ctx.EndLocation(), ctx.Location());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -116,7 +116,7 @@ A64EmitX64::BlockDescriptor A64EmitX64::Emit(IR::Block& block) noexcept {
|
|||||||
#undef A64OPC
|
#undef A64OPC
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto& inst : block) {
|
for (auto& inst : block.instructions) {
|
||||||
auto const opcode = inst.GetOpcode();
|
auto const opcode = inst.GetOpcode();
|
||||||
// Call the relevant Emit* member function.
|
// Call the relevant Emit* member function.
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -38,11 +38,6 @@ EmitContext::EmitContext(RegAlloc& reg_alloc, IR::Block& block)
|
|||||||
|
|
||||||
EmitContext::~EmitContext() = default;
|
EmitContext::~EmitContext() = default;
|
||||||
|
|
||||||
void EmitContext::EraseInstruction(IR::Inst* inst) {
|
|
||||||
block.Instructions().erase(inst);
|
|
||||||
inst->ClearArgs();
|
|
||||||
}
|
|
||||||
|
|
||||||
EmitX64::EmitX64(BlockOfCode& code)
|
EmitX64::EmitX64(BlockOfCode& code)
|
||||||
: code(code) {
|
: code(code) {
|
||||||
exception_handler.Register(code);
|
exception_handler.Register(code);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -56,11 +56,7 @@ using HalfVectorArray = std::array<T, A64FullVectorWidth::value / mcl::bitsizeof
|
|||||||
struct EmitContext {
|
struct EmitContext {
|
||||||
EmitContext(RegAlloc& reg_alloc, IR::Block& block);
|
EmitContext(RegAlloc& reg_alloc, IR::Block& block);
|
||||||
virtual ~EmitContext();
|
virtual ~EmitContext();
|
||||||
|
|
||||||
void EraseInstruction(IR::Inst* inst);
|
|
||||||
|
|
||||||
virtual FP::FPCR FPCR(bool fpcr_controlled = true) const = 0;
|
virtual FP::FPCR FPCR(bool fpcr_controlled = true) const = 0;
|
||||||
|
|
||||||
virtual bool HasOptimization(OptimizationFlag flag) const = 0;
|
virtual bool HasOptimization(OptimizationFlag flag) const = 0;
|
||||||
|
|
||||||
RegAlloc& reg_alloc;
|
RegAlloc& reg_alloc;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -34,14 +34,7 @@ namespace Dynarmic::Backend::X64 {
|
|||||||
|
|
||||||
using namespace Xbyak::util;
|
using namespace Xbyak::util;
|
||||||
|
|
||||||
#define ICODE(NAME) \
|
#define ICODE(NAME) [&](auto... args) { if (esize == 32) code.NAME##d(args...); else code.NAME##q(args...); }
|
||||||
[&code](auto... args) { \
|
|
||||||
if constexpr (esize == 32) { \
|
|
||||||
code.NAME##d(args...); \
|
|
||||||
} else { \
|
|
||||||
code.NAME##q(args...); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Function>
|
template<typename Function>
|
||||||
static void EmitVectorOperation(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, Function fn) {
|
static void EmitVectorOperation(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, Function fn) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -26,7 +26,7 @@ bool CondCanContinue(const ConditionalState cond_state, const A32::IREmitter& ir
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
// TODO: This is more conservative than necessary.
|
// TODO: This is more conservative than necessary.
|
||||||
return std::all_of(ir.block.begin(), ir.block.end(), [](const IR::Inst& inst) {
|
return std::all_of(ir.block.instructions.begin(), ir.block.instructions.end(), [](const IR::Inst& inst) {
|
||||||
return !WritesToCPSR(inst.GetOpcode());
|
return !WritesToCPSR(inst.GetOpcode());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ bool IsConditionPassed(TranslatorVisitor& v, IR::Cond cond) {
|
|||||||
|
|
||||||
// non-AL cond
|
// non-AL cond
|
||||||
|
|
||||||
if (!v.ir.block.empty()) {
|
if (!v.ir.block.instructions.empty()) {
|
||||||
// We've already emitted instructions. Quit for now, we'll make a new block here later.
|
// We've already emitted instructions. Quit for now, we'll make a new block here later.
|
||||||
v.cond_state = ConditionalState::Break;
|
v.cond_state = ConditionalState::Break;
|
||||||
v.ir.SetTerm(IR::Term::LinkBlockFast{v.ir.current_location});
|
v.ir.SetTerm(IR::Term::LinkBlockFast{v.ir.current_location});
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
* Copyright (c) 2018 MerryMage
|
* Copyright (c) 2018 MerryMage
|
||||||
* SPDX-License-Identifier: 0BSD
|
* SPDX-License-Identifier: 0BSD
|
||||||
@@ -126,7 +129,7 @@ bool TranslatorVisitor::MRS(Imm<1> o0, Imm<3> op1, Imm<4> CRn, Imm<4> CRm, Imm<3
|
|||||||
return true;
|
return true;
|
||||||
case SystemRegisterEncoding::CNTPCT_EL0:
|
case SystemRegisterEncoding::CNTPCT_EL0:
|
||||||
// HACK: Ensure that this is the first instruction in the block it's emitted in, so the cycle count is most up-to-date.
|
// HACK: Ensure that this is the first instruction in the block it's emitted in, so the cycle count is most up-to-date.
|
||||||
if (!ir.block.empty() && !options.wall_clock_cntpct) {
|
if (!ir.block.instructions.empty() && !options.wall_clock_cntpct) {
|
||||||
ir.block.CycleCount()--;
|
ir.block.CycleCount()--;
|
||||||
ir.SetTerm(IR::Term::LinkBlock{*ir.current_location});
|
ir.SetTerm(IR::Term::LinkBlock{*ir.current_location});
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -100,57 +100,39 @@ std::string DumpBlock(const IR::Block& block) noexcept {
|
|||||||
std::string ret = fmt::format("Block: location={}-{}\n", block.Location(), block.EndLocation())
|
std::string ret = fmt::format("Block: location={}-{}\n", block.Location(), block.EndLocation())
|
||||||
+ fmt::format("cycles={}", block.CycleCount())
|
+ fmt::format("cycles={}", block.CycleCount())
|
||||||
+ fmt::format(", entry_cond={}", A64::CondToString(block.GetCondition()));
|
+ fmt::format(", entry_cond={}", A64::CondToString(block.GetCondition()));
|
||||||
if (block.GetCondition() != Cond::AL) {
|
if (block.GetCondition() != Cond::AL)
|
||||||
ret += fmt::format(", cond_fail={}", block.ConditionFailedLocation());
|
ret += fmt::format(", cond_fail={}", block.ConditionFailedLocation());
|
||||||
}
|
|
||||||
ret += '\n';
|
ret += '\n';
|
||||||
|
|
||||||
const auto arg_to_string = [](const IR::Value& arg) -> std::string {
|
const auto arg_to_string = [](const IR::Value& arg) -> std::string {
|
||||||
if (arg.IsEmpty()) {
|
if (arg.IsEmpty()) {
|
||||||
return "<null>";
|
return "<null>";
|
||||||
} else if (!arg.IsImmediate()) {
|
} else if (!arg.IsImmediate()) {
|
||||||
if (const unsigned name = arg.GetInst()->GetName()) {
|
if (auto const name = arg.GetInst()->GetName())
|
||||||
return fmt::format("%{}", name);
|
return fmt::format("%{}", name);
|
||||||
}
|
return fmt::format("%<unnamed inst {:016x}>", u64(arg.GetInst()));
|
||||||
return fmt::format("%<unnamed inst {:016x}>", reinterpret_cast<u64>(arg.GetInst()));
|
|
||||||
}
|
}
|
||||||
switch (arg.GetType()) {
|
switch (arg.GetType()) {
|
||||||
case Type::U1:
|
case Type::U1: return fmt::format("#{}", arg.GetU1() ? '1' : '0');
|
||||||
return fmt::format("#{}", arg.GetU1() ? '1' : '0');
|
case Type::U8: return fmt::format("#{}", arg.GetU8());
|
||||||
case Type::U8:
|
case Type::U16: return fmt::format("#{:#x}", arg.GetU16());
|
||||||
return fmt::format("#{}", arg.GetU8());
|
case Type::U32: return fmt::format("#{:#x}", arg.GetU32());
|
||||||
case Type::U16:
|
case Type::U64: return fmt::format("#{:#x}", arg.GetU64());
|
||||||
return fmt::format("#{:#x}", arg.GetU16());
|
case Type::U128: return fmt::format("#<u128 imm>");
|
||||||
case Type::U32:
|
case Type::A32Reg: return A32::RegToString(arg.GetA32RegRef());
|
||||||
return fmt::format("#{:#x}", arg.GetU32());
|
case Type::A32ExtReg: return A32::ExtRegToString(arg.GetA32ExtRegRef());
|
||||||
case Type::U64:
|
case Type::A64Reg: return A64::RegToString(arg.GetA64RegRef());
|
||||||
return fmt::format("#{:#x}", arg.GetU64());
|
case Type::A64Vec: return A64::VecToString(arg.GetA64VecRef());
|
||||||
case Type::U128:
|
case Type::CoprocInfo: return fmt::format("#<coproc>");
|
||||||
return fmt::format("#<u128 imm>");
|
case Type::NZCVFlags: return fmt::format("#<NZCV flags>");
|
||||||
case Type::A32Reg:
|
case Type::Cond: return fmt::format("#<cond={}>", A32::CondToString(arg.GetCond()));
|
||||||
return A32::RegToString(arg.GetA32RegRef());
|
case Type::Table: return fmt::format("#<table>");
|
||||||
case Type::A32ExtReg:
|
case Type::AccType: return fmt::format("#<acc-type={}>", u32(arg.GetAccType()));
|
||||||
return A32::ExtRegToString(arg.GetA32ExtRegRef());
|
default: return fmt::format("<unknown immediate type {}>", arg.GetType());
|
||||||
case Type::A64Reg:
|
|
||||||
return A64::RegToString(arg.GetA64RegRef());
|
|
||||||
case Type::A64Vec:
|
|
||||||
return A64::VecToString(arg.GetA64VecRef());
|
|
||||||
case Type::CoprocInfo:
|
|
||||||
return fmt::format("#<coproc>");
|
|
||||||
case Type::NZCVFlags:
|
|
||||||
return fmt::format("#<NZCV flags>");
|
|
||||||
case Type::Cond:
|
|
||||||
return fmt::format("#<cond={}>", A32::CondToString(arg.GetCond()));
|
|
||||||
case Type::Table:
|
|
||||||
return fmt::format("#<table>");
|
|
||||||
case Type::AccType:
|
|
||||||
return fmt::format("#<acc-type={}>", u32(arg.GetAccType()));
|
|
||||||
default:
|
|
||||||
return fmt::format("<unknown immediate type {}>", arg.GetType());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto& inst : block) {
|
for (const auto& inst : block.instructions) {
|
||||||
const Opcode op = inst.GetOpcode();
|
const Opcode op = inst.GetOpcode();
|
||||||
|
|
||||||
ret += fmt::format("[{:016x}] ", reinterpret_cast<u64>(&inst));
|
ret += fmt::format("[{:016x}] ", reinterpret_cast<u64>(&inst));
|
||||||
@@ -180,13 +162,9 @@ std::string DumpBlock(const IR::Block& block) noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret += fmt::format(" (uses: {})", inst.UseCount());
|
ret += fmt::format(" (uses: {})", inst.UseCount()) + '\n';
|
||||||
|
|
||||||
ret += '\n';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret += "terminal = " + TerminalToString(block.GetTerminal()) + '\n';
|
ret += "terminal = " + TerminalToString(block.GetTerminal()) + '\n';
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -23,7 +23,6 @@
|
|||||||
#include "dynarmic/ir/microinstruction.h"
|
#include "dynarmic/ir/microinstruction.h"
|
||||||
#include "dynarmic/ir/terminal.h"
|
#include "dynarmic/ir/terminal.h"
|
||||||
#include "dynarmic/ir/value.h"
|
#include "dynarmic/ir/value.h"
|
||||||
#include "dynarmic/ir/dense_list.h"
|
|
||||||
|
|
||||||
namespace Dynarmic::IR {
|
namespace Dynarmic::IR {
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ enum class Opcode;
|
|||||||
/// Note that this is a linear IR and not a pure tree-based IR: i.e.: there is an ordering to
|
/// Note that this is a linear IR and not a pure tree-based IR: i.e.: there is an ordering to
|
||||||
/// the microinstructions. This only matters before chaining is done in order to correctly
|
/// the microinstructions. This only matters before chaining is done in order to correctly
|
||||||
/// order memory accesses.
|
/// order memory accesses.
|
||||||
class Block final {
|
class alignas(4096) Block final {
|
||||||
public:
|
public:
|
||||||
//using instruction_list_type = dense_list<Inst>;
|
//using instruction_list_type = dense_list<Inst>;
|
||||||
using instruction_list_type = mcl::intrusive_list<Inst>;
|
using instruction_list_type = mcl::intrusive_list<Inst>;
|
||||||
@@ -51,37 +50,12 @@ public:
|
|||||||
Block(Block&&) = default;
|
Block(Block&&) = default;
|
||||||
Block& operator=(Block&&) = default;
|
Block& operator=(Block&&) = default;
|
||||||
|
|
||||||
bool empty() const { return instructions.empty(); }
|
|
||||||
size_type size() const { return instructions.size(); }
|
|
||||||
|
|
||||||
Inst& front() { return instructions.front(); }
|
|
||||||
const Inst& front() const { return instructions.front(); }
|
|
||||||
|
|
||||||
Inst& back() { return instructions.back(); }
|
|
||||||
const Inst& back() const { return instructions.back(); }
|
|
||||||
|
|
||||||
iterator begin() { return instructions.begin(); }
|
|
||||||
const_iterator begin() const { return instructions.begin(); }
|
|
||||||
iterator end() { return instructions.end(); }
|
|
||||||
const_iterator end() const { return instructions.end(); }
|
|
||||||
|
|
||||||
reverse_iterator rbegin() { return instructions.rbegin(); }
|
|
||||||
const_reverse_iterator rbegin() const { return instructions.rbegin(); }
|
|
||||||
reverse_iterator rend() { return instructions.rend(); }
|
|
||||||
const_reverse_iterator rend() const { return instructions.rend(); }
|
|
||||||
|
|
||||||
const_iterator cbegin() const { return instructions.cbegin(); }
|
|
||||||
const_iterator cend() const { return instructions.cend(); }
|
|
||||||
|
|
||||||
const_reverse_iterator crbegin() const { return instructions.crbegin(); }
|
|
||||||
const_reverse_iterator crend() const { return instructions.crend(); }
|
|
||||||
|
|
||||||
/// Appends a new instruction to the end of this basic block,
|
/// Appends a new instruction to the end of this basic block,
|
||||||
/// handling any allocations necessary to do so.
|
/// handling any allocations necessary to do so.
|
||||||
/// @param op Opcode representing the instruction to add.
|
/// @param op Opcode representing the instruction to add.
|
||||||
/// @param args A sequence of Value instances used as arguments for the instruction.
|
/// @param args A sequence of Value instances used as arguments for the instruction.
|
||||||
inline void AppendNewInst(const Opcode opcode, const std::initializer_list<IR::Value> args) noexcept {
|
inline iterator AppendNewInst(const Opcode opcode, const std::initializer_list<IR::Value> args) noexcept {
|
||||||
PrependNewInst(end(), opcode, args);
|
return PrependNewInst(instructions.end(), opcode, args);
|
||||||
}
|
}
|
||||||
iterator PrependNewInst(iterator insertion_point, Opcode op, std::initializer_list<Value> args) noexcept;
|
iterator PrependNewInst(iterator insertion_point, Opcode op, std::initializer_list<Value> args) noexcept;
|
||||||
|
|
||||||
@@ -165,9 +139,9 @@ public:
|
|||||||
inline const size_t& CycleCount() const noexcept {
|
inline const size_t& CycleCount() const noexcept {
|
||||||
return cycle_count;
|
return cycle_count;
|
||||||
}
|
}
|
||||||
private:
|
|
||||||
/// "Hot cache" for small blocks so we don't call global allocator
|
/// "Hot cache" for small blocks so we don't call global allocator
|
||||||
boost::container::static_vector<Inst, 14> inlined_inst;
|
boost::container::static_vector<Inst, 30> inlined_inst;
|
||||||
/// List of instructions in this block.
|
/// List of instructions in this block.
|
||||||
instruction_list_type instructions;
|
instruction_list_type instructions;
|
||||||
/// "Long/far" memory pool
|
/// "Long/far" memory pool
|
||||||
@@ -187,7 +161,7 @@ private:
|
|||||||
/// Number of cycles this block takes to execute.
|
/// Number of cycles this block takes to execute.
|
||||||
size_t cycle_count = 0;
|
size_t cycle_count = 0;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(Block) == 2048);
|
static_assert(sizeof(Block) == 4096);
|
||||||
|
|
||||||
/// Returns a string representation of the contents of block. Intended for debugging.
|
/// Returns a string representation of the contents of block. Intended for debugging.
|
||||||
std::string DumpBlock(const IR::Block& block) noexcept;
|
std::string DumpBlock(const IR::Block& block) noexcept;
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <deque>
|
|
||||||
|
|
||||||
namespace Dynarmic {
|
|
||||||
template<typename T> struct dense_list {
|
|
||||||
using difference_type = std::ptrdiff_t;
|
|
||||||
using size_type = std::size_t;
|
|
||||||
using value_type = T;
|
|
||||||
using pointer = value_type*;
|
|
||||||
using const_pointer = const value_type*;
|
|
||||||
using reference = value_type&;
|
|
||||||
using const_reference = const value_type&;
|
|
||||||
using iterator = typename std::deque<value_type>::iterator;
|
|
||||||
using const_iterator = typename std::deque<value_type>::const_iterator;
|
|
||||||
using reverse_iterator = typename std::reverse_iterator<iterator>;
|
|
||||||
using const_reverse_iterator = typename std::reverse_iterator<const_iterator>;
|
|
||||||
|
|
||||||
inline bool empty() const noexcept { return list.empty(); }
|
|
||||||
inline size_type size() const noexcept { return list.size(); }
|
|
||||||
|
|
||||||
inline value_type& front() noexcept { return list.front(); }
|
|
||||||
inline const value_type& front() const noexcept { return list.front(); }
|
|
||||||
|
|
||||||
inline value_type& back() noexcept { return list.back(); }
|
|
||||||
inline const value_type& back() const noexcept { return list.back(); }
|
|
||||||
|
|
||||||
inline iterator begin() noexcept { return list.begin(); }
|
|
||||||
inline const_iterator begin() const noexcept { return list.begin(); }
|
|
||||||
inline iterator end() noexcept { return list.end(); }
|
|
||||||
inline const_iterator end() const noexcept { return list.end(); }
|
|
||||||
|
|
||||||
inline reverse_iterator rbegin() noexcept { return list.rbegin(); }
|
|
||||||
inline const_reverse_iterator rbegin() const noexcept { return list.rbegin(); }
|
|
||||||
inline reverse_iterator rend() noexcept { return list.rend(); }
|
|
||||||
inline const_reverse_iterator rend() const noexcept { return list.rend(); }
|
|
||||||
|
|
||||||
inline const_iterator cbegin() const noexcept { return list.cbegin(); }
|
|
||||||
inline const_iterator cend() const noexcept { return list.cend(); }
|
|
||||||
|
|
||||||
inline const_reverse_iterator crbegin() const noexcept { return list.crbegin(); }
|
|
||||||
inline const_reverse_iterator crend() const noexcept { return list.crend(); }
|
|
||||||
|
|
||||||
inline iterator insert_before(iterator it, value_type& value) noexcept {
|
|
||||||
if (it == list.begin()) {
|
|
||||||
list.push_front(value);
|
|
||||||
return list.begin();
|
|
||||||
}
|
|
||||||
auto const index = std::distance(list.begin(), it - 1);
|
|
||||||
list.insert(it - 1, value);
|
|
||||||
return list.begin() + index;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::deque<value_type> list;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -65,15 +65,12 @@ enum class MemOp {
|
|||||||
PREFETCH,
|
PREFETCH,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/// @brief Convenience class to construct a basic block of the intermediate representation.
|
||||||
* Convenience class to construct a basic block of the intermediate representation.
|
/// `block` is the resulting block.
|
||||||
* `block` is the resulting block.
|
/// The user of this class updates `current_location` as appropriate.
|
||||||
* The user of this class updates `current_location` as appropriate.
|
|
||||||
*/
|
|
||||||
class IREmitter {
|
class IREmitter {
|
||||||
public:
|
public:
|
||||||
explicit IREmitter(Block& block)
|
explicit IREmitter(Block& block) : block(block), insertion_point(block.instructions.end()) {}
|
||||||
: block(block), insertion_point(block.end()) {}
|
|
||||||
|
|
||||||
Block& block;
|
Block& block;
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
namespace Dynarmic::Optimization {
|
namespace Dynarmic::Optimization {
|
||||||
|
|
||||||
static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) {
|
static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) {
|
||||||
for (auto& inst : block) {
|
for (auto& inst : block.instructions) {
|
||||||
switch (inst.GetOpcode()) {
|
switch (inst.GetOpcode()) {
|
||||||
case IR::Opcode::A32ReadMemory8:
|
case IR::Opcode::A32ReadMemory8:
|
||||||
case IR::Opcode::A64ReadMemory8: {
|
case IR::Opcode::A64ReadMemory8: {
|
||||||
@@ -131,7 +131,7 @@ static void FlagsPass(IR::Block& block) {
|
|||||||
|
|
||||||
A32::IREmitter ir{block, A32::LocationDescriptor{block.Location()}, {}};
|
A32::IREmitter ir{block, A32::LocationDescriptor{block.Location()}, {}};
|
||||||
|
|
||||||
for (auto inst = block.rbegin(); inst != block.rend(); ++inst) {
|
for (auto inst = block.instructions.rbegin(); inst != block.instructions.rend(); ++inst) {
|
||||||
auto const opcode = inst->GetOpcode();
|
auto const opcode = inst->GetOpcode();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case IR::Opcode::A32GetCFlag: {
|
case IR::Opcode::A32GetCFlag: {
|
||||||
@@ -318,7 +318,7 @@ static void RegisterPass(IR::Block& block) {
|
|||||||
// Location and version don't matter here.
|
// Location and version don't matter here.
|
||||||
A32::IREmitter ir{block, A32::LocationDescriptor{block.Location()}, {}};
|
A32::IREmitter ir{block, A32::LocationDescriptor{block.Location()}, {}};
|
||||||
|
|
||||||
for (auto inst = block.begin(); inst != block.end(); ++inst) {
|
for (auto inst = block.instructions.begin(); inst != block.instructions.end(); ++inst) {
|
||||||
auto const opcode = inst->GetOpcode();
|
auto const opcode = inst->GetOpcode();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case IR::Opcode::A32GetRegister: {
|
case IR::Opcode::A32GetRegister: {
|
||||||
@@ -448,7 +448,7 @@ static void A64CallbackConfigPass(IR::Block& block, const A64::UserConfig& conf)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& inst : block) {
|
for (auto& inst : block.instructions) {
|
||||||
if (inst.GetOpcode() != IR::Opcode::A64DataCacheOperationRaised) {
|
if (inst.GetOpcode() != IR::Opcode::A64DataCacheOperationRaised) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -541,7 +541,7 @@ static void A64GetSetElimination(IR::Block& block) {
|
|||||||
do_nothing();
|
do_nothing();
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto inst = block.begin(); inst != block.end(); ++inst) {
|
for (auto inst = block.instructions.begin(); inst != block.instructions.end(); ++inst) {
|
||||||
auto const opcode = inst->GetOpcode();
|
auto const opcode = inst->GetOpcode();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case IR::Opcode::A64GetW: {
|
case IR::Opcode::A64GetW: {
|
||||||
@@ -1041,7 +1041,7 @@ static void FoldZeroExtendXToLong(IR::Inst& inst) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void ConstantPropagation(IR::Block& block) {
|
static void ConstantPropagation(IR::Block& block) {
|
||||||
for (auto& inst : block) {
|
for (auto& inst : block.instructions) {
|
||||||
auto const opcode = inst.GetOpcode();
|
auto const opcode = inst.GetOpcode();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case Op::LeastSignificantWord:
|
case Op::LeastSignificantWord:
|
||||||
@@ -1221,25 +1221,20 @@ static void ConstantPropagation(IR::Block& block) {
|
|||||||
static void DeadCodeElimination(IR::Block& block) {
|
static void DeadCodeElimination(IR::Block& block) {
|
||||||
// We iterate over the instructions in reverse order.
|
// We iterate over the instructions in reverse order.
|
||||||
// This is because removing an instruction reduces the number of uses for earlier instructions.
|
// This is because removing an instruction reduces the number of uses for earlier instructions.
|
||||||
for (auto it = block.rbegin(); it != block.rend(); ++it)
|
for (auto it = block.instructions.rbegin(); it != block.instructions.rend(); ++it)
|
||||||
if (!it->HasUses() && !MayHaveSideEffects(it->GetOpcode()))
|
if (!it->HasUses() && !MayHaveSideEffects(it->GetOpcode()))
|
||||||
it->Invalidate();
|
it->Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void IdentityRemovalPass(IR::Block& block) {
|
static void IdentityRemovalPass(IR::Block& block) {
|
||||||
boost::container::small_vector<IR::Inst*, 16> to_invalidate;
|
boost::container::small_vector<IR::Inst*, 128> to_invalidate;
|
||||||
for (auto it = block.begin(); it != block.end();) {
|
for (auto it = block.instructions.begin(); it != block.instructions.end();) {
|
||||||
const size_t num_args = it->NumArgs();
|
auto const num_args = it->NumArgs();
|
||||||
for (size_t i = 0; i < num_args; ++i) {
|
for (size_t i = 0; i < num_args; ++i)
|
||||||
IR::Value arg = it->GetArg(i);
|
if (IR::Value arg = it->GetArg(i); arg.IsIdentity()) {
|
||||||
if (arg.IsIdentity()) {
|
do arg = arg.GetInst()->GetArg(0); while (arg.IsIdentity());
|
||||||
do {
|
|
||||||
arg = arg.GetInst()->GetArg(0);
|
|
||||||
} while (arg.IsIdentity());
|
|
||||||
it->SetArg(i, arg);
|
it->SetArg(i, arg);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (it->GetOpcode() == IR::Opcode::Identity || it->GetOpcode() == IR::Opcode::Void) {
|
if (it->GetOpcode() == IR::Opcode::Identity || it->GetOpcode() == IR::Opcode::Void) {
|
||||||
to_invalidate.push_back(&*it);
|
to_invalidate.push_back(&*it);
|
||||||
it = block.Instructions().erase(it);
|
it = block.Instructions().erase(it);
|
||||||
@@ -1253,7 +1248,7 @@ static void IdentityRemovalPass(IR::Block& block) {
|
|||||||
|
|
||||||
static void NamingPass(IR::Block& block) {
|
static void NamingPass(IR::Block& block) {
|
||||||
u32 name = 1;
|
u32 name = 1;
|
||||||
for (auto& inst : block)
|
for (auto& inst : block.instructions)
|
||||||
inst.SetName(name++);
|
inst.SetName(name++);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1402,7 +1397,7 @@ static void PolyfillPass(IR::Block& block, const PolyfillOptions& polyfill) {
|
|||||||
|
|
||||||
IR::IREmitter ir{block};
|
IR::IREmitter ir{block};
|
||||||
|
|
||||||
for (auto& inst : block) {
|
for (auto& inst : block.instructions) {
|
||||||
ir.SetInsertionPointBefore(&inst);
|
ir.SetInsertionPointBefore(&inst);
|
||||||
|
|
||||||
switch (inst.GetOpcode()) {
|
switch (inst.GetOpcode()) {
|
||||||
@@ -1458,7 +1453,7 @@ static void PolyfillPass(IR::Block& block, const PolyfillOptions& polyfill) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void VerificationPass(const IR::Block& block) {
|
static void VerificationPass(const IR::Block& block) {
|
||||||
for (auto const& inst : block) {
|
for (auto const& inst : block.instructions) {
|
||||||
for (size_t i = 0; i < inst.NumArgs(); i++) {
|
for (size_t i = 0; i < inst.NumArgs(); i++) {
|
||||||
const IR::Type t1 = inst.GetArg(i).GetType();
|
const IR::Type t1 = inst.GetArg(i).GetType();
|
||||||
const IR::Type t2 = IR::GetArgTypeOf(inst.GetOpcode(), i);
|
const IR::Type t2 = IR::GetArgTypeOf(inst.GetOpcode(), i);
|
||||||
@@ -1466,7 +1461,7 @@ static void VerificationPass(const IR::Block& block) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ankerl::unordered_dense::map<IR::Inst*, size_t> actual_uses;
|
ankerl::unordered_dense::map<IR::Inst*, size_t> actual_uses;
|
||||||
for (auto const& inst : block) {
|
for (auto const& inst : block.instructions) {
|
||||||
for (size_t i = 0; i < inst.NumArgs(); i++)
|
for (size_t i = 0; i < inst.NumArgs(); i++)
|
||||||
if (IR::Value const arg = inst.GetArg(i); !arg.IsImmediate())
|
if (IR::Value const arg = inst.GetArg(i); !arg.IsImmediate())
|
||||||
actual_uses[arg.GetInst()]++;
|
actual_uses[arg.GetInst()]++;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -93,7 +93,7 @@ bool ShouldTestInst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst, A
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& ir_inst : block) {
|
for (const auto& ir_inst : block.instructions) {
|
||||||
switch (ir_inst.GetOpcode()) {
|
switch (ir_inst.GetOpcode()) {
|
||||||
case IR::Opcode::A32ExceptionRaised:
|
case IR::Opcode::A32ExceptionRaised:
|
||||||
case IR::Opcode::A32CallSupervisor:
|
case IR::Opcode::A32CallSupervisor:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -45,7 +45,7 @@ static bool ShouldTestInst(u32 instruction, u64 pc, bool is_last_inst) {
|
|||||||
return false;
|
return false;
|
||||||
if (auto terminal = block.GetTerminal(); boost::get<IR::Term::Interpret>(&terminal))
|
if (auto terminal = block.GetTerminal(); boost::get<IR::Term::Interpret>(&terminal))
|
||||||
return false;
|
return false;
|
||||||
for (const auto& ir_inst : block) {
|
for (const auto& ir_inst : block.instructions) {
|
||||||
switch (ir_inst.GetOpcode()) {
|
switch (ir_inst.GetOpcode()) {
|
||||||
case IR::Opcode::A64ExceptionRaised:
|
case IR::Opcode::A64ExceptionRaised:
|
||||||
case IR::Opcode::A64CallSupervisor:
|
case IR::Opcode::A64CallSupervisor:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -36,9 +36,9 @@ TEST_CASE("ASIMD Decoder: Ensure table order correctness", "[decode][a32][.]") {
|
|||||||
|
|
||||||
const auto is_decode_error = [&get_ir](const A32::ASIMDMatcher<A32::TranslatorVisitor>& matcher, u32 instruction) {
|
const auto is_decode_error = [&get_ir](const A32::ASIMDMatcher<A32::TranslatorVisitor>& matcher, u32 instruction) {
|
||||||
const auto block = get_ir(matcher, instruction);
|
const auto block = get_ir(matcher, instruction);
|
||||||
return std::find_if(block.cbegin(), block.cend(), [](auto const& e) {
|
return std::find_if(block.instructions.cbegin(), block.instructions.cend(), [](auto const& e) {
|
||||||
return e.GetOpcode() == IR::Opcode::A32ExceptionRaised && A32::Exception(e.GetArg(1).GetU64()) == A32::Exception::DecodeError;
|
return e.GetOpcode() == IR::Opcode::A32ExceptionRaised && A32::Exception(e.GetArg(1).GetU64()) == A32::Exception::DecodeError;
|
||||||
}) != block.cend();
|
}) != block.instructions.cend();
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto iter = table.cbegin(); iter != table.cend(); ++iter) {
|
for (auto iter = table.cbegin(); iter != table.cend(); ++iter) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* This file is part of the dynarmic project.
|
/* This file is part of the dynarmic project.
|
||||||
@@ -54,7 +54,7 @@ bool ShouldTestInst(IR::Block& block) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& ir_inst : block) {
|
for (const auto& ir_inst : block.instructions) {
|
||||||
switch (ir_inst.GetOpcode()) {
|
switch (ir_inst.GetOpcode()) {
|
||||||
// A32
|
// A32
|
||||||
case IR::Opcode::A32GetFpscr:
|
case IR::Opcode::A32GetFpscr:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
@@ -12,7 +12,8 @@ add_library(frontend_common STATIC
|
|||||||
firmware_manager.cpp
|
firmware_manager.cpp
|
||||||
data_manager.h data_manager.cpp
|
data_manager.h data_manager.cpp
|
||||||
play_time_manager.cpp
|
play_time_manager.cpp
|
||||||
play_time_manager.h)
|
play_time_manager.h
|
||||||
|
settings_generator.h settings_generator.cpp)
|
||||||
|
|
||||||
if (ENABLE_UPDATE_CHECKER)
|
if (ENABLE_UPDATE_CHECKER)
|
||||||
target_link_libraries(frontend_common PRIVATE httplib::httplib)
|
target_link_libraries(frontend_common PRIVATE httplib::httplib)
|
||||||
@@ -29,4 +30,6 @@ if (ENABLE_UPDATE_CHECKER)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
create_target_directory_groups(frontend_common)
|
create_target_directory_groups(frontend_common)
|
||||||
target_link_libraries(frontend_common PUBLIC core SimpleIni::SimpleIni PRIVATE common Boost::headers)
|
target_link_libraries(frontend_common
|
||||||
|
PUBLIC core SimpleIni::SimpleIni frozen::frozen-headers
|
||||||
|
PRIVATE common Boost::headers)
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
#include <frozen/string.h>
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "settings_generator.h"
|
||||||
|
|
||||||
|
namespace FrontendCommon {
|
||||||
|
|
||||||
|
void GenerateSettings() {
|
||||||
|
static std::random_device rd;
|
||||||
|
|
||||||
|
// Web Token //
|
||||||
|
auto &token_setting = Settings::values.eden_token;
|
||||||
|
if (token_setting.GetValue().empty()) {
|
||||||
|
static constexpr const size_t token_length = 48;
|
||||||
|
static constexpr const frozen::string token_set = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
static std::uniform_int_distribution<int> token_dist(0, token_set.size() - 1);
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < token_length; ++i) {
|
||||||
|
size_t idx = token_dist(rd);
|
||||||
|
result += token_set[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
token_setting.SetValue(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace FrontendCommon {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief GenerateSettings Generate platform-specific or randomized settings.
|
||||||
|
* Run this function at initialization time for your frontend.
|
||||||
|
*/
|
||||||
|
void GenerateSettings();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "update_checker.h"
|
#include <boost/algorithm/string/classification.hpp>
|
||||||
|
#include <boost/algorithm/string/split.hpp>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include <fmt/format.h>
|
#include "update_checker.h"
|
||||||
|
|
||||||
#include <httplib.h>
|
#include <httplib.h>
|
||||||
|
|
||||||
@@ -45,8 +48,7 @@ std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::stri
|
|||||||
};
|
};
|
||||||
|
|
||||||
client->set_follow_location(true);
|
client->set_follow_location(true);
|
||||||
httplib::Result result;
|
httplib::Result result = client->send(request);
|
||||||
result = client->send(request);
|
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
LOG_ERROR(Frontend, "GET to {}{} returned null", url, path);
|
LOG_ERROR(Frontend, "GET to {}{} returned null", url, path);
|
||||||
@@ -78,7 +80,7 @@ std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prereleases) {
|
std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease(bool include_prereleases) {
|
||||||
const auto update_check_url = std::string{Common::g_build_auto_update_api};
|
const auto update_check_url = std::string{Common::g_build_auto_update_api};
|
||||||
std::string update_check_path = fmt::format("/repos/{}",
|
std::string update_check_path = fmt::format("/repos/{}",
|
||||||
std::string{Common::g_build_auto_update_repo});
|
std::string{Common::g_build_auto_update_repo});
|
||||||
@@ -96,6 +98,9 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
|
|||||||
|
|
||||||
const std::string latest_tag
|
const std::string latest_tag
|
||||||
= nlohmann::json::parse(tags_response.value()).at(0).at("name");
|
= nlohmann::json::parse(tags_response.value()).at(0).at("name");
|
||||||
|
const std::string latest_name =
|
||||||
|
nlohmann::json::parse(releases_response.value()).at(0).at("name");
|
||||||
|
|
||||||
const bool latest_tag_has_release = releases_response.value().find(
|
const bool latest_tag_has_release = releases_response.value().find(
|
||||||
fmt::format("\"{}\"", latest_tag))
|
fmt::format("\"{}\"", latest_tag))
|
||||||
!= std::string::npos;
|
!= std::string::npos;
|
||||||
@@ -105,7 +110,7 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
|
|||||||
if (!latest_tag_has_release)
|
if (!latest_tag_has_release)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return latest_tag;
|
return Update{latest_tag, latest_name};
|
||||||
} else { // This is a stable release, only check for other stable releases.
|
} else { // This is a stable release, only check for other stable releases.
|
||||||
update_check_path += "/releases/latest";
|
update_check_path += "/releases/latest";
|
||||||
const auto response = UpdateChecker::GetResponse(update_check_url, update_check_path);
|
const auto response = UpdateChecker::GetResponse(update_check_url, update_check_path);
|
||||||
@@ -114,7 +119,9 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
|
|||||||
return {};
|
return {};
|
||||||
|
|
||||||
const std::string latest_tag = nlohmann::json::parse(response.value()).at("tag_name");
|
const std::string latest_tag = nlohmann::json::parse(response.value()).at("tag_name");
|
||||||
return latest_tag;
|
const std::string latest_name = nlohmann::json::parse(response.value()).at("name");
|
||||||
|
|
||||||
|
return Update{latest_tag, latest_name};
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (nlohmann::detail::out_of_range &) {
|
} catch (nlohmann::detail::out_of_range &) {
|
||||||
@@ -133,3 +140,41 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<UpdateChecker::Update> UpdateChecker::GetUpdate() {
|
||||||
|
const bool is_prerelease = ((strstr(Common::g_build_version, "pre-alpha") != NULL) ||
|
||||||
|
(strstr(Common::g_build_version, "alpha") != NULL) ||
|
||||||
|
(strstr(Common::g_build_version, "beta") != NULL) ||
|
||||||
|
(strstr(Common::g_build_version, "rc") != NULL));
|
||||||
|
const std::optional<UpdateChecker::Update> latest_release_tag =
|
||||||
|
UpdateChecker::GetLatestRelease(is_prerelease);
|
||||||
|
|
||||||
|
if (!latest_release_tag)
|
||||||
|
goto empty;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string tag, build;
|
||||||
|
if (Common::g_is_nightly_build) {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
|
||||||
|
boost::split(result, latest_release_tag->tag, boost::is_any_of("."));
|
||||||
|
if (result.size() != 2)
|
||||||
|
goto empty;
|
||||||
|
tag = result[1];
|
||||||
|
|
||||||
|
boost::split(result, std::string{Common::g_build_version}, boost::is_any_of("-"));
|
||||||
|
if (result.empty())
|
||||||
|
goto empty;
|
||||||
|
build = result[0];
|
||||||
|
} else {
|
||||||
|
tag = latest_release_tag->tag;
|
||||||
|
build = Common::g_build_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag != build)
|
||||||
|
return latest_release_tag.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
empty:
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
@@ -11,6 +11,13 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace UpdateChecker {
|
namespace UpdateChecker {
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
std::string tag;
|
||||||
|
std::string name;
|
||||||
|
} Update;
|
||||||
|
|
||||||
std::optional<std::string> GetResponse(std::string url, std::string path);
|
std::optional<std::string> GetResponse(std::string url, std::string path);
|
||||||
std::optional<std::string> GetLatestRelease(bool include_prereleases);
|
std::optional<Update> GetLatestRelease(bool include_prereleases);
|
||||||
|
std::optional<Update> GetUpdate();
|
||||||
} // namespace UpdateChecker
|
} // namespace UpdateChecker
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
add_library(qt_common STATIC
|
add_library(qt_common STATIC
|
||||||
@@ -78,14 +78,9 @@ target_compile_definitions(qt_common PUBLIC
|
|||||||
QT_NO_URL_CAST_FROM_STRING
|
QT_NO_URL_CAST_FROM_STRING
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(externals)
|
|
||||||
|
|
||||||
# pass targets
|
|
||||||
find_package(frozen)
|
|
||||||
|
|
||||||
target_link_libraries(qt_common PRIVATE core Qt6::Core Qt6::Concurrent SimpleIni::SimpleIni QuaZip::QuaZip)
|
target_link_libraries(qt_common PRIVATE core Qt6::Core Qt6::Concurrent SimpleIni::SimpleIni QuaZip::QuaZip)
|
||||||
target_link_libraries(qt_common PUBLIC frozen::frozen-headers)
|
target_link_libraries(qt_common PUBLIC frozen::frozen-headers)
|
||||||
target_link_libraries(qt_common PRIVATE gamemode::headers)
|
target_link_libraries(qt_common PRIVATE gamemode::headers frontend_common)
|
||||||
|
|
||||||
if (NOT APPLE AND ENABLE_OPENGL)
|
if (NOT APPLE AND ENABLE_OPENGL)
|
||||||
target_compile_definitions(qt_common PUBLIC HAS_OPENGL)
|
target_compile_definitions(qt_common PUBLIC HAS_OPENGL)
|
||||||
|
|||||||
@@ -329,6 +329,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
|||||||
barrier_feedback_loops,
|
barrier_feedback_loops,
|
||||||
tr("Barrier feedback loops"),
|
tr("Barrier feedback loops"),
|
||||||
tr("Improves rendering of transparency effects in specific games."));
|
tr("Improves rendering of transparency effects in specific games."));
|
||||||
|
INSERT(Settings,
|
||||||
|
enable_buffer_history,
|
||||||
|
tr("Enable buffer history"),
|
||||||
|
tr("Enables access to previous buffer states.\nThis option may improve rendering quality and performance consistency in some games."));
|
||||||
INSERT(Settings,
|
INSERT(Settings,
|
||||||
fix_bloom_effects,
|
fix_bloom_effects,
|
||||||
tr("Fix bloom effects"),
|
tr("Fix bloom effects"),
|
||||||
|
|||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
include(CPMUtil)
|
|
||||||
|
|
||||||
# Disable tests/tools in all externals supporting the standard option name
|
|
||||||
set(BUILD_TESTING OFF)
|
|
||||||
|
|
||||||
# Build only static externals
|
|
||||||
set(BUILD_SHARED_LIBS OFF)
|
|
||||||
|
|
||||||
# Skip install rules for all externals
|
|
||||||
set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON)
|
|
||||||
|
|
||||||
# QuaZip
|
|
||||||
AddJsonPackage(quazip)
|
|
||||||
|
|
||||||
# frozen
|
|
||||||
AddJsonPackage(frozen)
|
|
||||||
Vendored
-18
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"quazip": {
|
|
||||||
"package": "QuaZip-Qt6",
|
|
||||||
"repo": "crueter/quazip-qt6",
|
|
||||||
"sha": "f838774d63",
|
|
||||||
"hash": "e8f950f47c1f358e2666f08517a9b5b06980677540d3836384e2c27ff5bb129b218f1502b03fdb207d7fd4cd56893f0a0d9094ba8309f19a49cb11e3bb911594",
|
|
||||||
"version": "1.3",
|
|
||||||
"options": [
|
|
||||||
"QUAZIP_INSTALL OFF"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"frozen": {
|
|
||||||
"package": "frozen",
|
|
||||||
"repo": "serge-sans-paille/frozen",
|
|
||||||
"sha": "61dce5ae18",
|
|
||||||
"hash": "b8dfe741c82bc178dfc9749d4ab5a130cee718d9ee7b71d9b547cf5f7f23027ed0152ad250012a8546399fcc1e12187efc68d89d6731256c4d2df7d04eef8d5c"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -148,10 +151,13 @@ Id EmitConvertU32U64(EmitContext& ctx, Id value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Id EmitConvertF16F32(EmitContext& ctx, Id value) {
|
Id EmitConvertF16F32(EmitContext& ctx, Id value) {
|
||||||
|
#ifdef ANDROID
|
||||||
|
return ctx.OpFConvert(ctx.F16[1], value);
|
||||||
|
#else
|
||||||
const auto result = ctx.OpFConvert(ctx.F16[1], value);
|
const auto result = ctx.OpFConvert(ctx.F16[1], value);
|
||||||
const auto isOverflowing = ctx.OpIsNan(ctx.U1, result);
|
const auto isOverflowing = ctx.OpIsNan(ctx.U1, result);
|
||||||
return ctx.OpSelect(ctx.F16[1], isOverflowing, ctx.Constant(ctx.F16[1], 0), result);
|
return ctx.OpSelect(ctx.F16[1], isOverflowing, ctx.Constant(ctx.F16[1], 0), result);
|
||||||
//return ctx.OpFConvert(ctx.F16[1], value);
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Id EmitConvertF32F16(EmitContext& ctx, Id value) {
|
Id EmitConvertF32F16(EmitContext& ctx, Id value) {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -16,7 +19,7 @@ Block::Block(ObjectPool<Inst>& inst_pool_) : inst_pool{&inst_pool_} {}
|
|||||||
Block::~Block() = default;
|
Block::~Block() = default;
|
||||||
|
|
||||||
void Block::AppendNewInst(Opcode op, std::initializer_list<Value> args) {
|
void Block::AppendNewInst(Opcode op, std::initializer_list<Value> args) {
|
||||||
PrependNewInst(end(), op, args);
|
PrependNewInst(instructions.end(), op, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
Block::iterator Block::PrependNewInst(iterator insertion_point, const Inst& base_inst) {
|
Block::iterator Block::PrependNewInst(iterator insertion_point, const Inst& base_inst) {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
add_subdirectory(host_shaders)
|
add_subdirectory(host_shaders)
|
||||||
|
add_subdirectory(gpu_logging)
|
||||||
|
|
||||||
if(LIBVA_FOUND)
|
if(LIBVA_FOUND)
|
||||||
set_source_files_properties(host1x/ffmpeg/ffmpeg.cpp
|
set_source_files_properties(host1x/ffmpeg/ffmpeg.cpp
|
||||||
@@ -313,7 +314,7 @@ add_library(video_core STATIC
|
|||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(video_core PUBLIC common core)
|
target_link_libraries(video_core PUBLIC common core)
|
||||||
target_link_libraries(video_core PUBLIC glad shader_recompiler stb bc_decoder)
|
target_link_libraries(video_core PUBLIC glad shader_recompiler stb bc_decoder gpu_logging)
|
||||||
|
|
||||||
if (YUZU_USE_EXTERNAL_FFMPEG)
|
if (YUZU_USE_EXTERNAL_FFMPEG)
|
||||||
add_dependencies(video_core ffmpeg-build)
|
add_dependencies(video_core ffmpeg-build)
|
||||||
@@ -330,7 +331,7 @@ target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
|
|||||||
target_link_libraries(video_core PRIVATE sirit::sirit)
|
target_link_libraries(video_core PRIVATE sirit::sirit)
|
||||||
|
|
||||||
# Header-only stuff needed by all dependent targets
|
# Header-only stuff needed by all dependent targets
|
||||||
target_link_libraries(video_core PUBLIC Vulkan::UtilityHeaders GPUOpen::VulkanMemoryAllocator)
|
target_link_libraries(video_core PUBLIC Vulkan::Headers Vulkan::UtilityHeaders GPUOpen::VulkanMemoryAllocator)
|
||||||
|
|
||||||
if (ENABLE_NSIGHT_AFTERMATH)
|
if (ENABLE_NSIGHT_AFTERMATH)
|
||||||
if (NOT DEFINED ENV{NSIGHT_AFTERMATH_SDK})
|
if (NOT DEFINED ENV{NSIGHT_AFTERMATH_SDK})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
@@ -29,124 +29,104 @@ class MemoryTrackerBase {
|
|||||||
static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - HIGHER_PAGE_BITS);
|
static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - HIGHER_PAGE_BITS);
|
||||||
static constexpr size_t MANAGER_POOL_SIZE = 32;
|
static constexpr size_t MANAGER_POOL_SIZE = 32;
|
||||||
static constexpr size_t WORDS_STACK_NEEDED = HIGHER_PAGE_SIZE / BYTES_PER_WORD;
|
static constexpr size_t WORDS_STACK_NEEDED = HIGHER_PAGE_SIZE / BYTES_PER_WORD;
|
||||||
using Manager = WordManager<DeviceTracker, WORDS_STACK_NEEDED>;
|
using Manager = WordManager<DeviceTracker, WORDS_STACK_NEEDED, HIGHER_PAGE_SIZE>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MemoryTrackerBase(DeviceTracker& device_tracker_) : device_tracker{&device_tracker_} {}
|
MemoryTrackerBase(DeviceTracker& device_tracker_) : device_tracker{&device_tracker_} {}
|
||||||
~MemoryTrackerBase() = default;
|
~MemoryTrackerBase() = default;
|
||||||
|
|
||||||
/// Returns the inclusive CPU modified range in a begin end pair
|
/// Returns the inclusive CPU modified range in a begin end pair
|
||||||
[[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr,
|
[[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||||
u64 query_size) noexcept {
|
return IteratePairs<true>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||||
return IteratePairs<true>(
|
return manager->ModifiedRegion(Type::CPU, offset, size);
|
||||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
});
|
||||||
return manager->template ModifiedRegion<Type::CPU>(offset, size);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the inclusive GPU modified range in a begin end pair
|
/// Returns the inclusive GPU modified range in a begin end pair
|
||||||
[[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr,
|
[[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||||
u64 query_size) noexcept {
|
return IteratePairs<false>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||||
return IteratePairs<false>(
|
return manager->ModifiedRegion(Type::GPU, offset, size);
|
||||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
});
|
||||||
return manager->template ModifiedRegion<Type::GPU>(offset, size);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if a region has been modified from the CPU
|
/// Returns true if a region has been modified from the CPU
|
||||||
[[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
|
[[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||||
return IteratePages<true>(
|
return IteratePages<true>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
return manager->IsRegionModified(Type::CPU, offset, size);
|
||||||
return manager->template IsRegionModified<Type::CPU>(offset, size);
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if a region has been modified from the GPU
|
/// Returns true if a region has been modified from the GPU
|
||||||
[[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
|
[[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||||
return IteratePages<false>(
|
return IteratePages<false>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
return manager->IsRegionModified(Type::GPU, offset, size);
|
||||||
return manager->template IsRegionModified<Type::GPU>(offset, size);
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if a region has been marked as Preflushable
|
/// Returns true if a region has been marked as Preflushable
|
||||||
[[nodiscard]] bool IsRegionPreflushable(VAddr query_cpu_addr, u64 query_size) noexcept {
|
[[nodiscard]] bool IsRegionPreflushable(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||||
return IteratePages<false>(
|
return IteratePages<false>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
return manager->IsRegionModified(Type::Preflushable, offset, size);
|
||||||
return manager->template IsRegionModified<Type::Preflushable>(offset, size);
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark region as CPU modified, notifying the device_tracker about this change
|
/// Mark region as CPU modified, notifying the device_tracker about this change
|
||||||
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
|
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
|
||||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||||
[](Manager* manager, u64 offset, size_t size) {
|
manager->ChangeRegionState(Type::CPU, true, manager->cpu_addr + offset, size);
|
||||||
manager->template ChangeRegionState<Type::CPU, true>(
|
});
|
||||||
manager->GetCpuAddr() + offset, size);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unmark region as CPU modified, notifying the device_tracker about this change
|
/// Unmark region as CPU modified, notifying the device_tracker about this change
|
||||||
void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
|
void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
|
||||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||||
[](Manager* manager, u64 offset, size_t size) {
|
manager->ChangeRegionState(Type::CPU, false, manager->cpu_addr + offset, size);
|
||||||
manager->template ChangeRegionState<Type::CPU, false>(
|
});
|
||||||
manager->GetCpuAddr() + offset, size);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark region as modified from the host GPU
|
/// Mark region as modified from the host GPU
|
||||||
void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
||||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||||
[](Manager* manager, u64 offset, size_t size) {
|
manager->ChangeRegionState(Type::GPU, true, manager->cpu_addr + offset, size);
|
||||||
manager->template ChangeRegionState<Type::GPU, true>(
|
});
|
||||||
manager->GetCpuAddr() + offset, size);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark region as modified from the host GPU
|
/// Mark region as modified from the host GPU
|
||||||
void MarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
void MarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
||||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||||
[](Manager* manager, u64 offset, size_t size) {
|
manager->ChangeRegionState(Type::Preflushable, true, manager->cpu_addr + offset, size);
|
||||||
manager->template ChangeRegionState<Type::Preflushable, true>(
|
});
|
||||||
manager->GetCpuAddr() + offset, size);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unmark region as modified from the host GPU
|
/// Unmark region as modified from the host GPU
|
||||||
void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
||||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||||
[](Manager* manager, u64 offset, size_t size) {
|
manager->ChangeRegionState(Type::GPU, false, manager->cpu_addr + offset, size);
|
||||||
manager->template ChangeRegionState<Type::GPU, false>(
|
});
|
||||||
manager->GetCpuAddr() + offset, size);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unmark region as modified from the host GPU
|
/// Unmark region as modified from the host GPU
|
||||||
void UnmarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
void UnmarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
||||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||||
[](Manager* manager, u64 offset, size_t size) {
|
manager->ChangeRegionState(Type::Preflushable, false, manager->cpu_addr + offset, size);
|
||||||
manager->template ChangeRegionState<Type::Preflushable, false>(
|
});
|
||||||
manager->GetCpuAddr() + offset, size);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark region as modified from the CPU
|
/// Mark region as modified from the CPU
|
||||||
/// but don't mark it as modified until FlusHCachedWrites is called.
|
/// but don't mark it as modified until FlusHCachedWrites is called.
|
||||||
void CachedCpuWrite(VAddr dirty_cpu_addr, u64 query_size) {
|
void CachedCpuWrite(VAddr dirty_cpu_addr, u64 query_size) {
|
||||||
IteratePages<true>(
|
IteratePages<true>(dirty_cpu_addr, query_size, [this](Manager* manager, u64 offset, size_t size) {
|
||||||
dirty_cpu_addr, query_size, [this](Manager* manager, u64 offset, size_t size) {
|
const VAddr cpu_address = manager->cpu_addr + offset;
|
||||||
const VAddr cpu_address = manager->GetCpuAddr() + offset;
|
manager->ChangeRegionState(Type::CachedCPU, true, cpu_address, size);
|
||||||
manager->template ChangeRegionState<Type::CachedCPU, true>(cpu_address, size);
|
cached_pages.insert(u32(cpu_address >> HIGHER_PAGE_BITS));
|
||||||
cached_pages.insert(static_cast<u32>(cpu_address >> HIGHER_PAGE_BITS));
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Flushes cached CPU writes, and notify the device_tracker about the deltas
|
/// Flushes cached CPU writes, and notify the device_tracker about the deltas
|
||||||
void FlushCachedWrites(VAddr query_cpu_addr, u64 query_size) noexcept {
|
void FlushCachedWrites(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||||
IteratePages<false>(query_cpu_addr, query_size,
|
IteratePages<false>(query_cpu_addr, query_size, [](Manager* manager, [[maybe_unused]] u64 offset, [[maybe_unused]] size_t size) {
|
||||||
[](Manager* manager, [[maybe_unused]] u64 offset,
|
manager->FlushCachedWrites();
|
||||||
[[maybe_unused]] size_t size) { manager->FlushCachedWrites(); });
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlushCachedWrites() noexcept {
|
void FlushCachedWrites() noexcept {
|
||||||
@@ -159,35 +139,24 @@ public:
|
|||||||
/// Call 'func' for each CPU modified range and unmark those pages as CPU modified
|
/// Call 'func' for each CPU modified range and unmark those pages as CPU modified
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, Func&& func) {
|
void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, Func&& func) {
|
||||||
IteratePages<true>(query_cpu_range, query_size,
|
IteratePages<true>(query_cpu_range, query_size, [&func](Manager* manager, u64 offset, size_t size) {
|
||||||
[&func](Manager* manager, u64 offset, size_t size) {
|
manager->ForEachModifiedRange(Type::CPU, true, manager->cpu_addr + offset, size, func);
|
||||||
manager->template ForEachModifiedRange<Type::CPU, true>(
|
});
|
||||||
manager->GetCpuAddr() + offset, size, func);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call 'func' for each GPU modified range and unmark those pages as GPU modified
|
/// Call 'func' for each GPU modified range and unmark those pages as GPU modified
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
void ForEachDownloadRange(VAddr query_cpu_range, u64 query_size, bool clear, Func&& func) {
|
void ForEachDownloadRange(VAddr query_cpu_range, u64 query_size, bool clear, Func&& func) {
|
||||||
IteratePages<false>(query_cpu_range, query_size,
|
IteratePages<false>(query_cpu_range, query_size, [&func, clear](Manager* manager, u64 offset, size_t size) {
|
||||||
[&func, clear](Manager* manager, u64 offset, size_t size) {
|
manager->ForEachModifiedRange(Type::GPU, clear, manager->cpu_addr + offset, size, func);
|
||||||
if (clear) {
|
});
|
||||||
manager->template ForEachModifiedRange<Type::GPU, true>(
|
|
||||||
manager->GetCpuAddr() + offset, size, func);
|
|
||||||
} else {
|
|
||||||
manager->template ForEachModifiedRange<Type::GPU, false>(
|
|
||||||
manager->GetCpuAddr() + offset, size, func);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
void ForEachDownloadRangeAndClear(VAddr query_cpu_range, u64 query_size, Func&& func) {
|
void ForEachDownloadRangeAndClear(VAddr query_cpu_range, u64 query_size, Func&& func) {
|
||||||
IteratePages<false>(query_cpu_range, query_size,
|
IteratePages<false>(query_cpu_range, query_size, [&func](Manager* manager, u64 offset, size_t size) {
|
||||||
[&func](Manager* manager, u64 offset, size_t size) {
|
manager->ForEachModifiedRange(Type::GPU, true, manager->cpu_addr + offset, size, func);
|
||||||
manager->template ForEachModifiedRange<Type::GPU, true>(
|
});
|
||||||
manager->GetCpuAddr() + offset, size, func);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -271,31 +240,24 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Manager* GetNewManager(VAddr base_cpu_address) {
|
Manager* GetNewManager(VAddr base_cpu_address) {
|
||||||
const auto on_return = [&] {
|
if (free_managers.empty()) {
|
||||||
auto* new_manager = free_managers.front();
|
manager_pool.emplace_back();
|
||||||
new_manager->SetCpuAddress(base_cpu_address);
|
auto& last_pool = manager_pool.back();
|
||||||
free_managers.pop_front();
|
for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) {
|
||||||
return new_manager;
|
new (&last_pool[i]) Manager(0, *device_tracker);
|
||||||
};
|
free_managers.push_back(&last_pool[i]);
|
||||||
if (!free_managers.empty()) {
|
}
|
||||||
return on_return();
|
|
||||||
}
|
}
|
||||||
manager_pool.emplace_back();
|
Manager* new_manager = free_managers.front();
|
||||||
auto& last_pool = manager_pool.back();
|
new_manager->cpu_addr = base_cpu_address;
|
||||||
for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) {
|
free_managers.pop_front();
|
||||||
new (&last_pool[i]) Manager(0, *device_tracker, HIGHER_PAGE_SIZE);
|
return new_manager;
|
||||||
free_managers.push_back(&last_pool[i]);
|
|
||||||
}
|
|
||||||
return on_return();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::array<Manager*, NUM_HIGH_PAGES> top_tier{};
|
||||||
std::deque<std::array<Manager, MANAGER_POOL_SIZE>> manager_pool;
|
std::deque<std::array<Manager, MANAGER_POOL_SIZE>> manager_pool;
|
||||||
std::deque<Manager*> free_managers;
|
std::deque<Manager*> free_managers;
|
||||||
|
|
||||||
std::array<Manager*, NUM_HIGH_PAGES> top_tier{};
|
|
||||||
|
|
||||||
std::unordered_set<u32> cached_pages;
|
std::unordered_set<u32> cached_pages;
|
||||||
|
|
||||||
DeviceTracker* device_tracker = nullptr;
|
DeviceTracker* device_tracker = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
@@ -31,158 +31,26 @@ enum class Type {
|
|||||||
CachedCPU,
|
CachedCPU,
|
||||||
Untracked,
|
Untracked,
|
||||||
Preflushable,
|
Preflushable,
|
||||||
|
Max
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Vector tracking modified pages tightly packed with small vector optimization
|
template <class DeviceTracker, size_t stack_words, size_t size_bytes>
|
||||||
template <size_t stack_words = 1>
|
struct WordManager {
|
||||||
struct WordsArray {
|
static constexpr size_t num_words = Common::DivCeil(size_bytes, BYTES_PER_WORD);
|
||||||
/// Returns the pointer to the words state
|
|
||||||
[[nodiscard]] const u64* Pointer(bool is_short) const noexcept {
|
|
||||||
return is_short ? stack.data() : heap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the pointer to the words state
|
explicit WordManager(VAddr cpu_addr_, DeviceTracker& tracker_) : tracker{&tracker_}, cpu_addr{cpu_addr_} {
|
||||||
[[nodiscard]] u64* Pointer(bool is_short) noexcept {
|
std::fill_n(heap.data() + size_t(Type::CPU) * num_words, num_words, ~u64{0});
|
||||||
return is_short ? stack.data() : heap;
|
std::fill_n(heap.data() + size_t(Type::Untracked) * num_words, num_words, ~u64{0});
|
||||||
}
|
|
||||||
|
|
||||||
std::array<u64, stack_words> stack{}; ///< Small buffers storage
|
|
||||||
u64* heap; ///< Not-small buffers pointer to the storage
|
|
||||||
};
|
|
||||||
|
|
||||||
template <size_t stack_words = 1>
|
|
||||||
struct Words {
|
|
||||||
explicit Words() = default;
|
|
||||||
explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} {
|
|
||||||
num_words = Common::DivCeil(size_bytes, BYTES_PER_WORD);
|
|
||||||
if (IsShort()) {
|
|
||||||
cpu.stack.fill(~u64{0});
|
|
||||||
gpu.stack.fill(0);
|
|
||||||
cached_cpu.stack.fill(0);
|
|
||||||
untracked.stack.fill(~u64{0});
|
|
||||||
preflushable.stack.fill(0);
|
|
||||||
} else {
|
|
||||||
// Share allocation between CPU and GPU pages and set their default values
|
|
||||||
u64* const alloc = new u64[num_words * 5];
|
|
||||||
cpu.heap = alloc;
|
|
||||||
gpu.heap = alloc + num_words;
|
|
||||||
cached_cpu.heap = alloc + num_words * 2;
|
|
||||||
untracked.heap = alloc + num_words * 3;
|
|
||||||
preflushable.heap = alloc + num_words * 4;
|
|
||||||
std::fill_n(cpu.heap, num_words, ~u64{0});
|
|
||||||
std::fill_n(gpu.heap, num_words, 0);
|
|
||||||
std::fill_n(cached_cpu.heap, num_words, 0);
|
|
||||||
std::fill_n(untracked.heap, num_words, ~u64{0});
|
|
||||||
std::fill_n(preflushable.heap, num_words, 0);
|
|
||||||
}
|
|
||||||
// Clean up tailing bits
|
// Clean up tailing bits
|
||||||
const u64 last_word_size = size_bytes % BYTES_PER_WORD;
|
u64 const last_word_size = size_bytes % BYTES_PER_WORD;
|
||||||
const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
|
u64 const last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
|
||||||
const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
|
u64 const shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
|
||||||
const u64 last_word = (~u64{0} << shift) >> shift;
|
u64 const last_word = (~u64{0} << shift) >> shift;
|
||||||
cpu.Pointer(IsShort())[NumWords() - 1] = last_word;
|
heap[num_words * size_t(Type::CPU) + num_words - 1] = last_word;
|
||||||
untracked.Pointer(IsShort())[NumWords() - 1] = last_word;
|
heap[num_words * size_t(Type::Untracked) + num_words - 1] = last_word;
|
||||||
}
|
}
|
||||||
|
|
||||||
~Words() {
|
|
||||||
Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
Words& operator=(Words&& rhs) noexcept {
|
|
||||||
Release();
|
|
||||||
size_bytes = rhs.size_bytes;
|
|
||||||
num_words = rhs.num_words;
|
|
||||||
cpu = rhs.cpu;
|
|
||||||
gpu = rhs.gpu;
|
|
||||||
cached_cpu = rhs.cached_cpu;
|
|
||||||
untracked = rhs.untracked;
|
|
||||||
preflushable = rhs.preflushable;
|
|
||||||
rhs.cpu.heap = nullptr;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Words(Words&& rhs) noexcept
|
|
||||||
: size_bytes{rhs.size_bytes}, num_words{rhs.num_words}, cpu{rhs.cpu}, gpu{rhs.gpu},
|
|
||||||
cached_cpu{rhs.cached_cpu}, untracked{rhs.untracked}, preflushable{rhs.preflushable} {
|
|
||||||
rhs.cpu.heap = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Words& operator=(const Words&) = delete;
|
|
||||||
Words(const Words&) = delete;
|
|
||||||
|
|
||||||
/// Returns true when the buffer fits in the small vector optimization
|
|
||||||
[[nodiscard]] bool IsShort() const noexcept {
|
|
||||||
return num_words <= stack_words;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of words of the buffer
|
|
||||||
[[nodiscard]] size_t NumWords() const noexcept {
|
|
||||||
return num_words;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Release buffer resources
|
|
||||||
void Release() {
|
|
||||||
if (!IsShort()) {
|
|
||||||
// CPU written words is the base for the heap allocation
|
|
||||||
delete[] cpu.heap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <Type type>
|
|
||||||
std::span<u64> Span() noexcept {
|
|
||||||
if constexpr (type == Type::CPU) {
|
|
||||||
return std::span<u64>(cpu.Pointer(IsShort()), num_words);
|
|
||||||
} else if constexpr (type == Type::GPU) {
|
|
||||||
return std::span<u64>(gpu.Pointer(IsShort()), num_words);
|
|
||||||
} else if constexpr (type == Type::CachedCPU) {
|
|
||||||
return std::span<u64>(cached_cpu.Pointer(IsShort()), num_words);
|
|
||||||
} else if constexpr (type == Type::Untracked) {
|
|
||||||
return std::span<u64>(untracked.Pointer(IsShort()), num_words);
|
|
||||||
} else if constexpr (type == Type::Preflushable) {
|
|
||||||
return std::span<u64>(preflushable.Pointer(IsShort()), num_words);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <Type type>
|
|
||||||
std::span<const u64> Span() const noexcept {
|
|
||||||
if constexpr (type == Type::CPU) {
|
|
||||||
return std::span<const u64>(cpu.Pointer(IsShort()), num_words);
|
|
||||||
} else if constexpr (type == Type::GPU) {
|
|
||||||
return std::span<const u64>(gpu.Pointer(IsShort()), num_words);
|
|
||||||
} else if constexpr (type == Type::CachedCPU) {
|
|
||||||
return std::span<const u64>(cached_cpu.Pointer(IsShort()), num_words);
|
|
||||||
} else if constexpr (type == Type::Untracked) {
|
|
||||||
return std::span<const u64>(untracked.Pointer(IsShort()), num_words);
|
|
||||||
} else if constexpr (type == Type::Preflushable) {
|
|
||||||
return std::span<const u64>(preflushable.Pointer(IsShort()), num_words);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 size_bytes = 0;
|
|
||||||
size_t num_words = 0;
|
|
||||||
WordsArray<stack_words> cpu;
|
|
||||||
WordsArray<stack_words> gpu;
|
|
||||||
WordsArray<stack_words> cached_cpu;
|
|
||||||
WordsArray<stack_words> untracked;
|
|
||||||
WordsArray<stack_words> preflushable;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class DeviceTracker, size_t stack_words = 1>
|
|
||||||
class WordManager {
|
|
||||||
public:
|
|
||||||
explicit WordManager(VAddr cpu_addr_, DeviceTracker& tracker_, u64 size_bytes)
|
|
||||||
: cpu_addr{cpu_addr_}, tracker{&tracker_}, words{size_bytes} {}
|
|
||||||
|
|
||||||
explicit WordManager() = default;
|
explicit WordManager() = default;
|
||||||
|
|
||||||
void SetCpuAddress(VAddr new_cpu_addr) {
|
|
||||||
cpu_addr = new_cpu_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
VAddr GetCpuAddr() const {
|
|
||||||
return cpu_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static u64 ExtractBits(u64 word, size_t page_start, size_t page_end) {
|
static u64 ExtractBits(u64 word, size_t page_start, size_t page_end) {
|
||||||
constexpr size_t number_bits = sizeof(u64) * 8;
|
constexpr size_t number_bits = sizeof(u64) * 8;
|
||||||
const size_t limit_page_end = number_bits - (std::min)(page_end, number_bits);
|
const size_t limit_page_end = number_bits - (std::min)(page_end, number_bits);
|
||||||
@@ -192,7 +60,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static std::pair<size_t, size_t> GetWordPage(VAddr address) {
|
static std::pair<size_t, size_t> GetWordPage(VAddr address) {
|
||||||
const size_t converted_address = static_cast<size_t>(address);
|
const size_t converted_address = size_t(address);
|
||||||
const size_t word_number = converted_address / BYTES_PER_WORD;
|
const size_t word_number = converted_address / BYTES_PER_WORD;
|
||||||
const size_t amount_pages = converted_address % BYTES_PER_WORD;
|
const size_t amount_pages = converted_address % BYTES_PER_WORD;
|
||||||
return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE);
|
return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE);
|
||||||
@@ -201,32 +69,28 @@ public:
|
|||||||
template <typename Func>
|
template <typename Func>
|
||||||
void IterateWords(size_t offset, size_t size, Func&& func) const {
|
void IterateWords(size_t offset, size_t size, Func&& func) const {
|
||||||
using FuncReturn = std::invoke_result_t<Func, std::size_t, u64>;
|
using FuncReturn = std::invoke_result_t<Func, std::size_t, u64>;
|
||||||
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
|
const size_t start = size_t(std::max<s64>(s64(offset), 0LL));
|
||||||
const size_t start = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset), 0LL));
|
const size_t end = size_t(std::max<s64>(s64(offset + size), 0LL));
|
||||||
const size_t end = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset + size), 0LL));
|
if (!(start >= size_bytes || end <= start)) {
|
||||||
if (start >= SizeBytes() || end <= start) {
|
auto [start_word, start_page] = GetWordPage(start);
|
||||||
return;
|
auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL);
|
||||||
}
|
start_word = (std::min)(start_word, num_words);
|
||||||
auto [start_word, start_page] = GetWordPage(start);
|
end_word = (std::min)(end_word, num_words);
|
||||||
auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL);
|
const size_t diff = end_word - start_word;
|
||||||
const size_t num_words = NumWords();
|
end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD;
|
||||||
start_word = (std::min)(start_word, num_words);
|
end_word = (std::min)(end_word, num_words);
|
||||||
end_word = (std::min)(end_word, num_words);
|
end_page += diff * PAGES_PER_WORD;
|
||||||
const size_t diff = end_word - start_word;
|
constexpr u64 base_mask{~0ULL};
|
||||||
end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD;
|
for (size_t word_index = start_word; word_index < end_word; word_index++) {
|
||||||
end_word = (std::min)(end_word, num_words);
|
const u64 mask = ExtractBits(base_mask, start_page, end_page);
|
||||||
end_page += diff * PAGES_PER_WORD;
|
start_page = 0;
|
||||||
constexpr u64 base_mask{~0ULL};
|
end_page -= PAGES_PER_WORD;
|
||||||
for (size_t word_index = start_word; word_index < end_word; word_index++) {
|
if constexpr (std::is_same_v<FuncReturn, bool>) { // bool return
|
||||||
const u64 mask = ExtractBits(base_mask, start_page, end_page);
|
if (func(word_index, mask))
|
||||||
start_page = 0;
|
return;
|
||||||
end_page -= PAGES_PER_WORD;
|
} else {
|
||||||
if constexpr (BOOL_BREAK) {
|
func(word_index, mask);
|
||||||
if (func(word_index, mask)) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
func(word_index, mask);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,39 +110,32 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// @brief Change the state of a range of pages
|
||||||
* Change the state of a range of pages
|
/// @param type Type of the page
|
||||||
*
|
/// @param enable If enabling or disabling
|
||||||
* @param dirty_addr Base address to mark or unmark as modified
|
/// @param dirty_addr Base address to mark or unmark as modified
|
||||||
* @param size Size in bytes to mark or unmark as modified
|
/// @param size Size in bytes to mark or unmark as modified
|
||||||
*/
|
void ChangeRegionState(Type type, bool enable, u64 dirty_addr, u64 size) noexcept {
|
||||||
template <Type type, bool enable>
|
std::span<u64> state_words = Span(type);
|
||||||
void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) {
|
[[maybe_unused]] std::span<u64> untracked_words = Span(Type::Untracked);
|
||||||
std::span<u64> state_words = words.template Span<type>();
|
[[maybe_unused]] std::span<u64> cached_words = Span(Type::CachedCPU);
|
||||||
[[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
|
|
||||||
[[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
|
|
||||||
std::vector<std::pair<VAddr, u64>> ranges;
|
std::vector<std::pair<VAddr, u64>> ranges;
|
||||||
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
|
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
|
||||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
if (type == Type::CPU || type == Type::CachedCPU) {
|
||||||
CollectChangedRanges<(!enable)>(index, untracked_words[index], mask, ranges);
|
CollectChangedRanges(!enable, index, untracked_words[index], mask, ranges);
|
||||||
}
|
}
|
||||||
if constexpr (enable) {
|
if (enable) {
|
||||||
state_words[index] |= mask;
|
state_words[index] |= mask;
|
||||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
if (type == Type::CPU || type == Type::CachedCPU)
|
||||||
untracked_words[index] |= mask;
|
untracked_words[index] |= mask;
|
||||||
}
|
if (type == Type::CPU)
|
||||||
if constexpr (type == Type::CPU) {
|
|
||||||
cached_words[index] &= ~mask;
|
cached_words[index] &= ~mask;
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if constexpr (type == Type::CPU) {
|
if (type == Type::CPU)
|
||||||
const u64 word = state_words[index] & mask;
|
cached_words[index] &= ~(state_words[index] & mask);
|
||||||
cached_words[index] &= ~word;
|
|
||||||
}
|
|
||||||
state_words[index] &= ~mask;
|
state_words[index] &= ~mask;
|
||||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
if (type == Type::CPU || type == Type::CachedCPU)
|
||||||
untracked_words[index] &= ~mask;
|
untracked_words[index] &= ~mask;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!ranges.empty()) {
|
if (!ranges.empty()) {
|
||||||
@@ -286,22 +143,20 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// @brief Loop over each page in the given range.
|
||||||
* Loop over each page in the given range, turn off those bits and notify the tracker if
|
/// Turn off those bits and notify the tracker if needed. Call the given function on each turned off range.
|
||||||
* needed. Call the given function on each turned off range.
|
/// @param type Type of the address
|
||||||
*
|
/// @param clear Whetever to clear
|
||||||
* @param query_cpu_range Base CPU address to loop over
|
/// @param query_cpu_range Base CPU address to loop over
|
||||||
* @param size Size in bytes of the CPU range to loop over
|
/// @param size Size in bytes of the CPU range to loop over
|
||||||
* @param func Function to call for each turned off region
|
/// @param func Function to call for each turned off region
|
||||||
*/
|
template <typename Func>
|
||||||
template <Type type, bool clear, typename Func>
|
void ForEachModifiedRange(Type type, bool clear, VAddr query_cpu_range, s64 size, Func&& func) {
|
||||||
void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) {
|
//static_assert(type != Type::Untracked);
|
||||||
static_assert(type != Type::Untracked);
|
std::span<u64> state_words = Span(type);
|
||||||
|
std::span<u64> untracked_words = Span(Type::Untracked);
|
||||||
std::span<u64> state_words = words.template Span<type>();
|
std::span<u64> cached_words = Span(Type::CachedCPU);
|
||||||
[[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
|
size_t const offset = query_cpu_range - cpu_addr;
|
||||||
[[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
|
|
||||||
const size_t offset = query_cpu_range - cpu_addr;
|
|
||||||
bool pending = false;
|
bool pending = false;
|
||||||
size_t pending_offset{};
|
size_t pending_offset{};
|
||||||
size_t pending_pointer{};
|
size_t pending_pointer{};
|
||||||
@@ -311,39 +166,32 @@ public:
|
|||||||
};
|
};
|
||||||
std::vector<std::pair<VAddr, u64>> ranges;
|
std::vector<std::pair<VAddr, u64>> ranges;
|
||||||
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
||||||
if constexpr (type == Type::GPU) {
|
if (type == Type::GPU)
|
||||||
mask &= ~untracked_words[index];
|
mask &= ~untracked_words[index];
|
||||||
}
|
|
||||||
const u64 word = state_words[index] & mask;
|
const u64 word = state_words[index] & mask;
|
||||||
if constexpr (clear) {
|
if (clear) {
|
||||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
if (type == Type::CPU || type == Type::CachedCPU) {
|
||||||
CollectChangedRanges<true>(index, untracked_words[index], mask, ranges);
|
CollectChangedRanges(true, index, untracked_words[index], mask, ranges);
|
||||||
}
|
}
|
||||||
state_words[index] &= ~mask;
|
state_words[index] &= ~mask;
|
||||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
if (type == Type::CPU || type == Type::CachedCPU)
|
||||||
untracked_words[index] &= ~mask;
|
untracked_words[index] &= ~mask;
|
||||||
}
|
if (type == Type::CPU)
|
||||||
if constexpr (type == Type::CPU) {
|
|
||||||
cached_words[index] &= ~word;
|
cached_words[index] &= ~word;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const size_t base_offset = index * PAGES_PER_WORD;
|
const size_t base_offset = index * PAGES_PER_WORD;
|
||||||
IteratePages(word, [&](size_t pages_offset, size_t pages_size) {
|
IteratePages(word, [&](size_t pages_offset, size_t pages_size) {
|
||||||
const auto reset = [&]() {
|
if (!pending) {
|
||||||
pending_offset = base_offset + pages_offset;
|
pending_offset = base_offset + pages_offset;
|
||||||
pending_pointer = base_offset + pages_offset + pages_size;
|
pending_pointer = base_offset + pages_offset + pages_size;
|
||||||
};
|
|
||||||
if (!pending) {
|
|
||||||
reset();
|
|
||||||
pending = true;
|
pending = true;
|
||||||
return;
|
} else if (pending_pointer == base_offset + pages_offset) {
|
||||||
}
|
|
||||||
if (pending_pointer == base_offset + pages_offset) {
|
|
||||||
pending_pointer += pages_size;
|
pending_pointer += pages_size;
|
||||||
return;
|
} else {
|
||||||
|
func(cpu_addr + pending_offset * BYTES_PER_PAGE, (pending_pointer - pending_offset) * BYTES_PER_PAGE);
|
||||||
|
pending_offset = base_offset + pages_offset;
|
||||||
|
pending_pointer = base_offset + pages_offset + pages_size;
|
||||||
}
|
}
|
||||||
release();
|
|
||||||
reset();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (pending) {
|
if (pending) {
|
||||||
@@ -354,90 +202,55 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// @brief Returns true when a region has been modified
|
||||||
* Returns true when a region has been modified
|
/// @param type Type of region
|
||||||
*
|
/// @param offset Offset in bytes from the start of the buffer
|
||||||
* @param offset Offset in bytes from the start of the buffer
|
/// @param size Size in bytes of the region to query for modifications
|
||||||
* @param size Size in bytes of the region to query for modifications
|
[[nodiscard]] bool IsRegionModified(Type type, u64 offset, u64 size) const noexcept {
|
||||||
*/
|
//static_assert(type != Type::Untracked);
|
||||||
template <Type type>
|
const std::span<const u64> state_words = Span(type);
|
||||||
[[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
|
const std::span<const u64> untracked_words = Span(Type::Untracked);
|
||||||
static_assert(type != Type::Untracked);
|
|
||||||
|
|
||||||
const std::span<const u64> state_words = words.template Span<type>();
|
|
||||||
[[maybe_unused]] const std::span<const u64> untracked_words =
|
|
||||||
words.template Span<Type::Untracked>();
|
|
||||||
bool result = false;
|
bool result = false;
|
||||||
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
||||||
if constexpr (type == Type::GPU) {
|
if (type == Type::GPU)
|
||||||
mask &= ~untracked_words[index];
|
mask &= ~untracked_words[index];
|
||||||
}
|
return (state_words[index] & mask) != 0 ? (result = true) : false;
|
||||||
const u64 word = state_words[index] & mask;
|
|
||||||
if (word != 0) {
|
|
||||||
result = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/// @brief Returns a begin end pair with the inclusive modified region
|
||||||
* Returns a begin end pair with the inclusive modified region
|
/// @param offset Offset in bytes from the start of the buffer
|
||||||
*
|
/// @param size Size in bytes of the region to query for modifications
|
||||||
* @param offset Offset in bytes from the start of the buffer
|
[[nodiscard]] std::pair<u64, u64> ModifiedRegion(Type type, u64 offset, u64 size) const noexcept {
|
||||||
* @param size Size in bytes of the region to query for modifications
|
//static_assert(type != Type::Untracked);
|
||||||
*/
|
const std::span<const u64> state_words = Span(type);
|
||||||
template <Type type>
|
const std::span<const u64> untracked_words = Span(Type::Untracked);
|
||||||
[[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept {
|
u64 begin = (std::numeric_limits<u64>::max)(), end = 0;
|
||||||
static_assert(type != Type::Untracked);
|
|
||||||
const std::span<const u64> state_words = words.template Span<type>();
|
|
||||||
[[maybe_unused]] const std::span<const u64> untracked_words =
|
|
||||||
words.template Span<Type::Untracked>();
|
|
||||||
u64 begin = (std::numeric_limits<u64>::max)();
|
|
||||||
u64 end = 0;
|
|
||||||
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
||||||
if constexpr (type == Type::GPU) {
|
if (type == Type::GPU)
|
||||||
mask &= ~untracked_words[index];
|
mask &= ~untracked_words[index];
|
||||||
}
|
|
||||||
const u64 word = state_words[index] & mask;
|
const u64 word = state_words[index] & mask;
|
||||||
if (word == 0) {
|
if (word != 0) {
|
||||||
return;
|
const u64 local_page_begin = std::countr_zero(word);
|
||||||
|
const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
|
||||||
|
const u64 page_index = index * PAGES_PER_WORD;
|
||||||
|
begin = (std::min)(begin, page_index + local_page_begin);
|
||||||
|
end = page_index + local_page_end;
|
||||||
}
|
}
|
||||||
const u64 local_page_begin = std::countr_zero(word);
|
|
||||||
const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
|
|
||||||
const u64 page_index = index * PAGES_PER_WORD;
|
|
||||||
begin = (std::min)(begin, page_index + local_page_begin);
|
|
||||||
end = page_index + local_page_end;
|
|
||||||
});
|
});
|
||||||
static constexpr std::pair<u64, u64> EMPTY{0, 0};
|
return begin < end ? std::make_pair<u64, u64>(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE)
|
||||||
return begin < end ? std::make_pair(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE) : EMPTY;
|
: std::make_pair<u64, u64>(0, 0);
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of words of the manager
|
|
||||||
[[nodiscard]] size_t NumWords() const noexcept {
|
|
||||||
return words.NumWords();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the size in bytes of the manager
|
|
||||||
[[nodiscard]] u64 SizeBytes() const noexcept {
|
|
||||||
return words.size_bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true when the buffer fits in the small vector optimization
|
|
||||||
[[nodiscard]] bool IsShort() const noexcept {
|
|
||||||
return words.IsShort();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlushCachedWrites() noexcept {
|
void FlushCachedWrites() noexcept {
|
||||||
const u64 num_words = NumWords();
|
auto const cached_words = Span(Type::CachedCPU);
|
||||||
u64* const cached_words = Array<Type::CachedCPU>();
|
auto const untracked_words = Span(Type::Untracked);
|
||||||
u64* const untracked_words = Array<Type::Untracked>();
|
auto const cpu_words = Span(Type::CPU);
|
||||||
u64* const cpu_words = Array<Type::CPU>();
|
|
||||||
std::vector<std::pair<VAddr, u64>> ranges;
|
std::vector<std::pair<VAddr, u64>> ranges;
|
||||||
for (u64 word_index = 0; word_index < num_words; ++word_index) {
|
for (u64 word_index = 0; word_index < num_words; ++word_index) {
|
||||||
const u64 cached_bits = cached_words[word_index];
|
const u64 cached_bits = cached_words[word_index];
|
||||||
CollectChangedRanges<false>(word_index, untracked_words[word_index], cached_bits, ranges);
|
CollectChangedRanges(false, word_index, untracked_words[word_index], cached_bits, ranges);
|
||||||
untracked_words[word_index] |= cached_bits;
|
untracked_words[word_index] |= cached_bits;
|
||||||
cpu_words[word_index] |= cached_bits;
|
cpu_words[word_index] |= cached_bits;
|
||||||
cached_words[word_index] = 0;
|
cached_words[word_index] = 0;
|
||||||
@@ -447,45 +260,13 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
/// @brief Notify tracker about changes in the CPU tracking state of a word in the buffer
|
||||||
template <Type type>
|
/// @param add_to_tracker If add to tracker (selects changed bits)
|
||||||
u64* Array() noexcept {
|
/// @param word_index Index to the word to notify to the tracker
|
||||||
if constexpr (type == Type::CPU) {
|
/// @param current_bits Current state of the word
|
||||||
return words.cpu.Pointer(IsShort());
|
/// @param new_bits New state of the word
|
||||||
} else if constexpr (type == Type::GPU) {
|
/// @tparam add_to_tracker True when the tracker should start tracking the new pages
|
||||||
return words.gpu.Pointer(IsShort());
|
void CollectChangedRanges(bool add_to_tracker, u64 word_index, u64 current_bits, u64 new_bits, std::vector<std::pair<VAddr, u64>>& out_ranges) const {
|
||||||
} else if constexpr (type == Type::CachedCPU) {
|
|
||||||
return words.cached_cpu.Pointer(IsShort());
|
|
||||||
} else if constexpr (type == Type::Untracked) {
|
|
||||||
return words.untracked.Pointer(IsShort());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <Type type>
|
|
||||||
const u64* Array() const noexcept {
|
|
||||||
if constexpr (type == Type::CPU) {
|
|
||||||
return words.cpu.Pointer(IsShort());
|
|
||||||
} else if constexpr (type == Type::GPU) {
|
|
||||||
return words.gpu.Pointer(IsShort());
|
|
||||||
} else if constexpr (type == Type::CachedCPU) {
|
|
||||||
return words.cached_cpu.Pointer(IsShort());
|
|
||||||
} else if constexpr (type == Type::Untracked) {
|
|
||||||
return words.untracked.Pointer(IsShort());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify tracker about changes in the CPU tracking state of a word in the buffer
|
|
||||||
*
|
|
||||||
* @param word_index Index to the word to notify to the tracker
|
|
||||||
* @param current_bits Current state of the word
|
|
||||||
* @param new_bits New state of the word
|
|
||||||
*
|
|
||||||
* @tparam add_to_tracker True when the tracker should start tracking the new pages
|
|
||||||
*/
|
|
||||||
template <bool add_to_tracker>
|
|
||||||
void CollectChangedRanges(u64 word_index, u64 current_bits, u64 new_bits,
|
|
||||||
std::vector<std::pair<VAddr, u64>>& out_ranges) const {
|
|
||||||
u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits;
|
u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits;
|
||||||
VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
|
VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
|
||||||
IteratePages(changed_bits, [&](size_t offset, size_t size) {
|
IteratePages(changed_bits, [&](size_t offset, size_t size) {
|
||||||
@@ -494,9 +275,9 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ApplyCollectedRanges(std::vector<std::pair<VAddr, u64>>& ranges, int delta) const {
|
void ApplyCollectedRanges(std::vector<std::pair<VAddr, u64>>& ranges, int delta) const {
|
||||||
if (ranges.empty()) return;
|
if (ranges.empty())
|
||||||
std::sort(ranges.begin(), ranges.end(),
|
return;
|
||||||
[](const auto& a, const auto& b) { return a.first < b.first; });
|
std::sort(ranges.begin(), ranges.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
|
||||||
// Coalesce adjacent/contiguous ranges
|
// Coalesce adjacent/contiguous ranges
|
||||||
std::vector<std::pair<VAddr, size_t>> coalesced;
|
std::vector<std::pair<VAddr, size_t>> coalesced;
|
||||||
coalesced.reserve(ranges.size());
|
coalesced.reserve(ranges.size());
|
||||||
@@ -517,19 +298,30 @@ private:
|
|||||||
ranges.clear();
|
ranges.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool add_to_tracker>
|
/// @brief Notify tracker about changes in the CPU tracking state of a word in the buffer
|
||||||
void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const {
|
/// @param add_to_tracker True when the tracker should start tracking the new pages
|
||||||
|
/// @param word_index Index to the word to notify to the tracker
|
||||||
|
/// @param current_bits Current state of the word
|
||||||
|
/// @param new_bits New state of the word
|
||||||
|
void NotifyRasterizer(bool add_to_tracker, u64 word_index, u64 current_bits, u64 new_bits) const {
|
||||||
u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits;
|
u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits;
|
||||||
VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
|
VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
|
||||||
IteratePages(changed_bits, [&](size_t offset, size_t size) {
|
IteratePages(changed_bits, [&](size_t offset, size_t size) {
|
||||||
tracker->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, size * BYTES_PER_PAGE,
|
tracker->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, size * BYTES_PER_PAGE, add_to_tracker ? 1 : -1);
|
||||||
add_to_tracker ? 1 : -1);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
VAddr cpu_addr = 0;
|
std::span<u64> Span(Type type) noexcept {
|
||||||
|
return std::span<u64>(heap.data() + num_words * size_t(type), num_words);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::span<const u64> Span(Type type) const noexcept {
|
||||||
|
return std::span<const u64>(heap.data() + num_words * size_t(type), num_words);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<u64, size_t(Type::Max) * num_words> heap = {};
|
||||||
DeviceTracker* tracker = nullptr;
|
DeviceTracker* tracker = nullptr;
|
||||||
Words<stack_words> words;
|
VAddr cpu_addr = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace VideoCommon
|
} // namespace VideoCommon
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
add_library(gpu_logging STATIC
|
||||||
|
gpu_logging.cpp
|
||||||
|
gpu_logging.h
|
||||||
|
gpu_state_capture.cpp
|
||||||
|
gpu_state_capture.h
|
||||||
|
qualcomm_debug.cpp
|
||||||
|
qualcomm_debug.h
|
||||||
|
)
|
||||||
|
|
||||||
|
if(ANDROID)
|
||||||
|
target_sources(gpu_logging PRIVATE
|
||||||
|
freedreno_debug.cpp
|
||||||
|
freedreno_debug.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(gpu_logging PUBLIC common)
|
||||||
|
|
||||||
|
if(ANDROID)
|
||||||
|
# Link with adrenotools when available for future Qualcomm integration
|
||||||
|
# target_link_libraries(gpu_logging PUBLIC adrenotools)
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
|
||||||
|
#include "video_core/gpu_logging/freedreno_debug.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
namespace GPU::Logging::Freedreno {
|
||||||
|
|
||||||
|
bool FreedrenoDebugger::is_initialized = false;
|
||||||
|
|
||||||
|
void FreedrenoDebugger::Initialize() {
|
||||||
|
if (is_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_initialized = true;
|
||||||
|
LOG_INFO(Render_Vulkan, "[Freedreno Debug] Initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreedrenoDebugger::SetTUDebugFlags(const std::string& flags) {
|
||||||
|
if (flags.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set TU_DEBUG environment variable
|
||||||
|
// Note: This should be set BEFORE Vulkan driver is loaded
|
||||||
|
setenv("TU_DEBUG", flags.c_str(), 1);
|
||||||
|
|
||||||
|
LOG_INFO(Render_Vulkan, "[Freedreno Debug] TU_DEBUG set to: {}", flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreedrenoDebugger::EnableCommandStreamDump(bool frames_only) {
|
||||||
|
// Enable FD_RD_DUMP for command stream capture
|
||||||
|
const char* dump_flags = frames_only ? "frames" : "all";
|
||||||
|
setenv("FD_RD_DUMP", dump_flags, 1);
|
||||||
|
|
||||||
|
LOG_INFO(Render_Vulkan, "[Freedreno Debug] Command stream dump enabled: {}", dump_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FreedrenoDebugger::GetBreadcrumbs() {
|
||||||
|
// Breadcrumb reading requires driver-specific implementation
|
||||||
|
// This is a stub for future implementation
|
||||||
|
return "Breadcrumb capture not yet implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GPU::Logging::Freedreno
|
||||||
|
|
||||||
|
#endif // ANDROID
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace GPU::Logging::Freedreno {
|
||||||
|
|
||||||
|
class FreedrenoDebugger {
|
||||||
|
public:
|
||||||
|
// Initialize Freedreno debugging
|
||||||
|
static void Initialize();
|
||||||
|
|
||||||
|
// Set TU_DEBUG environment variable flags
|
||||||
|
static void SetTUDebugFlags(const std::string& flags);
|
||||||
|
|
||||||
|
// Enable command stream dump
|
||||||
|
static void EnableCommandStreamDump(bool frames_only = false);
|
||||||
|
|
||||||
|
// Get breadcrumb information (if available)
|
||||||
|
static std::string GetBreadcrumbs();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool is_initialized;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace GPU::Logging::Freedreno
|
||||||
|
|
||||||
|
#endif // ANDROID
|
||||||
@@ -0,0 +1,734 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "video_core/gpu_logging/gpu_logging.h"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "common/fs/file.h"
|
||||||
|
#include "common/fs/fs.h"
|
||||||
|
#include "common/fs/path_util.h"
|
||||||
|
#include "common/literals.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
|
||||||
|
namespace GPU::Logging {
|
||||||
|
|
||||||
|
// Static instance
|
||||||
|
static GPULogger* g_instance = nullptr;
|
||||||
|
|
||||||
|
GPULogger& GPULogger::GetInstance() {
|
||||||
|
if (!g_instance) {
|
||||||
|
g_instance = new GPULogger();
|
||||||
|
}
|
||||||
|
return *g_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
GPULogger::GPULogger() = default;
|
||||||
|
|
||||||
|
GPULogger::~GPULogger() {
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::Initialize(LogLevel level, DriverType driver) {
|
||||||
|
if (initialized) {
|
||||||
|
LOG_WARNING(Render_Vulkan, "[GPU Logging] Already initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_level = level;
|
||||||
|
detected_driver = driver;
|
||||||
|
|
||||||
|
if (current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create log directory
|
||||||
|
using namespace Common::FS;
|
||||||
|
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
|
||||||
|
[[maybe_unused]] const bool log_dir_created = CreateDir(log_dir);
|
||||||
|
|
||||||
|
// Create GPU crashes directory
|
||||||
|
const auto crashes_dir = log_dir / "gpu_crashes";
|
||||||
|
[[maybe_unused]] const bool crashes_dir_created = CreateDir(crashes_dir);
|
||||||
|
|
||||||
|
// Open GPU log file
|
||||||
|
const auto gpu_log_path = log_dir / "eden_gpu.log";
|
||||||
|
|
||||||
|
// Rotate old log
|
||||||
|
const auto old_log_path = log_dir / "eden_gpu.log.old.txt";
|
||||||
|
RemoveFile(old_log_path);
|
||||||
|
[[maybe_unused]] const bool log_renamed = RenameFile(gpu_log_path, old_log_path);
|
||||||
|
|
||||||
|
// Open new log file
|
||||||
|
gpu_log_file = std::make_unique<Common::FS::IOFile>(
|
||||||
|
gpu_log_path, Common::FS::FileAccessMode::Write, Common::FS::FileType::TextFile);
|
||||||
|
|
||||||
|
if (!gpu_log_file->IsOpen()) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "[GPU Logging] Failed to open GPU log file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize ring buffer
|
||||||
|
call_ring_buffer.resize(ring_buffer_size);
|
||||||
|
|
||||||
|
// Write header
|
||||||
|
const char* driver_name = "Unknown";
|
||||||
|
switch (detected_driver) {
|
||||||
|
case DriverType::Turnip:
|
||||||
|
driver_name = "Turnip (Mesa Freedreno)";
|
||||||
|
break;
|
||||||
|
case DriverType::Qualcomm:
|
||||||
|
driver_name = "Qualcomm Proprietary";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
driver_name = "Unknown";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* level_name = "Unknown";
|
||||||
|
switch (current_level) {
|
||||||
|
case LogLevel::Off:
|
||||||
|
level_name = "Off";
|
||||||
|
break;
|
||||||
|
case LogLevel::Errors:
|
||||||
|
level_name = "Errors";
|
||||||
|
break;
|
||||||
|
case LogLevel::Standard:
|
||||||
|
level_name = "Standard";
|
||||||
|
break;
|
||||||
|
case LogLevel::Verbose:
|
||||||
|
level_name = "Verbose";
|
||||||
|
break;
|
||||||
|
case LogLevel::All:
|
||||||
|
level_name = "All";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto header = fmt::format(
|
||||||
|
"=== Eden GPU Logging Started ===\n"
|
||||||
|
"Timestamp: {}\n"
|
||||||
|
"Log Level: {}\n"
|
||||||
|
"Driver: {}\n"
|
||||||
|
"Ring Buffer Size: {}\n"
|
||||||
|
"================================\n\n",
|
||||||
|
FormatTimestamp(std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch())),
|
||||||
|
level_name, driver_name, ring_buffer_size);
|
||||||
|
|
||||||
|
WriteToLog(header);
|
||||||
|
|
||||||
|
// Note: Crash handler is initialized independently in EmulationSession::InitializeSystem()
|
||||||
|
// to ensure it remains active even if Vulkan device initialization fails
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
LOG_INFO(Render_Vulkan, "[GPU Logging] Initialized with level: {}, driver: {}", level_name,
|
||||||
|
driver_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::Shutdown() {
|
||||||
|
if (!initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write statistics
|
||||||
|
const auto stats = fmt::format(
|
||||||
|
"\n=== GPU Logging Statistics ===\n"
|
||||||
|
"Total Vulkan Calls: {}\n"
|
||||||
|
"Total Memory Allocations: {}\n"
|
||||||
|
"Total Memory Deallocations: {}\n"
|
||||||
|
"Peak Memory Usage: {}\n"
|
||||||
|
"Current Memory Usage: {}\n"
|
||||||
|
"Log Size: {} bytes\n"
|
||||||
|
"==============================\n",
|
||||||
|
total_vulkan_calls, total_allocations, total_deallocations,
|
||||||
|
FormatMemorySize(peak_allocated_bytes), FormatMemorySize(current_allocated_bytes),
|
||||||
|
bytes_written);
|
||||||
|
|
||||||
|
WriteToLog(stats);
|
||||||
|
|
||||||
|
// Close file
|
||||||
|
if (gpu_log_file) {
|
||||||
|
gpu_log_file->Flush();
|
||||||
|
gpu_log_file->Close();
|
||||||
|
gpu_log_file.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Crash handler is NOT shut down here - it remains active throughout app lifetime
|
||||||
|
// It will be shut down when EmulationSession is destroyed
|
||||||
|
|
||||||
|
initialized = false;
|
||||||
|
LOG_INFO(Render_Vulkan, "[GPU Logging] Shutdown complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogVulkanCall(const std::string& call_name, const std::string& params,
|
||||||
|
int result) {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!track_vulkan_calls) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only log all calls in Verbose or All mode
|
||||||
|
if (current_level != LogLevel::Verbose && current_level != LogLevel::All) {
|
||||||
|
// In Standard mode, only log important calls
|
||||||
|
if (call_name.find("vkCmd") == std::string::npos &&
|
||||||
|
call_name.find("vkCreate") == std::string::npos &&
|
||||||
|
call_name.find("vkDestroy") == std::string::npos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
const auto thread_id = static_cast<u32>(std::hash<std::thread::id>{}(std::this_thread::get_id()));
|
||||||
|
|
||||||
|
// Add to ring buffer
|
||||||
|
{
|
||||||
|
std::lock_guard lock(ring_buffer_mutex);
|
||||||
|
call_ring_buffer[ring_buffer_index] = {
|
||||||
|
.timestamp = timestamp,
|
||||||
|
.call_name = call_name,
|
||||||
|
.parameters = params,
|
||||||
|
.result = result,
|
||||||
|
.thread_id = thread_id,
|
||||||
|
};
|
||||||
|
ring_buffer_index = (ring_buffer_index + 1) % ring_buffer_size;
|
||||||
|
total_vulkan_calls++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log to file
|
||||||
|
const auto log_entry =
|
||||||
|
fmt::format("[{}] [Vulkan] [Thread:{}] {}({}) -> {}\n", FormatTimestamp(timestamp),
|
||||||
|
thread_id, call_name, params, result);
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogMemoryAllocation(uintptr_t memory, u64 size, u32 memory_flags) {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!track_memory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
|
||||||
|
const bool is_device_local = (memory_flags & 0x1) != 0; // VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
|
||||||
|
const bool is_host_visible = (memory_flags & 0x2) != 0; // VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(memory_mutex);
|
||||||
|
memory_allocations[memory] = {
|
||||||
|
.memory_handle = memory,
|
||||||
|
.size = size,
|
||||||
|
.memory_flags = memory_flags,
|
||||||
|
.timestamp = timestamp,
|
||||||
|
.is_device_local = is_device_local,
|
||||||
|
.is_host_visible = is_host_visible,
|
||||||
|
};
|
||||||
|
|
||||||
|
total_allocations++;
|
||||||
|
current_allocated_bytes += size;
|
||||||
|
if (current_allocated_bytes > peak_allocated_bytes) {
|
||||||
|
peak_allocated_bytes = current_allocated_bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto log_entry = fmt::format(
|
||||||
|
"[{}] [Memory] Allocated {} at 0x{:x} (Device:{}, Host:{})\n", FormatTimestamp(timestamp),
|
||||||
|
FormatMemorySize(size), memory, is_device_local ? "Yes" : "No",
|
||||||
|
is_host_visible ? "Yes" : "No");
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogMemoryDeallocation(uintptr_t memory) {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!track_memory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
|
||||||
|
u64 size = 0;
|
||||||
|
{
|
||||||
|
std::lock_guard lock(memory_mutex);
|
||||||
|
auto it = memory_allocations.find(memory);
|
||||||
|
if (it != memory_allocations.end()) {
|
||||||
|
size = it->second.size;
|
||||||
|
current_allocated_bytes -= size;
|
||||||
|
memory_allocations.erase(it);
|
||||||
|
total_deallocations++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size > 0) {
|
||||||
|
const auto log_entry =
|
||||||
|
fmt::format("[{}] [Memory] Deallocated {} at 0x{:x}\n", FormatTimestamp(timestamp),
|
||||||
|
FormatMemorySize(size), memory);
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogShaderCompilation(const std::string& shader_name,
|
||||||
|
const std::string& shader_info,
|
||||||
|
std::span<const u32> spirv_code) {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dump_shaders && current_level < LogLevel::Verbose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
|
||||||
|
const auto log_entry = fmt::format("[{}] [Shader] Compiled: {} ({})\n",
|
||||||
|
FormatTimestamp(timestamp), shader_name, shader_info);
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
|
||||||
|
// Dump SPIR-V binary if enabled and we have data
|
||||||
|
if (dump_shaders && !spirv_code.empty()) {
|
||||||
|
using namespace Common::FS;
|
||||||
|
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
|
||||||
|
const auto shaders_dir = log_dir / "shaders";
|
||||||
|
|
||||||
|
// Create directory on first dump
|
||||||
|
if (!shader_dump_dir_created) {
|
||||||
|
[[maybe_unused]] const bool created = CreateDir(shaders_dir);
|
||||||
|
shader_dump_dir_created = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write SPIR-V binary file
|
||||||
|
const auto shader_path = shaders_dir / fmt::format("{}.spv", shader_name);
|
||||||
|
auto shader_file = std::make_unique<Common::FS::IOFile>(
|
||||||
|
shader_path, FileAccessMode::Write, FileType::BinaryFile);
|
||||||
|
|
||||||
|
if (shader_file->IsOpen()) {
|
||||||
|
const size_t bytes_to_write = spirv_code.size() * sizeof(u32);
|
||||||
|
static_cast<void>(shader_file->WriteSpan(spirv_code));
|
||||||
|
shader_file->Close();
|
||||||
|
|
||||||
|
const auto dump_log = fmt::format("[{}] [Shader] Dumped SPIR-V: {} ({} bytes)\n",
|
||||||
|
FormatTimestamp(timestamp), shader_path.string(), bytes_to_write);
|
||||||
|
WriteToLog(dump_log);
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Render_Vulkan, "[GPU Logging] Failed to dump shader: {}", shader_path.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogPipelineStateChange(const std::string& state_info) {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store pipeline state for crash dumps
|
||||||
|
{
|
||||||
|
std::lock_guard lock(state_mutex);
|
||||||
|
stored_pipeline_state = state_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_level < LogLevel::Verbose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
|
||||||
|
const auto log_entry =
|
||||||
|
fmt::format("[{}] [Pipeline] State change: {}\n", FormatTimestamp(timestamp), state_info);
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogDriverDebugInfo(const std::string& debug_info) {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store driver debug info for crash dumps
|
||||||
|
{
|
||||||
|
std::lock_guard lock(state_mutex);
|
||||||
|
stored_driver_debug_info = debug_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!capture_driver_debug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
|
||||||
|
const auto log_entry =
|
||||||
|
fmt::format("[{}] [Driver] {}\n", FormatTimestamp(timestamp), debug_info);
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogExtensionUsage(const std::string& extension_name, const std::string& function_name) {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
|
||||||
|
bool is_first_use = false;
|
||||||
|
{
|
||||||
|
std::lock_guard lock(extension_mutex);
|
||||||
|
auto [iter, inserted] = used_extensions.insert(extension_name);
|
||||||
|
is_first_use = inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_first_use) {
|
||||||
|
const auto log_entry = fmt::format("[{}] [Extension] First use of {} in {}\n",
|
||||||
|
FormatTimestamp(timestamp), extension_name, function_name);
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
LOG_INFO(Render_Vulkan, "[GPU Logging] First use of extension {} in {}",
|
||||||
|
extension_name, function_name);
|
||||||
|
} else if (current_level >= LogLevel::Verbose) {
|
||||||
|
const auto log_entry = fmt::format("[{}] [Extension] {} used in {}\n",
|
||||||
|
FormatTimestamp(timestamp), extension_name, function_name);
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogRenderPassBegin(const std::string& render_pass_info) {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
|
||||||
|
const auto log_entry = fmt::format("[{}] [RenderPass] Begin: {}\n",
|
||||||
|
FormatTimestamp(timestamp), render_pass_info);
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogRenderPassEnd() {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
|
||||||
|
const auto log_entry = fmt::format("[{}] [RenderPass] End\n", FormatTimestamp(timestamp));
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogPipelineBind(bool is_compute, const std::string& pipeline_info) {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
|
||||||
|
const char* pipeline_type = is_compute ? "Compute" : "Graphics";
|
||||||
|
const auto log_entry = fmt::format("[{}] [Pipeline] Bind {} pipeline: {}\n",
|
||||||
|
FormatTimestamp(timestamp), pipeline_type, pipeline_info);
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogDescriptorSetBind(const std::string& descriptor_info) {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_level < LogLevel::Verbose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
|
||||||
|
const auto log_entry = fmt::format("[{}] [Descriptor] Bind: {}\n",
|
||||||
|
FormatTimestamp(timestamp), descriptor_info);
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogPipelineBarrier(const std::string& barrier_info) {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_level < LogLevel::Verbose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
|
||||||
|
const auto log_entry = fmt::format("[{}] [Barrier] {}\n",
|
||||||
|
FormatTimestamp(timestamp), barrier_info);
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogImageOperation(const std::string& operation, const std::string& image_info) {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
|
||||||
|
const auto log_entry = fmt::format("[{}] [Image] {}: {}\n",
|
||||||
|
FormatTimestamp(timestamp), operation, image_info);
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::LogClearOperation(const std::string& clear_info) {
|
||||||
|
if (!initialized || current_level == LogLevel::Off) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
|
||||||
|
const auto log_entry = fmt::format("[{}] [Clear] {}\n",
|
||||||
|
FormatTimestamp(timestamp), clear_info);
|
||||||
|
WriteToLog(log_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
GPUStateSnapshot GPULogger::GetCurrentSnapshot() {
|
||||||
|
GPUStateSnapshot snapshot;
|
||||||
|
snapshot.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch());
|
||||||
|
snapshot.driver_type = detected_driver;
|
||||||
|
|
||||||
|
// Capture recent Vulkan calls
|
||||||
|
{
|
||||||
|
std::lock_guard lock(ring_buffer_mutex);
|
||||||
|
snapshot.recent_calls.reserve(ring_buffer_size);
|
||||||
|
|
||||||
|
// Copy from current position to end
|
||||||
|
for (size_t i = ring_buffer_index; i < ring_buffer_size; ++i) {
|
||||||
|
if (!call_ring_buffer[i].call_name.empty()) {
|
||||||
|
snapshot.recent_calls.push_back(call_ring_buffer[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy from beginning to current position
|
||||||
|
for (size_t i = 0; i < ring_buffer_index; ++i) {
|
||||||
|
if (!call_ring_buffer[i].call_name.empty()) {
|
||||||
|
snapshot.recent_calls.push_back(call_ring_buffer[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture memory status
|
||||||
|
{
|
||||||
|
std::lock_guard lock(memory_mutex);
|
||||||
|
snapshot.memory_status = fmt::format(
|
||||||
|
"Total Allocations: {}\n"
|
||||||
|
"Current Usage: {}\n"
|
||||||
|
"Peak Usage: {}\n"
|
||||||
|
"Active Allocations: {}\n",
|
||||||
|
total_allocations, FormatMemorySize(current_allocated_bytes),
|
||||||
|
FormatMemorySize(peak_allocated_bytes), memory_allocations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture stored pipeline and driver debug info
|
||||||
|
{
|
||||||
|
std::lock_guard lock(state_mutex);
|
||||||
|
snapshot.pipeline_state = stored_pipeline_state.empty() ?
|
||||||
|
"No pipeline state logged yet" : stored_pipeline_state;
|
||||||
|
snapshot.driver_debug_info = stored_driver_debug_info.empty() ?
|
||||||
|
"No driver debug info logged yet" : stored_driver_debug_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::DumpStateToFile(const std::string& crash_reason) {
|
||||||
|
using namespace Common::FS;
|
||||||
|
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
|
||||||
|
const auto crashes_dir = log_dir / "gpu_crashes";
|
||||||
|
[[maybe_unused]] const bool crashes_dir_created = CreateDir(crashes_dir);
|
||||||
|
|
||||||
|
// Generate crash dump filename with timestamp
|
||||||
|
const auto now = std::chrono::system_clock::now();
|
||||||
|
const auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
now.time_since_epoch()).count();
|
||||||
|
const auto crash_dump_path = crashes_dir / fmt::format("crash_{}.gpu-dump", timestamp);
|
||||||
|
|
||||||
|
auto crash_file =
|
||||||
|
std::make_unique<Common::FS::IOFile>(crash_dump_path, FileAccessMode::Write, FileType::TextFile);
|
||||||
|
|
||||||
|
if (!crash_file->IsOpen()) {
|
||||||
|
LOG_ERROR(Render_Vulkan, "[GPU Logging] Failed to create crash dump file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto snapshot = GetCurrentSnapshot();
|
||||||
|
|
||||||
|
const char* driver_name = "Unknown";
|
||||||
|
switch (snapshot.driver_type) {
|
||||||
|
case DriverType::Turnip:
|
||||||
|
driver_name = "Turnip (Mesa Freedreno)";
|
||||||
|
break;
|
||||||
|
case DriverType::Qualcomm:
|
||||||
|
driver_name = "Qualcomm Proprietary";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
driver_name = "Unknown";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write crash dump header
|
||||||
|
const auto header = fmt::format(
|
||||||
|
"=== GPU CRASH DUMP ===\n"
|
||||||
|
"Timestamp: {}\n"
|
||||||
|
"Reason: {}\n"
|
||||||
|
"Driver: {}\n"
|
||||||
|
"\n",
|
||||||
|
FormatTimestamp(snapshot.timestamp), crash_reason, driver_name);
|
||||||
|
static_cast<void>(crash_file->WriteString(header));
|
||||||
|
|
||||||
|
// Write recent Vulkan calls
|
||||||
|
static_cast<void>(crash_file->WriteString(fmt::format("=== RECENT VULKAN API CALLS (Last {}) ===\n",
|
||||||
|
snapshot.recent_calls.size())));
|
||||||
|
for (const auto& call : snapshot.recent_calls) {
|
||||||
|
const auto call_str =
|
||||||
|
fmt::format("[{}] [Thread:{}] {}({}) -> {}\n", FormatTimestamp(call.timestamp),
|
||||||
|
call.thread_id, call.call_name, call.parameters, call.result);
|
||||||
|
static_cast<void>(crash_file->WriteString(call_str));
|
||||||
|
}
|
||||||
|
static_cast<void>(crash_file->WriteString("\n"));
|
||||||
|
|
||||||
|
// Write memory status
|
||||||
|
static_cast<void>(crash_file->WriteString("=== MEMORY STATUS ===\n"));
|
||||||
|
static_cast<void>(crash_file->WriteString(snapshot.memory_status));
|
||||||
|
static_cast<void>(crash_file->WriteString("\n"));
|
||||||
|
|
||||||
|
// Write pipeline state
|
||||||
|
static_cast<void>(crash_file->WriteString("=== PIPELINE STATE ===\n"));
|
||||||
|
static_cast<void>(crash_file->WriteString(snapshot.pipeline_state));
|
||||||
|
static_cast<void>(crash_file->WriteString("\n"));
|
||||||
|
|
||||||
|
// Write driver debug info
|
||||||
|
static_cast<void>(crash_file->WriteString("=== DRIVER DEBUG INFO ===\n"));
|
||||||
|
static_cast<void>(crash_file->WriteString(snapshot.driver_debug_info));
|
||||||
|
static_cast<void>(crash_file->WriteString("\n"));
|
||||||
|
|
||||||
|
crash_file->Flush();
|
||||||
|
crash_file->Close();
|
||||||
|
|
||||||
|
LOG_CRITICAL(Render_Vulkan, "[GPU Logging] Crash dump written to: {}",
|
||||||
|
crash_dump_path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::SetLogLevel(LogLevel level) {
|
||||||
|
current_level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::EnableVulkanCallTracking(bool enabled) {
|
||||||
|
track_vulkan_calls = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::EnableShaderDumps(bool enabled) {
|
||||||
|
dump_shaders = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::EnableMemoryTracking(bool enabled) {
|
||||||
|
track_memory = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::EnableDriverDebugInfo(bool enabled) {
|
||||||
|
capture_driver_debug = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::SetRingBufferSize(size_t entries) {
|
||||||
|
std::lock_guard lock(ring_buffer_mutex);
|
||||||
|
ring_buffer_size = entries;
|
||||||
|
call_ring_buffer.resize(entries);
|
||||||
|
ring_buffer_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogLevel GPULogger::GetLogLevel() const {
|
||||||
|
return current_level;
|
||||||
|
}
|
||||||
|
|
||||||
|
DriverType GPULogger::GetDriverType() const {
|
||||||
|
return detected_driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GPULogger::GetStatistics() const {
|
||||||
|
std::lock_guard lock(memory_mutex);
|
||||||
|
return fmt::format(
|
||||||
|
"Vulkan Calls: {}, Allocations: {}, Deallocations: {}, "
|
||||||
|
"Current Memory: {}, Peak Memory: {}",
|
||||||
|
total_vulkan_calls, total_allocations, total_deallocations,
|
||||||
|
FormatMemorySize(current_allocated_bytes), FormatMemorySize(peak_allocated_bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GPULogger::IsInitialized() const {
|
||||||
|
return initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPULogger::WriteToLog(const std::string& message) {
|
||||||
|
if (!gpu_log_file || !gpu_log_file->IsOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock(file_mutex);
|
||||||
|
bytes_written += gpu_log_file->WriteString(message);
|
||||||
|
|
||||||
|
// Flush on errors or if we've written a lot
|
||||||
|
using namespace Common::Literals;
|
||||||
|
if (bytes_written % (1_MiB) == 0) {
|
||||||
|
gpu_log_file->Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GPULogger::FormatTimestamp(std::chrono::microseconds timestamp) const {
|
||||||
|
const auto seconds = timestamp.count() / 1000000;
|
||||||
|
const auto microseconds = timestamp.count() % 1000000;
|
||||||
|
return fmt::format("{:4d}.{:06d}", seconds, microseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GPULogger::FormatMemorySize(u64 bytes) const {
|
||||||
|
using namespace Common::Literals;
|
||||||
|
if (bytes >= 1_GiB) {
|
||||||
|
return fmt::format("{:.2f} GiB", static_cast<double>(bytes) / (1_GiB));
|
||||||
|
} else if (bytes >= 1_MiB) {
|
||||||
|
return fmt::format("{:.2f} MiB", static_cast<double>(bytes) / (1_MiB));
|
||||||
|
} else if (bytes >= 1_KiB) {
|
||||||
|
return fmt::format("{:.2f} KiB", static_cast<double>(bytes) / (1_KiB));
|
||||||
|
} else {
|
||||||
|
return fmt::format("{} B", bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GPU::Logging
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
namespace Common::FS {
|
||||||
|
class IOFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
class Device;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GPU::Logging {
|
||||||
|
|
||||||
|
enum class LogLevel : u8 {
|
||||||
|
Off = 0,
|
||||||
|
Errors = 1,
|
||||||
|
Standard = 2,
|
||||||
|
Verbose = 3,
|
||||||
|
All = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class DriverType : u8 {
|
||||||
|
Unknown,
|
||||||
|
Turnip, // Mesa Turnip driver
|
||||||
|
Qualcomm, // Qualcomm proprietary driver
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ring buffer entry for tracking Vulkan API calls
|
||||||
|
struct VulkanCallEntry {
|
||||||
|
std::chrono::microseconds timestamp;
|
||||||
|
std::string call_name; // e.g., "vkCmdDraw", "vkBeginRenderPass"
|
||||||
|
std::string parameters; // Serialized parameters
|
||||||
|
int result; // VkResult return code
|
||||||
|
u32 thread_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
// GPU memory allocation entry
|
||||||
|
struct MemoryAllocationEntry {
|
||||||
|
uintptr_t memory_handle;
|
||||||
|
u64 size;
|
||||||
|
u32 memory_flags;
|
||||||
|
std::chrono::microseconds timestamp;
|
||||||
|
bool is_device_local;
|
||||||
|
bool is_host_visible;
|
||||||
|
};
|
||||||
|
|
||||||
|
// GPU state snapshot for crash dumps
|
||||||
|
struct GPUStateSnapshot {
|
||||||
|
std::vector<VulkanCallEntry> recent_calls; // Last N API calls
|
||||||
|
std::vector<std::string> active_shaders; // Currently bound shaders
|
||||||
|
std::string pipeline_state; // Current pipeline state
|
||||||
|
std::string memory_status; // Current memory allocations
|
||||||
|
std::string driver_debug_info; // Driver-specific debug data
|
||||||
|
std::chrono::microseconds timestamp;
|
||||||
|
DriverType driver_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Main GPU logging system singleton
|
||||||
|
class GPULogger {
|
||||||
|
public:
|
||||||
|
static GPULogger& GetInstance();
|
||||||
|
|
||||||
|
// Prevent copying
|
||||||
|
GPULogger(const GPULogger&) = delete;
|
||||||
|
GPULogger& operator=(const GPULogger&) = delete;
|
||||||
|
|
||||||
|
// Initialization and control
|
||||||
|
void Initialize(LogLevel level, DriverType detected_driver = DriverType::Unknown);
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
// Logging API
|
||||||
|
void LogVulkanCall(const std::string& call_name, const std::string& params, int result);
|
||||||
|
void LogMemoryAllocation(uintptr_t memory, u64 size, u32 memory_flags);
|
||||||
|
void LogMemoryDeallocation(uintptr_t memory);
|
||||||
|
void LogShaderCompilation(const std::string& shader_name, const std::string& shader_info,
|
||||||
|
std::span<const u32> spirv_code = {});
|
||||||
|
void LogPipelineStateChange(const std::string& state_info);
|
||||||
|
void LogDriverDebugInfo(const std::string& debug_info);
|
||||||
|
|
||||||
|
// Extension usage tracking
|
||||||
|
void LogExtensionUsage(const std::string& extension_name, const std::string& function_name);
|
||||||
|
|
||||||
|
// Render pass logging
|
||||||
|
void LogRenderPassBegin(const std::string& render_pass_info);
|
||||||
|
void LogRenderPassEnd();
|
||||||
|
|
||||||
|
// Pipeline binding logging
|
||||||
|
void LogPipelineBind(bool is_compute, const std::string& pipeline_info);
|
||||||
|
|
||||||
|
// Descriptor set binding logging
|
||||||
|
void LogDescriptorSetBind(const std::string& descriptor_info);
|
||||||
|
|
||||||
|
// Pipeline barrier logging
|
||||||
|
void LogPipelineBarrier(const std::string& barrier_info);
|
||||||
|
|
||||||
|
// Image operation logging
|
||||||
|
void LogImageOperation(const std::string& operation, const std::string& image_info);
|
||||||
|
|
||||||
|
// Clear operation logging
|
||||||
|
void LogClearOperation(const std::string& clear_info);
|
||||||
|
|
||||||
|
// Crash handling
|
||||||
|
GPUStateSnapshot GetCurrentSnapshot();
|
||||||
|
void DumpStateToFile(const std::string& crash_reason);
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
void SetLogLevel(LogLevel level);
|
||||||
|
void EnableVulkanCallTracking(bool enabled);
|
||||||
|
void EnableShaderDumps(bool enabled);
|
||||||
|
void EnableMemoryTracking(bool enabled);
|
||||||
|
void EnableDriverDebugInfo(bool enabled);
|
||||||
|
void SetRingBufferSize(size_t entries);
|
||||||
|
|
||||||
|
// Query
|
||||||
|
LogLevel GetLogLevel() const;
|
||||||
|
DriverType GetDriverType() const;
|
||||||
|
std::string GetStatistics() const;
|
||||||
|
bool IsInitialized() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
GPULogger();
|
||||||
|
~GPULogger();
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
void WriteToLog(const std::string& message);
|
||||||
|
void RotateLogFile();
|
||||||
|
std::string FormatTimestamp(std::chrono::microseconds timestamp) const;
|
||||||
|
std::string FormatMemorySize(u64 bytes) const;
|
||||||
|
|
||||||
|
// State
|
||||||
|
bool initialized = false;
|
||||||
|
LogLevel current_level = LogLevel::Off;
|
||||||
|
DriverType detected_driver = DriverType::Unknown;
|
||||||
|
|
||||||
|
// Ring buffer for API calls
|
||||||
|
std::vector<VulkanCallEntry> call_ring_buffer;
|
||||||
|
size_t ring_buffer_index = 0;
|
||||||
|
size_t ring_buffer_size = 512;
|
||||||
|
mutable std::mutex ring_buffer_mutex;
|
||||||
|
|
||||||
|
// Memory tracking
|
||||||
|
std::unordered_map<uintptr_t, MemoryAllocationEntry> memory_allocations;
|
||||||
|
mutable std::mutex memory_mutex;
|
||||||
|
|
||||||
|
// Statistics
|
||||||
|
u64 total_vulkan_calls = 0;
|
||||||
|
u64 total_allocations = 0;
|
||||||
|
u64 total_deallocations = 0;
|
||||||
|
u64 current_allocated_bytes = 0;
|
||||||
|
u64 peak_allocated_bytes = 0;
|
||||||
|
|
||||||
|
// File backend for GPU logs
|
||||||
|
std::unique_ptr<Common::FS::IOFile> gpu_log_file;
|
||||||
|
mutable std::mutex file_mutex;
|
||||||
|
u64 bytes_written = 0;
|
||||||
|
|
||||||
|
// Feature flags
|
||||||
|
bool track_vulkan_calls = true;
|
||||||
|
bool dump_shaders = false;
|
||||||
|
bool track_memory = false;
|
||||||
|
bool capture_driver_debug = false;
|
||||||
|
|
||||||
|
// Extension usage tracking
|
||||||
|
std::set<std::string> used_extensions;
|
||||||
|
mutable std::mutex extension_mutex;
|
||||||
|
|
||||||
|
// Shader dump directory (created on demand)
|
||||||
|
bool shader_dump_dir_created = false;
|
||||||
|
|
||||||
|
// Stored state for crash dumps
|
||||||
|
std::string stored_driver_debug_info;
|
||||||
|
std::string stored_pipeline_state;
|
||||||
|
mutable std::mutex state_mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to get stage name from index
|
||||||
|
inline const char* GetShaderStageName(size_t stage_index) {
|
||||||
|
static constexpr std::array<const char*, 5> stage_names{
|
||||||
|
"vertex", "tess_control", "tess_eval", "geometry", "fragment"
|
||||||
|
};
|
||||||
|
return stage_index < stage_names.size() ? stage_names[stage_index] : "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GPU::Logging
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "video_core/gpu_logging/gpu_state_capture.h"
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
namespace GPU::Logging {
|
||||||
|
|
||||||
|
GPUStateSnapshot GPUStateCapture::CaptureState() {
|
||||||
|
return GPULogger::GetInstance().GetCurrentSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GPUStateCapture::SerializeState(const GPUStateSnapshot& snapshot) {
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
result += "=== GPU STATE SNAPSHOT ===\n\n";
|
||||||
|
|
||||||
|
result += fmt::format("Driver: {}\n", static_cast<int>(snapshot.driver_type));
|
||||||
|
result += fmt::format("Recent Calls: {}\n\n", snapshot.recent_calls.size());
|
||||||
|
|
||||||
|
result += "=== RECENT VULKAN CALLS ===\n";
|
||||||
|
for (const auto& call : snapshot.recent_calls) {
|
||||||
|
result += fmt::format("{}: {}({}) -> {}\n", call.timestamp.count(), call.call_name,
|
||||||
|
call.parameters, call.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
result += "\n=== MEMORY STATUS ===\n";
|
||||||
|
result += snapshot.memory_status;
|
||||||
|
|
||||||
|
result += "\n=== PIPELINE STATE ===\n";
|
||||||
|
result += snapshot.pipeline_state;
|
||||||
|
|
||||||
|
result += "\n=== DRIVER DEBUG INFO ===\n";
|
||||||
|
result += snapshot.driver_debug_info;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPUStateCapture::WriteCrashDump(const std::string& crash_reason) {
|
||||||
|
GPULogger::GetInstance().DumpStateToFile(crash_reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GPU::Logging
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "video_core/gpu_logging/gpu_logging.h"
|
||||||
|
|
||||||
|
namespace GPU::Logging {
|
||||||
|
|
||||||
|
class GPUStateCapture {
|
||||||
|
public:
|
||||||
|
// Capture current GPU state from logging system
|
||||||
|
static GPUStateSnapshot CaptureState();
|
||||||
|
|
||||||
|
// Serialize state to human-readable format
|
||||||
|
static std::string SerializeState(const GPUStateSnapshot& snapshot);
|
||||||
|
|
||||||
|
// Write detailed crash dump (implemented in GPULogger)
|
||||||
|
static void WriteCrashDump(const std::string& crash_reason);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace GPU::Logging
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "video_core/gpu_logging/qualcomm_debug.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
namespace GPU::Logging::Qualcomm {
|
||||||
|
|
||||||
|
bool QualcommDebugger::is_initialized = false;
|
||||||
|
|
||||||
|
void QualcommDebugger::Initialize() {
|
||||||
|
if (is_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_initialized = true;
|
||||||
|
LOG_INFO(Render_Vulkan, "[Qualcomm Debug] Initialized (stub)");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string QualcommDebugger::GetDebugInfo() {
|
||||||
|
// Stub for future Qualcomm proprietary driver debug extension support
|
||||||
|
// This requires libadrenotools integration and Qualcomm-specific APIs
|
||||||
|
return "Qualcomm debug info not yet implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GPU::Logging::Qualcomm
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace GPU::Logging::Qualcomm {
|
||||||
|
|
||||||
|
class QualcommDebugger {
|
||||||
|
public:
|
||||||
|
// Initialize Qualcomm debugging (stub for future implementation)
|
||||||
|
static void Initialize();
|
||||||
|
|
||||||
|
// Get debug information from Qualcomm driver
|
||||||
|
static std::string GetDebugInfo();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool is_initialized;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace GPU::Logging::Qualcomm
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr)
|
set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr)
|
||||||
@@ -19,7 +19,6 @@ set(SHADER_FILES
|
|||||||
block_linear_unswizzle_2d.comp
|
block_linear_unswizzle_2d.comp
|
||||||
block_linear_unswizzle_3d.comp
|
block_linear_unswizzle_3d.comp
|
||||||
block_linear_unswizzle_3d_bcn.comp
|
block_linear_unswizzle_3d_bcn.comp
|
||||||
convert_abgr8_srgb_to_d24s8.frag
|
|
||||||
convert_abgr8_to_d24s8.frag
|
convert_abgr8_to_d24s8.frag
|
||||||
convert_abgr8_to_d32f.frag
|
convert_abgr8_to_d32f.frag
|
||||||
convert_d32f_to_abgr8.frag
|
convert_d32f_to_abgr8.frag
|
||||||
@@ -77,14 +76,6 @@ set(SHADER_FILES
|
|||||||
vulkan_quad_indexed.comp
|
vulkan_quad_indexed.comp
|
||||||
vulkan_turbo_mode.comp
|
vulkan_turbo_mode.comp
|
||||||
vulkan_uint8.comp
|
vulkan_uint8.comp
|
||||||
convert_rgba8_to_bgra8.frag
|
|
||||||
convert_yuv420_to_rgb.comp
|
|
||||||
convert_rgb_to_yuv420.comp
|
|
||||||
convert_bc7_to_rgba8.comp
|
|
||||||
convert_astc_hdr_to_rgba16f.comp
|
|
||||||
convert_rgba16f_to_rgba8.frag
|
|
||||||
dither_temporal.frag
|
|
||||||
dynamic_resolution_scale.comp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (PLATFORM_HAIKU)
|
if (PLATFORM_HAIKU)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user