mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-14 21:04:41 +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
|
||||
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
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)
|
||||
Valid values are: Release, RelWithDebInfo, Debug
|
||||
Default: Debug
|
||||
-n, --nightly Create a nightly build.
|
||||
|
||||
Extra arguments are passed to CMake (e.g. -DCMAKE_OPTION_NAME=VALUE)
|
||||
Set the CCACHE variable to "true" to enable build caching.
|
||||
@@ -61,6 +62,7 @@ while true; do
|
||||
-r|--release) DEVEL=false ;;
|
||||
-t|--target) target "$2"; shift ;;
|
||||
-b|--build-type) type "$2"; shift ;;
|
||||
-n|--nightly) NIGHTLY=true ;;
|
||||
-h|--help) usage ;;
|
||||
*) break ;;
|
||||
esac
|
||||
@@ -101,7 +103,20 @@ cd src/android
|
||||
chmod +x ./gradlew
|
||||
|
||||
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..."
|
||||
|
||||
@@ -110,6 +125,7 @@ echo "-- building..."
|
||||
-Dorg.gradle.parallel="${CCACHE}" \
|
||||
-Dorg.gradle.workers.max="${NUM_JOBS}" \
|
||||
-PYUZU_ANDROID_ARGS="$*" \
|
||||
-Pnightly="$NIGHTLY" \
|
||||
--info
|
||||
|
||||
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>
|
||||
Date: Fri, 24 Oct 2025 23:41:09 -0700
|
||||
Subject: [PATCH] [build] Fix MinGW missing GetAddrInfoExCancel definition
|
||||
Date: Sun, 1 Feb 2026 16:21:10 -0500
|
||||
Subject: [PATCH] Fix build on MinGW
|
||||
|
||||
MinGW does not define GetAddrInfoExCancel in its wstcpi whatever header,
|
||||
so to get around this we can just load it with GetProcAddress et al.
|
||||
MinGW doesn't define GetAddrInfoExCancel.
|
||||
|
||||
Signed-off-by: crueter <crueter@eden-emu.dev>
|
||||
---
|
||||
httplib.h | 14 ++++++++++++--
|
||||
1 file changed, 12 insertions(+), 2 deletions(-)
|
||||
httplib.h | 18 ++++++++++++++++--
|
||||
1 file changed, 16 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/httplib.h b/httplib.h
|
||||
index e15ba44..90a76dc 100644
|
||||
index ec8d2a2..5f9a510 100644
|
||||
--- a/httplib.h
|
||||
+++ b/httplib.h
|
||||
@@ -203,11 +203,13 @@
|
||||
@@ -203,14 +203,17 @@
|
||||
#error Sorry, Visual Studio versions prior to 2015 are not supported
|
||||
#endif
|
||||
|
||||
-#pragma comment(lib, "ws2_32.lib")
|
||||
-
|
||||
#ifndef _SSIZE_T_DEFINED
|
||||
using ssize_t = __int64;
|
||||
#define _SSIZE_T_DEFINED
|
||||
#endif
|
||||
#endif // _MSC_VER
|
||||
|
||||
+#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
+#pragma comment(lib, "ws2_32.lib")
|
||||
+#endif
|
||||
+
|
||||
+
|
||||
#ifndef S_ISREG
|
||||
#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
|
||||
#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 =
|
||||
::WaitForSingleObject(event, static_cast<DWORD>(timeout_sec * 1000));
|
||||
if (wait_result == WAIT_TIMEOUT) {
|
||||
+#ifdef __MINGW32__
|
||||
+ typedef INT (WSAAPI *PFN_GETADDRINFOEXCANCEL)(HANDLE *CancelHandle);
|
||||
+ auto wsdll = LoadLibraryW((wchar_t*) "ws2_32.lib");
|
||||
+ PFN_GETADDRINFOEXCANCEL GetAddrInfoExCancel = (PFN_GETADDRINFOEXCANCEL) GetProcAddress(wsdll, "GetAddrInfoExCancel");
|
||||
+ typedef INT(WSAAPI * PFN_GETADDRINFOEXCANCEL)(HANDLE * CancelHandle);
|
||||
+ auto wsdll = LoadLibraryW((wchar_t *)"ws2_32.lib");
|
||||
+ PFN_GETADDRINFOEXCANCEL GetAddrInfoExCancel =
|
||||
+ (PFN_GETADDRINFOEXCANCEL)GetProcAddress(wsdll, "GetAddrInfoExCancel");
|
||||
+
|
||||
+ if (cancel_handle) { GetAddrInfoExCancel(&cancel_handle); }
|
||||
+#else
|
||||
if (cancel_handle) { ::GetAddrInfoExCancel(&cancel_handle); }
|
||||
+#endif
|
||||
+
|
||||
::CloseHandle(event);
|
||||
return EAI_AGAIN;
|
||||
}
|
||||
@@ -13952,3 +13965,4 @@ inline SSL_CTX *Client::ssl_context() const {
|
||||
} // namespace httplib
|
||||
|
||||
#endif // CPPHTTPLIB_HTTPLIB_H
|
||||
+
|
||||
--
|
||||
2.51.0
|
||||
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
|
||||
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()
|
||||
|
||||
# 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(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_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF)
|
||||
|
||||
@@ -585,6 +589,7 @@ if (NOT YUZU_STATIC_ROOM)
|
||||
find_package(sirit)
|
||||
find_package(gamemode)
|
||||
find_package(mcl)
|
||||
find_package(frozen)
|
||||
|
||||
if (ARCHITECTURE_riscv64)
|
||||
find_package(biscuit)
|
||||
@@ -691,6 +696,11 @@ if (ENABLE_QT)
|
||||
set(QT_MAJOR_VERSION 6)
|
||||
# Qt6 sets cxx_std_17 and we need to undo that
|
||||
set_target_properties(Qt6::Platform PROPERTIES INTERFACE_COMPILE_FEATURES "")
|
||||
|
||||
## Qt Externals ##
|
||||
|
||||
# QuaZip
|
||||
AddJsonPackage(quazip)
|
||||
endif()
|
||||
|
||||
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}")
|
||||
endfunction()
|
||||
|
||||
# propagate a variable to parent scope
|
||||
macro(Propagate var)
|
||||
set(${var} ${${var}} PARENT_SCOPE)
|
||||
endmacro()
|
||||
|
||||
function(array_to_list array length out)
|
||||
math(EXPR range "${length} - 1")
|
||||
|
||||
@@ -72,6 +77,159 @@ function(get_json_element object out member default)
|
||||
set("${out}" "${outvar}" PARENT_SCOPE)
|
||||
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
|
||||
function(AddJsonPackage)
|
||||
set(oneValueArgs
|
||||
@@ -80,7 +238,8 @@ function(AddJsonPackage)
|
||||
# these are overrides that can be generated at runtime,
|
||||
# so can be defined separately from the json
|
||||
DOWNLOAD_ONLY
|
||||
BUNDLED_PACKAGE)
|
||||
BUNDLED_PACKAGE
|
||||
FORCE_BUNDLED_PACKAGE)
|
||||
|
||||
set(multiValueArgs OPTIONS)
|
||||
|
||||
@@ -111,24 +270,9 @@ function(AddJsonPackage)
|
||||
cpm_utils_message(FATAL_ERROR ${JSON_NAME} "Not found in cpmfile")
|
||||
endif()
|
||||
|
||||
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 "")
|
||||
parse_object(${object})
|
||||
|
||||
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(
|
||||
VERSION ${version}
|
||||
NAME ${name}
|
||||
@@ -138,116 +282,38 @@ function(AddJsonPackage)
|
||||
MIN_VERSION ${min_version}
|
||||
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()
|
||||
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()
|
||||
|
||||
# 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
|
||||
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)
|
||||
|
||||
Propagate(${package}_ADDED)
|
||||
Propagate(${package}_SOURCE_DIR)
|
||||
Propagate(${package}_BINARY_DIR)
|
||||
endfunction()
|
||||
|
||||
function(AddPackage)
|
||||
@@ -343,7 +409,7 @@ function(AddPackage)
|
||||
|
||||
if(DEFINED PKG_ARGS_ARTIFACT)
|
||||
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()
|
||||
set(pkg_url
|
||||
${pkg_git_url}/archive/refs/tags/${PKG_ARGS_TAG}.tar.gz)
|
||||
@@ -625,7 +691,8 @@ function(AddCIPackage)
|
||||
endif()
|
||||
|
||||
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(
|
||||
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-FileCopyrightText: 2019 yuzu Emulator Project
|
||||
@@ -15,27 +15,40 @@ endfunction()
|
||||
get_timestamp(BUILD_DATE)
|
||||
|
||||
if (DEFINED GIT_RELEASE)
|
||||
set(BUILD_VERSION "${GIT_TAG}")
|
||||
set(GIT_REFSPEC "${GIT_RELEASE}")
|
||||
set(IS_DEV_BUILD false)
|
||||
set(BUILD_VERSION "${GIT_TAG}")
|
||||
set(GIT_REFSPEC "${GIT_RELEASE}")
|
||||
set(IS_DEV_BUILD false)
|
||||
else()
|
||||
string(SUBSTRING ${GIT_COMMIT} 0 10 BUILD_VERSION)
|
||||
set(BUILD_VERSION "${BUILD_VERSION}-${GIT_REFSPEC}")
|
||||
set(IS_DEV_BUILD true)
|
||||
string(SUBSTRING ${GIT_COMMIT} 0 10 BUILD_VERSION)
|
||||
set(BUILD_VERSION "${BUILD_VERSION}-${GIT_REFSPEC}")
|
||||
set(IS_DEV_BUILD true)
|
||||
endif()
|
||||
|
||||
if (NIGHTLY_BUILD)
|
||||
set(IS_NIGHTLY_BUILD true)
|
||||
else()
|
||||
set(IS_NIGHTLY_BUILD false)
|
||||
endif()
|
||||
|
||||
set(GIT_DESC ${BUILD_VERSION})
|
||||
|
||||
# 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
|
||||
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
|
||||
set(BUILD_AUTO_UPDATE_WEBSITE "https://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)
|
||||
|
||||
+25
-10
@@ -12,14 +12,12 @@
|
||||
"repo": "boostorg/boost",
|
||||
"tag": "boost-%VERSION%",
|
||||
"artifact": "%TAG%-cmake.tar.xz",
|
||||
"hash": "4fb7f6fde92762305aad8754d7643cd918dd1f3f67e104e9ab385b18c73178d72a17321354eb203b790b6702f2cf6d725a5d6e2dfbc63b1e35f9eb59fb42ece9",
|
||||
"git_version": "1.89.0",
|
||||
"hash": "6ae6e94664fe7f2fb01976b59b276ac5df8085c7503fa829d810fbfe495960cfec44fa2c36e2cb23480bc19c956ed199d4952b02639a00a6c07625d4e7130c2d",
|
||||
"git_version": "1.90.0",
|
||||
"version": "1.57",
|
||||
"find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem",
|
||||
"patches": [
|
||||
"0001-clang-cl.patch",
|
||||
"0002-use-marmasm.patch",
|
||||
"0003-armasm-options.patch"
|
||||
"0001-clang-cl.patch"
|
||||
]
|
||||
},
|
||||
"fmt": {
|
||||
@@ -48,9 +46,9 @@
|
||||
"package": "ZLIB",
|
||||
"repo": "madler/zlib",
|
||||
"tag": "v%VERSION%",
|
||||
"hash": "8c9642495bafd6fad4ab9fb67f09b268c69ff9af0f4f20cf15dfc18852ff1f312bd8ca41de761b3f8d8e90e77d79f2ccacd3d4c5b19e475ecf09d021fdfe9088",
|
||||
"hash": "06eaa3a1eaaeb31f461a2283b03a91ed8eb2406e62cd97ea1c69836324909edeecd93edd03ff0bf593d9dde223e3376149134c5b1fe2e8688c258cadf8cd60ff",
|
||||
"version": "1.2",
|
||||
"git_version": "1.3.1",
|
||||
"git_version": "1.3.1.2",
|
||||
"options": [
|
||||
"ZLIB_BUILD_SHARED OFF",
|
||||
"ZLIB_INSTALL OFF"
|
||||
@@ -69,13 +67,17 @@
|
||||
},
|
||||
"opus": {
|
||||
"package": "Opus",
|
||||
"repo": "crueter/opus",
|
||||
"sha": "ab19c44fad",
|
||||
"hash": "d632e8f83c5d3245db404bcb637113f9860bf16331498ba2c8e77979d1febee6b52d8b1da448e7d54eeac373e912cd55e3e300fc6c242244923323280dc43fbe",
|
||||
"repo": "xiph/opus",
|
||||
"sha": "a3f0ec02b3",
|
||||
"hash": "9506147b0de35befda8633ff272981cc2575c860874791bd455b752f797fd7dbd1079f0ba42ccdd7bb1fe6773fa5e84b3d75667c2883dd1fb2d0e4a5fa4f8387",
|
||||
"version": "1.3",
|
||||
"find_args": "MODULE",
|
||||
"options": [
|
||||
"OPUS_PRESUME_NEON ON"
|
||||
],
|
||||
"patches": [
|
||||
"0001-disable-clang-runtime-neon.patch",
|
||||
"0002-no-install.patch"
|
||||
]
|
||||
},
|
||||
"boost_headers": {
|
||||
@@ -99,5 +101,18 @@
|
||||
"git_version": "1.4.335.0",
|
||||
"artifact": "android-binaries-%VERSION%.zip",
|
||||
"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/dynarmic @Lizzie
|
||||
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/*_room @AleksandrPopovich
|
||||
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.
|
||||
|
||||
## 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.md) adds a specific version of Qt to your project.
|
||||
|
||||
Vendored
+4
-6
@@ -83,13 +83,11 @@ endif()
|
||||
# mcl
|
||||
AddJsonPackage(mcl)
|
||||
|
||||
# VulkanUtilityHeaders - pulls in headers and utility libs
|
||||
AddJsonPackage(vulkan-utility-headers)
|
||||
# Vulkan stuff
|
||||
AddDependentPackages(vulkan-headers vulkan-utility-libraries)
|
||||
|
||||
# small hack
|
||||
if (NOT VulkanUtilityLibraries_ADDED)
|
||||
find_package(VulkanHeaders 1.3.274 REQUIRED)
|
||||
endif()
|
||||
# frozen
|
||||
AddJsonPackage(frozen)
|
||||
|
||||
# DiscordRPC
|
||||
if (USE_DISCORD_PRESENCE)
|
||||
|
||||
Vendored
+32
-17
@@ -28,8 +28,8 @@
|
||||
"httplib": {
|
||||
"repo": "yhirose/cpp-httplib",
|
||||
"tag": "v%VERSION%",
|
||||
"hash": "e7a8877d489c97669a8ee536e1498575be921e558ed947253013fe6b67a49d4569eedd01f543caa70183b92d8ac0e8687d662a70d880954412e387317008a239",
|
||||
"git_version": "0.28.0",
|
||||
"hash": "a229e24cca4afe78e5c0aa2e482f15108ac34101fd8dbd927365f15e8c37dec4de38c5277d635017d692a5b320e1b929f8bfcc076f52b8e4dcdab8fe53bfdf2e",
|
||||
"git_version": "0.30.1",
|
||||
"find_args": "MODULE GLOBAL",
|
||||
"patches": [
|
||||
"0001-mingw.patch"
|
||||
@@ -37,12 +37,15 @@
|
||||
},
|
||||
"cpp-jwt": {
|
||||
"version": "1.4",
|
||||
"repo": "crueter/cpp-jwt",
|
||||
"sha": "9eaea6328f",
|
||||
"hash": "35b0b2bfb143585c7b2bd6dc6ca7df5ae5c6e2681000b2ebca077b0ac4bc1e6b6afbe1ce8e47f6d2edad12fcc6404f677acc2ad205661d819b8821ce6f4823fd",
|
||||
"repo": "arun11299/cpp-jwt",
|
||||
"sha": "7f24eb4c32",
|
||||
"hash": "d11cbd5ddb3197b4c5ca15679bcd76a49963e7b530b7dd132db91e042925efa20dfb2c24ccfbe7ef82a7012af80deff0f72ee25851312ae80381a462df8534b8",
|
||||
"find_args": "CONFIG",
|
||||
"options": [
|
||||
"CPP_JWT_USE_VENDORED_NLOHMANN_JSON OFF"
|
||||
],
|
||||
"patches": [
|
||||
"0001-fix-missing-decl.patch"
|
||||
]
|
||||
},
|
||||
"xbyak_sun": {
|
||||
@@ -115,18 +118,9 @@
|
||||
"git_version": "1.3.18",
|
||||
"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": {
|
||||
"package": "SPIRV-Tools",
|
||||
"repo": "crueter/SPIRV-Tools",
|
||||
"repo": "KhronosGroup/SPIRV-Tools",
|
||||
"sha": "0a7e28689a",
|
||||
"hash": "eadfcceb82f4b414528d99962335e4f806101168474028f3cf7691ac40c37f323decf2a42c525e2d5bfa6f14ff132d6c5cf9b87c151490efad01f5e13ade1520",
|
||||
"git_version": "2025.4",
|
||||
@@ -171,9 +165,9 @@
|
||||
"package": "Catch2",
|
||||
"repo": "catchorg/Catch2",
|
||||
"tag": "v%VERSION%",
|
||||
"hash": "a95495142f915d6e9c2a23e80fe360343e9097680066a2f9d3037a070ba5f81ee5559a0407cc9e972dc2afae325873f1fc7ea07a64012c0f01aac6e549f03e3f",
|
||||
"hash": "acb3f463a7404d6a3bce52e474075cdadf9bb241d93feaf147c182d756e5a2f8bd412f4658ca186d15ab8fed36fc587d79ec311f55642d8e4ded16df9e213656",
|
||||
"version": "3.0.1",
|
||||
"git_version": "3.11.0",
|
||||
"git_version": "3.12.0",
|
||||
"patches": [
|
||||
"0001-solaris-isnan-fix.patch"
|
||||
]
|
||||
@@ -278,5 +272,26 @@
|
||||
"tag": "%VERSION%",
|
||||
"hash": "dc37a189a44ce8b5c988ca550582431a6c7eadfd3c6e709bee6277116ee803e714333e85c9e6cbb5c69346a14d6f2cc7ed96e8aa09cc5fb8a89f945059651db6",
|
||||
"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,4 +20,4 @@ pkgs.mkShellNoCC {
|
||||
# optional components
|
||||
discord-rpc gamemode
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ if (YUZU_STATIC_BUILD)
|
||||
add_compile_definitions(QT_STATICPLUGIN)
|
||||
endif()
|
||||
|
||||
if (NIGHTLY_BUILD)
|
||||
add_compile_definitions(NIGHTLY_BUILD)
|
||||
endif()
|
||||
|
||||
# Set compilation flags
|
||||
if (MSVC AND NOT CXX_CLANG)
|
||||
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// import android.annotation.SuppressLint
|
||||
import com.android.build.gradle.api.ApplicationVariant
|
||||
import kotlin.collections.setOf
|
||||
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
|
||||
import com.github.triplet.gradle.androidpublisher.ReleaseStatus
|
||||
@@ -37,6 +38,9 @@ android {
|
||||
compileSdkVersion = "android-36"
|
||||
ndkVersion = "28.2.13676358"
|
||||
|
||||
val isNightly =
|
||||
providers.gradleProperty("nightly").orNull?.toBooleanStrictOrNull() ?: false
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
@@ -71,6 +75,7 @@ android {
|
||||
val extraCMakeArgs =
|
||||
(project.findProperty("YUZU_ANDROID_ARGS") as String?)?.split("\\s+".toRegex())
|
||||
?: emptyList()
|
||||
|
||||
arguments.addAll(
|
||||
listOf(
|
||||
"-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")
|
||||
}
|
||||
}
|
||||
@@ -125,7 +137,12 @@ android {
|
||||
signingConfigs.getByName("default")
|
||||
}
|
||||
|
||||
manifestPlaceholders += mapOf("appNameSuffix" to "")
|
||||
if (isNightly) {
|
||||
applicationIdSuffix = ".nightly"
|
||||
manifestPlaceholders += mapOf("appNameSuffix" to " Nightly")
|
||||
} else {
|
||||
manifestPlaceholders += mapOf("appNameSuffix" to "")
|
||||
}
|
||||
|
||||
isMinifyEnabled = true
|
||||
isDebuggable = false
|
||||
@@ -239,6 +256,15 @@ android {
|
||||
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 {
|
||||
@@ -258,7 +284,7 @@ tasks.register<Delete>("ktlintReset", fun Delete.() {
|
||||
val showFormatHelp = {
|
||||
logger.lifecycle(
|
||||
"If this check fails, please try running \"gradlew ktlintFormat\" for automatic " +
|
||||
"codestyle fixes"
|
||||
"codestyle fixes"
|
||||
)
|
||||
}
|
||||
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-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
@@ -218,7 +218,7 @@ object NativeLibrary {
|
||||
/**
|
||||
* Checks for available updates.
|
||||
*/
|
||||
external fun checkForUpdate(): String?
|
||||
external fun checkForUpdate(): Array<String>?
|
||||
|
||||
/**
|
||||
* Return the URL to the release page
|
||||
@@ -228,13 +228,18 @@ object NativeLibrary {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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).
|
||||
*/
|
||||
|
||||
@@ -22,14 +22,6 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||
|
||||
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() {
|
||||
if (emulationFragment.shouldUseCustom) {
|
||||
NativeConfig.savePerGameConfig()
|
||||
@@ -60,6 +52,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||
// settings
|
||||
|
||||
fun addIntSetting(
|
||||
name: Int,
|
||||
container: ViewGroup,
|
||||
setting: IntSetting,
|
||||
namesArrayId: Int,
|
||||
@@ -73,7 +66,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||
val expandIcon = itemView.findViewById<android.widget.ImageView>(R.id.expand_icon)
|
||||
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 values = emulationFragment.resources.getIntArray(valuesArrayId)
|
||||
@@ -115,6 +108,8 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||
}
|
||||
|
||||
fun addBooleanSetting(
|
||||
name: Int,
|
||||
|
||||
container: ViewGroup,
|
||||
setting: BooleanSetting
|
||||
) {
|
||||
@@ -125,7 +120,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||
val titleView = itemView.findViewById<TextView>(R.id.switch_title)
|
||||
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
|
||||
switchView.isChecked = setting.getBoolean()
|
||||
|
||||
@@ -141,6 +136,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||
}
|
||||
|
||||
fun addSliderSetting(
|
||||
name: Int,
|
||||
container: ViewGroup,
|
||||
setting: AbstractSetting,
|
||||
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)
|
||||
|
||||
|
||||
titleView.text = getSettingTitle(setting.key)
|
||||
titleView.text = YuzuApplication.appContext.getString(name)
|
||||
sliderContainer.visibility = View.VISIBLE
|
||||
|
||||
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_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
|
||||
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
||||
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
|
||||
SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
|
||||
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
|
||||
RENDERER_DEBUG("debug"),
|
||||
@@ -36,6 +37,9 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
USE_CUSTOM_RTC("custom_rtc_enabled"),
|
||||
BLACK_BACKGROUNDS("black_backgrounds"),
|
||||
|
||||
ENABLE_FOLDER_BUTTON("enable_folder_button"),
|
||||
ENABLE_QLAUNCH_BUTTON("enable_qlaunch_button"),
|
||||
|
||||
ENABLE_UPDATE_CHECKS("enable_update_checks"),
|
||||
JOYSTICK_REL_CENTER("joystick_rel_center"),
|
||||
DPAD_SLIDE("dpad_slide"),
|
||||
@@ -72,8 +76,16 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
USE_LRU_CACHE("use_lru_cache"),
|
||||
|
||||
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 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-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
@@ -9,7 +9,8 @@ package org.yuzu.yuzu_emu.features.settings.model
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
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)
|
||||
|
||||
|
||||
+2
-1
@@ -67,7 +67,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
MY_PAGE_APPLET("my_page_applet_mode"),
|
||||
INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide"),
|
||||
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)
|
||||
|
||||
+89
@@ -60,6 +60,11 @@ abstract class SettingsItem(
|
||||
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
|
||||
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
|
||||
return false
|
||||
@@ -740,6 +745,13 @@ abstract class SettingsItem(
|
||||
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(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SYNC_MEMORY_OPERATIONS,
|
||||
@@ -794,6 +806,27 @@ abstract class SettingsItem(
|
||||
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(
|
||||
SingleChoiceSetting(
|
||||
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 {
|
||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||
BooleanSetting.FASTMEM.getBoolean() &&
|
||||
|
||||
+19
@@ -275,6 +275,7 @@ class SettingsFragmentPresenter(
|
||||
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
||||
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
||||
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
|
||||
add(BooleanSetting.ENABLE_BUFFER_HISTORY.key)
|
||||
|
||||
add(HeaderSetting(R.string.hacks))
|
||||
|
||||
@@ -1074,6 +1075,8 @@ class SettingsFragmentPresenter(
|
||||
add(BooleanSetting.ENABLE_UPDATE_CHECKS.key)
|
||||
}
|
||||
|
||||
add(BooleanSetting.ENABLE_QUICK_SETTINGS.key)
|
||||
|
||||
add(HeaderSetting(R.string.theme_and_color))
|
||||
|
||||
|
||||
@@ -1191,6 +1194,13 @@ class SettingsFragmentPresenter(
|
||||
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(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)
|
||||
|
||||
if (!BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean()) {
|
||||
binding.drawerLayout.setDrawerLockMode(
|
||||
DrawerLayout.LOCK_MODE_LOCKED_CLOSED,
|
||||
binding.quickSettingsSheet
|
||||
)
|
||||
}
|
||||
|
||||
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 {
|
||||
val lockMode = IntSetting.LOCK_DRAWER.getInt()
|
||||
val titleId = if (lockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
|
||||
@@ -749,10 +759,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_quick_settings -> {
|
||||
openQuickSettingsMenu()
|
||||
true
|
||||
}
|
||||
if (BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean())
|
||||
R.id.menu_quick_settings else 0 -> {
|
||||
openQuickSettingsMenu()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_settings_per_game -> {
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
@@ -1045,11 +1056,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
quickSettings.addBooleanSetting(
|
||||
R.string.frame_limit_enable,
|
||||
container,
|
||||
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
|
||||
)
|
||||
|
||||
quickSettings.addSliderSetting(
|
||||
R.string.frame_limit_slider,
|
||||
container,
|
||||
ShortSetting.RENDERER_SPEED_LIMIT,
|
||||
minValue = 0,
|
||||
@@ -1058,6 +1071,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
)
|
||||
|
||||
quickSettings.addBooleanSetting(
|
||||
R.string.use_docked_mode,
|
||||
container,
|
||||
BooleanSetting.USE_DOCKED_MODE,
|
||||
)
|
||||
@@ -1065,6 +1079,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
quickSettings.addDivider(container)
|
||||
|
||||
quickSettings.addIntSetting(
|
||||
R.string.renderer_accuracy,
|
||||
container,
|
||||
IntSetting.RENDERER_ACCURACY,
|
||||
R.array.rendererAccuracyNames,
|
||||
@@ -1073,6 +1088,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
|
||||
quickSettings.addIntSetting(
|
||||
R.string.renderer_scaling_filter,
|
||||
container,
|
||||
IntSetting.RENDERER_SCALING_FILTER,
|
||||
R.array.rendererScalingFilterNames,
|
||||
@@ -1080,6 +1096,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
)
|
||||
|
||||
quickSettings.addSliderSetting(
|
||||
R.string.fsr_sharpness,
|
||||
container,
|
||||
IntSetting.FSR_SHARPENING_SLIDER,
|
||||
minValue = 0,
|
||||
@@ -1088,6 +1105,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
)
|
||||
|
||||
quickSettings.addIntSetting(
|
||||
R.string.renderer_anti_aliasing,
|
||||
container,
|
||||
IntSetting.RENDERER_ANTI_ALIASING,
|
||||
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
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
@@ -222,6 +222,14 @@ class HomeSettingsFragment : Fragment() {
|
||||
{ shareLog() }
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.share_gpu_log,
|
||||
R.string.share_gpu_log_description,
|
||||
R.drawable.ic_log,
|
||||
{ shareGpuLog() }
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
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() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||
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
|
||||
|
||||
package org.yuzu.yuzu_emu.ui
|
||||
@@ -13,6 +13,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
@@ -27,10 +28,14 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
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.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||
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.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
@@ -173,10 +178,16 @@ class GamesFragment : Fragment() {
|
||||
|
||||
setupTopView()
|
||||
|
||||
updateButtonsVisibility()
|
||||
|
||||
binding.addDirectory.setOnClickListener {
|
||||
getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
|
||||
}
|
||||
|
||||
binding.launchQlaunch?.setOnClickListener {
|
||||
launchQLaunch()
|
||||
}
|
||||
|
||||
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() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
@@ -498,6 +550,13 @@ class GamesFragment : Fragment() {
|
||||
mlpFab.rightMargin = rightInset + fabPadding
|
||||
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 gestureInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
|
||||
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
|
||||
|
||||
package org.yuzu.yuzu_emu.ui.main
|
||||
@@ -183,18 +183,26 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
val latestVersion = NativeLibrary.checkForUpdate()
|
||||
if (latestVersion != null) {
|
||||
runOnUiThread {
|
||||
showUpdateDialog(latestVersion)
|
||||
val tag: String = latestVersion[0]
|
||||
val name: String = latestVersion[1]
|
||||
showUpdateDialog(tag, name)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun showUpdateDialog(version: String) {
|
||||
private fun showUpdateDialog(tag: String, name: String) {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.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) { _, _ ->
|
||||
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, _ ->
|
||||
dialog.dismiss()
|
||||
@@ -207,11 +215,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun downloadAndInstallUpdate(version: String) {
|
||||
private fun downloadAndInstallUpdate(version: String, artifact: String) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val packageId = applicationContext.packageName
|
||||
val apkUrl = NativeLibrary.getUpdateApkUrl(version, packageId)
|
||||
val apkFile = File(cacheDir, "update-$version.apk")
|
||||
val apkUrl = NativeLibrary.getUpdateApkUrl(version, artifact, packageId)
|
||||
val apkFile = File(cacheDir, "update-$artifact.apk")
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
showDownloadProgressDialog()
|
||||
|
||||
@@ -65,6 +65,10 @@ namespace AndroidSettings {
|
||||
Settings::Setting<s32> app_language{linkage, 0, "app_language", Settings::Category::Android};
|
||||
Settings::Setting<bool> enable_update_checks{linkage, true, "enable_update_checks",
|
||||
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
|
||||
std::vector<OverlayControlData> overlay_control_data;
|
||||
@@ -198,9 +202,14 @@ namespace AndroidSettings {
|
||||
Settings::Specialization::Default, true, true,
|
||||
&show_soc_overlay};
|
||||
|
||||
// MISC
|
||||
Settings::Setting<bool> dont_show_driver_shader_warning{linkage, false,
|
||||
"dont_show_driver_shader_warning",
|
||||
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;
|
||||
|
||||
@@ -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-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);
|
||||
}
|
||||
|
||||
// return #ifdef ENABLE_UPDATE_CHECKER
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isUpdateCheckerEnabled(
|
||||
JNIEnv* env,
|
||||
jobject obj) {
|
||||
@@ -1606,22 +1605,39 @@ JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isUpdateChecker
|
||||
#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
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
|
||||
|
||||
JNIEXPORT jobjectArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
|
||||
JNIEnv* env,
|
||||
jobject obj) {
|
||||
const bool is_prerelease = ((strstr(Common::g_build_version, "pre-alpha") != nullptr) ||
|
||||
(strstr(Common::g_build_version, "alpha") != 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);
|
||||
std::optional<UpdateChecker::Update> release = UpdateChecker::GetUpdate();
|
||||
if (!release) return nullptr;
|
||||
|
||||
if (latest_release_tag && latest_release_tag.value() != Common::g_build_version) {
|
||||
return env->NewStringUTF(latest_release_tag.value().c_str());
|
||||
}
|
||||
return nullptr;
|
||||
const std::string tag = release->tag;
|
||||
const std::string name = release->name;
|
||||
|
||||
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(
|
||||
@@ -1640,9 +1656,11 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
|
||||
JNIEnv* env,
|
||||
jobject obj,
|
||||
jstring version,
|
||||
jstring tag,
|
||||
jstring artifact,
|
||||
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);
|
||||
|
||||
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) {
|
||||
variant = "optimized";
|
||||
} else {
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
variant = "standard";
|
||||
#else
|
||||
variant = "chromeos";
|
||||
#endif
|
||||
}
|
||||
|
||||
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,
|
||||
apk_filename);
|
||||
|
||||
env->ReleaseStringUTFChars(version, version_str);
|
||||
env->ReleaseStringUTFChars(tag, version_str);
|
||||
env->ReleaseStringUTFChars(packageId, package_id_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
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <jni.h>
|
||||
#include <common/fs/path_util.h>
|
||||
|
||||
#include "android_config.h"
|
||||
#include "android_settings.h"
|
||||
#include "common/android/android_common.h"
|
||||
#include "common/android/id_cache.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "frontend_common/settings_generator.h"
|
||||
#include "native.h"
|
||||
|
||||
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) {
|
||||
global_config = std::make_unique<AndroidConfig>();
|
||||
FrontendCommon::GenerateSettings();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) {
|
||||
|
||||
@@ -220,6 +220,23 @@
|
||||
android:textColor="?attr/colorOnPrimary"
|
||||
app:backgroundTint="?attr/colorPrimary"
|
||||
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>
|
||||
@@ -214,6 +214,22 @@
|
||||
|
||||
</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
|
||||
android:id="@+id/add_directory"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -227,6 +243,7 @@
|
||||
android:textColor="?attr/colorOnPrimary"
|
||||
app:backgroundTint="?attr/colorPrimary"
|
||||
app:iconTint="?attr/colorOnPrimary"
|
||||
app:rippleColor="#99FFFFFF"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -631,4 +631,21 @@
|
||||
<item>@string/error_keys_invalid_filename</item>
|
||||
<item>@string/error_keys_failed_init</item>
|
||||
</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>
|
||||
|
||||
@@ -325,6 +325,9 @@
|
||||
<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_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_description">Install game updates or DLC</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_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="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>
|
||||
@@ -552,6 +557,24 @@
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Audio settings strings -->
|
||||
@@ -715,6 +738,8 @@
|
||||
<string name="preferences_graphics">Graphics</string>
|
||||
<string name="preferences_graphics_description">Accuracy level, resolution, shader cache</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_description">Output engine, volume</string>
|
||||
<string name="preferences_controls">Controls</string>
|
||||
@@ -1140,6 +1165,12 @@
|
||||
<string name="use_black_backgrounds">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 -->
|
||||
<string name="app_language">App Language</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_API "@BUILD_AUTO_UPDATE_API@"
|
||||
#define BUILD_AUTO_UPDATE_REPO "@BUILD_AUTO_UPDATE_REPO@"
|
||||
#define IS_NIGHTLY_BUILD @IS_NIGHTLY_BUILD@
|
||||
|
||||
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_running[] = TITLE_BAR_FORMAT_RUNNING;
|
||||
constexpr const char g_compiler_id[] = COMPILER_ID;
|
||||
|
||||
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_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-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_shader_cache_version[];
|
||||
extern const char g_compiler_id[];
|
||||
|
||||
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_api[];
|
||||
extern const char g_build_auto_update_repo[];
|
||||
|
||||
@@ -49,6 +49,7 @@ SWITCHABLE(CpuBackend, true);
|
||||
SWITCHABLE(CpuAccuracy, true);
|
||||
SWITCHABLE(FullscreenMode, true);
|
||||
SWITCHABLE(GpuAccuracy, true);
|
||||
SWITCHABLE(GpuLogLevel, true);
|
||||
SWITCHABLE(Language, true);
|
||||
SWITCHABLE(MemoryLayout, true);
|
||||
SWITCHABLE(NvdecEmulation, false);
|
||||
|
||||
+21
-1
@@ -481,6 +481,14 @@ struct Values {
|
||||
SwitchableSetting<bool> barrier_feedback_loops{linkage, true, "barrier_feedback_loops",
|
||||
Category::RendererAdvanced};
|
||||
|
||||
SwitchableSetting<bool> enable_buffer_history{linkage,
|
||||
false,
|
||||
"enable_buffer_history",
|
||||
Category::RendererAdvanced,
|
||||
Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
|
||||
// Renderer Hacks //
|
||||
SwitchableSetting<GpuOverclock> fast_gpu_time{linkage,
|
||||
GpuOverclock::Medium,
|
||||
@@ -738,6 +746,18 @@ struct Values {
|
||||
Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", 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,
|
||||
0,
|
||||
0,
|
||||
@@ -766,7 +786,7 @@ struct Values {
|
||||
Category::WebService};
|
||||
Setting<std::string> eden_username{linkage, "Eden", "eden_username",
|
||||
Category::WebService};
|
||||
Setting<std::string> eden_token{linkage, "njausoolxygtpvraofqunuufhmupriifnpfggjxefntlyglr",
|
||||
Setting<std::string> eden_token{linkage, "",
|
||||
"eden_token", Category::WebService};
|
||||
|
||||
// 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-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(TemperatureUnits, Celsius, Fahrenheit)
|
||||
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
||||
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
|
||||
|
||||
template <typename Type>
|
||||
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-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/thread.h"
|
||||
#ifdef __APPLE__
|
||||
#include <mach/mach.h>
|
||||
@@ -18,6 +20,8 @@
|
||||
#include "common/string_util.h"
|
||||
#else
|
||||
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
#include <sys/cpuset.h>
|
||||
#include <sys/_cpuset.h>
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
#include <pthread.h>
|
||||
@@ -28,7 +32,7 @@
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#define cpu_set_t cpuset_t
|
||||
# define cpu_set_t cpuset_t
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
@@ -77,22 +81,14 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
#ifdef _MSC_VER
|
||||
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
static auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription");
|
||||
if (pf)
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
if (auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription"); pf)
|
||||
pf(GetCurrentThread(), UTF8ToUTF16W(name).data()); // Windows 10+
|
||||
}
|
||||
|
||||
#else // !MSVC_VER, so must be POSIX threads
|
||||
|
||||
// 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__
|
||||
else
|
||||
; // No-op
|
||||
#elif defined(__APPLE__)
|
||||
pthread_setname_np(name);
|
||||
#elif defined(__HAIKU__)
|
||||
rename_thread(find_thread(NULL), name);
|
||||
@@ -112,13 +108,38 @@ void SetCurrentThreadName(const char* name) {
|
||||
pthread_setname_np(pthread_self(), buf);
|
||||
}
|
||||
#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;
|
||||
#else
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
#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
|
||||
}
|
||||
}
|
||||
|
||||
} // 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-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
@@ -106,7 +106,7 @@ enum class ThreadPriority : u32 {
|
||||
};
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
||||
|
||||
void SetCurrentThreadName(const char* name);
|
||||
void PinCurrentThreadToPerformanceCore(size_t core_id);
|
||||
|
||||
} // 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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/fiber.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/cpu_manager.h"
|
||||
@@ -25,11 +26,8 @@ CpuManager::~CpuManager() = default;
|
||||
void CpuManager::Initialize() {
|
||||
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
|
||||
gpu_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
|
||||
|
||||
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); });
|
||||
}
|
||||
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); });
|
||||
}
|
||||
|
||||
void CpuManager::Shutdown() {
|
||||
@@ -188,14 +186,15 @@ void CpuManager::ShutdownThread() {
|
||||
void CpuManager::RunThread(std::stop_token token, std::size_t core) {
|
||||
/// Initialization
|
||||
system.RegisterCoreThread(core);
|
||||
std::string name;
|
||||
if (is_multicore) {
|
||||
name = "CPUCore_" + std::to_string(core);
|
||||
} else {
|
||||
name = "CPUThread";
|
||||
}
|
||||
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
|
||||
Common::SetCurrentThreadName(name.c_str());
|
||||
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];
|
||||
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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -144,7 +144,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
|
||||
{617, nullptr, "UnregisterExternalKey"},
|
||||
{620, nullptr, "SetSdCardEncryptionSeed"},
|
||||
{630, nullptr, "SetSdCardAccessibility"},
|
||||
{631, nullptr, "IsSdCardAccessible"},
|
||||
{631, D<&FSP_SRV::IsSdCardAccessible>, "IsSdCardAccessible"},
|
||||
{640, nullptr, "IsSignedSystemPartitionOnSdCardValid"},
|
||||
{700, nullptr, "OpenAccessFailureResolver"},
|
||||
{701, nullptr, "GetAccessFailureDetectionEvent"},
|
||||
@@ -524,6 +524,14 @@ Result FSP_SRV::OpenDataStorageWithProgramIndex(OutInterface<IStorage> out_inter
|
||||
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() {
|
||||
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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -101,6 +101,7 @@ private:
|
||||
Result OpenPatchDataStorageByCurrentProcess(OutInterface<IStorage> out_interface,
|
||||
FileSys::StorageId storage_id, u64 title_id);
|
||||
Result OpenDataStorageWithProgramIndex(OutInterface<IStorage> out_interface, u8 program_index);
|
||||
Result IsSdCardAccessible(Out<bool> out_is_accessible);
|
||||
Result DisableAutoSaveDataCreation();
|
||||
Result SetGlobalAccessLogMode(AccessLogMode 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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -99,11 +99,6 @@ Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer,
|
||||
slots[slot].acquire_called = true;
|
||||
slots[slot].needs_cleanup_on_release = false;
|
||||
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
|
||||
|
||||
@@ -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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -17,6 +17,39 @@ 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() {
|
||||
dequeue_possible.store(true);
|
||||
dequeue_condition.notify_all();
|
||||
@@ -30,7 +63,6 @@ bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk)
|
||||
}
|
||||
|
||||
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) {
|
||||
return 0;
|
||||
}
|
||||
@@ -55,8 +87,6 @@ s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
|
||||
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) {
|
||||
const auto state = slots[slot].buffer_state;
|
||||
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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -10,20 +10,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
|
||||
#include "core/hle/service/nvnflinger/buffer_item.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/status.h"
|
||||
#include "core/hle/service/nvnflinger/window.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
struct BufferHistoryInfo {
|
||||
u64 frame_number{};
|
||||
s64 queue_time{};
|
||||
s64 presentation_time{};
|
||||
BufferState state{};
|
||||
};
|
||||
|
||||
class IConsumerListener;
|
||||
class IProducerListener;
|
||||
|
||||
@@ -33,10 +44,14 @@ class BufferQueueCore final {
|
||||
|
||||
public:
|
||||
static constexpr s32 INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT;
|
||||
static constexpr u32 BUFFER_HISTORY_SIZE = 8;
|
||||
|
||||
BufferQueueCore();
|
||||
~BufferQueueCore();
|
||||
|
||||
void PushHistory(u64 frame_number, s64 queue_time, s64 presentation_time, BufferState state);
|
||||
void UpdateHistory(u64 frame_number, BufferState state);
|
||||
|
||||
private:
|
||||
void SignalDequeueCondition();
|
||||
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
|
||||
bool buffer_has_been_queued{};
|
||||
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{};
|
||||
bool is_allocating{};
|
||||
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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
@@ -26,7 +27,7 @@ BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext&
|
||||
std::shared_ptr<BufferQueueCore> buffer_queue_core_,
|
||||
Service::Nvidia::NvCore::NvMap& nvmap_)
|
||||
: 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");
|
||||
}
|
||||
|
||||
@@ -428,8 +429,7 @@ Status BufferQueueProducer::AttachBuffer(s32* out_slot,
|
||||
return return_flags;
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
||||
QueueBufferOutput* output) {
|
||||
Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, QueueBufferOutput* output) {
|
||||
s64 timestamp{};
|
||||
bool is_auto_timestamp{};
|
||||
Common::Rectangle<s32> crop;
|
||||
@@ -440,8 +440,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
||||
s32 swap_interval{};
|
||||
Fence fence{};
|
||||
|
||||
input.Deflate(×tamp, &is_auto_timestamp, &crop, &scaling_mode, &transform,
|
||||
&sticky_transform_, &async, &swap_interval, &fence);
|
||||
input.Deflate(×tamp, &is_auto_timestamp, &crop, &scaling_mode, &transform, &sticky_transform_, &async, &swap_interval, &fence);
|
||||
|
||||
switch (scaling_mode) {
|
||||
case NativeWindowScalingMode::Freeze:
|
||||
@@ -455,10 +454,9 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
std::shared_ptr<IConsumerListener> frame_available_listener;
|
||||
std::shared_ptr<IConsumerListener> frame_replaced_listener;
|
||||
s32 callback_ticket{};
|
||||
BufferItem item;
|
||||
std::shared_ptr<IConsumerListener> listener_available;
|
||||
std::shared_ptr<IConsumerListener> listener_replaced;
|
||||
|
||||
{
|
||||
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);
|
||||
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) {
|
||||
LOG_ERROR(Service_Nvnflinger, "slot index {} out of range [0, {})", slot,
|
||||
max_buffer_count);
|
||||
LOG_ERROR(Service_Nvnflinger, "slot {} out of range [0, {})", slot, max_buffer_count);
|
||||
return Status::BadValue;
|
||||
} else if (slots[slot].buffer_state != BufferState::Dequeued) {
|
||||
LOG_ERROR(Service_Nvnflinger,
|
||||
"slot {} is not owned by the producer "
|
||||
"(state = {})",
|
||||
slot, slots[slot].buffer_state);
|
||||
}
|
||||
if (slots[slot].buffer_state != BufferState::Dequeued) {
|
||||
LOG_ERROR(Service_Nvnflinger, "slot {} is not owned by producer", slot);
|
||||
return Status::BadValue;
|
||||
} else if (!slots[slot].request_buffer_called) {
|
||||
LOG_ERROR(Service_Nvnflinger,
|
||||
"slot {} was queued without requesting "
|
||||
"a buffer",
|
||||
slot);
|
||||
}
|
||||
if (!slots[slot].request_buffer_called) {
|
||||
LOG_ERROR(Service_Nvnflinger, "slot {} was queued without request", slot);
|
||||
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;
|
||||
slots[slot].buffer_state = BufferState::Queued;
|
||||
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.crop = crop;
|
||||
item.transform = transform & ~NativeWindowTransform::InverseDisplay;
|
||||
item.transform_to_display_inverse =
|
||||
(transform & NativeWindowTransform::InverseDisplay) != NativeWindowTransform::None;
|
||||
item.scaling_mode = static_cast<u32>(scaling_mode);
|
||||
item.frame_number = core->frame_counter;
|
||||
item.timestamp = timestamp;
|
||||
item.is_auto_timestamp = is_auto_timestamp;
|
||||
item.frame_number = core->frame_counter;
|
||||
item.slot = slot;
|
||||
item.crop = crop;
|
||||
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.is_droppable = core->dequeue_buffer_cannot_block || async;
|
||||
item.swap_interval = swap_interval;
|
||||
item.acquire_called = slots[slot].acquire_called;
|
||||
|
||||
sticky_transform = sticky_transform_;
|
||||
|
||||
if (core->queue.empty()) {
|
||||
// When the queue is empty, we can simply queue this buffer
|
||||
core->queue.push_back(item);
|
||||
frame_available_listener = core->consumer_listener;
|
||||
listener_available = core->consumer_listener;
|
||||
} else {
|
||||
// When the queue is not empty, we need to look at the front buffer
|
||||
// state to see if we need to replace it
|
||||
auto front(core->queue.begin());
|
||||
auto front = core->queue.begin();
|
||||
if (front->is_droppable && core->StillTracking(*front)) {
|
||||
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 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;
|
||||
frame_replaced_listener = core->consumer_listener;
|
||||
listener_replaced = core->consumer_listener;
|
||||
} else {
|
||||
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->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
|
||||
callback_ticket = next_callback_ticket++;
|
||||
output->Inflate(core->default_width, core->default_height, core->transform_hint, static_cast<u32>(core->queue.size()));
|
||||
}
|
||||
|
||||
// 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.slot = BufferItem::INVALID_BUFFER_SLOT;
|
||||
|
||||
// Call back without the main BufferQueue lock held, but with the callback lock held so we can
|
||||
// ensure that callbacks occur in order
|
||||
{
|
||||
std::scoped_lock lock{callback_mutex};
|
||||
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();
|
||||
if (listener_available) {
|
||||
listener_available->OnFrameAvailable(item);
|
||||
} else if (listener_replaced) {
|
||||
listener_replaced->OnFrameReplaced(item);
|
||||
}
|
||||
|
||||
return Status::NoError;
|
||||
@@ -810,6 +763,10 @@ Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot,
|
||||
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,
|
||||
std::span<u8> parcel_reply, u32 flags) {
|
||||
// Values used by BnGraphicBufferProducer onTransact
|
||||
@@ -929,9 +886,42 @@ void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
|
||||
status = SetBufferCount(buffer_count);
|
||||
break;
|
||||
}
|
||||
case TransactionId::GetBufferHistory:
|
||||
LOG_DEBUG(Service_Nvnflinger, "(STUBBED) called, transaction=GetBufferHistory");
|
||||
case TransactionId::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;
|
||||
}
|
||||
default:
|
||||
ASSERT_MSG(false, "Unimplemented TransactionId {}", code);
|
||||
break;
|
||||
@@ -944,8 +934,4 @@ void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
|
||||
(std::min)(parcel_reply.size(), serialized.size()));
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent* BufferQueueProducer::GetNativeHandle(u32 type_id) {
|
||||
return &buffer_wait_event->GetReadableEvent();
|
||||
}
|
||||
|
||||
} // 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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <mutex>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/wall_clock.h"
|
||||
#include "core/hle/service/nvdrv/nvdata.h"
|
||||
#include "core/hle/service/nvnflinger/binder.h"
|
||||
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
|
||||
@@ -88,6 +89,7 @@ private:
|
||||
s32 next_callback_ticket{};
|
||||
s32 current_callback_ticket{};
|
||||
std::condition_variable_any callback_condition;
|
||||
std::unique_ptr<Common::WallClock> clock;
|
||||
|
||||
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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -37,6 +37,7 @@ struct BufferSlot final {
|
||||
bool needs_cleanup_on_release{};
|
||||
bool attached_by_consumer{};
|
||||
bool is_preallocated{};
|
||||
s64 queue_time{}, presentation_time{};
|
||||
};
|
||||
|
||||
} // 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
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
for (auto iter = block.begin(); iter != block.end(); ++iter) {
|
||||
for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) {
|
||||
IR::Inst* inst = &*iter;
|
||||
|
||||
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
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -582,11 +582,9 @@ void EmitIR<IR::Opcode::A32BXWritePC>(oaknut::CodeGenerator& code, EmitContext&
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::A32UpdateUpperLocationDescriptor>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst*) {
|
||||
for (auto& inst : ctx.block) {
|
||||
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC) {
|
||||
for (auto& inst : ctx.block.instructions)
|
||||
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC)
|
||||
return;
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
/* 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
|
||||
|
||||
/* 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) {
|
||||
defined_insts.insert(inst);
|
||||
|
||||
ASSERT(!ValueLocation(inst));
|
||||
|
||||
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) {
|
||||
defined_insts.insert(inst);
|
||||
|
||||
ASSERT(!ValueLocation(inst));
|
||||
auto& info = reg.is_vector() ? fprs[reg.index()] : gprs[reg.index()];
|
||||
ASSERT(info.IsCompletelyEmpty());
|
||||
@@ -375,7 +373,6 @@ int RegAlloc::RealizeReadImpl(const IR::Value& value) {
|
||||
template<HostLoc::Kind kind>
|
||||
int RegAlloc::RealizeWriteImpl(const IR::Inst* value) {
|
||||
defined_insts.insert(value);
|
||||
|
||||
ASSERT(!ValueLocation(value));
|
||||
|
||||
if constexpr (kind == HostLoc::Kind::Gpr) {
|
||||
@@ -400,7 +397,6 @@ int RegAlloc::RealizeWriteImpl(const IR::Inst* value) {
|
||||
template<HostLoc::Kind kind>
|
||||
int RegAlloc::RealizeReadWriteImpl(const IR::Value& read_value, const IR::Inst* write_value) {
|
||||
defined_insts.insert(write_value);
|
||||
|
||||
// TODO: Move elimination
|
||||
|
||||
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) {
|
||||
defined_insts.insert(write);
|
||||
|
||||
const auto current_location = ValueLocation(read.value.GetInst());
|
||||
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
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -302,17 +302,12 @@ public:
|
||||
|
||||
private:
|
||||
friend struct Argument;
|
||||
template<typename>
|
||||
friend struct RAReg;
|
||||
template<typename> friend struct RAReg;
|
||||
|
||||
template<HostLoc::Kind kind>
|
||||
int GenerateImmediate(const IR::Value& value);
|
||||
template<HostLoc::Kind kind>
|
||||
int RealizeReadImpl(const IR::Value& 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);
|
||||
template<HostLoc::Kind kind> int GenerateImmediate(const IR::Value& value);
|
||||
template<HostLoc::Kind kind> int RealizeReadImpl(const IR::Value& 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;
|
||||
void SpillGpr(int index);
|
||||
@@ -337,7 +332,6 @@ private:
|
||||
std::array<HostLocInfo, SpillCount> spills;
|
||||
|
||||
mutable std::mt19937 rand_gen;
|
||||
|
||||
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
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -15,26 +15,24 @@
|
||||
|
||||
namespace Dynarmic::Backend {
|
||||
|
||||
template<typename ProgramCounterType>
|
||||
void BlockRangeInformation<ProgramCounterType>::AddRange(boost::icl::discrete_interval<ProgramCounterType> range, IR::LocationDescriptor location) {
|
||||
block_ranges.add(std::make_pair(range, std::set<IR::LocationDescriptor>{location}));
|
||||
template<typename P>
|
||||
void BlockRangeInformation<P>::AddRange(boost::icl::discrete_interval<P> range, IR::LocationDescriptor location) {
|
||||
block_ranges.add(std::make_pair(range, ankerl::unordered_dense::set<IR::LocationDescriptor>{location}));
|
||||
}
|
||||
|
||||
template<typename ProgramCounterType>
|
||||
void BlockRangeInformation<ProgramCounterType>::ClearCache() {
|
||||
template<typename P>
|
||||
void BlockRangeInformation<P>::ClearCache() {
|
||||
block_ranges.clear();
|
||||
}
|
||||
|
||||
template<typename ProgramCounterType>
|
||||
ankerl::unordered_dense::set<IR::LocationDescriptor> BlockRangeInformation<ProgramCounterType>::InvalidateRanges(const boost::icl::interval_set<ProgramCounterType>& ranges) {
|
||||
template<typename P>
|
||||
ankerl::unordered_dense::set<IR::LocationDescriptor> BlockRangeInformation<P>::InvalidateRanges(const boost::icl::interval_set<P>& ranges) {
|
||||
ankerl::unordered_dense::set<IR::LocationDescriptor> erase_locations;
|
||||
for (auto invalidate_interval : ranges) {
|
||||
auto pair = block_ranges.equal_range(invalidate_interval);
|
||||
for (auto it = pair.first; it != pair.second; ++it) {
|
||||
for (const auto& descriptor : it->second) {
|
||||
for (auto it = pair.first; it != pair.second; ++it)
|
||||
for (const auto& descriptor : it->second)
|
||||
erase_locations.insert(descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: EFFICIENCY: Remove ranges that are to be erased.
|
||||
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.
|
||||
* Copyright (c) 2018 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
@@ -15,15 +18,13 @@
|
||||
|
||||
namespace Dynarmic::Backend {
|
||||
|
||||
template<typename ProgramCounterType>
|
||||
template<typename P>
|
||||
class BlockRangeInformation {
|
||||
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();
|
||||
ankerl::unordered_dense::set<IR::LocationDescriptor> InvalidateRanges(const boost::icl::interval_set<ProgramCounterType>& ranges);
|
||||
|
||||
private:
|
||||
boost::icl::interval_map<ProgramCounterType, std::set<IR::LocationDescriptor>> block_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;
|
||||
};
|
||||
|
||||
} // 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
|
||||
|
||||
/* 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());
|
||||
|
||||
for (auto iter = block.begin(); iter != block.end(); ++iter) {
|
||||
for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) {
|
||||
IR::Inst* inst = &*iter;
|
||||
|
||||
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
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -123,7 +123,7 @@ A32EmitX64::BlockDescriptor A32EmitX64::Emit(IR::Block& block) {
|
||||
|
||||
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;
|
||||
// Call the relevant Emit* member function.
|
||||
switch (inst->GetOpcode()) {
|
||||
@@ -727,11 +727,9 @@ void A32EmitX64::EmitA32BXWritePC(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32UpdateUpperLocationDescriptor(A32EmitContext& ctx, IR::Inst*) {
|
||||
for (auto& inst : ctx.block) {
|
||||
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC) {
|
||||
for (auto& inst : ctx.block.instructions)
|
||||
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC)
|
||||
return;
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -116,7 +116,7 @@ A64EmitX64::BlockDescriptor A64EmitX64::Emit(IR::Block& block) noexcept {
|
||||
#undef A64OPC
|
||||
};
|
||||
|
||||
for (auto& inst : block) {
|
||||
for (auto& inst : block.instructions) {
|
||||
auto const opcode = inst.GetOpcode();
|
||||
// Call the relevant Emit* member function.
|
||||
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
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -38,11 +38,6 @@ EmitContext::EmitContext(RegAlloc& reg_alloc, IR::Block& block)
|
||||
|
||||
EmitContext::~EmitContext() = default;
|
||||
|
||||
void EmitContext::EraseInstruction(IR::Inst* inst) {
|
||||
block.Instructions().erase(inst);
|
||||
inst->ClearArgs();
|
||||
}
|
||||
|
||||
EmitX64::EmitX64(BlockOfCode& code)
|
||||
: code(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
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -56,11 +56,7 @@ using HalfVectorArray = std::array<T, A64FullVectorWidth::value / mcl::bitsizeof
|
||||
struct EmitContext {
|
||||
EmitContext(RegAlloc& reg_alloc, IR::Block& block);
|
||||
virtual ~EmitContext();
|
||||
|
||||
void EraseInstruction(IR::Inst* inst);
|
||||
|
||||
virtual FP::FPCR FPCR(bool fpcr_controlled = true) const = 0;
|
||||
|
||||
virtual bool HasOptimization(OptimizationFlag flag) const = 0;
|
||||
|
||||
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
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -34,14 +34,7 @@ namespace Dynarmic::Backend::X64 {
|
||||
|
||||
using namespace Xbyak::util;
|
||||
|
||||
#define ICODE(NAME) \
|
||||
[&code](auto... args) { \
|
||||
if constexpr (esize == 32) { \
|
||||
code.NAME##d(args...); \
|
||||
} else { \
|
||||
code.NAME##q(args...); \
|
||||
} \
|
||||
}
|
||||
#define ICODE(NAME) [&](auto... args) { if (esize == 32) code.NAME##d(args...); else code.NAME##q(args...); }
|
||||
|
||||
template<typename Function>
|
||||
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
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -26,7 +26,7 @@ bool CondCanContinue(const ConditionalState cond_state, const A32::IREmitter& ir
|
||||
return true;
|
||||
|
||||
// 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());
|
||||
});
|
||||
}
|
||||
@@ -66,7 +66,7 @@ bool IsConditionPassed(TranslatorVisitor& v, IR::Cond 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.
|
||||
v.cond_state = ConditionalState::Break;
|
||||
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.
|
||||
* Copyright (c) 2018 MerryMage
|
||||
* 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;
|
||||
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.
|
||||
if (!ir.block.empty() && !options.wall_clock_cntpct) {
|
||||
if (!ir.block.instructions.empty() && !options.wall_clock_cntpct) {
|
||||
ir.block.CycleCount()--;
|
||||
ir.SetTerm(IR::Term::LinkBlock{*ir.current_location});
|
||||
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
|
||||
|
||||
/* 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())
|
||||
+ fmt::format("cycles={}", block.CycleCount())
|
||||
+ 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 += '\n';
|
||||
|
||||
const auto arg_to_string = [](const IR::Value& arg) -> std::string {
|
||||
if (arg.IsEmpty()) {
|
||||
return "<null>";
|
||||
} 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("%<unnamed inst {:016x}>", reinterpret_cast<u64>(arg.GetInst()));
|
||||
return fmt::format("%<unnamed inst {:016x}>", u64(arg.GetInst()));
|
||||
}
|
||||
switch (arg.GetType()) {
|
||||
case Type::U1:
|
||||
return fmt::format("#{}", arg.GetU1() ? '1' : '0');
|
||||
case Type::U8:
|
||||
return fmt::format("#{}", arg.GetU8());
|
||||
case Type::U16:
|
||||
return fmt::format("#{:#x}", arg.GetU16());
|
||||
case Type::U32:
|
||||
return fmt::format("#{:#x}", arg.GetU32());
|
||||
case Type::U64:
|
||||
return fmt::format("#{:#x}", arg.GetU64());
|
||||
case Type::U128:
|
||||
return fmt::format("#<u128 imm>");
|
||||
case Type::A32Reg:
|
||||
return A32::RegToString(arg.GetA32RegRef());
|
||||
case Type::A32ExtReg:
|
||||
return A32::ExtRegToString(arg.GetA32ExtRegRef());
|
||||
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());
|
||||
case Type::U1: return fmt::format("#{}", arg.GetU1() ? '1' : '0');
|
||||
case Type::U8: return fmt::format("#{}", arg.GetU8());
|
||||
case Type::U16: return fmt::format("#{:#x}", arg.GetU16());
|
||||
case Type::U32: return fmt::format("#{:#x}", arg.GetU32());
|
||||
case Type::U64: return fmt::format("#{:#x}", arg.GetU64());
|
||||
case Type::U128: return fmt::format("#<u128 imm>");
|
||||
case Type::A32Reg: return A32::RegToString(arg.GetA32RegRef());
|
||||
case Type::A32ExtReg: return A32::ExtRegToString(arg.GetA32ExtRegRef());
|
||||
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();
|
||||
|
||||
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 += '\n';
|
||||
ret += fmt::format(" (uses: {})", inst.UseCount()) + '\n';
|
||||
}
|
||||
|
||||
ret += "terminal = " + TerminalToString(block.GetTerminal()) + '\n';
|
||||
|
||||
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
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "dynarmic/ir/microinstruction.h"
|
||||
#include "dynarmic/ir/terminal.h"
|
||||
#include "dynarmic/ir/value.h"
|
||||
#include "dynarmic/ir/dense_list.h"
|
||||
|
||||
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
|
||||
/// the microinstructions. This only matters before chaining is done in order to correctly
|
||||
/// order memory accesses.
|
||||
class Block final {
|
||||
class alignas(4096) Block final {
|
||||
public:
|
||||
//using instruction_list_type = dense_list<Inst>;
|
||||
using instruction_list_type = mcl::intrusive_list<Inst>;
|
||||
@@ -51,37 +50,12 @@ public:
|
||||
Block(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,
|
||||
/// handling any allocations necessary to do so.
|
||||
/// @param op Opcode representing the instruction to add.
|
||||
/// @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 {
|
||||
PrependNewInst(end(), opcode, args);
|
||||
inline iterator AppendNewInst(const Opcode opcode, const std::initializer_list<IR::Value> args) noexcept {
|
||||
return PrependNewInst(instructions.end(), opcode, args);
|
||||
}
|
||||
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 {
|
||||
return cycle_count;
|
||||
}
|
||||
private:
|
||||
|
||||
/// "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.
|
||||
instruction_list_type instructions;
|
||||
/// "Long/far" memory pool
|
||||
@@ -187,7 +161,7 @@ private:
|
||||
/// Number of cycles this block takes to execute.
|
||||
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.
|
||||
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
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -65,15 +65,12 @@ enum class MemOp {
|
||||
PREFETCH,
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenience class to construct a basic block of the intermediate representation.
|
||||
* `block` is the resulting block.
|
||||
* The user of this class updates `current_location` as appropriate.
|
||||
*/
|
||||
/// @brief Convenience class to construct a basic block of the intermediate representation.
|
||||
/// `block` is the resulting block.
|
||||
/// The user of this class updates `current_location` as appropriate.
|
||||
class IREmitter {
|
||||
public:
|
||||
explicit IREmitter(Block& block)
|
||||
: block(block), insertion_point(block.end()) {}
|
||||
explicit IREmitter(Block& block) : block(block), insertion_point(block.instructions.end()) {}
|
||||
|
||||
Block& block;
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
namespace Dynarmic::Optimization {
|
||||
|
||||
static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) {
|
||||
for (auto& inst : block) {
|
||||
for (auto& inst : block.instructions) {
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::Opcode::A32ReadMemory8:
|
||||
case IR::Opcode::A64ReadMemory8: {
|
||||
@@ -131,7 +131,7 @@ static void FlagsPass(IR::Block& block) {
|
||||
|
||||
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();
|
||||
switch (opcode) {
|
||||
case IR::Opcode::A32GetCFlag: {
|
||||
@@ -318,7 +318,7 @@ static void RegisterPass(IR::Block& block) {
|
||||
// Location and version don't matter here.
|
||||
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();
|
||||
switch (opcode) {
|
||||
case IR::Opcode::A32GetRegister: {
|
||||
@@ -448,7 +448,7 @@ static void A64CallbackConfigPass(IR::Block& block, const A64::UserConfig& conf)
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& inst : block) {
|
||||
for (auto& inst : block.instructions) {
|
||||
if (inst.GetOpcode() != IR::Opcode::A64DataCacheOperationRaised) {
|
||||
continue;
|
||||
}
|
||||
@@ -541,7 +541,7 @@ static void A64GetSetElimination(IR::Block& block) {
|
||||
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();
|
||||
switch (opcode) {
|
||||
case IR::Opcode::A64GetW: {
|
||||
@@ -1041,7 +1041,7 @@ static void FoldZeroExtendXToLong(IR::Inst& inst) {
|
||||
}
|
||||
|
||||
static void ConstantPropagation(IR::Block& block) {
|
||||
for (auto& inst : block) {
|
||||
for (auto& inst : block.instructions) {
|
||||
auto const opcode = inst.GetOpcode();
|
||||
switch (opcode) {
|
||||
case Op::LeastSignificantWord:
|
||||
@@ -1221,25 +1221,20 @@ static void ConstantPropagation(IR::Block& block) {
|
||||
static void DeadCodeElimination(IR::Block& block) {
|
||||
// We iterate over the instructions in reverse order.
|
||||
// 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()))
|
||||
it->Invalidate();
|
||||
}
|
||||
|
||||
static void IdentityRemovalPass(IR::Block& block) {
|
||||
boost::container::small_vector<IR::Inst*, 16> to_invalidate;
|
||||
for (auto it = block.begin(); it != block.end();) {
|
||||
const size_t num_args = it->NumArgs();
|
||||
for (size_t i = 0; i < num_args; ++i) {
|
||||
IR::Value arg = it->GetArg(i);
|
||||
if (arg.IsIdentity()) {
|
||||
do {
|
||||
arg = arg.GetInst()->GetArg(0);
|
||||
} while (arg.IsIdentity());
|
||||
boost::container::small_vector<IR::Inst*, 128> to_invalidate;
|
||||
for (auto it = block.instructions.begin(); it != block.instructions.end();) {
|
||||
auto const num_args = it->NumArgs();
|
||||
for (size_t i = 0; i < num_args; ++i)
|
||||
if (IR::Value arg = it->GetArg(i); arg.IsIdentity()) {
|
||||
do arg = arg.GetInst()->GetArg(0); while (arg.IsIdentity());
|
||||
it->SetArg(i, arg);
|
||||
}
|
||||
}
|
||||
|
||||
if (it->GetOpcode() == IR::Opcode::Identity || it->GetOpcode() == IR::Opcode::Void) {
|
||||
to_invalidate.push_back(&*it);
|
||||
it = block.Instructions().erase(it);
|
||||
@@ -1253,7 +1248,7 @@ static void IdentityRemovalPass(IR::Block& block) {
|
||||
|
||||
static void NamingPass(IR::Block& block) {
|
||||
u32 name = 1;
|
||||
for (auto& inst : block)
|
||||
for (auto& inst : block.instructions)
|
||||
inst.SetName(name++);
|
||||
}
|
||||
|
||||
@@ -1402,7 +1397,7 @@ static void PolyfillPass(IR::Block& block, const PolyfillOptions& polyfill) {
|
||||
|
||||
IR::IREmitter ir{block};
|
||||
|
||||
for (auto& inst : block) {
|
||||
for (auto& inst : block.instructions) {
|
||||
ir.SetInsertionPointBefore(&inst);
|
||||
|
||||
switch (inst.GetOpcode()) {
|
||||
@@ -1458,7 +1453,7 @@ static void PolyfillPass(IR::Block& block, const PolyfillOptions& polyfill) {
|
||||
}
|
||||
|
||||
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++) {
|
||||
const IR::Type t1 = inst.GetArg(i).GetType();
|
||||
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;
|
||||
for (auto const& inst : block) {
|
||||
for (auto const& inst : block.instructions) {
|
||||
for (size_t i = 0; i < inst.NumArgs(); i++)
|
||||
if (IR::Value const arg = inst.GetArg(i); !arg.IsImmediate())
|
||||
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
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
for (const auto& ir_inst : block) {
|
||||
for (const auto& ir_inst : block.instructions) {
|
||||
switch (ir_inst.GetOpcode()) {
|
||||
case IR::Opcode::A32ExceptionRaised:
|
||||
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
|
||||
|
||||
/* 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;
|
||||
if (auto terminal = block.GetTerminal(); boost::get<IR::Term::Interpret>(&terminal))
|
||||
return false;
|
||||
for (const auto& ir_inst : block) {
|
||||
for (const auto& ir_inst : block.instructions) {
|
||||
switch (ir_inst.GetOpcode()) {
|
||||
case IR::Opcode::A64ExceptionRaised:
|
||||
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
|
||||
|
||||
/* 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 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;
|
||||
}) != block.cend();
|
||||
}) != block.instructions.cend();
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -54,7 +54,7 @@ bool ShouldTestInst(IR::Block& block) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& ir_inst : block) {
|
||||
for (const auto& ir_inst : block.instructions) {
|
||||
switch (ir_inst.GetOpcode()) {
|
||||
// A32
|
||||
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-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
@@ -12,7 +12,8 @@ add_library(frontend_common STATIC
|
||||
firmware_manager.cpp
|
||||
data_manager.h data_manager.cpp
|
||||
play_time_manager.cpp
|
||||
play_time_manager.h)
|
||||
play_time_manager.h
|
||||
settings_generator.h settings_generator.cpp)
|
||||
|
||||
if (ENABLE_UPDATE_CHECKER)
|
||||
target_link_libraries(frontend_common PRIVATE httplib::httplib)
|
||||
@@ -29,4 +30,6 @@ if (ENABLE_UPDATE_CHECKER)
|
||||
endif()
|
||||
|
||||
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
|
||||
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// 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/scm_rev.h"
|
||||
#include <fmt/format.h>
|
||||
#include "update_checker.h"
|
||||
|
||||
#include <httplib.h>
|
||||
|
||||
@@ -45,8 +48,7 @@ std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::stri
|
||||
};
|
||||
|
||||
client->set_follow_location(true);
|
||||
httplib::Result result;
|
||||
result = client->send(request);
|
||||
httplib::Result result = client->send(request);
|
||||
|
||||
if (!result) {
|
||||
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};
|
||||
std::string update_check_path = fmt::format("/repos/{}",
|
||||
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
|
||||
= 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(
|
||||
fmt::format("\"{}\"", latest_tag))
|
||||
!= std::string::npos;
|
||||
@@ -105,7 +110,7 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
|
||||
if (!latest_tag_has_release)
|
||||
return {};
|
||||
|
||||
return latest_tag;
|
||||
return Update{latest_tag, latest_name};
|
||||
} else { // This is a stable release, only check for other stable releases.
|
||||
update_check_path += "/releases/latest";
|
||||
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 {};
|
||||
|
||||
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 &) {
|
||||
@@ -133,3 +140,41 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
|
||||
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
|
||||
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
@@ -11,6 +11,13 @@
|
||||
#include <string>
|
||||
|
||||
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> GetLatestRelease(bool include_prereleases);
|
||||
std::optional<Update> GetLatestRelease(bool include_prereleases);
|
||||
std::optional<Update> GetUpdate();
|
||||
} // 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
|
||||
|
||||
add_library(qt_common STATIC
|
||||
@@ -78,14 +78,9 @@ target_compile_definitions(qt_common PUBLIC
|
||||
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 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)
|
||||
target_compile_definitions(qt_common PUBLIC HAS_OPENGL)
|
||||
|
||||
@@ -329,6 +329,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
||||
barrier_feedback_loops,
|
||||
tr("Barrier feedback loops"),
|
||||
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,
|
||||
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-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -148,10 +151,13 @@ Id EmitConvertU32U64(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 isOverflowing = ctx.OpIsNan(ctx.U1, 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) {
|
||||
|
||||
@@ -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-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -16,7 +19,7 @@ Block::Block(ObjectPool<Inst>& inst_pool_) : inst_pool{&inst_pool_} {}
|
||||
Block::~Block() = default;
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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-FileCopyrightText: 2018 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
add_subdirectory(host_shaders)
|
||||
add_subdirectory(gpu_logging)
|
||||
|
||||
if(LIBVA_FOUND)
|
||||
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 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)
|
||||
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)
|
||||
|
||||
# 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 (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-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 MANAGER_POOL_SIZE = 32;
|
||||
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:
|
||||
MemoryTrackerBase(DeviceTracker& device_tracker_) : device_tracker{&device_tracker_} {}
|
||||
~MemoryTrackerBase() = default;
|
||||
|
||||
/// Returns the inclusive CPU modified range in a begin end pair
|
||||
[[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr,
|
||||
u64 query_size) noexcept {
|
||||
return IteratePairs<true>(
|
||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->template ModifiedRegion<Type::CPU>(offset, size);
|
||||
});
|
||||
[[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||
return IteratePairs<true>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->ModifiedRegion(Type::CPU, offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the inclusive GPU modified range in a begin end pair
|
||||
[[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr,
|
||||
u64 query_size) noexcept {
|
||||
return IteratePairs<false>(
|
||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->template ModifiedRegion<Type::GPU>(offset, size);
|
||||
});
|
||||
[[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||
return IteratePairs<false>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->ModifiedRegion(Type::GPU, offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns true if a region has been modified from the CPU
|
||||
[[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||
return IteratePages<true>(
|
||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->template IsRegionModified<Type::CPU>(offset, size);
|
||||
});
|
||||
return IteratePages<true>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->IsRegionModified(Type::CPU, offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns true if a region has been modified from the GPU
|
||||
[[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||
return IteratePages<false>(
|
||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->template IsRegionModified<Type::GPU>(offset, size);
|
||||
});
|
||||
return IteratePages<false>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->IsRegionModified(Type::GPU, offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns true if a region has been marked as Preflushable
|
||||
[[nodiscard]] bool IsRegionPreflushable(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||
return IteratePages<false>(
|
||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->template IsRegionModified<Type::Preflushable>(offset, size);
|
||||
});
|
||||
return IteratePages<false>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->IsRegionModified(Type::Preflushable, offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Mark region as CPU modified, notifying the device_tracker about this change
|
||||
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
|
||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
||||
[](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ChangeRegionState<Type::CPU, true>(
|
||||
manager->GetCpuAddr() + offset, size);
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ChangeRegionState(Type::CPU, true, manager->cpu_addr + offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Unmark region as CPU modified, notifying the device_tracker about this change
|
||||
void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
|
||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
||||
[](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ChangeRegionState<Type::CPU, false>(
|
||||
manager->GetCpuAddr() + offset, size);
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ChangeRegionState(Type::CPU, false, manager->cpu_addr + offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Mark region as modified from the host GPU
|
||||
void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
||||
[](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ChangeRegionState<Type::GPU, true>(
|
||||
manager->GetCpuAddr() + offset, size);
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ChangeRegionState(Type::GPU, true, manager->cpu_addr + offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Mark region as modified from the host GPU
|
||||
void MarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
||||
[](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ChangeRegionState<Type::Preflushable, true>(
|
||||
manager->GetCpuAddr() + offset, size);
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ChangeRegionState(Type::Preflushable, true, manager->cpu_addr + offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Unmark region as modified from the host GPU
|
||||
void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
||||
[](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ChangeRegionState<Type::GPU, false>(
|
||||
manager->GetCpuAddr() + offset, size);
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ChangeRegionState(Type::GPU, false, manager->cpu_addr + offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Unmark region as modified from the host GPU
|
||||
void UnmarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
||||
[](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ChangeRegionState<Type::Preflushable, false>(
|
||||
manager->GetCpuAddr() + offset, size);
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ChangeRegionState(Type::Preflushable, false, manager->cpu_addr + offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Mark region as modified from the CPU
|
||||
/// but don't mark it as modified until FlusHCachedWrites is called.
|
||||
void CachedCpuWrite(VAddr dirty_cpu_addr, u64 query_size) {
|
||||
IteratePages<true>(
|
||||
dirty_cpu_addr, query_size, [this](Manager* manager, u64 offset, size_t size) {
|
||||
const VAddr cpu_address = manager->GetCpuAddr() + offset;
|
||||
manager->template ChangeRegionState<Type::CachedCPU, true>(cpu_address, size);
|
||||
cached_pages.insert(static_cast<u32>(cpu_address >> HIGHER_PAGE_BITS));
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [this](Manager* manager, u64 offset, size_t size) {
|
||||
const VAddr cpu_address = manager->cpu_addr + offset;
|
||||
manager->ChangeRegionState(Type::CachedCPU, true, cpu_address, size);
|
||||
cached_pages.insert(u32(cpu_address >> HIGHER_PAGE_BITS));
|
||||
});
|
||||
}
|
||||
|
||||
/// Flushes cached CPU writes, and notify the device_tracker about the deltas
|
||||
void FlushCachedWrites(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||
IteratePages<false>(query_cpu_addr, query_size,
|
||||
[](Manager* manager, [[maybe_unused]] u64 offset,
|
||||
[[maybe_unused]] size_t size) { manager->FlushCachedWrites(); });
|
||||
IteratePages<false>(query_cpu_addr, query_size, [](Manager* manager, [[maybe_unused]] u64 offset, [[maybe_unused]] size_t size) {
|
||||
manager->FlushCachedWrites();
|
||||
});
|
||||
}
|
||||
|
||||
void FlushCachedWrites() noexcept {
|
||||
@@ -159,35 +139,24 @@ public:
|
||||
/// Call 'func' for each CPU modified range and unmark those pages as CPU modified
|
||||
template <typename Func>
|
||||
void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, Func&& func) {
|
||||
IteratePages<true>(query_cpu_range, query_size,
|
||||
[&func](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ForEachModifiedRange<Type::CPU, true>(
|
||||
manager->GetCpuAddr() + offset, size, func);
|
||||
});
|
||||
IteratePages<true>(query_cpu_range, query_size, [&func](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ForEachModifiedRange(Type::CPU, true, manager->cpu_addr + offset, size, func);
|
||||
});
|
||||
}
|
||||
|
||||
/// Call 'func' for each GPU modified range and unmark those pages as GPU modified
|
||||
template <typename Func>
|
||||
void ForEachDownloadRange(VAddr query_cpu_range, u64 query_size, bool clear, Func&& func) {
|
||||
IteratePages<false>(query_cpu_range, query_size,
|
||||
[&func, clear](Manager* manager, u64 offset, size_t size) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
IteratePages<false>(query_cpu_range, query_size, [&func, clear](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ForEachModifiedRange(Type::GPU, clear, manager->cpu_addr + offset, size, func);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void ForEachDownloadRangeAndClear(VAddr query_cpu_range, u64 query_size, Func&& func) {
|
||||
IteratePages<false>(query_cpu_range, query_size,
|
||||
[&func](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ForEachModifiedRange<Type::GPU, true>(
|
||||
manager->GetCpuAddr() + offset, size, func);
|
||||
});
|
||||
IteratePages<false>(query_cpu_range, query_size, [&func](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ForEachModifiedRange(Type::GPU, true, manager->cpu_addr + offset, size, func);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -271,31 +240,24 @@ private:
|
||||
}
|
||||
|
||||
Manager* GetNewManager(VAddr base_cpu_address) {
|
||||
const auto on_return = [&] {
|
||||
auto* new_manager = free_managers.front();
|
||||
new_manager->SetCpuAddress(base_cpu_address);
|
||||
free_managers.pop_front();
|
||||
return new_manager;
|
||||
};
|
||||
if (!free_managers.empty()) {
|
||||
return on_return();
|
||||
if (free_managers.empty()) {
|
||||
manager_pool.emplace_back();
|
||||
auto& last_pool = manager_pool.back();
|
||||
for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) {
|
||||
new (&last_pool[i]) Manager(0, *device_tracker);
|
||||
free_managers.push_back(&last_pool[i]);
|
||||
}
|
||||
}
|
||||
manager_pool.emplace_back();
|
||||
auto& last_pool = manager_pool.back();
|
||||
for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) {
|
||||
new (&last_pool[i]) Manager(0, *device_tracker, HIGHER_PAGE_SIZE);
|
||||
free_managers.push_back(&last_pool[i]);
|
||||
}
|
||||
return on_return();
|
||||
Manager* new_manager = free_managers.front();
|
||||
new_manager->cpu_addr = base_cpu_address;
|
||||
free_managers.pop_front();
|
||||
return new_manager;
|
||||
}
|
||||
|
||||
std::array<Manager*, NUM_HIGH_PAGES> top_tier{};
|
||||
std::deque<std::array<Manager, MANAGER_POOL_SIZE>> manager_pool;
|
||||
std::deque<Manager*> free_managers;
|
||||
|
||||
std::array<Manager*, NUM_HIGH_PAGES> top_tier{};
|
||||
|
||||
std::unordered_set<u32> cached_pages;
|
||||
|
||||
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-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
@@ -31,158 +31,26 @@ enum class Type {
|
||||
CachedCPU,
|
||||
Untracked,
|
||||
Preflushable,
|
||||
Max
|
||||
};
|
||||
|
||||
/// Vector tracking modified pages tightly packed with small vector optimization
|
||||
template <size_t stack_words = 1>
|
||||
struct WordsArray {
|
||||
/// Returns the pointer to the words state
|
||||
[[nodiscard]] const u64* Pointer(bool is_short) const noexcept {
|
||||
return is_short ? stack.data() : heap;
|
||||
}
|
||||
template <class DeviceTracker, size_t stack_words, size_t size_bytes>
|
||||
struct WordManager {
|
||||
static constexpr size_t num_words = Common::DivCeil(size_bytes, BYTES_PER_WORD);
|
||||
|
||||
/// Returns the pointer to the words state
|
||||
[[nodiscard]] u64* Pointer(bool is_short) noexcept {
|
||||
return is_short ? stack.data() : heap;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
explicit WordManager(VAddr cpu_addr_, DeviceTracker& tracker_) : tracker{&tracker_}, cpu_addr{cpu_addr_} {
|
||||
std::fill_n(heap.data() + size_t(Type::CPU) * num_words, num_words, ~u64{0});
|
||||
std::fill_n(heap.data() + size_t(Type::Untracked) * num_words, num_words, ~u64{0});
|
||||
// Clean up tailing bits
|
||||
const u64 last_word_size = size_bytes % BYTES_PER_WORD;
|
||||
const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
|
||||
const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
|
||||
const u64 last_word = (~u64{0} << shift) >> shift;
|
||||
cpu.Pointer(IsShort())[NumWords() - 1] = last_word;
|
||||
untracked.Pointer(IsShort())[NumWords() - 1] = last_word;
|
||||
u64 const last_word_size = size_bytes % BYTES_PER_WORD;
|
||||
u64 const last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
|
||||
u64 const shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
|
||||
u64 const last_word = (~u64{0} << shift) >> shift;
|
||||
heap[num_words * size_t(Type::CPU) + num_words - 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;
|
||||
|
||||
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) {
|
||||
constexpr size_t number_bits = sizeof(u64) * 8;
|
||||
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) {
|
||||
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 amount_pages = converted_address % BYTES_PER_WORD;
|
||||
return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE);
|
||||
@@ -201,32 +69,28 @@ public:
|
||||
template <typename Func>
|
||||
void IterateWords(size_t offset, size_t size, Func&& func) const {
|
||||
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 = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset), 0LL));
|
||||
const size_t end = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset + size), 0LL));
|
||||
if (start >= SizeBytes() || end <= start) {
|
||||
return;
|
||||
}
|
||||
auto [start_word, start_page] = GetWordPage(start);
|
||||
auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL);
|
||||
const size_t num_words = NumWords();
|
||||
start_word = (std::min)(start_word, num_words);
|
||||
end_word = (std::min)(end_word, num_words);
|
||||
const size_t diff = end_word - start_word;
|
||||
end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD;
|
||||
end_word = (std::min)(end_word, num_words);
|
||||
end_page += diff * PAGES_PER_WORD;
|
||||
constexpr u64 base_mask{~0ULL};
|
||||
for (size_t word_index = start_word; word_index < end_word; word_index++) {
|
||||
const u64 mask = ExtractBits(base_mask, start_page, end_page);
|
||||
start_page = 0;
|
||||
end_page -= PAGES_PER_WORD;
|
||||
if constexpr (BOOL_BREAK) {
|
||||
if (func(word_index, mask)) {
|
||||
return;
|
||||
const size_t start = size_t(std::max<s64>(s64(offset), 0LL));
|
||||
const size_t end = size_t(std::max<s64>(s64(offset + size), 0LL));
|
||||
if (!(start >= size_bytes || end <= start)) {
|
||||
auto [start_word, start_page] = GetWordPage(start);
|
||||
auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL);
|
||||
start_word = (std::min)(start_word, num_words);
|
||||
end_word = (std::min)(end_word, num_words);
|
||||
const size_t diff = end_word - start_word;
|
||||
end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD;
|
||||
end_word = (std::min)(end_word, num_words);
|
||||
end_page += diff * PAGES_PER_WORD;
|
||||
constexpr u64 base_mask{~0ULL};
|
||||
for (size_t word_index = start_word; word_index < end_word; word_index++) {
|
||||
const u64 mask = ExtractBits(base_mask, start_page, end_page);
|
||||
start_page = 0;
|
||||
end_page -= PAGES_PER_WORD;
|
||||
if constexpr (std::is_same_v<FuncReturn, bool>) { // bool return
|
||||
if (func(word_index, mask))
|
||||
return;
|
||||
} else {
|
||||
func(word_index, mask);
|
||||
}
|
||||
} else {
|
||||
func(word_index, mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,39 +110,32 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the state of a range of pages
|
||||
*
|
||||
* @param dirty_addr Base address to mark or unmark as modified
|
||||
* @param size Size in bytes to mark or unmark as modified
|
||||
*/
|
||||
template <Type type, bool enable>
|
||||
void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) {
|
||||
std::span<u64> state_words = words.template Span<type>();
|
||||
[[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
|
||||
[[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
|
||||
/// @brief 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 size Size in bytes to mark or unmark as modified
|
||||
void ChangeRegionState(Type type, bool enable, u64 dirty_addr, u64 size) noexcept {
|
||||
std::span<u64> state_words = Span(type);
|
||||
[[maybe_unused]] std::span<u64> untracked_words = Span(Type::Untracked);
|
||||
[[maybe_unused]] std::span<u64> cached_words = Span(Type::CachedCPU);
|
||||
std::vector<std::pair<VAddr, u64>> ranges;
|
||||
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
|
||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
||||
CollectChangedRanges<(!enable)>(index, untracked_words[index], mask, ranges);
|
||||
if (type == Type::CPU || type == Type::CachedCPU) {
|
||||
CollectChangedRanges(!enable, index, untracked_words[index], mask, ranges);
|
||||
}
|
||||
if constexpr (enable) {
|
||||
if (enable) {
|
||||
state_words[index] |= mask;
|
||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
||||
if (type == Type::CPU || type == Type::CachedCPU)
|
||||
untracked_words[index] |= mask;
|
||||
}
|
||||
if constexpr (type == Type::CPU) {
|
||||
if (type == Type::CPU)
|
||||
cached_words[index] &= ~mask;
|
||||
}
|
||||
} else {
|
||||
if constexpr (type == Type::CPU) {
|
||||
const u64 word = state_words[index] & mask;
|
||||
cached_words[index] &= ~word;
|
||||
}
|
||||
if (type == Type::CPU)
|
||||
cached_words[index] &= ~(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;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!ranges.empty()) {
|
||||
@@ -286,22 +143,20 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop over each page in the given range, turn off those bits and notify the tracker if
|
||||
* needed. Call the given function on each turned off range.
|
||||
*
|
||||
* @param query_cpu_range Base CPU address 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
|
||||
*/
|
||||
template <Type type, bool clear, typename Func>
|
||||
void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) {
|
||||
static_assert(type != Type::Untracked);
|
||||
|
||||
std::span<u64> state_words = words.template Span<type>();
|
||||
[[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
|
||||
[[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
|
||||
const size_t offset = query_cpu_range - cpu_addr;
|
||||
/// @brief Loop over each page in the given range.
|
||||
/// Turn off those bits and notify the tracker if 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 size Size in bytes of the CPU range to loop over
|
||||
/// @param func Function to call for each turned off region
|
||||
template <typename Func>
|
||||
void ForEachModifiedRange(Type type, bool clear, VAddr query_cpu_range, s64 size, Func&& func) {
|
||||
//static_assert(type != Type::Untracked);
|
||||
std::span<u64> state_words = Span(type);
|
||||
std::span<u64> untracked_words = Span(Type::Untracked);
|
||||
std::span<u64> cached_words = Span(Type::CachedCPU);
|
||||
size_t const offset = query_cpu_range - cpu_addr;
|
||||
bool pending = false;
|
||||
size_t pending_offset{};
|
||||
size_t pending_pointer{};
|
||||
@@ -311,39 +166,32 @@ public:
|
||||
};
|
||||
std::vector<std::pair<VAddr, u64>> ranges;
|
||||
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
||||
if constexpr (type == Type::GPU) {
|
||||
if (type == Type::GPU)
|
||||
mask &= ~untracked_words[index];
|
||||
}
|
||||
const u64 word = state_words[index] & mask;
|
||||
if constexpr (clear) {
|
||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
||||
CollectChangedRanges<true>(index, untracked_words[index], mask, ranges);
|
||||
if (clear) {
|
||||
if (type == Type::CPU || type == Type::CachedCPU) {
|
||||
CollectChangedRanges(true, index, untracked_words[index], mask, ranges);
|
||||
}
|
||||
state_words[index] &= ~mask;
|
||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
||||
if (type == Type::CPU || type == Type::CachedCPU)
|
||||
untracked_words[index] &= ~mask;
|
||||
}
|
||||
if constexpr (type == Type::CPU) {
|
||||
if (type == Type::CPU)
|
||||
cached_words[index] &= ~word;
|
||||
}
|
||||
}
|
||||
const size_t base_offset = index * PAGES_PER_WORD;
|
||||
IteratePages(word, [&](size_t pages_offset, size_t pages_size) {
|
||||
const auto reset = [&]() {
|
||||
if (!pending) {
|
||||
pending_offset = base_offset + pages_offset;
|
||||
pending_pointer = base_offset + pages_offset + pages_size;
|
||||
};
|
||||
if (!pending) {
|
||||
reset();
|
||||
pending = true;
|
||||
return;
|
||||
}
|
||||
if (pending_pointer == base_offset + pages_offset) {
|
||||
} else if (pending_pointer == base_offset + pages_offset) {
|
||||
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) {
|
||||
@@ -354,90 +202,55 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when a region has been modified
|
||||
*
|
||||
* @param offset Offset in bytes from the start of the buffer
|
||||
* @param size Size in bytes of the region to query for modifications
|
||||
*/
|
||||
template <Type type>
|
||||
[[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
|
||||
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>();
|
||||
/// @brief 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 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);
|
||||
const std::span<const u64> state_words = Span(type);
|
||||
const std::span<const u64> untracked_words = Span(Type::Untracked);
|
||||
bool result = false;
|
||||
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
||||
if constexpr (type == Type::GPU) {
|
||||
if (type == Type::GPU)
|
||||
mask &= ~untracked_words[index];
|
||||
}
|
||||
const u64 word = state_words[index] & mask;
|
||||
if (word != 0) {
|
||||
result = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return (state_words[index] & mask) != 0 ? (result = true) : false;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
template <Type type>
|
||||
[[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept {
|
||||
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;
|
||||
/// @brief 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
|
||||
[[nodiscard]] std::pair<u64, u64> ModifiedRegion(Type type, u64 offset, u64 size) const noexcept {
|
||||
//static_assert(type != Type::Untracked);
|
||||
const std::span<const u64> state_words = Span(type);
|
||||
const std::span<const u64> untracked_words = Span(Type::Untracked);
|
||||
u64 begin = (std::numeric_limits<u64>::max)(), end = 0;
|
||||
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
||||
if constexpr (type == Type::GPU) {
|
||||
if (type == Type::GPU)
|
||||
mask &= ~untracked_words[index];
|
||||
}
|
||||
const u64 word = state_words[index] & mask;
|
||||
if (word == 0) {
|
||||
return;
|
||||
if (word != 0) {
|
||||
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(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE) : EMPTY;
|
||||
}
|
||||
|
||||
/// 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();
|
||||
return begin < end ? std::make_pair<u64, u64>(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE)
|
||||
: std::make_pair<u64, u64>(0, 0);
|
||||
}
|
||||
|
||||
void FlushCachedWrites() noexcept {
|
||||
const u64 num_words = NumWords();
|
||||
u64* const cached_words = Array<Type::CachedCPU>();
|
||||
u64* const untracked_words = Array<Type::Untracked>();
|
||||
u64* const cpu_words = Array<Type::CPU>();
|
||||
auto const cached_words = Span(Type::CachedCPU);
|
||||
auto const untracked_words = Span(Type::Untracked);
|
||||
auto const cpu_words = Span(Type::CPU);
|
||||
std::vector<std::pair<VAddr, u64>> ranges;
|
||||
for (u64 word_index = 0; word_index < num_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;
|
||||
cpu_words[word_index] |= cached_bits;
|
||||
cached_words[word_index] = 0;
|
||||
@@ -447,45 +260,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template <Type type>
|
||||
u64* Array() 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());
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
/// @brief Notify tracker about changes in the CPU tracking state of a word in the buffer
|
||||
/// @param add_to_tracker If add to tracker (selects changed bits)
|
||||
/// @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
|
||||
void CollectChangedRanges(bool add_to_tracker, 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;
|
||||
VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
|
||||
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 {
|
||||
if (ranges.empty()) return;
|
||||
std::sort(ranges.begin(), ranges.end(),
|
||||
[](const auto& a, const auto& b) { return a.first < b.first; });
|
||||
if (ranges.empty())
|
||||
return;
|
||||
std::sort(ranges.begin(), ranges.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
|
||||
// Coalesce adjacent/contiguous ranges
|
||||
std::vector<std::pair<VAddr, size_t>> coalesced;
|
||||
coalesced.reserve(ranges.size());
|
||||
@@ -517,19 +298,30 @@ private:
|
||||
ranges.clear();
|
||||
}
|
||||
|
||||
template <bool add_to_tracker>
|
||||
void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const {
|
||||
/// @brief Notify tracker about changes in the CPU tracking state of a word in the buffer
|
||||
/// @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;
|
||||
VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
|
||||
IteratePages(changed_bits, [&](size_t offset, size_t size) {
|
||||
tracker->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, size * BYTES_PER_PAGE,
|
||||
add_to_tracker ? 1 : -1);
|
||||
tracker->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, size * BYTES_PER_PAGE, 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;
|
||||
Words<stack_words> words;
|
||||
VAddr cpu_addr = 0;
|
||||
};
|
||||
|
||||
} // 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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user