Compare commits

..

29 Commits

Author SHA1 Message Date
wildcard 213a9a7813 [host_shaders] Store the value of results instead of discarding it
the function generated the value but we never stored it effectively discarding the result.
2026-02-04 22:13:11 +01:00
wildcard 48ba1f3f24 [vk, cmake] Bump minimum VulkanTools to 1.4.317 and add UnifiedImageLayouts (#3318)
It seems too easy, the specification does not state anything more to be done for it towork.
Requires performance testing on android.

Co-authored-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3318
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2026-02-04 19:15:10 +01:00
crueter d59fcf01bf [frontend] Generate web token at runtime (#3462)
Rather than having that hardcoded one like before. Also adds
infrastructure which should make it easier to setup defaults at runtime
(e.g. GPU stuff?)

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3462
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
2026-02-04 04:16:39 +01:00
PavelBARABANOV b8456394f1 [spirv] Fix F16 conversion on Android (skip NaN check) (#3458)
Fixes the yellow texture issue in Outer Wilds on Android.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3458
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Co-committed-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
2026-02-04 00:44:03 +01:00
wildcard a467dd1ba6 [vk] fix flickering caused by recent swizzle changes (#3457)
Co-authored-by: Maufeat <sahyno1996@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3457
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2026-02-04 00:37:58 +01:00
lizzie c156f4760f [meta] update codeowners (#3460)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3460
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-03 23:42:53 +01:00
MaranBr 13f11ebf49 [nvnflinger] Reimplement GetBufferHistory (#3394)
Reimplements GetBufferHistory, enabling tracking and retrieval of recent buffer states. This can improve rendering performance and stability in some games.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3394
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-02-03 18:25:15 +01:00
xbzk 6065e9aa09 [cmake] Reduce spam on builds for MSVC (#3454)
Fix for disable-warning-as-error flags set for MSVC not CLANG dripping into externals and causing override warnings on Ninja (cl)

Signed-off-by: xbzk <xbzk@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3454
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-02-03 13:53:07 +01:00
crueter 8ed0ed5828 [desktop] Add smooth scrolling and shift scrolling to game list (#3452)
Intercepts Qt's awful scroll wheel handling and passes it off to a
continuously self-integrating animation on the scroll bar positions.
Code kind of sucks right now and I think has a bit of a perf bottleneck,
but that's somewhat expected in the way I implemented it. Maybe look for
a better sln? QScroller?

Also while I was at it I added shift scrolling.

TODO:
- [ ] TouchGesture? With overshoot off
- [ ] Perf

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3452
2026-02-02 19:07:12 +01:00
crueter 33067af283 [settings] Disable GPU logging by default (#3455)
Why was this enabled?

Signed-off-by: crueter <crueter@eden-emu.dev>
Co-authored-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3455
Reviewed-by: DraVee <dravee@eden-emu.dev>
2026-02-02 19:00:44 +01:00
crueter d0a054270e [cmake] Move back to individual handling of Vulkan deps (#3450)
VUL version mismatches are solved upstream. Anything else is user error,
not that the prior solution really helped let's be real.

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3450
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
2026-02-02 06:54:58 +01:00
crueter 2a3507c2b9 [cmake] Move Qt externals handling to root CMakeLists.txt, and move QuaZip to upstream (#3447)
QuaZip lets you disable QTextCodec now so we can use it.

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3447
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
2026-02-02 06:17:57 +01:00
crueter f71f43561d [externals] Update zlib, httplib, and catch2 (#3449)
whynot

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3449
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
2026-02-02 06:17:41 +01:00
crueter ecbfad4193 [externals] Update Boost to 1.90.0 (#3448)
Removes the need for those MSVC/ARM patches

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3448
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
2026-02-02 06:12:36 +01:00
nekle d76b2b5d26 [android] add toggle for quick settings to hide it and fix setting names (#3435)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3435
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: nekle <nekle@protonmail.com>
Co-committed-by: nekle <nekle@protonmail.com>
2026-02-02 06:07:26 +01:00
crueter cd9527072d [frontend] fix nightly update checker again (#3446)
I ❤️ std::optional

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3446
2026-02-01 22:27:49 +01:00
PavelBARABANOV ffdaf7369a [android] add qlaunch button (#3439)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3439
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Co-committed-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
2026-02-01 22:03:35 +01:00
crueter 5113f503d1 [frontend] Fix nightly update checker (#3444)
Splitting

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3444
2026-02-01 20:38:02 +01:00
rayman30 a5f1c2bcb0 [qt] fix refresh loop when storage is missing (#3434)
Added a check to see if a folder actually exists before adding it to the watcher. fixes a nasty infinite refresh loop in the interface when a network drive or usb stick is disconnected but still in the config.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3434
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: rayman30 <silentbitdev@gmail.com>
Co-committed-by: rayman30 <silentbitdev@gmail.com>
2026-02-01 19:05:29 +01:00
Maufeat 2e432c9d17 [fsp-svr] Stub IsSdCardAcccessible (#3443)
without it, some games prevent booting into it

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3443
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: Maufeat <sahyno1996@gmail.com>
Co-committed-by: Maufeat <sahyno1996@gmail.com>
2026-02-01 18:08:34 +01:00
MrPurple666 6637810fe6 [android] gpu logs functions (#3389)
Since Android is a pain when it comes to checking GPU logs in more depth, this is a better way to see what's going on, especially for testers...

This should be expanded to Mali, Xclipse, and Tensor in the future. Since I don't own any of these devices, it's up to developers with similar capabilities to add support for this system.

~~The GPU log sharing button should also be added in the future... For now, they are available in the same location as the traditional logs.~~ Added on 572810e022

Co-authored-by: DraVee <dravee@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3389
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: MrPurple666 <antoniosacramento666usa@gmail.com>
Co-committed-by: MrPurple666 <antoniosacramento666usa@gmail.com>
2026-02-01 02:02:23 +01:00
lizzie 8118557c17 [video_core/buffer_cache] Simplify word manager logic (#3221)
no significant performance gains expected
reduces memory (4096 word managers are active at any given time, this reduces total amount of memory used by a tiny margin)
also reduces code duplication due to templatery fuckery

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3221
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-01 01:55:47 +01:00
lizzie 1925726b96 [android, core] pin core threads to logical CPUs 0-3 (#3121)
this basically allows the threads to exist in these logical CPUs, undisturbed, and without trashing each other's cache
this could improve performance, very tricky thing to pull off correctly, but again, this is mostly an experiment
will mainly benefit: Linux, Android, FreeBSD, Windows (not ARM)
Additionally, this means no context trashing :)

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Co-authored-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3121
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-01 01:54:59 +01:00
lizzie acf7deea95 [vk] remove unused vk_texture_manager.cpp and unused shader conversions (#3432)
- Trivial dead code removal, also these shaders are useless

TODO: maybe in a future do YUV280 or whatever in shader for vic :)

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3432
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-01-31 20:33:35 +01:00
crueter 84fdbbaaa1 [frontend] Fix build issues with update checker (#3433)
Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3433
2026-01-31 01:45:12 +01:00
crueter 638663b28e [cmake, frontend] Add nightly build modifier (#3431)
The `NIGHTLY_BUILD` option changes the app name to "Eden Nightly" and
changes the auto-update URL to use our new Nightly repository.

This needs added to Android, but I can't right now as I have to leave.

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3431
2026-01-31 01:18:36 +01:00
lizzie df838a57fd [dynarmic] reduce CPU usage on Spooky Mansion by making blocks be page-sized and page-aligned; replace std::set<> with ankerl set in arm64 (#3253)
reduces CPU usage by about 10-20%, may be placebo
Signed-off-by: lizzie lizzie@eden-emu.dev

Co-authored-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3253
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-01-31 00:21:45 +01:00
MaranBr b2b73ecb62 [opengl] Fix SwapBuffers warning when window is not exposed (#3429)
This fixes an annoying warning when opening any game using OpenGL.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3429
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-01-30 23:06:22 +01:00
crueter 2b7b44c3e4 [externals] Use patches for spirv-tools, cpp-jwt, and opus instead of forks (#3430)
oops

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3430
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-01-30 20:11:03 +01:00
132 changed files with 3385 additions and 1855 deletions
+18 -2
View File
@@ -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
-11
View File
@@ -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()
-14
View File
@@ -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 {
+24 -14
View File
@@ -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)
+153
View File
@@ -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
View File
@@ -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
View File
@@ -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}
+25 -12
View File
@@ -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
View File
@@ -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
View File
@@ -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
+41
View File
@@ -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.
+4
View File
@@ -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.
+4 -6
View File
@@ -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)
+32 -17
View File
@@ -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"
}
}
+1 -1
View File
@@ -20,4 +20,4 @@ pkgs.mkShellNoCC {
# optional components
discord-rpc gamemode
];
}
}
+4
View File
@@ -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)
+28 -2
View File
@@ -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()
@@ -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
@@ -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)
@@ -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)
@@ -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() &&
@@ -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;
+38 -16
View File
@@ -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>
+3
View File
@@ -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;
+4 -1
View File
@@ -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[];
+1
View File
@@ -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
View File
@@ -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
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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(&timestamp, &is_auto_timestamp, &crop, &scaling_mode, &transform,
&sticky_transform_, &async, &swap_interval, &fence);
input.Deflate(&timestamp, &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;
+22 -44
View File
@@ -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;
}
+7 -33
View File
@@ -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;
-61
View File
@@ -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;
};
}
+5 -8
View File
@@ -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;
+17 -22
View File
@@ -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()]++;
+2 -2
View File
@@ -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:
+2 -2
View File
@@ -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:
+3 -3
View File
@@ -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) {
+2 -2
View File
@@ -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:
+6 -3
View File
@@ -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);
}
}
}
+14
View File
@@ -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();
}
+53 -8
View File
@@ -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;
}
+9 -2
View File
@@ -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
+2 -7
View File
@@ -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
View File
@@ -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)
-18
View File
@@ -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) {
+4 -3
View File
@@ -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})
+66 -104
View File
@@ -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;
};
+137 -345
View File
@@ -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
+25
View File
@@ -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
+734
View File
@@ -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
+199
View File
@@ -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