Compare commits

...

36 Commits

Author SHA1 Message Date
lizzie b25782b9eb license 2026-02-17 16:23:52 +01:00
lizzie ea8af8d93b fix 2026-02-17 16:23:52 +01:00
lizzie 865a3b6be2 [vk] Add option to force smaller buffers
This can fix some games which run out of memory (Smash bros ultimate with 6 G&W) due to recent VMA changes

Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-02-17 16:23:52 +01:00
John 5fb3ae487c [windows] Return x86 microsleep for Windows (#3563)
Microsleep removal may have regressed AOC in msvc

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3563
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: John <john@eden-emu.dev>
Co-committed-by: John <john@eden-emu.dev>
2026-02-17 06:15:45 +01:00
lizzie 86e02aceba [video_core] remove static texture scratch buffer and just use something allocated on vec (#3560)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3560
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-16 21:51:46 +01:00
lizzie 50ec0892c6 [meta] restore normal icon, valentine is over (#3556)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3556
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-15 23:11:46 +01:00
crueter f4e7fc91ba [cmake, frontend] Fix update checker and move to self-hosted Git (#3558)
httplib doesn't like when you include the protocol, as it would seem

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3558
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
2026-02-15 20:51:05 +01:00
crueter 45c9f9bbb3 [desktop] Add basic Frametime/FPS overlay (#3537)
Just displays min, max, avg frametime/fps, alongside a chart of FPS in
the last 30 seconds.

Notes:
- Qt Charts is now required
- FPS/frametime collector now runs 2x as often. TODO: keep status bar at
  500ms, but put perf overlay at 250ms

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3537
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-02-15 19:22:19 +01:00
MaranBr 8e373eb714 [vulkan] Improve frame pacing (#3535)
This improves frame pacing control, fixes accumulated drift, and adds the ability to change it while the game is running.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3535
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-02-15 04:12:01 +01:00
lizzie 75ebfaa090 [common] autogenerate unique console serial for every install (#3550)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3550
Reviewed-by: Maufeat <sahyno1996@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-02-15 04:11:39 +01:00
DraVee e3035ae8f2 [cmake] Use https over http (#3554)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3554
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: DraVee <dravee@eden-emu.dev>
Co-committed-by: DraVee <dravee@eden-emu.dev>
2026-02-15 03:42:05 +01:00
lizzie bcd22d9f8b [windows] return x86 microsleep for mingw only (#3544)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3544
Reviewed-by: Maufeat <sahyno1996@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-02-15 03:41:25 +01:00
lizzie 4e7c036c7e [hle] fix spl being registered as 'spl' instead of 'spl:' (#3549)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3549
Reviewed-by: DraVee <dravee@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-15 02:39:27 +01:00
crueter 19e2dba35a [cmake] CPMUtil: Don't consider system packages if FORCE_BUNDLED is on (#3539)
Thanks, Debian.

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3539
2026-02-14 03:33:03 +01:00
crueter 0634b4a278 [externals] Update SDL2 CI (#3538)
https://github.com/eden-emulator/Issue-Reports/issues/327

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3538
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-02-14 03:24:36 +01:00
John ee428deb1e [settings] Set EDS to 2 as default on Windows/FreeBSD (#3532)
<video src="/attachments/9a1b2e43-0acf-4a40-9be5-db93b1164111" title="Regressions" controls></video>

This PR fixes Sonic's broken graphics.

- EDS3 hard coded as _WIN32 default broke graphics and was the issue. Manually adjusting EDS[0-3] per-game/global settings can not fix graphics.
- EDS2 hard coded as _WIN32 default fixes graphical issues. Manually adjusting EDS[0-3] per-game/global settings works as intended.
- EDS0 as _WIN32 default was also tested and works. Old logic behavior before #292 regression.

Co-authored-by: DraVee <dravee@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3532
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: John <john@eden-emu.dev>
Co-committed-by: John <john@eden-emu.dev>
2026-02-13 18:37:42 +01:00
lizzie 07bc77c7e7 [dynarmic] Fix Sonic X shadow regression due to aggresive const prop discarding NZCV side ffects (#3534)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3534
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-13 16:10:09 +01:00
lizzie 72973fe582 [dynarmic] fix NSBU regressions (#3533)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3533
Reviewed-by: Maufeat <sahyno1996@gmail.com>
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-13 11:51:34 +01:00
lizzie c263b6af6f [dynarmic] add current code page cache (#3459)
should make JIT translation a bit faster - especially for non-fastmem
test if thumb still werks

previously:
we read 32-bits one by one, and do translation **for each u32 we read**

now:
one big read of 4096 bytes (aligned of course), only 1 VAddr translation

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3459
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-12 02:14:50 +01:00
PavelBARABANOV e46576b4c3 [android] add graphics artifacts warning to bloom fix description (#3471)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3471
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Co-committed-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
2026-02-12 01:56:16 +01:00
lizzie 89bcb73d47 [dynarmic] x86 direct immediate call for host calls within 2G (#3508)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3508
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-12 01:45:22 +01:00
MaranBr 7d81a724ef [shader_recompiler] Fix Shuffle handling for Position attributes (#3406)
This fixes the rescaling path to properly convert to F32, matching native 1x behavior for any resolution.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3406
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-02-12 01:34:25 +01:00
lizzie 634686d59b [dynarmic] add back A64 identity pass (#3519)
results in way better codegen (less noise for movs and all dat)
i think it was removed by accident by me, oops

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3519
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-12 01:33:51 +01:00
lizzie d0af14632e [dynarmic] UseScratchGpr for path with immediate additions; lea materialisation with in-range s32 immediate (+immediate carry) (#2958)
Allows regalloc to more aggressively take on registers for immediate codepath; also uses lea for stc/adc (since carry can equate +1 in some cases)

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2958
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-12 01:33:22 +01:00
lizzie c87986b276 [dynarmic] reuse IR::Block memory instead of creating new block each time (#3501)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3501
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-12 01:32:55 +01:00
lizzie b600b90d53 [dynarmic] Make SetX/GetW pairs optimized by getSet pass (#3518)
this results in removed reloads for any code that uses those heavily :)

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3518
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-12 01:32:26 +01:00
crueter 2b979024cb [frontend] Slow and Turbo modes (#3525)
Closes #3344

Adds slow and turbo modes with configurable speeds that can then be
toggled by the user. Behavior is:
- Standard/slow limit, toggle turbo = turbo
- Turbo limit, toggle turbo = standard
- Standard/turbo limit, toggle slow = slow
- Slow limit, toggle slow = standard

Enabling the turbo/slow mode enables the frame limiter unconditionally.

This has some conflicts with VSync. For example when I set my refresh
rate to 60hz and enable vsync, turbo mode does nothing. Not sure how to
go about fixing this, @MaranBr probably knows better the proper
solution.

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3525
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-02-12 01:31:55 +01:00
MaranBr 5f676a6a55 [vulkan] Add support for target FPS frame pacing (#3494)
This allows users to choose how the emulator manages frame pacing to reduce stuttering and provide a smoother and more consistent frame rate.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3494
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-02-12 00:23:19 +01:00
xbzk 2ab5b37137 [android, ui] unswizzle combo picker core (#3516)
Combo picker for unswizzle. Attempt to combine settings + Enable toggle added.
WARNING! The toggle won't have effect! It just controls GPU_UNSWIZZLE_ENABLED boolean setting, and will need @PavelBARABANOV unswizzle enable/disable integration.

Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3516
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-02-12 00:11:54 +01:00
lizzie f6547fac8c [dynarmic] Define LSW on the same argument/use scratch gpr (#3520)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3520
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-11 23:13:40 +01:00
lizzie 9c685c1449 [dynarmic] inline SM4 ARM operation (#3474)
most useless function call ever
why even call? just look it up! it's right there!

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3474
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-11 19:41:36 +01:00
crueter aaf1a597ef [externals] Don't force xbyak to be bundled (#3526)
I explicitly asked for it to be removed, and it was added back again.
Why?

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3526
2026-02-11 19:37:20 +01:00
lizzie 850fc372b7 [dynarmic] AVX512CD impl for lzcnt16 (#3499)
dont have AVX512CD to verify the change; but should[tm] work

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3499
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-11 19:36:23 +01:00
lizzie bad9d8043b [dynarmic, externals] update to xbyak v7.33.2 (#3505)
PLEASE test, & provide gdb logs kthx!

mainly because there is a bug with pop/push for r8-r15:
https://github.com/herumi/xbyak/commit/94f6fe6941103a826db63d1e98cc27ba19024668
(may have been linked as to why Engage had SCREECH when doing xmm spills!)

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/3505
Reviewed-by: DraVee <dravee@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-11 19:35:29 +01:00
lizzie 8e8e66f880 [vk] load VK_KHR_GET_SURFACE_CAPABILITIES_2 on drivers (not a hard requirement) for maintainance 1 (#3514)
fixes VVL, but doesnt make it a hard requirement so drivers which cant load it for some reason just, dont

Validation Error: [ VUID-vkCreateInstance-ppEnabledExtensionNames-01388 ] | MessageID = 0xe5e52180
vkCreateInstance(): pCreateInfo->ppEnabledExtensionNames[3] Missing extension required by the instance extension VK_EXT_surface_maintenance1: VK_KHR_get_surface_capabilities2.
The Vulkan spec states: All required extensions for each extension in the VkInstanceCreateInfo::ppEnabledExtensionNames list must also be present in that list (https://docs.vulkan.org/spec/latest/chapters/initialization.html#VUID-vkCreateInstance-ppEnabledExtensionNames-01388)

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3514
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-02-11 18:54:24 +01:00
lizzie fec5ac2298 [meta] add San Valentine's 2026 icon variation (#3476)
Pinkish theme as well, very fruity, very nice

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3476
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-11 05:04:39 +01:00
95 changed files with 2519 additions and 471 deletions
+2 -2
View File
@@ -641,7 +641,7 @@ if (ENABLE_QT)
list(APPEND CMAKE_PREFIX_PATH "${Qt6_DIR}")
endif()
find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets Concurrent)
find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets Charts Concurrent)
if (YUZU_USE_QT_MULTIMEDIA)
find_package(Qt6 REQUIRED COMPONENTS Multimedia)
@@ -680,7 +680,7 @@ if (ENABLE_QT)
## Components ##
# Best practice is to ask for all components at once, so they are from the same version
set(YUZU_QT_COMPONENTS Core Widgets Concurrent)
set(YUZU_QT_COMPONENTS Core Widgets Charts Concurrent)
if (PLATFORM_LINUX)
list(APPEND YUZU_QT_COMPONENTS DBus)
endif()
+5 -1
View File
@@ -84,7 +84,11 @@ function(SystemPackageViable JSON_NAME)
parse_object(${object})
string(REPLACE " " ";" find_args "${find_args}")
find_package(${package} ${version} ${find_args} QUIET NO_POLICY_SCOPE)
if (${package}_FORCE_BUNDLED)
set(${package}_FOUND OFF)
else()
find_package(${package} ${version} ${find_args} QUIET NO_POLICY_SCOPE)
endif()
set(${pkg}_VIABLE ${${package}_FOUND} PARENT_SCOPE)
set(${pkg}_PACKAGE ${package} PARENT_SCOPE)
+7 -4
View File
@@ -36,14 +36,17 @@ set(GIT_DESC ${BUILD_VERSION})
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
# 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")
if (NIGHTLY_BUILD)
set(BUILD_AUTO_UPDATE_WEBSITE "https://github.com")
set(BUILD_AUTO_UPDATE_API "api.github.com")
set(BUILD_AUTO_UPDATE_API_PATH "/repos/")
set(BUILD_AUTO_UPDATE_REPO "Eden-CI/Nightly")
set(REPO_NAME "Eden Nightly")
else()
set(BUILD_AUTO_UPDATE_REPO "eden-emulator/Releases")
set(BUILD_AUTO_UPDATE_WEBSITE "https://git.eden-emu.dev")
set(BUILD_AUTO_UPDATE_API "git.eden-emu.dev")
set(BUILD_AUTO_UPDATE_API_PATH "/api/v1/repos/")
set(BUILD_AUTO_UPDATE_REPO "eden-emu/eden")
set(REPO_NAME "Eden")
endif()
+211
View File
@@ -0,0 +1,211 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="512"
height="512"
fill="none"
viewBox="0 0 512 512"
version="1.1"
id="svg7"
sodipodi:docname="EdenLogoLoveWhiteV3.svg"
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
xml:space="preserve"
inkscape:export-filename="dev.eden_emu.eden.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata1">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:creator>
<cc:Agent>
<dc:title>Madeline_Dev</dc:title>
<dc:identifier>mailto:madelvidel@gmail.com</dc:identifier>
</cc:Agent>
</dc:creator>
<dc:date>2025</dc:date>
<dc:license
rdf:resource="https://www.gnu.org/licenses/gpl-3.0.html" />
<dc:rights>2025 Eden Emulator Project</dc:rights>
<dc:source>https://git.eden-emu.dev</dc:source>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs7"><linearGradient
id="swatch14"
inkscape:swatch="solid"><stop
style="stop-color:#66003b;stop-opacity:1;"
offset="0"
id="stop14" /></linearGradient><linearGradient
id="linearGradient1"
inkscape:collect="always"><stop
style="stop-color:#ffffff;stop-opacity:0.50980395;"
offset="0"
id="stop1" /><stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop2" /></linearGradient><linearGradient
id="linearGradient11"
inkscape:collect="always"><stop
style="stop-color:#9523a7;stop-opacity:0.50980395;"
offset="0"
id="stop11" /><stop
style="stop-color:#ffabad;stop-opacity:1;"
offset="0.99898213"
id="stop20" /><stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="0.99898213"
id="stop12" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient11"
id="linearGradient12"
x1="109.74531"
y1="106.54533"
x2="431.05463"
y2="427.85461"
gradientUnits="userSpaceOnUse"
spreadMethod="reflect"
gradientTransform="matrix(1.0945321,0,0,1.0945321,-39.661525,-35.159057)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1"
id="linearGradient2"
x1="125.40197"
y1="271.834"
x2="431.02424"
y2="271.834"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0964243,0,0,1.0253208,-40.187969,-20.060025)" /><filter
inkscape:label="Light Contour"
inkscape:menu="Image Paint and Draw"
inkscape:menu-tooltip="Uses vertical specular light to draw lines"
style="color-interpolation-filters:sRGB"
id="filter11"
x="-0.01907517"
y="-0.054959154"
width="1.0379885"
height="1.1092314"><feGaussianBlur
in="SourceGraphic"
stdDeviation="0.38250006"
result="result3"
id="feGaussianBlur9" /><feComponentTransfer
result="result1"
in="result3"
id="feComponentTransfer9"><feFuncR
type="discrete"
tableValues="0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1"
id="feFuncR9" /><feFuncG
type="discrete"
tableValues="0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1"
id="feFuncG9" /><feFuncB
type="discrete"
tableValues="0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1"
id="feFuncB9" /></feComponentTransfer><feGaussianBlur
result="result5"
stdDeviation="0.01"
id="feGaussianBlur10" /><feBlend
in2="result5"
result="result6"
mode="lighten"
in="result5"
id="feBlend10" /><feColorMatrix
in="result6"
type="luminanceToAlpha"
result="result2"
id="feColorMatrix10" /><feSpecularLighting
surfaceScale="5"
result="result9"
specularExponent="20"
in="result2"
specularConstant="1"
id="feSpecularLighting10"><feDistantLight
azimuth="180"
elevation="90"
id="feDistantLight10" /></feSpecularLighting><feComposite
in2="result6"
operator="arithmetic"
in="result9"
k1="0.4"
k3="0.7"
result="result3"
id="feComposite10"
k2="0"
k4="0" /><feBlend
in2="result1"
in="result3"
mode="normal"
result="result8"
id="feBlend11" /><feComposite
in2="SourceGraphic"
in="result8"
operator="in"
result="result7"
id="feComposite11" /></filter></defs><sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="1.35"
inkscape:cx="254.81481"
inkscape:cy="260.37037"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg7"
showguides="false" /><circle
style="fill:url(#linearGradient12);fill-opacity:1;stroke:#e4e4e4;stroke-width:14.0448;stroke-opacity:1;paint-order:stroke fill markers"
id="path8"
cx="256.2999"
cy="257.2999"
r="248.67769" /><path
id="path15"
style="fill:url(#linearGradient2);fill-opacity:1;stroke:#ffffff;stroke-width:16.9642;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.52156866;paint-order:stroke fill markers"
d="m 306.72111,24.233031 c 0,0 -11.98397,40.08696 -18.0546,60.431848 -12.96613,9.503601 -21.49377,18.397701 -21.49377,18.397701 0,0 -23.41313,-31.029398 -45.74145,-43.934598 -22.32833,-12.905201 -52.42065,-11.242483 -52.42065,-11.242483 0,0 -12.50052,4e-4 -27.63117,5.537132 -15.13066,5.536732 -21.27107,9.227888 -21.27107,9.227888 0,0 15.35165,-0.410529 37.93799,6.766716 22.58635,7.177243 32.45374,11.484796 32.45374,11.484796 l 31.02752,-6.562453 -8.44161,11.074275 c 0,0 14.80259,8.920284 22.80648,16.917787 8.0039,7.9975 11.73088,12.50812 11.73088,12.50812 0,0 -13.92373,-5.43341 -56.68427,-1.74226 -42.76055,3.69116 -84.86368,56.39265 -84.86368,56.39265 0,0 41.22428,-15.9958 65.5649,-21.32747 24.34062,-5.33166 47.58524,-7.9983 47.58524,-7.9983 0,0 -18.41865,7.3827 -38.15428,38.3474 -19.73564,30.96468 -14.0351,80.18128 -14.0351,80.18128 0,0 31.35774,-59.05848 61.39977,-78.94969 30.04203,-19.89124 36.182,-20.50642 36.182,-20.50642 0,0 -24.1209,48.39514 -33.55015,126.11445 -9.42924,77.71931 30.26089,207.72959 30.26089,207.72959 l 47.80367,-6.97298 c 0,0 -45.61041,-42.24281 -49.55753,-151.33695 -3.94713,-109.09413 16.66479,-174.30453 16.66479,-174.30453 0,0 17.10508,1.23039 42.54213,32.81027 25.43704,31.57987 40.34713,57.41795 40.34713,57.41795 0,0 9.65024,-51.26683 -11.4011,-74.64415 -21.05135,-23.3773 -53.50637,-33.62931 -53.50637,-33.62931 0,0 13.70573,-6.66419 42.10313,-4.71609 28.39738,1.94813 87.30278,27.12897 87.30278,27.12896 0,0 -15.8158,-25.5214 -52.87463,-43.21167 -37.05881,-17.69028 -81.35597,0.29238 -81.35597,0.29238 0,0 4.13589,-15.37277 29.10021,-32.628037 24.96432,-17.25527 56.907,-34.36427 56.907,-34.36427 0,0 -26.3595,1.160335 -55.20025,12.035504 -7.176,2.7059 -14.064,6.299542 -20.36309,10.173104 l 17.40574,-51.051762 z" /><path
id="path4-2-6"
style="fill:#ffffff;fill-opacity:0.509804;stroke:#ffffff;stroke-width:8.49614;stroke-dasharray:none;stroke-opacity:0.521569"
d="m 105.64857,363.95957 a 27.851731,25.309928 0 0 0 -7.115581,35.20471 27.851731,25.309928 0 0 0 5.656921,5.48617 c 7.16052,5.83605 21.58543,14.65841 45.88391,17.847 37.57172,4.93039 41.7105,10.38005 41.71122,10.381 -4.5e-4,-6e-4 -4.13529,-5.45309 4.14459,-39.11953 5.35491,-21.77281 1.40624,-37.16078 -2.05217,-45.31698 a 27.851731,25.309928 0 0 0 -3.63281,-6.75471 27.851731,25.309928 0 0 0 -38.74027,-6.4662 27.851731,25.309928 0 0 0 -7.11556,35.20472 27.851731,25.309928 0 0 0 -38.74025,-6.46618 z" /><path
id="path4-2-6-1"
style="fill:#ffffff;fill-opacity:0.50980395;stroke:#ffffff;stroke-width:6.89965;stroke-dasharray:none;stroke-opacity:0.519962"
d="m 351.29524,378.32915 a 20.862787,22.283365 87.929759 0 0 -28.94663,11.7882 20.862787,22.283365 87.929759 0 0 -1.50914,6.04125 c -1.09835,7.13663 -1.16134,20.16231 7.28339,36.72931 13.0577,25.61689 10.90177,30.69793 10.90138,30.69882 2.4e-4,-5.6e-4 2.16,-5.08045 30.49632,-15.43688 18.32576,-6.69765 27.84648,-16.23659 32.287,-22.14109 a 20.862787,22.283365 87.929759 0 0 3.33646,-5.36755 20.862787,22.283365 87.929759 0 0 -12.45108,-27.05014 20.862787,22.283365 87.929759 0 0 -28.94664,11.78822 20.862787,22.283365 87.929759 0 0 -12.45106,-27.05014 z" /><path
id="path4-2-6-1-5"
style="fill:#ffffff;fill-opacity:0.50980395;stroke:#ffffff;stroke-width:6.89965;stroke-dasharray:none;stroke-opacity:0.52156866"
d="m 83.024352,249.4105 a 22.283365,20.862787 0 0 0 -29.353581,10.73482 22.283365,20.862787 0 0 0 -1.72639,5.98278 c -1.35544,7.0923 -1.88894,20.10721 5.951801,36.96846 12.12378,26.07187 9.7857,31.07171 9.78528,31.07259 2.6e-4,-5.5e-4 2.34212,-4.99911 31.03406,-14.32513 18.555748,-6.03127 28.414858,-15.22005 33.065768,-20.96029 a 22.283365,20.862787 0 0 0 3.52818,-5.24352 22.283365,20.862787 0 0 0 -11.46577,-27.48227 22.283365,20.862787 0 0 0 -29.353588,10.73483 22.283365,20.862787 0 0 0 -11.46576,-27.48227 z" /><path
id="path4-2-6-1-9"
style="fill:#ffffff;fill-opacity:0.50980395;stroke:#ffffff;stroke-width:5.01078;stroke-dasharray:none;stroke-opacity:0.519962"
d="m 320.97602,291.36362 a 16.625347,14.748181 0 0 0 -21.90036,7.58859 16.625347,14.748181 0 0 0 -1.28804,4.2293 c -1.01128,5.01364 -1.40932,14.21406 4.44057,26.1335 9.0454,18.43055 7.30098,21.965 7.30067,21.96562 2e-4,-3.9e-4 1.74743,-3.53394 23.15415,-10.12663 13.84423,-4.26358 21.19999,-10.75925 24.66998,-14.8171 a 16.625347,14.748181 0 0 0 2.63233,-3.70671 16.625347,14.748181 0 0 0 -8.55447,-19.42758 16.625347,14.748181 0 0 0 -21.90037,7.58859 16.625347,14.748181 0 0 0 -8.55446,-19.42758 z" /><path
id="path4-2-6-1-9-8"
style="fill:#ffffff;fill-opacity:0.50980395;stroke:#ffffff;stroke-width:4.22313;stroke-dasharray:none;stroke-opacity:0.519962"
d="m 416.90426,193.13811 a 12.322476,14.134158 82.30909 0 0 -17.56469,8.83612 12.322476,14.134158 82.30909 0 0 -0.59553,3.65007 c -0.27175,4.26676 0.45709,11.92642 6.76182,21.10665 9.74867,14.19503 8.68847,17.32338 8.68827,17.3239 1.2e-4,-3.3e-4 1.06282,-3.12827 18.32699,-11.08266 11.16516,-5.14424 16.60821,-11.37802 19.06101,-15.14093 a 12.322476,14.134158 82.30909 0 0 1.788,-3.37453 12.322476,14.134158 82.30909 0 0 -9.45059,-15.07737 12.322476,14.134158 82.30909 0 0 -17.5647,8.83612 12.322476,14.134158 82.30909 0 0 -9.45058,-15.07737 z" /><path
id="path4-2-6-1-9-8-4"
style="fill:#ffffff;fill-opacity:0.50980395;stroke:#ffffff;stroke-width:3.48342;stroke-dasharray:none;stroke-opacity:0.519962"
d="m 96.169666,99.754795 a 10.296362,11.508677 62.365085 0 0 -7.330189,13.841365 10.296362,11.508677 62.365085 0 0 1.473186,2.6805 c 2.008889,2.93989 6.437543,7.66269 15.415927,10.84633 13.88288,4.92276 14.77391,7.47086 14.77407,7.4713 -1e-4,-2.8e-4 -0.88925,-2.54921 6.69094,-15.67164 4.90232,-8.48655 5.37758,-15.08279 5.10162,-18.6847 a 10.296362,11.508677 62.365085 0 0 -0.5257,-3.042454 10.296362,11.508677 62.365085 0 0 -14.1348,-5.641051 10.296362,11.508677 62.365085 0 0 -7.33021,13.841385 10.296362,11.508677 62.365085 0 0 -14.134844,-5.641035 z" /><path
id="path4-2-6-1-9-8-4-9-2"
style="fill:#ffffff;fill-opacity:0.5117;stroke:#ffffff;stroke-width:3.05139;stroke-dasharray:none;stroke-opacity:0.519962"
d="m 296.64657,233.26347 a 8.8114666,10.319195 80.445391 0 0 -11.58972,8.06989 8.8114666,10.319195 80.445391 0 0 0.0366,2.62543 c 0.35019,3.02392 1.86017,8.32306 7.60386,14.09672 8.8812,8.92759 8.51432,11.23624 8.51426,11.23663 5e-5,-2.5e-4 0.36876,-2.30885 11.85385,-9.72788 7.4277,-4.79799 10.57103,-9.75243 11.86523,-12.65455 a 8.8114666,10.319195 80.445391 0 0 0.86244,-2.55882 8.8114666,10.319195 80.445391 0 0 -8.77837,-9.57865 8.8114666,10.319195 80.445391 0 0 -11.58975,8.06989 8.8114666,10.319195 80.445391 0 0 -8.77842,-9.57866 z" /><path
id="path4-2-6-1-9-8-4-9-2-6"
style="fill:#ffffff;fill-opacity:0.5117;stroke:#ffffff;stroke-width:2.49738;stroke-dasharray:none;stroke-opacity:0.519962"
d="m 403.95446,91.44744 a 7.0950375,8.5844443 82.142743 0 0 -9.64888,6.492875 7.0950375,8.5844443 82.142743 0 0 0.0305,2.112375 c 0.29155,2.43298 1.54866,6.69657 6.33049,11.34195 7.39392,7.18298 7.08848,9.04046 7.08843,9.04078 4e-5,-2.1e-4 0.307,-1.85765 9.86876,-7.82686 6.18383,-3.86038 8.80077,-7.84663 9.87824,-10.18161 a 7.0950375,8.5844443 82.142743 0 0 0.718,-2.05879 7.0950375,8.5844443 82.142743 0 0 -7.30831,-7.706798 7.0950375,8.5844443 82.142743 0 0 -9.64889,6.492875 7.0950375,8.5844443 82.142743 0 0 -7.30834,-7.706797 z" /><path
id="path4-2-6-1-9-8-4-9-5"
style="fill:#ffffff;fill-opacity:0.5117;stroke:#ffffff;stroke-width:7.54176;stroke-dasharray:none;stroke-opacity:0.521569"
d="m 410.46352,265.86621 a 24.802737,22.39469 2.0523303 0 0 -30.17074,15.95063 24.802737,22.39469 2.0523303 0 0 -0.74427,6.61415 c -0.12337,7.7333 1.79614,21.62064 13.65297,38.28529 18.33368,25.76802 16.72835,31.4347 16.72811,31.43572 1.4e-4,-6.3e-4 1.60998,-5.66652 31.32518,-20.02007 19.21759,-9.28266 28.27643,-20.56192 32.27921,-27.37361 a 24.802737,22.39469 2.0523303 0 0 2.8654,-6.11021 24.802737,22.39469 2.0523303 0 0 -17.88248,-27.36631 24.802737,22.39469 2.0523303 0 0 -30.17081,15.95066 24.802737,22.39469 2.0523303 0 0 -17.88257,-27.36625 z" /><path
id="path4-2-6-1-9-8-5"
style="fill:#ffffff;fill-opacity:0.50980395;stroke:#ffffff;stroke-width:3.6405;stroke-dasharray:none;stroke-opacity:0.519962"
d="m 114.43099,189.33754 a 10.333135,12.525309 79.004057 0 0 -14.00848,9.75499 10.333135,12.525309 79.004057 0 0 0.0547,3.08382 c 0.43602,3.54457 2.28588,9.73496 9.26636,16.38295 10.79359,10.27954 10.35819,13.00063 10.3581,13.00111 6e-5,-3e-4 0.4377,-2.72141 14.32197,-11.70923 8.9793,-5.8126 12.76786,-11.70785 14.32433,-15.14812 a 10.333135,12.525309 79.004057 0 0 1.03481,-3.02677 10.333135,12.525309 79.004057 0 0 -10.67161,-11.04688 10.333135,12.525309 79.004057 0 0 -14.00848,9.75501 10.333135,12.525309 79.004057 0 0 -10.6716,-11.04688 z" /><path
id="path4-2-6-1-9-8-4-9-2-2"
style="fill:#ffffff;fill-opacity:0.5117;stroke:#ffffff;stroke-width:3.62674;stroke-dasharray:none;stroke-opacity:0.519962"
d="m 178.56055,273.32338 a 10.656883,12.053184 66.089768 0 0 -7.97821,14.16205 10.656883,12.053184 66.089768 0 0 1.49012,2.79504 c 2.04675,3.07265 6.5954,8.03049 15.94965,11.48124 14.46412,5.33577 15.34612,7.98353 15.34628,7.98398 -8e-5,-2.8e-4 -0.88012,-2.64887 7.3455,-16.0639 5.31973,-8.67576 5.95524,-15.47956 5.74045,-19.20452 a 10.656883,12.053184 66.089768 0 0 -0.48843,-3.15169 10.656883,12.053184 66.089768 0 0 -14.7135,-6.08217 10.656883,12.053184 66.089768 0 0 -7.97826,14.1621 10.656883,12.053184 66.089768 0 0 -14.71355,-6.08215 z" /></svg>

After

Width:  |  Height:  |  Size: 15 KiB

+1
View File
@@ -0,0 +1 @@
#e48cc9ff
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 42 KiB

+1 -4
View File
@@ -6,7 +6,4 @@ Debugging on physical hardware can get tedious and time consuming. Users are emp
**Standard key prefix**: Allows to redirect the key manager to a file other than `prod.keys` (for example `other` would redirect to `other.keys`). This is useful for testing multiple keysets. Default is `prod`.
**Changing serial**: Very basic way to set debug values for the serial (and battery number). Developers do not need to write the full serial as it will be writen in-place (that is, it will be filled with the default serial and then overwrite the serial from the beginning).
- Battery serial: `YUZU0EMULATOR14022024`
- Board serial: `YUZ10000000001`
If the user were to set their board serial as `ABC`, then it will be written in-place and the resulting serial would be `ABC10000000001`. There are no underlying checks to ensure correctness of serials other than a hard limit of 16-characters for both.
**Changing serial**: Very basic way to set debug values for the serial (and battery number). Developers do not need to write the full serial as only the first digits (excluding the last) will be accoutned for. Region settings will affect the generated serial. The serial corresponds to a non-OLED/Lite console.
+1 -5
View File
@@ -27,11 +27,7 @@ set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON)
# Xbyak
if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
if (PLATFORM_SUN OR PLATFORM_OPENBSD OR PLATFORM_NETBSD OR PLATFORM_DRAGONFLY)
AddJsonPackage(xbyak_sun)
else()
AddJsonPackage(xbyak)
endif()
AddJsonPackage(xbyak)
endif()
# enet
+3 -16
View File
@@ -48,23 +48,12 @@
"0001-fix-missing-decl.patch"
]
},
"xbyak_sun": {
"package": "xbyak",
"repo": "herumi/xbyak",
"tag": "v%VERSION%",
"hash": "b40dade90fb0e46a2bd52934f7ce461e37be931b571e58cbe2203bc08ed5b54c7ff1a29026c74c7f9805e4e3f6c9636deca528e6b4a8093ce7eae145218599f1",
"git_version": "7.29",
"bundled": true,
"skip_updates": true
},
"xbyak": {
"package": "xbyak",
"repo": "herumi/xbyak",
"tag": "v%VERSION%",
"hash": "1042090405c426e339506c179d53e91d4d545ce9c9f53d8f797caa092d589f913a9bcb9c8f31c4c60870acb954c556e305fb6732c66bc3c8f1cd924f9172def9",
"git_version": "7.22",
"bundled": true,
"skip_updates": true
"hash": "ac333d7bea1d61865bebebb116201a58db431946aa2f11aa042ef5795c390ff30af4d6c90ed3b3d24443a1d430703b08f14fc13b2fa405c155a241456ed78a47",
"git_version": "7.33.2"
},
"oaknut": {
"repo": "eden-emulator/oaknut",
@@ -161,7 +150,7 @@
"package": "SDL2",
"name": "SDL2",
"repo": "crueter-ci/SDL2",
"version": "2.32.10-a65111bd2d",
"version": "2.32.10-cf5dabd6ea",
"min_version": "2.26.4"
},
"catch2": {
@@ -195,7 +184,6 @@
"repo": "libsdl-org/SDL",
"tag": "release-%VERSION%",
"hash": "d5622d6bb7266f7942a7b8ad43e8a22524893bf0c2ea1af91204838d9b78d32768843f6faa248757427b8404b8c6443776d4afa6b672cd8571a4e0c03a829383",
"key": "generic",
"bundled": true,
"git_version": "2.32.10",
"skip_updates": true
@@ -205,7 +193,6 @@
"repo": "libsdl-org/SDL",
"sha": "cc016b0046",
"hash": "b8d9873446cdb922387471df9968e078714683046674ef0d0edddf8e25da65a539a3bae83d635496b970237f90b07b36a69f8d7855d450de59311d6d6e8c3dbc",
"key": "steamdeck",
"bundled": true,
"skip_updates": "true"
},
@@ -203,6 +203,24 @@ object NativeLibrary {
external fun getDebugKnobAt(index: Int): Boolean
/**
* Set the current speed limit to the configured turbo speed.
*/
external fun setTurboSpeedLimit(enabled: Boolean)
/**
* Set the current speed limit to the configured slow speed.
*/
external fun setSlowSpeedLimit(enabled: Boolean)
/**
* Set the current speed limit to the configured standard speed.
*/
external fun setStandardSpeedLimit(enabled: Boolean)
external fun isTurboMode(): Boolean
external fun isSlowMode(): Boolean
/**
* Returns Vulkan driver version / API version / GPU model
*/
@@ -11,6 +11,7 @@ import android.widget.RadioGroup
import android.widget.TextView
import androidx.drawerlayout.widget.DrawerLayout
import com.google.android.material.color.MaterialColors
import com.google.android.material.materialswitch.MaterialSwitch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
@@ -135,6 +136,39 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
container.addView(itemView)
}
fun addCustomToggle(
name: Int,
isChecked: Boolean,
isEnabled: Boolean,
container: ViewGroup,
callback: (Boolean) -> Unit
): MaterialSwitch? {
val inflater = LayoutInflater.from(emulationFragment.requireContext())
val itemView = inflater.inflate(R.layout.item_quick_settings_menu, container, false)
val switchContainer = itemView.findViewById<ViewGroup>(R.id.switch_container)
val titleView = itemView.findViewById<TextView>(R.id.switch_title)
val switchView = itemView.findViewById<MaterialSwitch>(R.id.setting_switch)
titleView.text = YuzuApplication.appContext.getString(name)
switchContainer.visibility = View.VISIBLE
switchView.isChecked = isChecked
switchView.setOnCheckedChangeListener { _, checked ->
callback(checked)
saveSettings()
}
switchContainer.setOnClickListener {
switchView.toggle()
}
container.addView(itemView)
return switchView
}
fun addSliderSetting(
name: Int,
container: ViewGroup,
@@ -33,6 +33,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_PROVOKING_VERTEX("provoking_vertex"),
RENDERER_DESCRIPTOR_INDEXING("descriptor_indexing"),
RENDERER_SAMPLE_SHADING("sample_shading"),
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
PICTURE_IN_PICTURE("picture_in_picture"),
USE_CUSTOM_RTC("custom_rtc_enabled"),
BLACK_BACKGROUNDS("black_backgrounds"),
@@ -29,6 +29,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
RENDERER_DYNA_STATE("dyna_state"),
DMA_ACCURACY("dma_accuracy"),
FRAME_PACING_MODE("frame_pacing_mode"),
AUDIO_OUTPUT_ENGINE("output_engine"),
MAX_ANISOTROPY("max_anisotropy"),
THEME("theme"),
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -6,7 +9,10 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ShortSetting(override val key: String) : AbstractShortSetting {
RENDERER_SPEED_LIMIT("speed_limit");
RENDERER_SPEED_LIMIT("speed_limit"),
RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"),
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit"),
;
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)
@@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.model.view
import androidx.annotation.ArrayRes
import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
class GpuUnswizzleSetting(
@StringRes titleId: Int = 0,
titleString: String = "",
@StringRes descriptionId: Int = 0,
descriptionString: String = "",
@ArrayRes val textureSizeChoicesId: Int,
@ArrayRes val textureSizeValuesId: Int,
@ArrayRes val streamSizeChoicesId: Int,
@ArrayRes val streamSizeValuesId: Int,
@ArrayRes val chunkSizeChoicesId: Int,
@ArrayRes val chunkSizeValuesId: Int
) : SettingsItem(
object : AbstractSetting {
override val key: String = SettingsItem.GPU_UNSWIZZLE_COMBINED
override val defaultValue: Any = false
override val isSaveable = true
override val isRuntimeModifiable = true
override val isSwitchable = true
override fun getValueAsString(needsGlobal: Boolean): String = "combined"
override fun reset() {
BooleanSetting.GPU_UNSWIZZLE_ENABLED.reset()
IntSetting.GPU_UNSWIZZLE_TEXTURE_SIZE.reset()
IntSetting.GPU_UNSWIZZLE_STREAM_SIZE.reset()
IntSetting.GPU_UNSWIZZLE_CHUNK_SIZE.reset()
}
},
titleId,
titleString,
descriptionId,
descriptionString
) {
override val type = SettingsItem.TYPE_GPU_UNSWIZZLE
// Check if GPU unswizzle is enabled via the dedicated boolean setting
fun isEnabled(needsGlobal: Boolean = false): Boolean =
BooleanSetting.GPU_UNSWIZZLE_ENABLED.getBoolean(needsGlobal)
fun setEnabled(value: Boolean) =
BooleanSetting.GPU_UNSWIZZLE_ENABLED.setBoolean(value)
fun enable() = setEnabled(true)
fun disable() = setEnabled(false)
fun getTextureSize(needsGlobal: Boolean = false): Int =
IntSetting.GPU_UNSWIZZLE_TEXTURE_SIZE.getInt(needsGlobal)
fun setTextureSize(value: Int) =
IntSetting.GPU_UNSWIZZLE_TEXTURE_SIZE.setInt(value)
fun getStreamSize(needsGlobal: Boolean = false): Int =
IntSetting.GPU_UNSWIZZLE_STREAM_SIZE.getInt(needsGlobal)
fun setStreamSize(value: Int) =
IntSetting.GPU_UNSWIZZLE_STREAM_SIZE.setInt(value)
fun getChunkSize(needsGlobal: Boolean = false): Int =
IntSetting.GPU_UNSWIZZLE_CHUNK_SIZE.getInt(needsGlobal)
fun setChunkSize(value: Int) =
IntSetting.GPU_UNSWIZZLE_CHUNK_SIZE.setInt(value)
fun reset() = setting.reset()
}
@@ -104,8 +104,10 @@ abstract class SettingsItem(
const val TYPE_SPINBOX = 12
const val TYPE_LAUNCHABLE = 13
const val TYPE_PATH = 14
const val TYPE_GPU_UNSWIZZLE = 15
const val FASTMEM_COMBINED = "fastmem_combined"
const val GPU_UNSWIZZLE_COMBINED = "gpu_unswizzle_combined"
val emptySetting = object : AbstractSetting {
override val key: String = ""
@@ -178,6 +180,26 @@ abstract class SettingsItem(
units = "%"
)
)
put(
SliderSetting(
ShortSetting.RENDERER_TURBO_SPEED_LIMIT,
titleId = R.string.turbo_speed_limit,
descriptionId = R.string.turbo_speed_limit_description,
min = 1,
max = 400,
units = "%"
)
)
put(
SliderSetting(
ShortSetting.RENDERER_SLOW_SPEED_LIMIT,
titleId = R.string.slow_speed_limit,
descriptionId = R.string.slow_speed_limit_description,
min = 1,
max = 400,
units = "%"
)
)
put(
SingleChoiceSetting(
IntSetting.CPU_BACKEND,
@@ -641,6 +663,15 @@ abstract class SettingsItem(
valuesId = R.array.dmaAccuracyValues
)
)
put(
SingleChoiceSetting(
IntSetting.FRAME_PACING_MODE,
titleId = R.string.frame_pacing_mode,
descriptionId = R.string.frame_pacing_mode_description,
choicesId = R.array.framePacingModeNames,
valuesId = R.array.framePacingModeValues
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
@@ -684,6 +715,18 @@ abstract class SettingsItem(
valuesId = R.array.gpuSwizzleChunkValues
)
)
put(
GpuUnswizzleSetting(
titleId = R.string.gpu_unswizzle_settings,
descriptionId = R.string.gpu_unswizzle_settings_description,
textureSizeChoicesId = R.array.gpuTextureSizeSwizzleEntries,
textureSizeValuesId = R.array.gpuTextureSizeSwizzleValues,
streamSizeChoicesId = R.array.gpuSwizzleEntries,
streamSizeValuesId = R.array.gpuSwizzleValues,
chunkSizeChoicesId = R.array.gpuSwizzleChunkEntries,
chunkSizeValuesId = R.array.gpuSwizzleChunkValues
)
)
put(
SingleChoiceSetting(
IntSetting.FAST_CPU_TIME,
@@ -0,0 +1,206 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.widget.ArrayAdapter
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogGpuUnswizzleBinding
import org.yuzu.yuzu_emu.features.settings.model.view.GpuUnswizzleSetting
class GpuUnswizzleDialogFragment : DialogFragment() {
private var position = 0
private val settingsViewModel: SettingsViewModel by activityViewModels()
private lateinit var binding: DialogGpuUnswizzleBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
position = requireArguments().getInt(POSITION)
if (settingsViewModel.clickedItem == null) dismiss()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
binding = DialogGpuUnswizzleBinding.inflate(LayoutInflater.from(requireContext()))
val item = settingsViewModel.clickedItem as GpuUnswizzleSetting
// Setup texture size dropdown
val textureSizeEntries = resources.getStringArray(item.textureSizeChoicesId)
val textureSizeValues = resources.getIntArray(item.textureSizeValuesId)
val textureSizeAdapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_dropdown_item_1line,
textureSizeEntries.toMutableList()
)
binding.dropdownTextureSize.setAdapter(textureSizeAdapter)
// Setup stream size dropdown
val streamSizeEntries = resources.getStringArray(item.streamSizeChoicesId)
val streamSizeValues = resources.getIntArray(item.streamSizeValuesId)
val streamSizeAdapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_dropdown_item_1line,
streamSizeEntries.toMutableList()
)
binding.dropdownStreamSize.setAdapter(streamSizeAdapter)
// Setup chunk size dropdown
val chunkSizeEntries = resources.getStringArray(item.chunkSizeChoicesId)
val chunkSizeValues = resources.getIntArray(item.chunkSizeValuesId)
val chunkSizeAdapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_dropdown_item_1line,
chunkSizeEntries.toMutableList()
)
binding.dropdownChunkSize.setAdapter(chunkSizeAdapter)
// Load current values
val isEnabled = item.isEnabled()
binding.switchEnable.isChecked = isEnabled
if (isEnabled) {
val textureSizeIndex = textureSizeValues.indexOf(item.getTextureSize())
if (textureSizeIndex >= 0) {
binding.dropdownTextureSize.setText(textureSizeEntries[textureSizeIndex], false)
}
val streamSizeIndex = streamSizeValues.indexOf(item.getStreamSize())
if (streamSizeIndex >= 0) {
binding.dropdownStreamSize.setText(streamSizeEntries[streamSizeIndex], false)
}
val chunkSizeIndex = chunkSizeValues.indexOf(item.getChunkSize())
if (chunkSizeIndex >= 0) {
binding.dropdownChunkSize.setText(chunkSizeEntries[chunkSizeIndex], false)
}
} else {
// Set default/recommended values when disabling
binding.dropdownTextureSize.setText(textureSizeEntries[3], false)
binding.dropdownStreamSize.setText(streamSizeEntries[3], false)
binding.dropdownChunkSize.setText(chunkSizeEntries[3], false)
}
// Clear adapter filters after setText to fix rotation bug
textureSizeAdapter.filter.filter(null)
streamSizeAdapter.filter.filter(null)
chunkSizeAdapter.filter.filter(null)
// Enable/disable dropdowns based on switch state
updateDropdownsState(isEnabled)
binding.switchEnable.setOnCheckedChangeListener { _, checked ->
updateDropdownsState(checked)
}
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(item.title)
.setView(binding.root)
.create()
// Setup button listeners
binding.btnDefault.setOnClickListener {
// Reset to defaults
item.reset()
// Refresh values with adapters reset
val textureSizeIndex = textureSizeValues.indexOf(item.getTextureSize())
if (textureSizeIndex >= 0) {
binding.dropdownTextureSize.setText(textureSizeEntries[textureSizeIndex], false)
}
val streamSizeIndex = streamSizeValues.indexOf(item.getStreamSize())
if (streamSizeIndex >= 0) {
binding.dropdownStreamSize.setText(streamSizeEntries[streamSizeIndex], false)
}
val chunkSizeIndex = chunkSizeValues.indexOf(item.getChunkSize())
if (chunkSizeIndex >= 0) {
binding.dropdownChunkSize.setText(chunkSizeEntries[chunkSizeIndex], false)
}
// Clear filters
textureSizeAdapter.filter.filter(null)
streamSizeAdapter.filter.filter(null)
chunkSizeAdapter.filter.filter(null)
settingsViewModel.setAdapterItemChanged(position)
settingsViewModel.setShouldReloadSettingsList(true)
}
binding.btnCancel.setOnClickListener {
dialog.dismiss()
}
binding.btnOk.setOnClickListener {
if (binding.switchEnable.isChecked) {
item.enable()
// Save the selected values
val selectedTextureIndex = textureSizeEntries.indexOf(
binding.dropdownTextureSize.text.toString()
)
if (selectedTextureIndex >= 0) {
item.setTextureSize(textureSizeValues[selectedTextureIndex])
}
val selectedStreamIndex = streamSizeEntries.indexOf(
binding.dropdownStreamSize.text.toString()
)
if (selectedStreamIndex >= 0) {
item.setStreamSize(streamSizeValues[selectedStreamIndex])
}
val selectedChunkIndex = chunkSizeEntries.indexOf(
binding.dropdownChunkSize.text.toString()
)
if (selectedChunkIndex >= 0) {
item.setChunkSize(chunkSizeValues[selectedChunkIndex])
}
} else {
// Disable GPU unswizzle
item.disable()
}
settingsViewModel.setAdapterItemChanged(position)
settingsViewModel.setShouldReloadSettingsList(true)
dialog.dismiss()
}
// Ensure filters are cleared after dialog is shown
binding.root.post {
textureSizeAdapter.filter.filter(null)
streamSizeAdapter.filter.filter(null)
chunkSizeAdapter.filter.filter(null)
}
return dialog
}
private fun updateDropdownsState(enabled: Boolean) {
binding.layoutTextureSize.isEnabled = enabled
binding.dropdownTextureSize.isEnabled = enabled
binding.layoutStreamSize.isEnabled = enabled
binding.dropdownStreamSize.isEnabled = enabled
binding.layoutChunkSize.isEnabled = enabled
binding.dropdownChunkSize.isEnabled = enabled
}
companion object {
const val TAG = "GpuUnswizzleDialogFragment"
const val POSITION = "Position"
fun newInstance(
settingsViewModel: SettingsViewModel,
item: GpuUnswizzleSetting,
position: Int
): GpuUnswizzleDialogFragment {
val dialog = GpuUnswizzleDialogFragment()
val args = Bundle()
args.putInt(POSITION, position)
dialog.arguments = args
settingsViewModel.clickedItem = item
return dialog
}
}
}
@@ -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.features.settings.ui
@@ -101,6 +101,11 @@ class SettingsAdapter(
SettingsItem.TYPE_PATH -> {
PathViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
SettingsItem.TYPE_GPU_UNSWIZZLE -> {
GpuUnswizzleViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
else -> {
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
}
@@ -474,6 +479,14 @@ class SettingsAdapter(
settingsViewModel.setShouldShowPathResetDialog(true)
}
fun onGpuUnswizzleClick(item: GpuUnswizzleSetting, position: Int) {
GpuUnswizzleDialogFragment.newInstance(
settingsViewModel,
item,
position
).show(fragment.childFragmentManager, GpuUnswizzleDialogFragment.TAG)
}
private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
return oldItem.setting.key == newItem.setting.key
@@ -227,6 +227,8 @@ class SettingsFragmentPresenter(
add(StringSetting.DEVICE_NAME.key)
add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
add(ShortSetting.RENDERER_SPEED_LIMIT.key)
add(ShortSetting.RENDERER_TURBO_SPEED_LIMIT.key)
add(ShortSetting.RENDERER_SLOW_SPEED_LIMIT.key)
add(BooleanSetting.USE_DOCKED_MODE.key)
add(IntSetting.REGION_INDEX.key)
add(IntSetting.LANGUAGE_INDEX.key)
@@ -265,6 +267,7 @@ class SettingsFragmentPresenter(
add(IntSetting.RENDERER_ACCURACY.key)
add(IntSetting.DMA_ACCURACY.key)
add(IntSetting.FRAME_PACING_MODE.key)
add(IntSetting.MAX_ANISOTROPY.key)
add(IntSetting.RENDERER_VRAM_USAGE_MODE.key)
add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key)
@@ -282,9 +285,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
add(IntSetting.GPU_UNSWIZZLE_TEXTURE_SIZE.key)
add(IntSetting.GPU_UNSWIZZLE_STREAM_SIZE.key)
add(IntSetting.GPU_UNSWIZZLE_CHUNK_SIZE.key)
add(SettingsItem.GPU_UNSWIZZLE_COMBINED)
add(HeaderSetting(R.string.extensions))
@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.GpuUnswizzleSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
class GpuUnswizzleViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
private lateinit var setting: GpuUnswizzleSetting
override fun bind(item: SettingsItem) {
setting = item as GpuUnswizzleSetting
binding.textSettingName.text = setting.title
binding.textSettingDescription.setVisible(item.description.isNotEmpty())
binding.textSettingDescription.text = item.description
binding.textSettingValue.setVisible(true)
val resMgr = binding.root.context.resources
if (setting.isEnabled()) {
// Show a summary of current settings
val textureSizeEntries = resMgr.getStringArray(setting.textureSizeChoicesId)
val textureSizeValues = resMgr.getIntArray(setting.textureSizeValuesId)
val textureSizeIndex = textureSizeValues.indexOf(setting.getTextureSize())
val textureSizeLabel = if (textureSizeIndex >= 0) textureSizeEntries[textureSizeIndex] else "?"
val streamSizeEntries = resMgr.getStringArray(setting.streamSizeChoicesId)
val streamSizeValues = resMgr.getIntArray(setting.streamSizeValuesId)
val streamSizeIndex = streamSizeValues.indexOf(setting.getStreamSize())
val streamSizeLabel = if (streamSizeIndex >= 0) streamSizeEntries[streamSizeIndex] else "?"
val chunkSizeEntries = resMgr.getStringArray(setting.chunkSizeChoicesId)
val chunkSizeValues = resMgr.getIntArray(setting.chunkSizeValuesId)
val chunkSizeIndex = chunkSizeValues.indexOf(setting.getChunkSize())
val chunkSizeLabel = if (chunkSizeIndex >= 0) chunkSizeEntries[chunkSizeIndex] else "?"
binding.textSettingValue.text = "$textureSizeLabel$streamSizeLabel$chunkSizeLabel"
} else {
binding.textSettingValue.text = resMgr.getString(R.string.gpu_unswizzle_disabled)
}
binding.buttonClear.setVisible(setting.clearable)
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
}
setStyle(setting.isEditable, binding)
}
override fun onClick(clicked: View) {
if (!setting.isEditable) {
return
}
adapter.onGpuUnswizzleClick(setting, bindingAdapterPosition)
}
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
return adapter.onLongClick(setting, bindingAdapterPosition)
}
return false
}
}
@@ -55,6 +55,7 @@ import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.materialswitch.MaterialSwitch
import com.google.android.material.textview.MaterialTextView
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
@@ -1055,11 +1056,47 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
quickSettings.addPerGameConfigStatusIndicator(container)
}
quickSettings.addBooleanSetting(
lateinit var slowSpeed: MaterialSwitch
lateinit var turboSpeed: MaterialSwitch
turboSpeed = quickSettings.addCustomToggle(
R.string.turbo_speed_limit,
NativeLibrary.isTurboMode(),
BooleanSetting.RENDERER_USE_SPEED_LIMIT.getBoolean(false),
container
) { enabled ->
if (enabled)
slowSpeed.isChecked = false
NativeLibrary.setTurboSpeedLimit(enabled)
}!!
slowSpeed = quickSettings.addCustomToggle(
R.string.slow_speed_limit,
NativeLibrary.isSlowMode(),
BooleanSetting.RENDERER_USE_SPEED_LIMIT.getBoolean(false),
container
) { enabled ->
if (enabled)
turboSpeed.isChecked = false
NativeLibrary.setSlowSpeedLimit(enabled)
}!!
quickSettings.addCustomToggle(
R.string.frame_limit_enable,
container,
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
)
BooleanSetting.RENDERER_USE_SPEED_LIMIT.getBoolean(false),
true,
container
) { enabled ->
if (!enabled) {
turboSpeed.isChecked = false
slowSpeed.isChecked = false
}
turboSpeed.isEnabled = enabled
slowSpeed.isEnabled = enabled
NativeLibrary.setStandardSpeedLimit(enabled)
}!!
quickSettings.addSliderSetting(
R.string.frame_limit_slider,
+33
View File
@@ -1233,6 +1233,39 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobje
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
if (enabled) {
Settings::values.use_speed_limit.SetValue(true);
Settings::SetSpeedMode(Settings::SpeedMode::Turbo);
} else {
Settings::SetSpeedMode(Settings::SpeedMode::Standard);
}
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setSlowSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
if (enabled) {
Settings::values.use_speed_limit.SetValue(true);
Settings::SetSpeedMode(Settings::SpeedMode::Slow);
} else {
Settings::SetSpeedMode(Settings::SpeedMode::Standard);
}
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setStandardSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
Settings::values.use_speed_limit.SetValue(enabled);
if (enabled) {
Settings::SetSpeedMode(Settings::SpeedMode::Standard);
}
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isTurboMode(JNIEnv *env, jobject jobj) {
return Settings::values.current_speed_mode.GetValue() == Settings::SpeedMode::Turbo;
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isSlowMode(JNIEnv *env, jobject jobj) {
return Settings::values.current_speed_mode.GetValue() == Settings::SpeedMode::Slow;
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run(JNIEnv* env, jobject jobj, jstring j_path,
jint j_program_index,
jboolean j_frontend_initiated) {
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch_enable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="@string/gpu_unswizzle_enable" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_texture_size"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:hint="@string/gpu_unswizzle_texture_size">
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/dropdown_texture_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_stream_size"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:hint="@string/gpu_unswizzle_stream_size">
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/dropdown_stream_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_chunk_size"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/gpu_unswizzle_chunk_size">
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/dropdown_chunk_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="horizontal"
android:gravity="end"
android:spacing="8dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_default"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/gpu_unswizzle_default_button" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_cancel"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/cancel" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_ok"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/ok" />
</LinearLayout>
</LinearLayout>
</ScrollView>
@@ -530,6 +530,21 @@
<item>2</item>
</integer-array>
<string-array name="framePacingModeNames">
<item>@string/frame_pacing_mode_target_Auto</item>
<item>@string/frame_pacing_mode_target_30</item>
<item>@string/frame_pacing_mode_target_60</item>
<item>@string/frame_pacing_mode_target_90</item>
<item>@string/frame_pacing_mode_target_120</item>
</string-array>
<integer-array name="framePacingModeValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</integer-array>
<string-array name="appletEntries">
<item>@string/applet_hle</item>
<item>@string/applet_lle</item>
@@ -422,6 +422,10 @@
<string name="frame_limit_enable_description">Limits emulation speed to a specified percentage of normal speed.</string>
<string name="frame_limit_slider">Limit speed percent</string>
<string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string>
<string name="turbo_speed_limit">Turbo speed</string>
<string name="turbo_speed_limit_description">When Turbo Mode is enabled, emulation will run at this speed.</string>
<string name="slow_speed_limit">Slow speed</string>
<string name="slow_speed_limit_description">When Slow Mode is enabled, emulation will run at this speed.</string>
<string name="cpu_backend">CPU backend</string>
<string name="cpu_accuracy">CPU accuracy</string>
<string name="value_with_units">%1$s%2$s</string>
@@ -473,6 +477,8 @@
<string name="renderer_accuracy_description">Controls the GPU emulation mode. Most games render fine with Fast or Balanced modes, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode.</string>
<string name="dma_accuracy">DMA Accuracy</string>
<string name="dma_accuracy_description">Controls the DMA precision accuracy. Safe precision can fix issues in some games, but it can also impact performance in some cases. If unsure, leave this on Default.</string>
<string name="frame_pacing_mode">Frame Pacing Mode</string>
<string name="frame_pacing_mode_description">Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent.</string>
<string name="anisotropic_filtering">Anisotropic filtering</string>
<string name="anisotropic_filtering_description">Improves the quality of textures when viewed at oblique angles</string>
<string name="vram_usage_mode">VRAM Usage Mode</string>
@@ -501,15 +507,20 @@
<string name="skip_cpu_inner_invalidation">Skip CPU Inner Invalidation</string>
<string name="skip_cpu_inner_invalidation_description">Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games.</string>
<string name="fix_bloom_effects">Fix Bloom Effects</string>
<string name="fix_bloom_effects_description">Reduces bloom blur in LA/EOW (Adreno 700), removes bloom in Burnout</string>
<string name="fix_bloom_effects_description">Reduces bloom blur in LA/EOW (Adreno 700), removes bloom in Burnout. Warning: may cause graphical artifacts in other games.</string>
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
<string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string>
<string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string>
<string name="gpu_unswizzle_disabled">Disabled</string>
<string name="gpu_unswizzle_texture_size">GPU Unswizzle Max Texture Size</string>
<string name="gpu_unswizzle_texture_size_description">Sets the maximum size (MB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead.</string>
<string name="gpu_unswizzle_stream_size">GPU Unswizzle Stream Size</string>
<string name="gpu_unswizzle_stream_size_description">Sets the data limit per frame for unswizzling large textures. Higher values speed up texture loading at the cost of higher frame latency; lower values reduce GPU overhead but may cause visible texture pop-in.</string>
<string name="gpu_unswizzle_chunk_size">GPU Unswizzle Chunk Size</string>
<string name="gpu_unswizzle_chunk_size_description">Defines the number of depth slices processed per batch for 3D textures. Increasing this improves throughput efficiency on powerful GPUs but may cause stuttering or driver timeouts on weaker hardware.</string>
<string name="gpu_unswizzle_default_button">Default</string>
<string name="extensions">Extensions</string>
@@ -1023,6 +1034,13 @@
<string name="dma_accuracy_unsafe">Unsafe</string>
<string name="dma_accuracy_safe">Safe</string>
<!-- Frame Pacing Mode -->
<string name="frame_pacing_mode_target_Auto">Auto</string>
<string name="frame_pacing_mode_target_30">30 FPS</string>
<string name="frame_pacing_mode_target_60">60 FPS</string>
<string name="frame_pacing_mode_target_90">90 FPS</string>
<string name="frame_pacing_mode_target_120">120 FPS</string>
<!-- ASTC Decoding Method Choices -->
<string name="accelerate_astc_cpu" translatable="false">CPU</string>
<string name="accelerate_astc_gpu" translatable="false">GPU</string>
+2
View File
@@ -186,6 +186,8 @@ if(ARCHITECTURE_x86_64)
common
PRIVATE x64/cpu_detect.cpp
x64/cpu_detect.h
x64/cpu_wait.cpp
x64/cpu_wait.h
x64/native_clock.cpp
x64/native_clock.h
x64/rdtsc.cpp
+2
View File
@@ -20,6 +20,7 @@
#define COMPILER_ID "@CXX_COMPILER@"
#define BUILD_AUTO_UPDATE_WEBSITE "@BUILD_AUTO_UPDATE_WEBSITE@"
#define BUILD_AUTO_UPDATE_API "@BUILD_AUTO_UPDATE_API@"
#define BUILD_AUTO_UPDATE_API_PATH "@BUILD_AUTO_UPDATE_API_PATH@"
#define BUILD_AUTO_UPDATE_REPO "@BUILD_AUTO_UPDATE_REPO@"
#define IS_NIGHTLY_BUILD @IS_NIGHTLY_BUILD@
@@ -42,6 +43,7 @@ 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;
constexpr const char g_build_auto_update_api_path[] = BUILD_AUTO_UPDATE_API_PATH;
constexpr const char g_build_auto_update_repo[] = BUILD_AUTO_UPDATE_REPO;
} // namespace Common
+1
View File
@@ -26,6 +26,7 @@ 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_api_path[];
extern const char g_build_auto_update_repo[];
} // namespace Common
+48
View File
@@ -379,4 +379,52 @@ void SetConfiguringGlobal(bool is_global) {
configuring_global = is_global;
}
u16 SpeedLimit() {
switch (SpeedMode(values.current_speed_mode)) {
case SpeedMode::Standard:
return values.speed_limit.GetValue();
case SpeedMode::Turbo:
return values.turbo_speed_limit.GetValue();
case SpeedMode::Slow:
return values.slow_speed_limit.GetValue();
default:
UNIMPLEMENTED();
}
return 100;
}
void SetSpeedMode(const SpeedMode& mode) {
values.current_speed_mode.SetValue(mode);
switch (mode) {
case SpeedMode::Turbo:
case SpeedMode::Slow:
values.use_speed_limit.SetValue(true);
break;
case SpeedMode::Standard:
default:
break;
}
}
void ToggleStandardMode() {
values.use_speed_limit.SetValue(!values.use_speed_limit.GetValue());
SetSpeedMode(SpeedMode::Standard);
}
void ToggleTurboMode() {
if (values.current_speed_mode.GetValue() != SpeedMode::Turbo)
SetSpeedMode(SpeedMode::Turbo);
else
SetSpeedMode(SpeedMode::Standard);
}
void ToggleSlowMode() {
if (values.current_speed_mode.GetValue() != SpeedMode::Slow)
SetSpeedMode(SpeedMode::Slow);
else
SetSpeedMode(SpeedMode::Standard);
}
} // namespace Settings
+52 -14
View File
@@ -204,6 +204,7 @@ struct Values {
true};
SwitchableSetting<bool> use_speed_limit{
linkage, true, "use_speed_limit", Category::Core, Specialization::Paired, true, true};
SwitchableSetting<u16, true> speed_limit{linkage,
100,
0,
@@ -214,6 +215,30 @@ struct Values {
true,
true,
&use_speed_limit};
SwitchableSetting<u16, true> slow_speed_limit{linkage,
50,
0,
9999,
"slow_speed_limit",
Category::Core,
Specialization::Countable | Specialization::Percentage,
true,
true};
SwitchableSetting<u16, true> turbo_speed_limit{linkage,
200,
0,
9999,
"turbo_speed_limit",
Category::Core,
Specialization::Countable | Specialization::Percentage,
true,
true};
// The currently used speed mode.
Setting<SpeedMode> current_speed_mode{linkage, SpeedMode::Standard, "current_speed_mode", Category::Core, Specialization::Default, false, true};
SwitchableSetting<bool> sync_core_speed{linkage, false, "sync_core_speed", Category::Core,
Specialization::Default};
@@ -434,6 +459,16 @@ struct Values {
"accelerate_astc",
Category::RendererAdvanced};
SwitchableSetting<FramePacingMode, true> frame_pacing_mode{linkage,
FramePacingMode::Target_Auto,
FramePacingMode::Target_Auto,
FramePacingMode::Target_120,
"frame_pacing_mode",
Category::RendererAdvanced,
Specialization::Default,
true,
true};
SwitchableSetting<AstcRecompression, true> astc_recompression{linkage,
AstcRecompression::Uncompressed,
"astc_recompression",
@@ -510,7 +545,8 @@ struct Values {
SwitchableSetting<bool> fix_bloom_effects{linkage, false, "fix_bloom_effects",
Category::RendererHacks};
SwitchableSetting<bool> force_smaller_buffers{linkage, false, "force_smaller_buffers",
Category::RendererHacks};
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererHacks};
@@ -532,14 +568,11 @@ struct Values {
Category::RendererHacks,
Specialization::Default};
SwitchableSetting<bool> gpu_unswizzle_enabled{linkage, false, "gpu_unswizzle_enabled",
Category::RendererHacks};
SwitchableSetting<ExtendedDynamicState> dyna_state{linkage,
#if defined (_WIN32)
ExtendedDynamicState::EDS3,
#elif defined (__FreeBSD__)
ExtendedDynamicState::EDS3,
#elif defined (ANDROID)
ExtendedDynamicState::Disabled,
#elif defined (__APPLE__)
#if defined (ANDROID) || defined (__APPLE__)
ExtendedDynamicState::Disabled,
#else
ExtendedDynamicState::EDS2,
@@ -591,11 +624,11 @@ struct Values {
"language_index",
Category::System};
SwitchableSetting<Region, true> region_index{linkage, Region::Usa, "region_index", Category::System};
SwitchableSetting<TimeZone, true> time_zone_index{linkage, TimeZone::Auto,
"time_zone_index", Category::System};
SwitchableSetting<TimeZone, true> time_zone_index{linkage, TimeZone::Auto, "time_zone_index", Category::System};
Setting<u32> serial_battery{linkage, 0, "serial_battery", Category::System};
Setting<u32> serial_unit{linkage, 0, "serial_unit", Category::System};
// Measured in seconds since epoch
SwitchableSetting<bool> custom_rtc_enabled{
linkage, false, "custom_rtc_enabled", Category::System, Specialization::Paired, true, true};
SwitchableSetting<bool> custom_rtc_enabled{linkage, false, "custom_rtc_enabled", Category::System, Specialization::Paired, true, true};
SwitchableSetting<s64> custom_rtc{
linkage, 0, "custom_rtc", Category::System, Specialization::Time,
false, true, &custom_rtc_enabled};
@@ -765,8 +798,6 @@ struct Values {
true};
// Miscellaneous
Setting<std::string> serial_battery{linkage, std::string(), "serial_battery", Category::Miscellaneous};
Setting<std::string> serial_unit{linkage, std::string(), "serial_unit", Category::Miscellaneous};
Setting<std::string> log_filter{linkage, "*:Info", "log_filter", Category::Miscellaneous};
Setting<bool> log_flush_line{linkage, false, "flush_line", Category::Miscellaneous, Specialization::Default, true, true};
Setting<bool> censor_username{linkage, true, "censor_username", Category::Miscellaneous};
@@ -814,6 +845,13 @@ bool IsDockedMode();
float Volume();
// speed limit ops
u16 SpeedLimit();
void SetSpeedMode(const SpeedMode &mode);
void ToggleStandardMode();
void ToggleTurboMode();
void ToggleSlowMode();
std::string GetTimeZoneString(TimeZone time_zone);
void LogSettings();
+2
View File
@@ -129,6 +129,7 @@ ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt
ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16, X32, X64, None);
ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous);
ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_90, Target_120);
ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed);
ENUM(VramUsageMode, Conservative, Aggressive);
ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV);
@@ -156,6 +157,7 @@ ENUM(TemperatureUnits, Celsius, Fahrenheit)
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
ENUM(GameListMode, TreeView, GridView);
ENUM(SpeedMode, Standard, Turbo, Slow);
template <typename Type>
inline std::string_view CanonicalizeEnum(Type id) {
+75
View File
@@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <thread>
#ifdef _MSC_VER
#include <intrin.h>
#endif
#include "common/x64/cpu_detect.h"
#include "common/x64/cpu_wait.h"
#include "common/x64/rdtsc.h"
namespace Common::X64 {
namespace {
// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
// For reference:
// At 1 GHz, 100K cycles is 100us
// At 2 GHz, 100K cycles is 50us
// At 4 GHz, 100K cycles is 25us
constexpr auto PauseCycles = 100'000U;
} // Anonymous namespace
#if defined(_MSC_VER) && !defined(__clang__)
__forceinline static void TPAUSE() {
static constexpr auto RequestC02State = 0U;
_tpause(RequestC02State, FencedRDTSC() + PauseCycles);
}
__forceinline static void MWAITX() {
static constexpr auto EnableWaitTimeFlag = 1U << 1;
static constexpr auto RequestC1State = 0U;
// monitor_var should be aligned to a cache line.
alignas(64) u64 monitor_var{};
_mm_monitorx(&monitor_var, 0, 0);
_mm_mwaitx(EnableWaitTimeFlag, RequestC1State, PauseCycles);
}
#else
static void TPAUSE() {
static constexpr auto RequestC02State = 0U;
const auto tsc = FencedRDTSC() + PauseCycles;
const auto eax = static_cast<u32>(tsc & 0xFFFFFFFF);
const auto edx = static_cast<u32>(tsc >> 32);
asm volatile("tpause %0" : : "r"(RequestC02State), "d"(edx), "a"(eax));
}
static void MWAITX() {
static constexpr auto EnableWaitTimeFlag = 1U << 1;
static constexpr auto RequestC1State = 0U;
// monitor_var should be aligned to a cache line.
alignas(64) u64 monitor_var{};
asm volatile("monitorx" : : "a"(&monitor_var), "c"(0), "d"(0));
asm volatile("mwaitx" : : "a"(RequestC1State), "b"(PauseCycles), "c"(EnableWaitTimeFlag));
}
#endif
void MicroSleep() {
static const bool has_waitpkg = GetCPUCaps().waitpkg;
static const bool has_monitorx = GetCPUCaps().monitorx;
if (has_waitpkg) {
TPAUSE();
} else if (has_monitorx) {
MWAITX();
} else {
std::this_thread::yield();
}
}
} // namespace Common::X64
+10
View File
@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
namespace Common::X64 {
void MicroSleep();
} // namespace Common::X64
+8 -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: Copyright 2020 yuzu Emulator Project
@@ -38,10 +38,16 @@ u64 DynarmicCallbacks32::MemoryRead64(u32 vaddr) {
CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read);
return m_memory.Read64(vaddr);
}
std::optional<u32> DynarmicCallbacks32::MemoryReadCode(u32 vaddr) {
if (!m_memory.IsValidVirtualAddressRange(vaddr, sizeof(u32)))
return std::nullopt;
return m_memory.Read32(vaddr);
auto const aligned_vaddr = vaddr & ~Core::Memory::YUZU_PAGEMASK;
if (last_code_addr != aligned_vaddr) {
m_memory.ReadBlock(aligned_vaddr, &cached_code_page, sizeof(cached_code_page));
last_code_addr = aligned_vaddr;
}
return cached_code_page.inst[(vaddr & Core::Memory::YUZU_PAGEMASK) / sizeof(u32)];
}
void DynarmicCallbacks32::MemoryWrite8(u32 vaddr, u8 value) {
+5 -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 2020 yuzu Emulator Project
@@ -7,6 +7,7 @@
#pragma once
#include <dynarmic/interface/A32/a32.h>
#include <dynarmic/interface/code_page.h>
#include "core/arm/arm_interface.h"
#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
@@ -49,6 +50,9 @@ public:
u64 GetTicksRemaining() override;
bool CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type);
void ReturnException(u32 pc, Dynarmic::HaltReason hr);
//
Dynarmic::CodePage cached_code_page;
u64 last_code_addr = 0;
ArmDynarmic32& m_parent;
Core::Memory::Memory& m_memory;
Kernel::KProcess* m_process{};
+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
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -41,10 +41,17 @@ Dynarmic::A64::Vector DynarmicCallbacks64::MemoryRead128(u64 vaddr) {
CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Read);
return {m_memory.Read64(vaddr), m_memory.Read64(vaddr + 8)};
}
std::optional<u32> DynarmicCallbacks64::MemoryReadCode(u64 vaddr) {
if (!m_memory.IsValidVirtualAddressRange(vaddr, sizeof(u32)))
return std::nullopt;
return m_memory.Read32(vaddr);
// return m_memory.Read32(vaddr);
auto const aligned_vaddr = vaddr & ~Core::Memory::YUZU_PAGEMASK;
if (last_code_addr != aligned_vaddr) {
m_memory.ReadBlock(aligned_vaddr, &cached_code_page, sizeof(cached_code_page));
last_code_addr = aligned_vaddr;
}
return cached_code_page.inst[(vaddr & Core::Memory::YUZU_PAGEMASK) / sizeof(u32)];
}
void DynarmicCallbacks64::MemoryWrite8(u64 vaddr, u8 value) {
+3
View File
@@ -11,6 +11,7 @@
#include <ankerl/unordered_dense.h>
#include <dynarmic/interface/A64/a64.h>
#include <dynarmic/interface/code_page.h>
#include "common/common_types.h"
#include "common/hash.h"
#include "core/arm/arm_interface.h"
@@ -61,6 +62,8 @@ public:
bool CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type);
void ReturnException(u64 pc, Dynarmic::HaltReason hr);
Dynarmic::CodePage cached_code_page;
u64 last_code_addr = 0;
ArmDynarmic64& m_parent;
Core::Memory::Memory& m_memory;
u64 m_tpidrro_el0{};
+27 -2
View File
@@ -13,6 +13,10 @@
#include "common/windows/timer_resolution.h"
#endif
#ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_wait.h"
#endif
#include "common/settings.h"
#include "core/core_timing.h"
#include "core/hardware_properties.h"
@@ -201,7 +205,7 @@ u64 CoreTiming::GetClockTicks() const {
if (Settings::values.sync_core_speed.GetValue()) {
const auto ticks = double(fres);
const auto speed_limit = double(Settings::values.speed_limit.GetValue())*0.01;
const auto speed_limit = double(Settings::SpeedLimit())*0.01;
return u64(ticks/speed_limit);
} else {
return fres;
@@ -283,7 +287,28 @@ void CoreTiming::ThreadLoop() {
if (next_time) {
// There are more events left in the queue, wait until the next event.
auto wait_time = *next_time - GetGlobalTimeNs().count();
event.WaitFor(std::chrono::nanoseconds(wait_time));
if (wait_time > 0) {
#ifdef _WIN32
while (!paused && !event.IsSet() && wait_time > 0) {
wait_time = *next_time - GetGlobalTimeNs().count();
if (wait_time >= timer_resolution_ns) {
Common::Windows::SleepForOneTick();
} else {
#ifdef ARCHITECTURE_x86_64
Common::X64::MicroSleep();
#else
std::this_thread::yield();
#endif
}
}
if (event.IsSet()) {
event.Reset();
}
#else
event.WaitFor(std::chrono::nanoseconds(wait_time));
#endif
}
} else {
// Queue is empty, wait until another event is scheduled and signals us to
// continue.
+15 -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: Copyright 2022 yuzu Emulator Project
@@ -10,6 +10,7 @@
#include <boost/icl/interval_set.hpp>
#include <dynarmic/interface/A64/a64.h>
#include <dynarmic/interface/A64/config.h>
#include <dynarmic/interface/code_page.h>
#include "common/alignment.h"
#include "common/common_funcs.h"
@@ -46,6 +47,15 @@ public:
: memory{memory_}, local_memory{local_memory_},
mapped_ranges{mapped_ranges_}, parent{parent_} {}
std::optional<std::uint32_t> MemoryReadCode(VAddr vaddr) override {
static_assert(Core::Memory::YUZU_PAGESIZE == Dynarmic::CODE_PAGE_SIZE);
auto const aligned_vaddr = vaddr & ~Core::Memory::YUZU_PAGEMASK;
if (last_code_addr != aligned_vaddr) {
cached_code_page = ReadMemory<Dynarmic::CodePage>(aligned_vaddr);
last_code_addr = aligned_vaddr;
}
return cached_code_page.inst[(vaddr & Core::Memory::YUZU_PAGEMASK) / sizeof(u32)];
}
u8 MemoryRead8(u64 vaddr) override {
return ReadMemory<u8>(vaddr);
}
@@ -116,8 +126,7 @@ public:
return 0;
}
template <class T>
T ReadMemory(u64 vaddr) {
template<typename T> T ReadMemory(u64 vaddr) {
T ret{};
if (boost::icl::contains(mapped_ranges, vaddr)) {
memory.ReadBlock(vaddr, &ret, sizeof(T));
@@ -146,6 +155,9 @@ private:
std::vector<u8>& local_memory;
IntervalSet& mapped_ranges;
JITContextImpl& parent;
Dynarmic::CodePage cached_code_page;
u64 last_code_addr = 0;
};
class JITContextImpl {
@@ -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
@@ -980,25 +980,82 @@ Result ISystemSettingsServer::SetPrimaryAlbumStorage(PrimaryAlbumStorage primary
R_SUCCEED();
}
static void Fill3DS_CRC(u32 d, char* data) {
std::array<u8, 10> digits = {
u8((d / 1000000000) % 100),
u8((d / 100000000) % 10),
u8((d / 10000000) % 10),
u8((d / 1000000) % 10),
u8((d / 100000) % 10),
u8((d / 10000) % 10),
u8((d / 1000) % 10),
u8((d / 100) % 10),
u8((d / 10) % 10),
u8(d % 10),
};
// Normalize to retail values
std::array<u8, 4> retail_digits = { 1, 4, 5, 7 };
digits[0] = retail_digits[(d % 10) % 4];
digits[1] = 0;
//
for (size_t i = 0; i < sizeof(digits); ++i)
data[i] = char(digits[i] + '0');
u8 sum_odd = 0, sum_even = 0;
for (size_t i = 0; i < sizeof(digits); i += 2) {
sum_odd += digits[i + 0];
sum_even += digits[i + 1];
}
u8 sum_digit = u8(((sum_even * 3) + sum_odd) % 10);
if (sum_digit != 0)
sum_digit = 10 - sum_digit;
data[sizeof(digits)] = char(sum_digit + '0');
}
Result ISystemSettingsServer::GetBatteryLot(Out<BatteryLot> out_battery_lot) {
LOG_INFO(Service_SET, "called");
*out_battery_lot = {"YUZU0EMULATOR14022024"};
if (auto const s = ::Settings::values.serial_battery.GetValue(); !s.empty()) {
auto const max_size = out_battery_lot->lot_number.size();
auto const end = s.size() > max_size ? s.begin() + max_size : s.end();
std::copy(s.begin(), end, out_battery_lot->lot_number.begin());
}
*out_battery_lot = []{
u32 d = ::Settings::values.serial_battery.GetValue();
BatteryLot c{};
c.lot_number[0] = 'B';
c.lot_number[1] = 'H';
c.lot_number[2] = 'A';
c.lot_number[3] = 'C';
// TODO: I have no fucking idea what the letters mean
c.lot_number[4] = 'H';
c.lot_number[5] = 'Z';
c.lot_number[6] = 'Z';
c.lot_number[7] = 'A';
c.lot_number[8] = 'D';
c.lot_number[9] = char(((d / 100000) % 26) + 'A');
Fill3DS_CRC(d, c.lot_number.data() + 10);
return c;
}();
R_SUCCEED();
}
Result ISystemSettingsServer::GetSerialNumber(Out<SerialNumber> out_console_serial) {
LOG_INFO(Service_SET, "called");
*out_console_serial = {"YUZ10000000001"};
if (auto const s = ::Settings::values.serial_unit.GetValue(); !s.empty()) {
auto const max_size = out_console_serial->serial_number.size();
auto const end = s.size() > max_size ? s.begin() + max_size : s.end();
std::copy(s.begin(), end, out_console_serial->serial_number.begin());
}
*out_console_serial = []{
u32 d = ::Settings::values.serial_unit.GetValue();
SerialNumber c{};
c.serial_number[0] = 'X';
c.serial_number[1] = 'A';
c.serial_number[2] = [] {
// Adding another setting would be tedious so... let's just reuse region_index :)
switch (::Settings::values.region_index.GetValue()) {
case ::Settings::Region::Japan: return 'J';
case ::Settings::Region::Usa: return 'W';
case ::Settings::Region::Europe: return 'E';
case ::Settings::Region::Australia: return 'M'; //pretend its Malaysia
case ::Settings::Region::China:
case ::Settings::Region::Taiwan: return 'C';
case ::Settings::Region::Korea: return 'K';
default: return 'W';
}
}();
Fill3DS_CRC(d, c.serial_number.data() + 3);
return c;
}();
R_SUCCEED();
}
+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: Copyright 2018 yuzu Emulator Project
@@ -178,7 +178,7 @@ void LoopProcess(Core::System& system) {
auto module = std::make_shared<Module>();
server_manager->RegisterNamedService("csrng", std::make_shared<CSRNG>(system, module));
server_manager->RegisterNamedService("spl", std::make_shared<SPL>(system, module));
server_manager->RegisterNamedService("spl:", std::make_shared<SPL>(system, module));
server_manager->RegisterNamedService("spl:mig", std::make_shared<SPL_MIG>(system, module));
server_manager->RegisterNamedService("spl:fs", std::make_shared<SPL_FS>(system, module));
server_manager->RegisterNamedService("spl:ssl", std::make_shared<SPL_SSL>(system, module));
+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: Copyright 2024 yuzu Emulator Project
@@ -95,7 +95,7 @@ s64 Conductor::GetNextTicks() const {
if (settings.use_speed_limit.GetValue()) {
// Scales the speed based on speed_limit setting on MC. SC is handled by
// SpeedLimiter::DoSpeedLimiting.
speed_scale = 100.f / settings.speed_limit.GetValue();
speed_scale = 100.f / Settings::SpeedLimit();
} else {
// Run at unlocked framerate.
speed_scale = 0.01f;
+2 -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: 2015 Citra Emulator Project
@@ -36,8 +36,7 @@
namespace Core::Memory {
static inline bool AddressSpaceContains(const Common::PageTable& table, const Common::ProcessAddress addr,
const std::size_t size) {
static inline bool AddressSpaceContains(const Common::PageTable& table, const Common::ProcessAddress addr, const std::size_t size) {
const Common::ProcessAddress max_addr = 1ULL << table.GetAddressSpaceBits();
return addr + size >= addr && addr + size <= max_addr;
}
+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: 2017 Citra Emulator Project
@@ -143,7 +143,7 @@ void SpeedLimiter::DoSpeedLimiting(microseconds current_system_time_us) {
auto now = Clock::now();
const double sleep_scale = Settings::values.speed_limit.GetValue() / 100.0;
const double sleep_scale = Settings::SpeedLimit() / 100.0;
// Max lag caused by slow frames. Shouldn't be more than the length of a frame at the current
// speed percent or it will clamp too much and prevent this from properly limiting to that
-2
View File
@@ -15,8 +15,6 @@ add_library(dynarmic STATIC
common/crypto/aes.h
common/crypto/crc32.cpp
common/crypto/crc32.h
common/crypto/sm4.cpp
common/crypto/sm4.h
common/fp/fpcr.h
common/fp/fpsr.h
common/fp/fused.cpp
@@ -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.
@@ -164,10 +164,10 @@ A32AddressSpace::A32AddressSpace(const A32::UserConfig& conf)
EmitPrelude();
}
IR::Block A32AddressSpace::GenerateIR(IR::LocationDescriptor descriptor) const {
IR::Block ir_block = A32::Translate(A32::LocationDescriptor{descriptor}, conf.callbacks, {conf.arch_version, conf.define_unpredictable_behaviour, conf.hook_hint_instructions});
void A32AddressSpace::GenerateIR(IR::Block& ir_block, IR::LocationDescriptor descriptor) const {
ir_block.Reset(descriptor);
A32::Translate(ir_block, A32::LocationDescriptor{descriptor}, conf.callbacks, {conf.arch_version, conf.define_unpredictable_behaviour, conf.hook_hint_instructions});
Optimization::Optimize(ir_block, conf, {});
return ir_block;
}
void A32AddressSpace::InvalidateCacheRanges(const boost::icl::interval_set<u32>& ranges) {
@@ -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) 2022 MerryMage
* SPDX-License-Identifier: 0BSD
@@ -17,7 +20,7 @@ class A32AddressSpace final : public AddressSpace {
public:
explicit A32AddressSpace(const A32::UserConfig& conf);
IR::Block GenerateIR(IR::LocationDescriptor) const override;
void GenerateIR(IR::Block& ir_block, IR::LocationDescriptor) const override;
void InvalidateCacheRanges(const boost::icl::interval_set<u32>& ranges);
@@ -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.
@@ -330,12 +330,11 @@ A64AddressSpace::A64AddressSpace(const A64::UserConfig& conf)
EmitPrelude();
}
IR::Block A64AddressSpace::GenerateIR(IR::LocationDescriptor descriptor) const {
void A64AddressSpace::GenerateIR(IR::Block& ir_block, IR::LocationDescriptor descriptor) const {
ir_block.Reset(descriptor);
const auto get_code = [this](u64 vaddr) { return conf.callbacks->MemoryReadCode(vaddr); };
IR::Block ir_block = A64::Translate(A64::LocationDescriptor{descriptor}, get_code,
{conf.define_unpredictable_behaviour, conf.wall_clock_cntpct});
A64::Translate(ir_block, A64::LocationDescriptor{descriptor}, get_code, {conf.define_unpredictable_behaviour, conf.wall_clock_cntpct});
Optimization::Optimize(ir_block, conf, {});
return ir_block;
}
void A64AddressSpace::InvalidateCacheRanges(const boost::icl::interval_set<u64>& ranges) {
@@ -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) 2022 MerryMage
* SPDX-License-Identifier: 0BSD
@@ -17,7 +20,7 @@ class A64AddressSpace final : public AddressSpace {
public:
explicit A64AddressSpace(const A64::UserConfig& conf);
IR::Block GenerateIR(IR::LocationDescriptor) const override;
void GenerateIR(IR::Block& ir_block, IR::LocationDescriptor) const override;
void InvalidateCacheRanges(const boost::icl::interval_set<u64>& ranges);
@@ -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.
@@ -20,14 +20,17 @@
#include "dynarmic/common/fp/fpcr.h"
#include "dynarmic/common/llvm_disassemble.h"
#include "dynarmic/interface/exclusive_monitor.h"
#include "dynarmic/ir/location_descriptor.h"
namespace Dynarmic::Backend::Arm64 {
AddressSpace::AddressSpace(size_t code_cache_size)
: code_cache_size(code_cache_size)
, mem(code_cache_size)
, code(mem.ptr(), mem.ptr())
, fastmem_manager(exception_handler) {
: ir_block{IR::LocationDescriptor{0}}
, code_cache_size(code_cache_size)
, mem(code_cache_size)
, code(mem.ptr(), mem.ptr())
, fastmem_manager(exception_handler)
{
ASSERT(code_cache_size <= 128 * 1024 * 1024 && "code_cache_size > 128 MiB not currently supported");
exception_handler.Register(mem, code_cache_size);
@@ -67,8 +70,7 @@ CodePtr AddressSpace::GetOrEmit(IR::LocationDescriptor descriptor) {
if (CodePtr block_entry = Get(descriptor)) {
return block_entry;
}
IR::Block ir_block = GenerateIR(descriptor);
GenerateIR(ir_block, descriptor);
const EmittedBlockInfo block_info = Emit(std::move(ir_block));
return block_info.entry_point;
}
@@ -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.
@@ -29,7 +29,7 @@ public:
explicit AddressSpace(size_t code_cache_size);
virtual ~AddressSpace();
virtual IR::Block GenerateIR(IR::LocationDescriptor) const = 0;
virtual void GenerateIR(IR::Block& ir_block, IR::LocationDescriptor) const = 0;
CodePtr Get(IR::LocationDescriptor descriptor);
@@ -68,6 +68,7 @@ protected:
FakeCall FastmemCallback(u64 host_pc);
IR::Block ir_block;
const size_t code_cache_size;
oaknut::CodeBlock mem;
oaknut::CodeGenerator 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.
@@ -26,10 +26,9 @@ A32AddressSpace::A32AddressSpace(const A32::UserConfig& conf)
EmitPrelude();
}
IR::Block A32AddressSpace::GenerateIR(IR::LocationDescriptor descriptor) const {
IR::Block ir_block = A32::Translate(A32::LocationDescriptor{descriptor}, conf.callbacks, {conf.arch_version, conf.define_unpredictable_behaviour, conf.hook_hint_instructions});
void A32AddressSpace::GenerateIR(IR::Block& ir_block, IR::LocationDescriptor descriptor) const {
A32::Translate(ir_block, A32::LocationDescriptor{descriptor}, conf.callbacks, {conf.arch_version, conf.define_unpredictable_behaviour, conf.hook_hint_instructions});
Optimization::Optimize(ir_block, conf, {});
return ir_block;
}
CodePtr A32AddressSpace::Get(IR::LocationDescriptor descriptor) {
@@ -44,7 +43,8 @@ CodePtr A32AddressSpace::GetOrEmit(IR::LocationDescriptor descriptor) {
return block_entry;
}
IR::Block ir_block = GenerateIR(descriptor);
IR::Block ir_block{descriptor};
GenerateIR(ir_block, descriptor);
const EmittedBlockInfo block_info = Emit(std::move(ir_block));
block_infos.insert_or_assign(descriptor.Value(), block_info);
@@ -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) 2024 MerryMage
* SPDX-License-Identifier: 0BSD
@@ -23,7 +26,7 @@ class A32AddressSpace final {
public:
explicit A32AddressSpace(const A32::UserConfig& conf);
IR::Block GenerateIR(IR::LocationDescriptor) const;
void GenerateIR(IR::Block& ir_block, IR::LocationDescriptor) const;
CodePtr Get(IR::LocationDescriptor descriptor);
@@ -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.
@@ -25,6 +25,7 @@
#include "dynarmic/backend/x64/devirtualize.h"
#include "dynarmic/backend/x64/jitstate_info.h"
#include "dynarmic/common/atomic.h"
#include "dynarmic/frontend/A32/a32_location_descriptor.h"
#include "dynarmic/frontend/A32/translate/a32_translate.h"
#include "dynarmic/interface/A32/a32.h"
#include "dynarmic/ir/basic_block.h"
@@ -63,12 +64,13 @@ static Optimization::PolyfillOptions GenPolyfillOptions(const BlockOfCode& code)
}
struct Jit::Impl {
Impl(Jit* jit, A32::UserConfig conf)
: block_of_code(GenRunCodeCallbacks(conf.callbacks, &GetCurrentBlockThunk, this, conf), JitStateInfo{jit_state}, conf.code_cache_size, GenRCP(conf))
, emitter(block_of_code, conf, jit)
, polyfill_options(GenPolyfillOptions(block_of_code))
, conf(std::move(conf))
, jit_interface(jit) {}
Impl(Jit* jit, A32::UserConfig conf) noexcept
: block_of_code(GenRunCodeCallbacks(conf.callbacks, &GetCurrentBlockThunk, this, conf), JitStateInfo{jit_state}, conf.code_cache_size, GenRCP(conf))
, emitter(block_of_code, conf, jit)
, polyfill_options(GenPolyfillOptions(block_of_code))
, conf(std::move(conf))
, jit_interface(jit)
{}
~Impl() = default;
@@ -201,8 +203,7 @@ private:
}
A32EmitX64::BlockDescriptor GetBasicBlock(IR::LocationDescriptor descriptor) {
auto block = emitter.GetBasicBlock(descriptor);
if (block)
if (auto block = emitter.GetBasicBlock(descriptor))
return *block;
constexpr size_t MINIMUM_REMAINING_CODESIZE = 1 * 1024 * 1024;
@@ -212,7 +213,10 @@ private:
}
block_of_code.EnsureMemoryCommitted(MINIMUM_REMAINING_CODESIZE);
IR::Block ir_block = A32::Translate(A32::LocationDescriptor{descriptor}, conf.callbacks, {conf.arch_version, conf.define_unpredictable_behaviour, conf.hook_hint_instructions});
// LocationDescriptor ctor() does important ops (like tflags) do not skip
auto const arch_descriptor = A32::LocationDescriptor{descriptor};
ir_block.Reset(arch_descriptor);
A32::Translate(ir_block, arch_descriptor, conf.callbacks, {conf.arch_version, conf.define_unpredictable_behaviour, conf.hook_hint_instructions});
Optimization::Optimize(ir_block, conf, polyfill_options);
return emitter.Emit(ir_block);
}
@@ -239,13 +243,13 @@ private:
}
}
IR::Block ir_block = {LocationDescriptor(0, PSR(0), FPSCR(0), false)};
A32JitState jit_state;
BlockOfCode block_of_code;
A32EmitX64 emitter;
Optimization::PolyfillOptions polyfill_options;
// Keep it here, you don't wanna mess with the fuckery that's initializer lists
const A32::UserConfig conf;
Jit* jit_interface;
// Requests made during execution to invalidate the cache are queued up here.
@@ -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.
@@ -12,6 +12,7 @@
#include <boost/icl/interval_set.hpp>
#include "dynarmic/common/assert.h"
#include "dynarmic/common/fp/fpcr.h"
#include "dynarmic/common/llvm_disassemble.h"
#include <bit>
#include <mcl/scope_exit.hpp>
@@ -61,10 +62,11 @@ static Optimization::PolyfillOptions GenPolyfillOptions(const BlockOfCode& code)
struct Jit::Impl final {
public:
Impl(Jit* jit, UserConfig conf)
: conf(conf)
, block_of_code(GenRunCodeCallbacks(conf.callbacks, &GetCurrentBlockThunk, this, conf), JitStateInfo{jit_state}, conf.code_cache_size, GenRCP(conf))
, emitter(block_of_code, conf, jit)
, polyfill_options(GenPolyfillOptions(block_of_code)) {
: conf(conf)
, block_of_code(GenRunCodeCallbacks(conf.callbacks, &GetCurrentBlockThunk, this, conf), JitStateInfo{jit_state}, conf.code_cache_size, GenRCP(conf))
, emitter(block_of_code, conf, jit)
, polyfill_options(GenPolyfillOptions(block_of_code))
{
ASSERT(conf.page_table_address_space_bits >= 12 && conf.page_table_address_space_bits <= 64);
}
@@ -254,8 +256,8 @@ private:
return GetBlock(A64::LocationDescriptor{GetCurrentLocation()}.SetSingleStepping(true));
}
CodePtr GetBlock(IR::LocationDescriptor current_location) {
if (auto block = emitter.GetBasicBlock(current_location))
CodePtr GetBlock(IR::LocationDescriptor descriptor) {
if (auto block = emitter.GetBasicBlock(descriptor))
return block->entrypoint;
constexpr size_t MINIMUM_REMAINING_CODESIZE = 1 * 1024 * 1024;
@@ -268,8 +270,10 @@ private:
// JIT Compile
const auto get_code = [this](u64 vaddr) { return conf.callbacks->MemoryReadCode(vaddr); };
IR::Block ir_block = A64::Translate(A64::LocationDescriptor{current_location}, get_code,
{conf.define_unpredictable_behaviour, conf.wall_clock_cntpct});
// LocationDescriptor ctor() does important ops (like tflags) do not skip
auto const arch_descriptor = A64::LocationDescriptor{descriptor};
ir_block.Reset(arch_descriptor);
A64::Translate(ir_block, arch_descriptor, get_code, {conf.define_unpredictable_behaviour, conf.wall_clock_cntpct});
Optimization::Optimize(ir_block, conf, polyfill_options);
return emitter.Emit(ir_block).entrypoint;
}
@@ -296,14 +300,13 @@ private:
}
}
bool is_executing = false;
IR::Block ir_block = {LocationDescriptor(0, FP::FPCR(0), false)};
const UserConfig conf;
A64JitState jit_state;
BlockOfCode block_of_code;
A64EmitX64 emitter;
Optimization::PolyfillOptions polyfill_options;
bool is_executing = false;
bool invalidate_entire_cache = false;
boost::icl::interval_set<u64> invalid_cache_ranges;
std::mutex invalidation_mutex;
@@ -71,11 +71,22 @@ void EmitX64::EmitBreakpoint(EmitContext&, IR::Inst*) {
code.int3();
}
constexpr bool IsWithin2G(uintptr_t ref, uintptr_t target) {
const u64 distance = target - (ref + 5);
return !(distance >= 0x8000'0000ULL && distance <= ~0x8000'0000ULL);
}
void EmitX64::EmitCallHostFunction(EmitContext& ctx, IR::Inst* inst) {
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
ctx.reg_alloc.HostCall(code, nullptr, args[1], args[2], args[3]);
code.mov(rax, args[0].GetImmediateU64());
code.call(rax);
auto target = args[0].GetImmediateU64();
if (IsWithin2G(uintptr_t(code.getCurr()), target)) {
auto const f = std::bit_cast<void(*)(void)>(target);
code.call(f);
} else {
code.mov(rax, target);
code.call(rax);
}
}
void EmitX64::PushRSBHelper(Xbyak::Reg64 loc_desc_reg, Xbyak::Reg64 index_reg, IR::LocationDescriptor target) {
@@ -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.
@@ -57,10 +57,9 @@ void EmitX64::EmitLeastSignificantWord(EmitContext& ctx, IR::Inst* inst) {
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
// TODO: DefineValue directly on Argument
const Xbyak::Reg64 result = ctx.reg_alloc.ScratchGpr(code);
const Xbyak::Reg64 source = ctx.reg_alloc.UseGpr(code, args[0]);
code.mov(result.cvt32(), source.cvt32());
ctx.reg_alloc.DefineValue(code, inst, result);
const Xbyak::Reg64 res = ctx.reg_alloc.UseScratchGpr(code, args[0]);
code.mov(res.cvt32(), res.cvt32());
ctx.reg_alloc.DefineValue(code, inst, res);
}
void EmitX64::EmitMostSignificantWord(EmitContext& ctx, IR::Inst* inst) {
@@ -911,15 +910,15 @@ static Xbyak::Reg8 DoCarry(BlockOfCode& code, RegAlloc& reg_alloc, Argument& car
// AL contains flags (after LAHF + SETO sequence)
static Xbyak::Reg64 DoNZCV(BlockOfCode& code, RegAlloc& reg_alloc, IR::Inst* nzcv_out) {
if (!nzcv_out) {
return Xbyak::Reg64{-1};
if (nzcv_out) {
const Xbyak::Reg64 nzcv = reg_alloc.ScratchGpr(code, HostLoc::RAX);
code.xor_(nzcv.cvt32(), nzcv.cvt32());
return nzcv;
}
const Xbyak::Reg64 nzcv = reg_alloc.ScratchGpr(code, HostLoc::RAX);
code.xor_(nzcv.cvt32(), nzcv.cvt32());
return nzcv;
return Xbyak::Reg64{-1};
}
static void EmitAdd(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, int bitsize) {
static void EmitAdd(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, size_t bitsize) {
const auto carry_inst = inst->GetAssociatedPseudoOperation(IR::Opcode::GetCarryFromOp);
const auto overflow_inst = inst->GetAssociatedPseudoOperation(IR::Opcode::GetOverflowFromOp);
const auto nzcv_inst = inst->GetAssociatedPseudoOperation(IR::Opcode::GetNZCVFromOp);
@@ -930,19 +929,13 @@ static void EmitAdd(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, int bit
// Consider using LEA.
if (!carry_inst && !overflow_inst && !nzcv_inst && carry_in.IsImmediate() && !carry_in.GetImmediateU1()) {
if (args[1].IsImmediate() && args[1].FitsInImmediateS32()) {
const Xbyak::Reg op1 = ctx.reg_alloc.UseGpr(code, args[0]).changeBit(bitsize);
const Xbyak::Reg result = ctx.reg_alloc.ScratchGpr(code).changeBit(bitsize);
code.lea(result, code.ptr[op1 + args[1].GetImmediateS32()]);
Xbyak::Reg const result = ctx.reg_alloc.UseScratchGpr(code, args[0]).changeBit(bitsize);
code.lea(result, code.ptr[result + args[1].GetImmediateS32()]);
ctx.reg_alloc.DefineValue(code, inst, result);
} else {
const Xbyak::Reg op1 = ctx.reg_alloc.UseGpr(code, args[0]).changeBit(bitsize);
const Xbyak::Reg op2 = ctx.reg_alloc.UseGpr(code, args[1]).changeBit(bitsize);
const Xbyak::Reg result = ctx.reg_alloc.ScratchGpr(code).changeBit(bitsize);
code.lea(result, code.ptr[op1 + op2]);
Xbyak::Reg const result = ctx.reg_alloc.UseScratchGpr(code, args[0]).changeBit(bitsize);
Xbyak::Reg const op2 = ctx.reg_alloc.UseGpr(code, args[1]).changeBit(bitsize);
code.lea(result, code.ptr[result + op2]);
ctx.reg_alloc.DefineValue(code, inst, result);
}
return;
@@ -957,8 +950,14 @@ static void EmitAdd(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, int bit
const u32 op_arg = args[1].GetImmediateU32();
if (carry_in.IsImmediate()) {
if (carry_in.GetImmediateU1()) {
code.stc();
code.adc(result, op_arg);
// In range for a valid LEA materialisation
auto const in_range = s32(op_arg) >= -0x7ffffffe && s32(op_arg) <= 0x7ffffffe;
if (in_range && (carry_inst || nzcv_inst || overflow_inst)) {
code.stc();
code.adc(result, op_arg);
} else {
code.lea(result, code.ptr[result + op_arg + 1]);
}
} else {
code.add(result, op_arg);
}
@@ -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.
@@ -8,17 +8,54 @@
#include "dynarmic/backend/x64/block_of_code.h"
#include "dynarmic/backend/x64/emit_x64.h"
#include "dynarmic/common/crypto/sm4.h"
#include "dynarmic/ir/microinstruction.h"
namespace Dynarmic::Backend::X64 {
alignas(64) static constexpr std::array<u8, 256> substitution_box = {
0xD6, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7,
0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05,
0x2B, 0x67, 0x9A, 0x76, 0x2A, 0xBE, 0x04, 0xC3,
0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A,
0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62,
0xE4, 0xB3, 0x1C, 0xA9, 0xC9, 0x08, 0xE8, 0x95,
0x80, 0xDF, 0x94, 0xFA, 0x75, 0x8F, 0x3F, 0xA6,
0x47, 0x07, 0xA7, 0xFC, 0xF3, 0x73, 0x17, 0xBA,
0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0x4F, 0xA8,
0x68, 0x6B, 0x81, 0xB2, 0x71, 0x64, 0xDA, 0x8B,
0xF8, 0xEB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35,
0x1E, 0x24, 0x0E, 0x5E, 0x63, 0x58, 0xD1, 0xA2,
0x25, 0x22, 0x7C, 0x3B, 0x01, 0x21, 0x78, 0x87,
0xD4, 0x00, 0x46, 0x57, 0x9F, 0xD3, 0x27, 0x52,
0x4C, 0x36, 0x02, 0xE7, 0xA0, 0xC4, 0xC8, 0x9E,
0xEA, 0xBF, 0x8A, 0xD2, 0x40, 0xC7, 0x38, 0xB5,
0xA3, 0xF7, 0xF2, 0xCE, 0xF9, 0x61, 0x15, 0xA1,
0xE0, 0xAE, 0x5D, 0xA4, 0x9B, 0x34, 0x1A, 0x55,
0xAD, 0x93, 0x32, 0x30, 0xF5, 0x8C, 0xB1, 0xE3,
0x1D, 0xF6, 0xE2, 0x2E, 0x82, 0x66, 0xCA, 0x60,
0xC0, 0x29, 0x23, 0xAB, 0x0D, 0x53, 0x4E, 0x6F,
0xD5, 0xDB, 0x37, 0x45, 0xDE, 0xFD, 0x8E, 0x2F,
0x03, 0xFF, 0x6A, 0x72, 0x6D, 0x6C, 0x5B, 0x51,
0x8D, 0x1B, 0xAF, 0x92, 0xBB, 0xDD, 0xBC, 0x7F,
0x11, 0xD9, 0x5C, 0x41, 0x1F, 0x10, 0x5A, 0xD8,
0x0A, 0xC1, 0x31, 0x88, 0xA5, 0xCD, 0x7B, 0xBD,
0x2D, 0x74, 0xD0, 0x12, 0xB8, 0xE5, 0xB4, 0xB0,
0x89, 0x69, 0x97, 0x4A, 0x0C, 0x96, 0x77, 0x7E,
0x65, 0xB9, 0xF1, 0x09, 0xC5, 0x6E, 0xC6, 0x84,
0x18, 0xF0, 0x7D, 0xEC, 0x3A, 0xDC, 0x4D, 0x20,
0x79, 0xEE, 0x5F, 0x3E, 0xD7, 0xCB, 0x39, 0x48
};
void EmitX64::EmitSM4AccessSubstitutionBox(EmitContext& ctx, IR::Inst* inst) {
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
ctx.reg_alloc.HostCall(code, inst, args[0]);
code.CallFunction(&Common::Crypto::SM4::AccessSubstitutionBox);
code.movzx(code.ABI_RETURN.cvt32(), code.ABI_RETURN.cvt8());
const Xbyak::Reg64 tmp0 = ctx.reg_alloc.UseScratchGpr(code, args[0]);
const Xbyak::Reg tmp1 = ctx.reg_alloc.ScratchGpr(code);
// return subtitution_box[index * 1];
code.movzx(tmp0.cvt32(), tmp0.cvt8());
code.lea(tmp1, code.ptr[std::addressof(substitution_box)]);
code.movzx(tmp1, code.byte[tmp1 + tmp0 * 1]);
ctx.reg_alloc.DefineValue(code, inst, tmp1);
}
} // namespace Dynarmic::Backend::X64
@@ -927,7 +927,21 @@ void EmitX64::EmitVectorCountLeadingZeros8(EmitContext& ctx, IR::Inst* inst) {
}
void EmitX64::EmitVectorCountLeadingZeros16(EmitContext& ctx, IR::Inst* inst) {
if (code.HasHostFeature(HostFeature::AVX)) {
if (code.HasHostFeature(HostFeature::AVX512_Ortho | HostFeature::AVX512CD)) {
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
auto const tmp0 = ctx.reg_alloc.UseScratchXmm(code, args[0]);
auto const tmp1 = ctx.reg_alloc.ScratchXmm(code);
// 11-latency: unpack 16-bit(128-bit) XMM into 32-bit(256-bit) YMM vector
// then just lzcnt that, pack it back (unsigned) to 16-bit
// then subtract 16-bit cuz of the zext32
/*4*/ code.vpmovzxwd(tmp0.cvt256(), tmp0);
/*4*/ code.vplzcntd(tmp0.cvt256(), tmp0.cvt256());
/*1*/ code.vpxor(tmp1, tmp1, tmp1);
/*1*/ code.vpackusdw(tmp0, tmp0, tmp1);
/*1*/ code.vpaddw(tmp0, tmp0, code.BConst<16>(xword, 65520));
/*4*/ code.vzeroupper();
ctx.reg_alloc.DefineValue(code, inst, tmp0);
} else if (code.HasHostFeature(HostFeature::AVX)) {
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
auto const data = ctx.reg_alloc.UseScratchXmm(code, args[0]);
auto const result = ctx.reg_alloc.ScratchXmm(code);
@@ -1,57 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 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
*/
#include "dynarmic/common/crypto/sm4.h"
#include <array>
#include "dynarmic/common/common_types.h"
namespace Dynarmic::Common::Crypto::SM4 {
using SubstitutionTable = std::array<u8, 256>;
constexpr SubstitutionTable substitution_box{
{0xD6, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7,
0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05,
0x2B, 0x67, 0x9A, 0x76, 0x2A, 0xBE, 0x04, 0xC3,
0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A,
0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62,
0xE4, 0xB3, 0x1C, 0xA9, 0xC9, 0x08, 0xE8, 0x95,
0x80, 0xDF, 0x94, 0xFA, 0x75, 0x8F, 0x3F, 0xA6,
0x47, 0x07, 0xA7, 0xFC, 0xF3, 0x73, 0x17, 0xBA,
0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0x4F, 0xA8,
0x68, 0x6B, 0x81, 0xB2, 0x71, 0x64, 0xDA, 0x8B,
0xF8, 0xEB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35,
0x1E, 0x24, 0x0E, 0x5E, 0x63, 0x58, 0xD1, 0xA2,
0x25, 0x22, 0x7C, 0x3B, 0x01, 0x21, 0x78, 0x87,
0xD4, 0x00, 0x46, 0x57, 0x9F, 0xD3, 0x27, 0x52,
0x4C, 0x36, 0x02, 0xE7, 0xA0, 0xC4, 0xC8, 0x9E,
0xEA, 0xBF, 0x8A, 0xD2, 0x40, 0xC7, 0x38, 0xB5,
0xA3, 0xF7, 0xF2, 0xCE, 0xF9, 0x61, 0x15, 0xA1,
0xE0, 0xAE, 0x5D, 0xA4, 0x9B, 0x34, 0x1A, 0x55,
0xAD, 0x93, 0x32, 0x30, 0xF5, 0x8C, 0xB1, 0xE3,
0x1D, 0xF6, 0xE2, 0x2E, 0x82, 0x66, 0xCA, 0x60,
0xC0, 0x29, 0x23, 0xAB, 0x0D, 0x53, 0x4E, 0x6F,
0xD5, 0xDB, 0x37, 0x45, 0xDE, 0xFD, 0x8E, 0x2F,
0x03, 0xFF, 0x6A, 0x72, 0x6D, 0x6C, 0x5B, 0x51,
0x8D, 0x1B, 0xAF, 0x92, 0xBB, 0xDD, 0xBC, 0x7F,
0x11, 0xD9, 0x5C, 0x41, 0x1F, 0x10, 0x5A, 0xD8,
0x0A, 0xC1, 0x31, 0x88, 0xA5, 0xCD, 0x7B, 0xBD,
0x2D, 0x74, 0xD0, 0x12, 0xB8, 0xE5, 0xB4, 0xB0,
0x89, 0x69, 0x97, 0x4A, 0x0C, 0x96, 0x77, 0x7E,
0x65, 0xB9, 0xF1, 0x09, 0xC5, 0x6E, 0xC6, 0x84,
0x18, 0xF0, 0x7D, 0xEC, 0x3A, 0xDC, 0x4D, 0x20,
0x79, 0xEE, 0x5F, 0x3E, 0xD7, 0xCB, 0x39, 0x48}};
u8 AccessSubstitutionBox(u8 index) {
return substitution_box[index];
}
} // namespace Dynarmic::Common::Crypto::SM4
@@ -1,17 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 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
*/
#pragma once
#include "dynarmic/common/common_types.h"
namespace Dynarmic::Common::Crypto::SM4 {
u8 AccessSubstitutionBox(u8 index);
} // namespace Dynarmic::Common::Crypto::SM4
@@ -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) 2016 MerryMage
* SPDX-License-Identifier: 0BSD
@@ -10,11 +13,11 @@
namespace Dynarmic::A32 {
IR::Block TranslateArm(LocationDescriptor descriptor, TranslateCallbacks* tcb, const TranslationOptions& options);
IR::Block TranslateThumb(LocationDescriptor descriptor, TranslateCallbacks* tcb, const TranslationOptions& options);
void TranslateArm(IR::Block& block, LocationDescriptor descriptor, TranslateCallbacks* tcb, const TranslationOptions& options);
void TranslateThumb(IR::Block& block, LocationDescriptor descriptor, TranslateCallbacks* tcb, const TranslationOptions& options);
IR::Block Translate(LocationDescriptor descriptor, TranslateCallbacks* tcb, const TranslationOptions& options) {
return (descriptor.TFlag() ? TranslateThumb : TranslateArm)(descriptor, tcb, options);
void Translate(IR::Block& block, LocationDescriptor descriptor, TranslateCallbacks* tcb, const TranslationOptions& options) {
return (descriptor.TFlag() ? TranslateThumb : TranslateArm)(block, descriptor, tcb, options);
}
bool TranslateSingleArmInstruction(IR::Block& block, LocationDescriptor descriptor, u32 instruction);
@@ -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.
@@ -41,7 +41,7 @@ struct TranslationOptions {
* @param options Configures how certain instructions are translated.
* @return A translated basic block in the intermediate representation.
*/
IR::Block Translate(LocationDescriptor descriptor, TranslateCallbacks* tcb, const TranslationOptions& options);
void Translate(IR::Block& block, LocationDescriptor descriptor, TranslateCallbacks* tcb, const TranslationOptions& options);
/**
* This function translates a single provided instruction into our intermediate representation.
@@ -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.
@@ -22,12 +22,9 @@
namespace Dynarmic::A32 {
IR::Block TranslateArm(LocationDescriptor descriptor, TranslateCallbacks* tcb, const TranslationOptions& options) {
void TranslateArm(IR::Block& block, LocationDescriptor descriptor, TranslateCallbacks* tcb, const TranslationOptions& options) {
const bool single_step = descriptor.SingleStepping();
IR::Block block{descriptor};
TranslatorVisitor visitor{block, descriptor, options};
bool should_continue = true;
do {
const u32 arm_pc = visitor.ir.current_location.PC();
@@ -76,12 +73,8 @@ IR::Block TranslateArm(LocationDescriptor descriptor, TranslateCallbacks* tcb, c
}
}
}
ASSERT(block.HasTerminal() && "Terminal has not been set");
block.SetEndLocation(visitor.ir.current_location);
return block;
}
bool TranslateSingleArmInstruction(IR::Block& block, LocationDescriptor descriptor, u32 arm_instruction) {
@@ -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.
@@ -24,6 +24,7 @@
#include "dynarmic/frontend/A32/translate/translate_callbacks.h"
#include "dynarmic/frontend/imm.h"
#include "dynarmic/interface/A32/config.h"
#include "dynarmic/ir/basic_block.h"
namespace Dynarmic::A32 {
namespace {
@@ -103,12 +104,9 @@ inline bool MaybeVFPOrASIMDInstruction(u32 thumb_instruction) noexcept {
} // namespace
IR::Block TranslateThumb(LocationDescriptor descriptor, TranslateCallbacks* tcb, const TranslationOptions& options) {
void TranslateThumb(IR::Block& block, LocationDescriptor descriptor, TranslateCallbacks* tcb, const TranslationOptions& options) {
const bool single_step = descriptor.SingleStepping();
IR::Block block{descriptor};
TranslatorVisitor visitor{block, descriptor, options};
bool should_continue = true;
do {
const u32 arm_pc = visitor.ir.current_location.PC();
@@ -175,12 +173,8 @@ IR::Block TranslateThumb(LocationDescriptor descriptor, TranslateCallbacks* tcb,
}
}
}
ASSERT(block.HasTerminal() && "Terminal has not been set");
block.SetEndLocation(visitor.ir.current_location);
return block;
}
bool TranslateSingleThumbInstruction(IR::Block& block, LocationDescriptor descriptor, u32 thumb_instruction) {
@@ -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.
@@ -16,10 +16,8 @@
namespace Dynarmic::A64 {
IR::Block Translate(LocationDescriptor descriptor, MemoryReadCodeFuncType memory_read_code, TranslationOptions options) {
void Translate(IR::Block& block, LocationDescriptor descriptor, MemoryReadCodeFuncType memory_read_code, TranslationOptions options) {
const bool single_step = descriptor.SingleStepping();
IR::Block block{descriptor};
TranslatorVisitor visitor{block, descriptor, std::move(options)};
bool should_continue = true;
@@ -43,12 +41,8 @@ IR::Block Translate(LocationDescriptor descriptor, MemoryReadCodeFuncType memory
if (single_step && should_continue) {
visitor.ir.SetTerm(IR::Term::LinkBlock{*visitor.ir.current_location});
}
ASSERT(block.HasTerminal() && "Terminal has not been set");
block.SetEndLocation(*visitor.ir.current_location);
return block;
}
bool TranslateSingleInstruction(IR::Block& block, LocationDescriptor descriptor, u32 instruction) {
@@ -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.
@@ -47,7 +47,7 @@ struct TranslationOptions {
* @param options Configures how certain instructions are translated.
* @return A translated basic block in the intermediate representation.
*/
IR::Block Translate(LocationDescriptor descriptor, MemoryReadCodeFuncType memory_read_code, TranslationOptions options);
void Translate(IR::Block& block, LocationDescriptor descriptor, MemoryReadCodeFuncType memory_read_code, TranslationOptions options);
/**
* This function translates a single provided instruction into our intermediate representation.
@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <cstddef>
#include <cstdint>
namespace Dynarmic {
/// @brief Smallest valid page (may change for Apple?)
constexpr inline uint64_t CODE_PAGE_SIZE = 4096;
struct CodePage {
uint32_t inst[CODE_PAGE_SIZE / sizeof(uint32_t)];
};
}
+24 -12
View File
@@ -22,13 +22,10 @@
namespace Dynarmic::IR {
Block::Block(const LocationDescriptor& location)
: location{location},
end_location{location},
cond{Cond::AL}
{
}
Block::Block(LocationDescriptor location) noexcept
: location{location}
, end_location{location}
{}
/// Prepends a new instruction to this basic block before the insertion point,
/// handling any allocations necessary to do so.
@@ -60,6 +57,21 @@ Block::iterator Block::PrependNewInst(iterator insertion_point, Opcode opcode, s
return instructions.insert_before(insertion_point, inst);
}
void Block::Reset(LocationDescriptor location_) noexcept {
mcl::intrusive_list<IR::Inst> tmp = {};
instructions.swap(tmp);
inlined_inst.clear();
pooled_inst.clear();
cond_failed.reset();
location = location_;
end_location = location_;
cond = Cond::AL;
terminal = Term::Invalid{};
cond_failed_cycle_count = 0;
cycle_count = 0;
ASSERT(instructions.size() == 0);
}
static std::string TerminalToString(const Terminal& terminal_variant) noexcept {
struct : boost::static_visitor<std::string> {
std::string operator()(const Term::Invalid&) const {
@@ -123,11 +135,11 @@ std::string DumpBlock(const IR::Block& block) noexcept {
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()));
case Type::CoprocInfo: return fmt::format("$coproc{}", arg.GetCoprocInfo()[0]);
case Type::NZCVFlags: return fmt::format("$nzcv");
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());
}
};
+3 -2
View File
@@ -43,7 +43,7 @@ public:
using reverse_iterator = instruction_list_type::reverse_iterator;
using const_reverse_iterator = instruction_list_type::const_reverse_iterator;
explicit Block(const LocationDescriptor& location);
Block(LocationDescriptor location) noexcept;
~Block() = default;
Block(const Block&) = delete;
Block& operator=(const Block&) = delete;
@@ -58,6 +58,7 @@ public:
return PrependNewInst(instructions.end(), opcode, args);
}
iterator PrependNewInst(iterator insertion_point, Opcode op, std::initializer_list<Value> args) noexcept;
void Reset(LocationDescriptor location_) noexcept;
/// Gets a mutable reference to the instruction list for this basic block.
inline instruction_list_type& Instructions() noexcept {
@@ -153,7 +154,7 @@ public:
/// Description of the end location of this block
LocationDescriptor end_location;
/// Conditional to pass in order to execute this block
Cond cond;
Cond cond = Cond::AL;
/// Terminal instruction of this block.
Terminal terminal = Term::Invalid{};
/// Number of cycles this block takes to execute if the conditional fails.
+48 -43
View File
@@ -485,6 +485,16 @@ static void A64CallbackConfigPass(IR::Block& block, const A64::UserConfig& conf)
}
}
// Tiny helper to avoid the need to store based off the opcode
// bit size all over the place within folding functions.
static void ReplaceUsesWith(IR::Inst& inst, bool is_32_bit, u64 value) {
if (is_32_bit) {
inst.ReplaceUsesWith(IR::Value{u32(value)});
} else {
inst.ReplaceUsesWith(IR::Value{value});
}
}
static void A64GetSetElimination(IR::Block& block) {
using Iterator = IR::Block::iterator;
@@ -501,8 +511,8 @@ static void A64GetSetElimination(IR::Block& block) {
struct RegisterInfo {
IR::Value register_value;
TrackingType tracking_type;
bool set_instruction_present = false;
Iterator last_set_instruction;
bool set_instruction_present = false;
};
std::array<RegisterInfo, 31> reg_info;
std::array<RegisterInfo, 32> vec_info;
@@ -514,59 +524,58 @@ static void A64GetSetElimination(IR::Block& block) {
info.last_set_instruction->Invalidate();
block.Instructions().erase(info.last_set_instruction);
}
info.register_value = value;
info.tracking_type = tracking_type;
info.set_instruction_present = true;
info.last_set_instruction = set_inst;
};
const auto do_get = [](RegisterInfo& info, Iterator get_inst, TrackingType tracking_type) {
const auto do_nothing = [&] {
const auto do_get = [&block](RegisterInfo& info, Iterator get_inst, TrackingType tracking_type) {
if (!info.register_value.IsEmpty() && info.tracking_type == tracking_type) {
get_inst->ReplaceUsesWith(info.register_value);
} else if (!info.register_value.IsEmpty()
&& tracking_type == TrackingType::W
&& info.tracking_type == TrackingType::X) {
// A sequence like
// SetX r1 -> GetW r1, is just reading off the lowest 32-bits of the register
if (info.register_value.IsImmediate()) {
ReplaceUsesWith(*get_inst, true, u32(info.register_value.GetImmediateAsU64()));
} else {
A64::IREmitter ir{block};
ir.SetInsertionPointBefore(&*get_inst);
get_inst->ReplaceUsesWith(ir.LeastSignificantWord(IR::U64{info.register_value}));
}
} else {
info = {};
info.register_value = IR::Value(&*get_inst);
info.tracking_type = tracking_type;
};
if (info.register_value.IsEmpty()) {
do_nothing();
return;
}
if (info.tracking_type == tracking_type) {
get_inst->ReplaceUsesWith(info.register_value);
return;
}
do_nothing();
};
for (auto inst = block.instructions.begin(); inst != block.instructions.end(); ++inst) {
auto const opcode = inst->GetOpcode();
switch (opcode) {
case IR::Opcode::A64GetW: {
const size_t index = A64::RegNumber(inst->GetArg(0).GetA64RegRef());
do_get(reg_info.at(index), inst, TrackingType::W);
do_get(reg_info[index], inst, TrackingType::W);
break;
}
case IR::Opcode::A64GetX: {
const size_t index = A64::RegNumber(inst->GetArg(0).GetA64RegRef());
do_get(reg_info.at(index), inst, TrackingType::X);
do_get(reg_info[index], inst, TrackingType::X);
break;
}
case IR::Opcode::A64GetS: {
const size_t index = A64::VecNumber(inst->GetArg(0).GetA64VecRef());
do_get(vec_info.at(index), inst, TrackingType::S);
do_get(vec_info[index], inst, TrackingType::S);
break;
}
case IR::Opcode::A64GetD: {
const size_t index = A64::VecNumber(inst->GetArg(0).GetA64VecRef());
do_get(vec_info.at(index), inst, TrackingType::D);
do_get(vec_info[index], inst, TrackingType::D);
break;
}
case IR::Opcode::A64GetQ: {
const size_t index = A64::VecNumber(inst->GetArg(0).GetA64VecRef());
do_get(vec_info.at(index), inst, TrackingType::Q);
do_get(vec_info[index], inst, TrackingType::Q);
break;
}
case IR::Opcode::A64GetSP: {
@@ -579,27 +588,27 @@ static void A64GetSetElimination(IR::Block& block) {
}
case IR::Opcode::A64SetW: {
const size_t index = A64::RegNumber(inst->GetArg(0).GetA64RegRef());
do_set(reg_info.at(index), inst->GetArg(1), inst, TrackingType::W);
do_set(reg_info[index], inst->GetArg(1), inst, TrackingType::W);
break;
}
case IR::Opcode::A64SetX: {
const size_t index = A64::RegNumber(inst->GetArg(0).GetA64RegRef());
do_set(reg_info.at(index), inst->GetArg(1), inst, TrackingType::X);
do_set(reg_info[index], inst->GetArg(1), inst, TrackingType::X);
break;
}
case IR::Opcode::A64SetS: {
const size_t index = A64::VecNumber(inst->GetArg(0).GetA64VecRef());
do_set(vec_info.at(index), inst->GetArg(1), inst, TrackingType::S);
do_set(vec_info[index], inst->GetArg(1), inst, TrackingType::S);
break;
}
case IR::Opcode::A64SetD: {
const size_t index = A64::VecNumber(inst->GetArg(0).GetA64VecRef());
do_set(vec_info.at(index), inst->GetArg(1), inst, TrackingType::D);
do_set(vec_info[index], inst->GetArg(1), inst, TrackingType::D);
break;
}
case IR::Opcode::A64SetQ: {
const size_t index = A64::VecNumber(inst->GetArg(0).GetA64VecRef());
do_set(vec_info.at(index), inst->GetArg(1), inst, TrackingType::Q);
do_set(vec_info[index], inst->GetArg(1), inst, TrackingType::Q);
break;
}
case IR::Opcode::A64SetSP: {
@@ -668,16 +677,6 @@ static void A64MergeInterpretBlocksPass(IR::Block& block, A64::UserCallbacks* cb
using Op = Dynarmic::IR::Opcode;
// Tiny helper to avoid the need to store based off the opcode
// bit size all over the place within folding functions.
static void ReplaceUsesWith(IR::Inst& inst, bool is_32_bit, u64 value) {
if (is_32_bit) {
inst.ReplaceUsesWith(IR::Value{u32(value)});
} else {
inst.ReplaceUsesWith(IR::Value{value});
}
}
static IR::Value Value(bool is_32_bit, u64 value) {
return is_32_bit ? IR::Value{u32(value)} : IR::Value{value};
}
@@ -1043,6 +1042,11 @@ static void FoldZeroExtendXToLong(IR::Inst& inst) {
static void ConstantPropagation(IR::Block& block) {
for (auto& inst : block.instructions) {
auto const opcode = inst.GetOpcode();
// skip NZCV so we dont end up discarding side effects :)
// TODO(lizzie): hey stupid maybe fix the A64 codegen for folded constants AND
// redirect the mfer properly?!??! just saying :)
if (IR::MayGetNZCVFromOp(opcode) && inst.GetAssociatedPseudoOperation(IR::Opcode::GetNZCVFromOp))
continue;
switch (opcode) {
case Op::LeastSignificantWord:
FoldLeastSignificantWord(inst);
@@ -1473,11 +1477,11 @@ static void VerificationPass(const IR::Block& block) {
void Optimize(IR::Block& block, const A32::UserConfig& conf, const Optimization::PolyfillOptions& polyfill_options) {
Optimization::PolyfillPass(block, polyfill_options);
Optimization::NamingPass(block);
if (conf.HasOptimization(OptimizationFlag::GetSetElimination)) [[likely]] {
if (conf.HasOptimization(OptimizationFlag::GetSetElimination)) {
Optimization::A32GetSetElimination(block, {.convert_nzc_to_nz = true});
Optimization::DeadCodeElimination(block);
}
if (conf.HasOptimization(OptimizationFlag::ConstProp)) [[likely]] {
if (conf.HasOptimization(OptimizationFlag::ConstProp)) {
Optimization::ConstantMemoryReads(block, conf.callbacks);
Optimization::ConstantPropagation(block);
Optimization::DeadCodeElimination(block);
@@ -1492,17 +1496,18 @@ void Optimize(IR::Block& block, const A64::UserConfig& conf, const Optimization:
Optimization::PolyfillPass(block, polyfill_options);
Optimization::A64CallbackConfigPass(block, conf);
Optimization::NamingPass(block);
if (conf.HasOptimization(OptimizationFlag::GetSetElimination) && !conf.check_halt_on_memory_access) [[likely]] {
if (conf.HasOptimization(OptimizationFlag::GetSetElimination) && !conf.check_halt_on_memory_access) {
Optimization::A64GetSetElimination(block);
Optimization::DeadCodeElimination(block);
}
if (conf.HasOptimization(OptimizationFlag::ConstProp)) [[likely]] {
if (conf.HasOptimization(OptimizationFlag::ConstProp)) {
Optimization::ConstantPropagation(block);
Optimization::DeadCodeElimination(block);
}
if (conf.HasOptimization(OptimizationFlag::MiscIROpt)) [[likely]] {
if (conf.HasOptimization(OptimizationFlag::MiscIROpt)) {
Optimization::A64MergeInterpretBlocksPass(block, conf.callbacks);
}
Optimization::IdentityRemovalPass(block);
if (!conf.HasOptimization(OptimizationFlag::DisableVerification)) {
Optimization::VerificationPass(block);
}
+1 -1
View File
@@ -24,7 +24,7 @@ if (ENABLE_UPDATE_CHECKER)
update_checker.h)
if (ENABLE_OPENSSL)
target_compile_definitions(frontend_common PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
target_compile_definitions(frontend_common PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(frontend_common PRIVATE OpenSSL::SSL OpenSSL::Crypto)
endif()
+13 -5
View File
@@ -11,9 +11,8 @@ namespace FrontendCommon {
void GenerateSettings() {
static std::random_device rd;
// Web Token //
auto &token_setting = Settings::values.eden_token;
if (token_setting.GetValue().empty()) {
// Web Token
if (Settings::values.eden_token.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);
@@ -23,9 +22,18 @@ void GenerateSettings() {
size_t idx = token_dist(rd);
result += token_set[idx];
}
token_setting.SetValue(result);
Settings::values.eden_token.SetValue(result);
}
// Randomly generated number because, well, we fill the rest automagically ;)
// Other serial parts are filled by Region_Index
std::random_device device;
std::mt19937 gen(device());
std::uniform_int_distribution<u32> distribution(1, (std::numeric_limits<u32>::max)());
if (Settings::values.serial_unit.GetValue() == 0)
Settings::values.serial_unit.SetValue(distribution(gen));
if (Settings::values.serial_battery.GetValue() == 0)
Settings::values.serial_battery.SetValue(distribution(gen));
}
}
+4 -4
View File
@@ -82,8 +82,8 @@ std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::stri
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});
auto update_check_path = fmt::format("{}{}", std::string{Common::g_build_auto_update_api_path},
std::string{Common::g_build_auto_update_repo});
try {
if (include_prereleases) { // This can return either a prerelease or a stable release,
// whichever is more recent.
@@ -124,14 +124,14 @@ std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease(bool includ
return Update{latest_tag, latest_name};
}
} catch (nlohmann::detail::out_of_range &) {
} catch (nlohmann::detail::out_of_range&) {
LOG_ERROR(Frontend,
"Parsing JSON response from {}{} failed during update check: "
"nlohmann::detail::out_of_range",
update_check_url,
update_check_path);
return {};
} catch (nlohmann::detail::type_error &) {
} catch (nlohmann::detail::type_error&) {
LOG_ERROR(Frontend,
"Parsing JSON response from {}{} failed during update check: "
"nlohmann::detail::type_error",
+29 -2
View File
@@ -69,10 +69,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
Settings,
memory_layout_mode,
tr("Memory Layout"),
tr("Increases the amount of emulated RAM from 4GB of the board to the "
"devkit 8/6GB.\nDoesn't affect performance/stability but may allow HD texture "
tr("Increases the amount of emulated RAM.\nDoesn't affect performance/stability but may allow HD texture "
"mods to load."));
INSERT(Settings, use_speed_limit, QString(), QString());
INSERT(Settings, current_speed_mode, QString(), QString());
INSERT(Settings,
speed_limit,
tr("Limit Speed Percent"),
@@ -80,6 +80,14 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
"faster or not.\n200% for a 30 FPS game is 60 FPS, and for a "
"60 FPS game it will be 120 FPS.\nDisabling it means unlocking the framerate to the "
"maximum your PC can reach."));
INSERT(Settings, turbo_speed_limit, tr("Turbo Speed"),
tr("When the Turbo Speed hotkey is pressed, the speed will be limited to this "
"percentage."));
INSERT(Settings, slow_speed_limit, tr("Slow Speed"),
tr("When the Slow Speed hotkey is pressed, the speed will be limited to this "
"percentage."));
INSERT(Settings,
sync_core_speed,
tr("Synchronize Core Speed"),
@@ -225,6 +233,8 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
"intermediate format: RGBA8.\n"
"BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format,\n"
" saving VRAM but degrading image quality."));
INSERT(Settings, frame_pacing_mode, tr("Frame Pacing Mode (Vulkan only)"),
tr("Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent."));
INSERT(Settings,
vram_usage_mode,
tr("VRAM Usage Mode:"),
@@ -284,6 +294,11 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
tr("Fast GPU Time"),
tr("Overclocks the emulated GPU to increase dynamic resolution and render "
"distance.\nUse 256 for maximal performance and 512 for maximal graphics fidelity."));
INSERT(Settings,
gpu_unswizzle_enabled,
tr("GPU Unswizzle"),
tr("Accelerates BCn 3D texture decoding using GPU compute.\n"
"Disable if experiencing crashes or graphical glitches."));
INSERT(Settings,
gpu_unswizzle_texture_size,
tr("GPU Unswizzle Max Texture Size"),
@@ -325,6 +340,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
tr("Sync to framerate of video playback"),
tr("Run the game at normal speed during video playback, even when the framerate is "
"unlocked."));
INSERT(Settings,
force_smaller_buffers,
tr("Force smaller buffers"),
tr("Forces buffers to be smaller, may cause reads/writes out of bounds for some games but can fix Out-of-memory errors."));
INSERT(Settings,
barrier_feedback_loops,
tr("Barrier feedback loops"),
@@ -497,6 +516,14 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
PAIR(AstcRecompression, Bc1, tr("BC1 (Low quality)")),
PAIR(AstcRecompression, Bc3, tr("BC3 (Medium quality)")),
}});
translations->insert({Settings::EnumMetadata<Settings::FramePacingMode>::Index(),
{
PAIR(FramePacingMode, Target_Auto, tr("Auto")),
PAIR(FramePacingMode, Target_30, tr("30 FPS")),
PAIR(FramePacingMode, Target_60, tr("60 FPS")),
PAIR(FramePacingMode, Target_90, tr("90 FPS")),
PAIR(FramePacingMode, Target_120, tr("120 FPS")),
}});
translations->insert({Settings::EnumMetadata<Settings::VramUsageMode>::Index(),
{
PAIR(VramUsageMode, Conservative, tr("Conservative")),
+37 -31
View File
@@ -219,6 +219,9 @@ struct Values {
QVector<u64> favorited_ids;
QMap<u64, QDir> ryujinx_link_paths;
// perf overlay
Setting<bool> show_perf_overlay{linkage, false, "show_perf_overlay", Category::UiGameList};
// Compatibility List
Setting<bool> show_compat{linkage, true, "show_compat", Category::UiGameList};
@@ -249,37 +252,40 @@ void RestoreWindowState(std::unique_ptr<QtConfig>& qtConfig);
// This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
const std::array<Shortcut, 30> default_hotkeys{{
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+M"), std::string("Home+Dpad_Right"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("-"), std::string("Home+Dpad_Down"), Qt::ApplicationShortcut, true}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("="), std::string("Home+Dpad_Up"), Qt::ApplicationShortcut, true}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+P"), std::string("Screenshot"), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F8"), std::string("Home+L"), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F10"), std::string("Home+X"), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Mode")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F9"), std::string("Home+R"), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Configure")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+,"), std::string(""), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Configure Current Game")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+."), std::string(""), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F4"), std::string("Home+Plus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Esc"), std::string(""), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Eden")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+Q"), std::string("Home+Minus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F11"), std::string("Home+B"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+O"), std::string(""), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F2"), std::string("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Browse Public Game Lobby")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+B"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Create Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+N"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Direct Connect to Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+C"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Leave Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+L"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Multiplayer Show Current Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+R"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F6"), std::string("R+Plus+Minus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F5"), std::string("L+Plus+Minus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F7"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F6"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F5"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F"), std::string(""), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+U"), std::string("Home+Y"), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F9"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Renderdoc Capture")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string(""), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+S"), std::string(""), Qt::WindowShortcut, false}},
const std::array<Shortcut, 33> default_hotkeys{{
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+M"), std::string("Home+Dpad_Right"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("-"), std::string("Home+Dpad_Down"), Qt::ApplicationShortcut, true}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("="), std::string("Home+Dpad_Up"), Qt::ApplicationShortcut, true}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+P"), std::string("Screenshot"), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F8"), std::string("Home+L"), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F10"), std::string("Home+X"), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Mode")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F9"), std::string("Home+R"), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Configure")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+,"), std::string(""), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Configure Current Game")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+."), std::string(""), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F4"), std::string("Home+Plus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Esc"), std::string(""), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Eden")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+Q"), std::string("Home+Minus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F11"), std::string("Home+B"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+O"), std::string(""), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F2"), std::string("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Browse Public Game Lobby")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+B"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Create Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+N"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Direct Connect to Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+C"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Leave Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+L"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Show Current Room")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+R"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F6"), std::string("R+Plus+Minus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F5"), std::string("L+Plus+Minus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F7"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F6"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F5"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F"), std::string(""), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+U"), std::string("Home+Y"), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Turbo Speed")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+Z"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Slow Speed")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+X"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F9"), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Renderdoc Capture")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string(""), std::string(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+S"), std::string(""), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Performance Overlay")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+V"), std::string(""), Qt::WindowShortcut, false}},
}};
// clang-format on
@@ -304,17 +304,9 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
Optimization::GlobalMemoryToStorageBufferPass(program, host_info);
Optimization::TexturePass(env, program, host_info);
bool should_rescale =
#ifdef __ANDROID__
Settings::values.resolution_info.active;
#else
true;
#endif
if (should_rescale) {
if (Settings::values.resolution_info.active) {
Optimization::RescalingPass(program);
}
Optimization::DeadCodeEliminationPass(program);
if (Settings::values.renderer_debug) {
Optimization::VerificationPass(program);
@@ -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
@@ -63,10 +66,11 @@ void VisitMark(IR::Block& block, IR::Inst& inst) {
}
if (must_patch_outside) {
const auto it{IR::Block::InstructionList::s_iterator_to(inst)};
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
const IR::F32 new_inst{&*block.PrependNewInst(it, inst)};
IR::IREmitter ir{block, it};
IR::Inst* const new_inst{&*block.PrependNewInst(it, inst)};
const IR::F32 new_bitcast{ir.ConvertUToF(32, 32, IR::Value{new_inst})};
const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())};
const IR::Value converted{ir.FPMul(new_inst, up_factor)};
const IR::Value converted{ir.FPMul(new_bitcast, up_factor)};
inst.ReplaceUsesWith(converted);
}
break;
@@ -15,6 +15,7 @@
#include <vector>
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "common/bit_util.h"
#include "common/settings.h"
#include "common/common_types.h"
#include "video_core/engines/draw_manager.h"
#include "video_core/host1x/gpu_device_memory_manager.h"
@@ -543,7 +544,9 @@ private:
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = SamplesQueryBank::QUERY_SIZE * (1ULL << log_2),
.size = ::Settings::values.force_smaller_buffers.GetValue()
? (SamplesQueryBank::QUERY_SIZE * (1ULL << log_2))
: (SamplesQueryBank::QUERY_SIZE * num_needed),
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+30 -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
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -16,6 +16,7 @@
#include "common/alignment.h"
#include "common/common_types.h"
#include "common/settings.h"
#include "common/polyfill_thread.h"
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -111,13 +112,31 @@ public:
return master_semaphore->IsFree(tick);
}
/// Waits for the given tick to trigger on the GPU.
void Wait(u64 tick) {
if (tick >= master_semaphore->CurrentTick()) {
// Make sure we are not waiting for the current tick without signalling
Flush();
/// Waits for the given GPU tick, optionally pacing frames.
void Wait(u64 tick, double target_fps = 0.0) {
if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) {
const auto now = std::chrono::steady_clock::now();
if (start_time == std::chrono::steady_clock::time_point{} || current_target_fps != target_fps) {
start_time = now;
frame_counter = 0;
current_target_fps = target_fps;
}
frame_counter++;
std::chrono::duration<double> frame_interval(1.0 / current_target_fps);
auto target_time = start_time + frame_interval * frame_counter;
if (target_time > now) {
std::this_thread::sleep_until(target_time);
} else {
start_time = now;
frame_counter = 0;
}
}
if (tick > 0) {
if (tick >= master_semaphore->CurrentTick()) {
Flush();
}
master_semaphore->Wait(tick);
}
master_semaphore->Wait(tick);
}
/// Returns the master timeline semaphore.
@@ -261,6 +280,10 @@ private:
std::mutex queue_mutex;
std::condition_variable_any event_cv;
std::jthread worker_thread;
std::chrono::steady_clock::time_point start_time{};
u64 frame_counter{};
double current_target_fps{};
};
} // namespace Vulkan
@@ -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
@@ -14,6 +14,7 @@
#include "common/assert.h"
#include "common/bit_util.h"
#include "common/common_types.h"
#include "common/settings.h"
#include "common/literals.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -186,7 +187,7 @@ StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = 1ULL << log2,
.size = ::Settings::values.force_smaller_buffers.GetValue() ? size : (1ULL << log2),
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
@@ -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 2019 yuzu Emulator Project
@@ -194,7 +194,24 @@ bool Swapchain::AcquireNextImage() {
break;
}
scheduler.Wait(resource_ticks[image_index]);
switch (Settings::values.frame_pacing_mode.GetValue()) {
case Settings::FramePacingMode::Target_Auto:
scheduler.Wait(resource_ticks[image_index]);
break;
case Settings::FramePacingMode::Target_30:
scheduler.Wait(resource_ticks[image_index], 30.0);
break;
case Settings::FramePacingMode::Target_60:
scheduler.Wait(resource_ticks[image_index], 60.0);
break;
case Settings::FramePacingMode::Target_90:
scheduler.Wait(resource_ticks[image_index], 90.0);
break;
case Settings::FramePacingMode::Target_120:
scheduler.Wait(resource_ticks[image_index], 120.0);
break;
}
resource_ticks[image_index] = scheduler.CurrentTick();
return is_suboptimal || is_outdated;
@@ -893,8 +893,10 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
}
}
bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
staging_buffer_pool, compute_pass_descriptor_queue);
if (Settings::values.gpu_unswizzle_enabled.GetValue()) {
bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
staging_buffer_pool, compute_pass_descriptor_queue);
}
// --- Create swizzle table buffer ---
{
@@ -2538,6 +2540,14 @@ void TextureCacheRuntime::AccelerateImageUpload(
return astc_decoder_pass->Assemble(image, map, swizzles);
}
if (!Settings::values.gpu_unswizzle_enabled.GetValue() || !bl3d_unswizzle_pass) {
if (IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D) {
ASSERT_MSG(false, "GPU unswizzle is disabled for BCn 3D texture");
}
ASSERT(false);
return;
}
if (bl3d_unswizzle_pass &&
IsPixelFormatBCn(image.info.format) &&
image.info.type == ImageType::e3D &&
+49 -41
View File
@@ -45,8 +45,8 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
sampler_descriptor.cubemap_anisotropy.Assign(1);
// These values were chosen based on typical peak swizzle data sizes seen in some titles
static constexpr size_t SWIZZLE_DATA_BUFFER_INITIAL_CAPACITY = 8_MiB;
static constexpr size_t UNSWIZZLE_DATA_BUFFER_INITIAL_CAPACITY = 1_MiB;
constexpr size_t SWIZZLE_DATA_BUFFER_INITIAL_CAPACITY = 8_MiB;
constexpr size_t UNSWIZZLE_DATA_BUFFER_INITIAL_CAPACITY = 1_MiB;
swizzle_data_buffer.resize_destructive(SWIZZLE_DATA_BUFFER_INITIAL_CAPACITY);
unswizzle_data_buffer.resize_destructive(UNSWIZZLE_DATA_BUFFER_INITIAL_CAPACITY);
@@ -80,31 +80,39 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
lowmemorydevice = true;
}
switch (Settings::values.gpu_unswizzle_texture_size.GetValue()) {
case Settings::GpuUnswizzleSize::VerySmall: gpu_unswizzle_maxsize = 16_MiB; break;
case Settings::GpuUnswizzleSize::Small: gpu_unswizzle_maxsize = 32_MiB; break;
case Settings::GpuUnswizzleSize::Normal: gpu_unswizzle_maxsize = 128_MiB; break;
case Settings::GpuUnswizzleSize::Large: gpu_unswizzle_maxsize = 256_MiB; break;
case Settings::GpuUnswizzleSize::VeryLarge: gpu_unswizzle_maxsize = 512_MiB; break;
default: gpu_unswizzle_maxsize = 128_MiB; break;
}
const bool gpu_unswizzle_enabled = Settings::values.gpu_unswizzle_enabled.GetValue();
switch (Settings::values.gpu_unswizzle_stream_size.GetValue()) {
case Settings::GpuUnswizzle::VeryLow: swizzle_chunk_size = 4_MiB; break;
case Settings::GpuUnswizzle::Low: swizzle_chunk_size = 8_MiB; break;
case Settings::GpuUnswizzle::Normal: swizzle_chunk_size = 16_MiB; break;
case Settings::GpuUnswizzle::Medium: swizzle_chunk_size = 32_MiB; break;
case Settings::GpuUnswizzle::High: swizzle_chunk_size = 64_MiB; break;
default: swizzle_chunk_size = 16_MiB;
}
if (gpu_unswizzle_enabled) {
switch (Settings::values.gpu_unswizzle_texture_size.GetValue()) {
case Settings::GpuUnswizzleSize::VerySmall: gpu_unswizzle_maxsize = 16_MiB; break;
case Settings::GpuUnswizzleSize::Small: gpu_unswizzle_maxsize = 32_MiB; break;
case Settings::GpuUnswizzleSize::Normal: gpu_unswizzle_maxsize = 128_MiB; break;
case Settings::GpuUnswizzleSize::Large: gpu_unswizzle_maxsize = 256_MiB; break;
case Settings::GpuUnswizzleSize::VeryLarge: gpu_unswizzle_maxsize = 512_MiB; break;
default: gpu_unswizzle_maxsize = 128_MiB; break;
}
switch (Settings::values.gpu_unswizzle_chunk_size.GetValue()) {
case Settings::GpuUnswizzleChunk::VeryLow: swizzle_slices_per_batch = 32; break;
case Settings::GpuUnswizzleChunk::Low: swizzle_slices_per_batch = 64; break;
case Settings::GpuUnswizzleChunk::Normal: swizzle_slices_per_batch = 128; break;
case Settings::GpuUnswizzleChunk::Medium: swizzle_slices_per_batch = 256; break;
case Settings::GpuUnswizzleChunk::High: swizzle_slices_per_batch = 512; break;
default: swizzle_slices_per_batch = 128;
switch (Settings::values.gpu_unswizzle_stream_size.GetValue()) {
case Settings::GpuUnswizzle::VeryLow: swizzle_chunk_size = 4_MiB; break;
case Settings::GpuUnswizzle::Low: swizzle_chunk_size = 8_MiB; break;
case Settings::GpuUnswizzle::Normal: swizzle_chunk_size = 16_MiB; break;
case Settings::GpuUnswizzle::Medium: swizzle_chunk_size = 32_MiB; break;
case Settings::GpuUnswizzle::High: swizzle_chunk_size = 64_MiB; break;
default: swizzle_chunk_size = 16_MiB;
}
switch (Settings::values.gpu_unswizzle_chunk_size.GetValue()) {
case Settings::GpuUnswizzleChunk::VeryLow: swizzle_slices_per_batch = 32; break;
case Settings::GpuUnswizzleChunk::Low: swizzle_slices_per_batch = 64; break;
case Settings::GpuUnswizzleChunk::Normal: swizzle_slices_per_batch = 128; break;
case Settings::GpuUnswizzleChunk::Medium: swizzle_slices_per_batch = 256; break;
case Settings::GpuUnswizzleChunk::High: swizzle_slices_per_batch = 512; break;
default: swizzle_slices_per_batch = 128;
}
} else {
gpu_unswizzle_maxsize = 0;
swizzle_chunk_size = 0;
swizzle_slices_per_batch = 0;
}
}
@@ -1161,7 +1169,11 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
QueueAsyncDecode(image, image_id);
return;
}
if (IsPixelFormatBCn(image.info.format) &&
const bool gpu_unswizzle_enabled = Settings::values.gpu_unswizzle_enabled.GetValue();
if (gpu_unswizzle_enabled &&
IsPixelFormatBCn(image.info.format) &&
image.info.type == ImageType::e3D &&
image.info.resources.levels == 1 &&
image.info.resources.layers == 1 &&
@@ -1391,13 +1403,9 @@ void TextureCache<P>::QueueAsyncDecode(Image& image, ImageId image_id) {
decode->image_id = image_id;
async_decodes.push_back(std::move(decode));
static Common::ScratchBuffer<u8> local_unswizzle_data_buffer;
local_unswizzle_data_buffer.resize_destructive(image.unswizzled_size_bytes);
Tegra::Memory::GpuGuestMemory<u8, Tegra::Memory::GuestMemoryFlags::UnsafeRead> swizzle_data(
*gpu_memory, image.gpu_addr, image.guest_size_bytes, &swizzle_data_buffer);
auto copies = UnswizzleImage(*gpu_memory, image.gpu_addr, image.info, swizzle_data,
local_unswizzle_data_buffer);
std::vector<u8> local_unswizzle_data_buffer(image.unswizzled_size_bytes, 0);
Tegra::Memory::GpuGuestMemory<u8, Tegra::Memory::GuestMemoryFlags::UnsafeRead> swizzle_data(*gpu_memory, image.gpu_addr, image.guest_size_bytes, &swizzle_data_buffer);
auto copies = UnswizzleImage(*gpu_memory, image.gpu_addr, image.info, swizzle_data, local_unswizzle_data_buffer);
const size_t out_size = MapSizeBytes(image);
auto func = [out_size, copies, info = image.info,
@@ -1666,7 +1674,7 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DA
return;
}
join_overlaps_found.insert(overlap_id);
static constexpr bool strict_size = true;
constexpr bool strict_size = true;
const std::optional<OverlapResult> solution = ResolveOverlap(
new_info, gpu_addr, cpu_addr, overlap, strict_size, broken_views, native_bgr);
if (solution) {
@@ -1677,7 +1685,7 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DA
join_copies_to_do.emplace_back(JoinCopy{false, overlap_id});
return;
}
static constexpr auto options = RelaxedOptions::Size | RelaxedOptions::Format;
constexpr auto options = RelaxedOptions::Size | RelaxedOptions::Format;
const ImageBase new_image_base(new_info, gpu_addr, cpu_addr);
if (IsSubresource(new_info, overlap, gpu_addr, options, broken_views, native_bgr)) {
join_left_aliased_ids.push_back(overlap_id);
@@ -1846,7 +1854,7 @@ std::optional<typename TextureCache<P>::BlitImages> TextureCache<P>::GetBlitImag
const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Surface& src,
const Tegra::Engines::Fermi2D::Config& copy) {
static constexpr auto FIND_OPTIONS = RelaxedOptions::Samples;
constexpr auto FIND_OPTIONS = RelaxedOptions::Samples;
const GPUVAddr dst_addr = dst.Address();
const GPUVAddr src_addr = src.Address();
ImageInfo dst_info(dst);
@@ -2028,7 +2036,7 @@ void TextureCache<P>::TrimInactiveSamplers(size_t budget) {
if (channel_state->samplers.empty()) {
return;
}
static constexpr size_t SAMPLER_GC_SLACK = 1024;
constexpr size_t SAMPLER_GC_SLACK = 1024;
auto mark_active = [](auto& set, SamplerId id) {
if (!id || id == CORRUPT_ID || id == NULL_SAMPLER_ID) {
return;
@@ -2137,7 +2145,7 @@ template <class P>
template <typename Func>
void TextureCache<P>::ForEachImageInRegion(DAddr cpu_addr, size_t size, Func&& func) {
using FuncReturn = typename std::invoke_result<Func, ImageId, Image&>::type;
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
boost::container::small_vector<ImageId, 32> images;
boost::container::small_vector<ImageMapId, 32> maps;
ForEachCPUPage(cpu_addr, size, [this, &images, &maps, cpu_addr, size, func](u64 page) {
@@ -2190,7 +2198,7 @@ template <typename Func>
void TextureCache<P>::ForEachImageInRegionGPU(size_t as_id, GPUVAddr gpu_addr, size_t size,
Func&& func) {
using FuncReturn = typename std::invoke_result<Func, ImageId, Image&>::type;
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
boost::container::small_vector<ImageId, 8> images;
auto storage_id = getStorageID(as_id);
if (!storage_id) {
@@ -2239,7 +2247,7 @@ template <typename Func>
void TextureCache<P>::ForEachSparseImageInRegion(size_t as_id, GPUVAddr gpu_addr, size_t size,
Func&& func) {
using FuncReturn = typename std::invoke_result<Func, ImageId, Image&>::type;
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
boost::container::small_vector<ImageId, 8> images;
auto storage_id = getStorageID(as_id);
if (!storage_id) {
@@ -2287,7 +2295,7 @@ template <class P>
template <typename Func>
void TextureCache<P>::ForEachSparseSegment(ImageBase& image, Func&& func) {
using FuncReturn = typename std::invoke_result<Func, GPUVAddr, DAddr, size_t>::type;
static constexpr bool RETURNS_BOOL = std::is_same_v<FuncReturn, bool>;
constexpr bool RETURNS_BOOL = std::is_same_v<FuncReturn, bool>;
const auto segments = gpu_memory->GetSubmappedRange(image.gpu_addr, image.guest_size_bytes);
for (const auto& [gpu_addr, size] : segments) {
std::optional<DAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
@@ -1,9 +1,10 @@
// 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 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstddef>
#include <future>
#include <optional>
#include <span>
@@ -13,6 +14,7 @@
#include "common/dynamic_library.h"
#include "common/logging/log.h"
#include <ranges>
#include <vulkan/vulkan_core.h>
#include "core/frontend/emu_window.h"
#include "video_core/vulkan_common/vulkan_instance.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -80,8 +82,13 @@ namespace {
if (enable_validation && AreExtensionsSupported(dld, *properties, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME}))
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
// VK_EXT_surface_maintenance1 is required for VK_EXT_swapchain_maintenance1
if (window_type != Core::Frontend::WindowSystemType::Headless && AreExtensionsSupported(dld, *properties, std::array{VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME}))
if (window_type != Core::Frontend::WindowSystemType::Headless && AreExtensionsSupported(dld, *properties, std::array{VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME})) {
extensions.push_back(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME);
// Some(which?) drivers dont like being told to load this extension(why?)
// NVIDIA on FreeBSD is totally fine with this through
if (AreExtensionsSupported(dld, *properties, std::array{VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME}))
extensions.push_back(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME);
}
}
return extensions;
}
+3 -1
View File
@@ -242,6 +242,8 @@ add_executable(yuzu
configuration/system/new_user_dialog.h configuration/system/new_user_dialog.cpp configuration/system/new_user_dialog.ui
configuration/system/profile_avatar_dialog.h configuration/system/profile_avatar_dialog.cpp
configuration/addon/mod_select_dialog.h configuration/addon/mod_select_dialog.cpp configuration/addon/mod_select_dialog.ui
render/performance_overlay.h render/performance_overlay.cpp render/performance_overlay.ui
)
set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden")
@@ -399,7 +401,7 @@ endif()
target_link_libraries(yuzu PRIVATE nlohmann_json::nlohmann_json)
target_link_libraries(yuzu PRIVATE common core input_common frontend_common network video_core qt_common)
target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets Qt6::Concurrent)
target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets Qt6::Charts Qt6::Concurrent)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (UNIX AND NOT APPLE)
+1 -5
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: 2016 Citra Emulator Project
@@ -57,10 +57,6 @@ void ConfigureDebug::SetConfiguration() {
#endif
// Immutable after starting
ui->serial_battery_edit->setEnabled(runtime_lock);
ui->serial_battery_edit->setText(QString::fromStdString(Settings::values.serial_battery.GetValue()));
ui->serial_board_edit->setEnabled(runtime_lock);
ui->serial_board_edit->setText(QString::fromStdString(Settings::values.serial_unit.GetValue()));
ui->homebrew_args_edit->setEnabled(runtime_lock);
ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args.GetValue()));
ui->toggle_console->setEnabled(runtime_lock);
@@ -38,9 +38,9 @@ ConfigureFilesystem::ConfigureFilesystem(QWidget* parent)
connect(ui->reset_game_list_cache, &QPushButton::pressed, this,
&ConfigureFilesystem::ResetMetadata);
connect(ui->gamecard_inserted, &QCheckBox::stateChanged, this,
connect(ui->gamecard_inserted, &QCheckBox::STATE_CHANGED, this,
&ConfigureFilesystem::UpdateEnabledControls);
connect(ui->gamecard_current_game, &QCheckBox::stateChanged, this,
connect(ui->gamecard_current_game, &QCheckBox::STATE_CHANGED, this,
&ConfigureFilesystem::UpdateEnabledControls);
}
+9
View File
@@ -146,6 +146,7 @@
<addaction name="action_Enable_Overlay_Applet"/>
<addaction name="action_Show_Filter_Bar"/>
<addaction name="action_Show_Status_Bar"/>
<addaction name="action_Show_Performance_Overlay"/>
<addaction name="separator"/>
<addaction name="menu_Reset_Window_Size"/>
<addaction name="menu_View_Debugging"/>
@@ -611,6 +612,14 @@
<string>Show Game &amp;Name</string>
</property>
</action>
<action name="action_Show_Performance_Overlay">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Show &amp;Performance Overlay</string>
</property>
</action>
</widget>
<resources>
<include location="yuzu.qrc"/>
+67 -12
View File
@@ -3,9 +3,11 @@
// Qt on macOS doesn't define VMA shit
#include <boost/algorithm/string/split.hpp>
#include "common/settings.h"
#include "common/settings_enums.h"
#include "frontend_common/settings_generator.h"
#include "qt_common/qt_string_lookup.h"
#include "render/performance_overlay.h"
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
#undef VMA_IMPLEMENTATION
#endif
@@ -1400,18 +1402,19 @@ void MainWindow::InitializeHotkeys() {
LinkActionShortcut(ui->action_Stop, QStringLiteral("Stop Emulation"));
LinkActionShortcut(ui->action_Show_Filter_Bar, QStringLiteral("Toggle Filter Bar"));
LinkActionShortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar"));
LinkActionShortcut(ui->action_Show_Performance_Overlay, QStringLiteral("Toggle Performance Overlay"));
LinkActionShortcut(ui->action_Fullscreen, QStringLiteral("Fullscreen"));
LinkActionShortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot"));
LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop"), true);
LinkActionShortcut(ui->action_TAS_Record, QStringLiteral("TAS Record"), true);
LinkActionShortcut(ui->action_TAS_Reset, QStringLiteral("TAS Reset"), true);
LinkActionShortcut(ui->action_View_Lobby,
QStringLiteral("Multiplayer Browse Public Game Lobby"));
LinkActionShortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room"));
QStringLiteral("Browse Public Game Lobby"));
LinkActionShortcut(ui->action_Start_Room, QStringLiteral("Create Room"));
LinkActionShortcut(ui->action_Connect_To_Room,
QStringLiteral("Multiplayer Direct Connect to Room"));
LinkActionShortcut(ui->action_Show_Room, QStringLiteral("Multiplayer Show Current Room"));
LinkActionShortcut(ui->action_Leave_Room, QStringLiteral("Multiplayer Leave Room"));
QStringLiteral("Direct Connect to Room"));
LinkActionShortcut(ui->action_Show_Room, QStringLiteral("Show Current Room"));
LinkActionShortcut(ui->action_Leave_Room, QStringLiteral("Leave Room"));
LinkActionShortcut(ui->action_Configure, QStringLiteral("Configure"));
LinkActionShortcut(ui->action_Configure_Current_Game, QStringLiteral("Configure Current Game"));
@@ -1440,9 +1443,25 @@ void MainWindow::InitializeHotkeys() {
connect_shortcut(QStringLiteral("Audio Mute/Unmute"), &MainWindow::OnMute);
connect_shortcut(QStringLiteral("Audio Volume Down"), &MainWindow::OnDecreaseVolume);
connect_shortcut(QStringLiteral("Audio Volume Up"), &MainWindow::OnIncreaseVolume);
connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] {
Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue());
connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [this] {
Settings::ToggleStandardMode();
const bool limited = Settings::values.use_speed_limit.GetValue();
m_fpsSuffix = limited ? QString{} : tr("Unlocked");
});
connect_shortcut(QStringLiteral("Toggle Turbo Speed"), [this] {
Settings::ToggleTurboMode();
const bool turbo = Settings::values.current_speed_mode.GetValue() == Settings::SpeedMode::Turbo;
m_fpsSuffix = turbo ? tr("Turbo") : QString{};
});
connect_shortcut(QStringLiteral("Toggle Slow Speed"), [this] {
Settings::ToggleSlowMode();
const bool slow = Settings::values.current_speed_mode.GetValue() == Settings::SpeedMode::Slow;
m_fpsSuffix = slow ? tr("Slow") : QString{};
});
connect_shortcut(QStringLiteral("Toggle Renderdoc Capture"), [] {
if (Settings::values.enable_renderdoc_hotkey) {
QtCommon::system->GetRenderdocAPI().ToggleCapture();
@@ -1494,6 +1513,9 @@ void MainWindow::RestoreUIState() {
ui->action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar.GetValue());
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
ui->action_Show_Performance_Overlay->setChecked(UISettings::values.show_perf_overlay.GetValue());
if (perf_overlay) perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked());
Debugger::ToggleConsole();
}
@@ -1613,6 +1635,7 @@ void MainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Single_Window_Mode, &MainWindow::ToggleWindowMode);
connect_menu(ui->action_Show_Filter_Bar, &MainWindow::OnToggleFilterBar);
connect_menu(ui->action_Show_Status_Bar, &MainWindow::OnToggleStatusBar);
connect_menu(ui->action_Show_Performance_Overlay, &MainWindow::OnTogglePerfOverlay);
connect_menu(ui->action_Reset_Window_Size_720, &MainWindow::ResetWindowSize720);
connect_menu(ui->action_Reset_Window_Size_900, &MainWindow::ResetWindowSize900);
@@ -2119,7 +2142,7 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa
game_list->hide();
game_list_placeholder->hide();
}
status_bar_update_timer.start(500);
status_bar_update_timer.start(250);
renderer_status_button->setDisabled(true);
refresh_button->setDisabled(true);
@@ -2193,6 +2216,10 @@ bool MainWindow::OnShutdownBegin() {
return false;
}
perf_overlay->hide();
perf_overlay->deleteLater();
perf_overlay = nullptr;
QtCommon::system->SetShuttingDown(true);
discord_rpc->Pause();
@@ -3194,6 +3221,13 @@ bool MainWindow::ConfirmShutdownGame() {
void MainWindow::OnLoadComplete() {
loading_screen->OnLoadComplete();
perf_overlay = new PerformanceOverlay(this);
perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked());
connect(perf_overlay, &PerformanceOverlay::closed, perf_overlay, [this]() {
ui->action_Show_Performance_Overlay->setChecked(false);
});
}
void MainWindow::OnExecuteProgram(std::size_t program_index) {
@@ -4015,6 +4049,12 @@ void MainWindow::OnToggleStatusBar() {
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
}
void MainWindow::OnTogglePerfOverlay() {
if (perf_overlay) {
perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked());
}
}
void MainWindow::OnGameListRefresh() {
// Resets metadata cache and reloads
QtCommon::Game::ResetMetadata(false);
@@ -4239,6 +4279,8 @@ void MainWindow::UpdateStatusBar() {
auto& shader_notify = QtCommon::system->GPU().ShaderNotify();
const int shaders_building = shader_notify.ShadersBuilding();
emit statsUpdated(results, shader_notify);
if (shaders_building > 0) {
shader_building_label->setText(tr("Building: %n shader(s)", "", shaders_building));
shader_building_label->setVisible(true);
@@ -4254,14 +4296,15 @@ void MainWindow::UpdateStatusBar() {
if (Settings::values.use_speed_limit.GetValue()) {
emu_speed_label->setText(tr("Speed: %1% / %2%")
.arg(results.emulation_speed * 100.0, 0, 'f', 0)
.arg(Settings::values.speed_limit.GetValue()));
.arg(Settings::SpeedLimit()));
} else {
emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
}
game_fps_label->setText(
tr("Game: %1 FPS").arg(std::round(results.average_game_fps), 0, 'f', 0) +
tr(Settings::values.use_speed_limit ? "" : " (Unlocked)"));
QString fpsText = tr("Game: %1 FPS").arg(std::round(results.average_game_fps), 0, 'f', 0);
if (!m_fpsSuffix.isEmpty()) fpsText = fpsText % QStringLiteral(" (%1)").arg(m_fpsSuffix);
game_fps_label->setText(fpsText);
emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
@@ -4332,6 +4375,7 @@ void MainWindow::UpdateStatusButtons() {
UpdateVolumeUI();
}
// TODO(crueter): Use this for game list stuff
void MainWindow::UpdateUISettings() {
if (!ui->action_Fullscreen->isChecked()) {
UISettings::values.geometry = saveGeometry();
@@ -4342,6 +4386,8 @@ void MainWindow::UpdateUISettings() {
UISettings::values.fullscreen = ui->action_Fullscreen->isChecked();
UISettings::values.show_filter_bar = ui->action_Show_Filter_Bar->isChecked();
UISettings::values.show_status_bar = ui->action_Show_Status_Bar->isChecked();
UISettings::values.show_perf_overlay = ui->action_Show_Performance_Overlay->isChecked();
UISettings::values.first_start = false;
Settings::values.enable_overlay = ui->action_Enable_Overlay_Applet->isChecked();
@@ -4570,6 +4616,15 @@ void MainWindow::closeEvent(QCloseEvent* event) {
QWidget::closeEvent(event);
}
void MainWindow::resizeEvent(QResizeEvent* event) {
emit sizeChanged(event->size());
}
void MainWindow::moveEvent(QMoveEvent* event) {
auto window_frame_height = frameGeometry().height() - geometry().height();
emit positionChanged(event->pos() - QPoint{0, window_frame_height});
}
static bool IsSingleFileDropEvent(const QMimeData* mime) {
return mime->hasUrls() && mime->urls().length() == 1;
}
+14
View File
@@ -55,6 +55,7 @@ class QProgressDialog;
class QSlider;
class QHBoxLayout;
class WaitTreeWidget;
class PerformanceOverlay;
enum class GameListOpenTarget;
enum class DumpRomFSTarget;
class GameListPlaceholder;
@@ -70,6 +71,9 @@ enum class StartGameType {
Global, // Only uses global configuration
};
namespace VideoCore {
class ShaderNotify;
}
namespace Core {
enum class SystemResultStatus : u32;
} // namespace Core
@@ -214,6 +218,9 @@ signals:
void WebBrowserClosed(Service::AM::Frontend::WebExitReason exit_reason, std::string last_url);
void SigInterrupt();
void sizeChanged(const QSize &size);
void positionChanged(const QPoint &pos);
void statsUpdated(const Core::PerfStatsResults &results, const VideoCore::ShaderNotify &shaders);
public slots:
void OnLoadComplete();
@@ -310,6 +317,8 @@ private:
void RequestGameExit();
void changeEvent(QEvent* event) override;
void closeEvent(QCloseEvent* event) override;
void resizeEvent(QResizeEvent *event) override;
void moveEvent(QMoveEvent *event) override;
std::string CreateTASFramesString(
std::array<size_t, InputCommon::TasInput::PLAYER_NUMBER> frames) const;
@@ -392,6 +401,7 @@ private slots:
void OnDataDialog();
void OnToggleFilterBar();
void OnToggleStatusBar();
void OnTogglePerfOverlay();
void OnGameListRefresh();
void InitializeHotkeys();
void ToggleFullscreen();
@@ -493,6 +503,7 @@ private:
LoadingScreen* loading_screen = nullptr;
QTimer shutdown_timer;
OverlayDialog* shutdown_dialog{};
PerformanceOverlay *perf_overlay = nullptr;
GameListPlaceholder* game_list_placeholder = nullptr;
@@ -518,6 +529,9 @@ private:
QSlider* volume_slider = nullptr;
QTimer status_bar_update_timer;
// Stores what suffix to add to the FPS counter, e.g. Unlocked.
QString m_fpsSuffix{};
UserDataMigrator user_data_migrator;
std::unique_ptr<QtConfig> config;
+207
View File
@@ -0,0 +1,207 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/perf_stats.h"
#include "performance_overlay.h"
#include "ui_performance_overlay.h"
#include "main_window.h"
#include <QChart>
#include <QChartView>
#include <QGraphicsLayout>
#include <QLineSeries>
#include <QMouseEvent>
#include <QPainter>
#include <QValueAxis>
// TODO(crueter): Reset samples when user changes turbo, slow, etc.
PerformanceOverlay::PerformanceOverlay(MainWindow* parent)
: QWidget(parent), m_mainWindow{parent}, ui(new Ui::PerformanceOverlay) {
ui->setupUi(this);
setAttribute(Qt::WA_TranslucentBackground);
setWindowFlags(Qt::FramelessWindowHint | Qt::Tool);
raise();
// chart setup
m_fpsSeries = new QLineSeries(this);
QPen pen(Qt::red);
pen.setWidth(2);
m_fpsSeries->setPen(pen);
m_fpsChart = new QChart;
m_fpsChart->addSeries(m_fpsSeries);
m_fpsChart->legend()->hide();
m_fpsChart->setBackgroundBrush(Qt::black);
m_fpsChart->setBackgroundVisible(true);
m_fpsChart->layout()->setContentsMargins(2, 2, 2, 2);
m_fpsChart->setMargins(QMargins{4, 4, 4, 4});
// axes
m_fpsX = new QValueAxis(this);
m_fpsX->setRange(0, NUM_FPS_SAMPLES);
m_fpsX->setVisible(false);
m_fpsY = new QValueAxis(this);
m_fpsY->setRange(0, 60);
m_fpsY->setLabelFormat(QStringLiteral("%d"));
m_fpsY->setLabelsColor(Qt::white);
QFont axisFont = m_fpsY->labelsFont();
axisFont.setPixelSize(10);
m_fpsY->setLabelsFont(axisFont);
m_fpsY->setTickCount(3);
// gray-ish label w/ white lines
m_fpsY->setLabelsVisible(true);
m_fpsY->setGridLineColor(QColor(50, 50, 50));
m_fpsY->setLinePenColor(Qt::white);
m_fpsChart->addAxis(m_fpsX, Qt::AlignBottom);
m_fpsChart->addAxis(m_fpsY, Qt::AlignLeft);
m_fpsSeries->attachAxis(m_fpsX);
m_fpsSeries->attachAxis(m_fpsY);
// chart view
m_fpsChartView = new QChartView(m_fpsChart, this);
m_fpsChartView->setRenderHint(QPainter::Antialiasing);
m_fpsChartView->setMinimumHeight(100);
ui->verticalLayout->addWidget(m_fpsChartView, 1);
// thanks Debian.
QFont font = ui->fps->font();
font.setWeight(QFont::DemiBold);
ui->fps->setFont(font);
ui->frametime->setFont(font);
// pos/stats
resetPosition(m_mainWindow->pos());
connect(parent, &MainWindow::positionChanged, this, &PerformanceOverlay::resetPosition);
connect(m_mainWindow, &MainWindow::statsUpdated, this, &PerformanceOverlay::updateStats);
}
PerformanceOverlay::~PerformanceOverlay() {
delete ui;
}
void PerformanceOverlay::resetPosition(const QPoint& _) {
auto pos = m_mainWindow->pos();
move(pos.x() + m_offset.x(), pos.y() + m_offset.y());
}
void PerformanceOverlay::updateStats(const Core::PerfStatsResults& results,
const VideoCore::ShaderNotify& shaders) {
auto fps = results.average_game_fps;
if (!std::isnan(fps)) {
// don't sample measurements < 3 fps because they are probably outliers or freezes
static constexpr double FPS_SAMPLE_THRESHOLD = 3.0;
QString fpsText = tr("%1 fps").arg(std::round(fps), 0, 'f', 0);
// if (!m_fpsSuffix.isEmpty()) fpsText = fpsText % QStringLiteral(" (%1)").arg(m_fpsSuffix);
ui->fps->setText(fpsText);
// sampling
if (fps > FPS_SAMPLE_THRESHOLD) {
m_fpsSamples.push_back(fps);
m_fpsPoints.push_back(QPointF{m_xPos++, fps});
}
if (m_fpsSamples.size() > NUM_FPS_SAMPLES) {
m_fpsSamples.pop_front();
m_fpsPoints.pop_front();
}
// For the average only go back 10 samples max
if (m_fpsSamples.size() >= 2) {
const int back_search = std::min(size_t(10), m_fpsSamples.size() - 1);
double sum = std::accumulate(m_fpsSamples.end() - back_search, m_fpsSamples.end(), 0.0);
double avg = sum / back_search;
ui->fps_avg->setText(tr("Avg: %1").arg(avg, 0, 'f', 0));
}
// chart it :)
if (!m_fpsPoints.empty()) {
auto [min_it, max_it] = std::minmax_element(m_fpsSamples.begin(), m_fpsSamples.end());
double min_fps = *min_it;
double max_fps = *max_it;
ui->fps_min->setText(tr("Min: %1").arg(min_fps, 0, 'f', 0));
ui->fps_max->setText(tr("Max: %1").arg(max_fps, 0, 'f', 0));
m_fpsSeries->replace(QList<QPointF>(m_fpsPoints.begin(), m_fpsPoints.end()));
qreal x_min = std::max(0.0, m_xPos - NUM_FPS_SAMPLES);
qreal x_max = std::max(qreal(10), m_xPos);
m_fpsX->setRange(x_min, x_max);
m_fpsY->setRange(0.0, max_fps);
}
}
auto ft = results.frametime;
if (!std::isnan(ft)) {
// don't sample measurements > 500 ms because they are probably outliers
static constexpr double FT_SAMPLE_THRESHOLD = 500.0;
double ft_ms = results.frametime * 1000.0;
ui->frametime->setText(tr("%1 ms").arg(ft_ms, 0, 'f', 2));
// sampling
if (ft_ms <= FT_SAMPLE_THRESHOLD)
m_frametimeSamples.push_back(ft_ms);
if (m_frametimeSamples.size() > NUM_FRAMETIME_SAMPLES)
m_frametimeSamples.pop_front();
if (!m_frametimeSamples.empty()) {
auto [min_it, max_it] =
std::minmax_element(m_frametimeSamples.begin(), m_frametimeSamples.end());
ui->ft_min->setText(tr("Min: %1").arg(*min_it, 0, 'f', 1));
ui->ft_max->setText(tr("Max: %1").arg(*max_it, 0, 'f', 1));
}
// For the average only go back 10 samples max
if (m_frametimeSamples.size() >= 2) {
const int back_search = std::min(size_t(10), m_frametimeSamples.size() - 1);
double sum = std::accumulate(m_frametimeSamples.end() - back_search,
m_frametimeSamples.end(), 0.0);
double avg = sum / back_search;
ui->ft_avg->setText(tr("Avg: %1").arg(avg, 0, 'f', 1));
}
}
}
void PerformanceOverlay::paintEvent(QPaintEvent* event) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(m_background);
painter.setPen(Qt::NoPen);
painter.drawRoundedRect(rect(), 10.0, 10.0);
}
void PerformanceOverlay::mousePressEvent(QMouseEvent* event) {
if (event->button() == Qt::LeftButton) {
m_drag_start_pos = event->pos();
}
}
void PerformanceOverlay::mouseMoveEvent(QMouseEvent* event) {
// drag
if (event->buttons() & Qt::LeftButton) {
QPoint new_global_pos = event->globalPosition().toPoint() - m_drag_start_pos;
m_offset = new_global_pos - m_mainWindow->pos();
move(new_global_pos);
}
}
void PerformanceOverlay::closeEvent(QCloseEvent* event) {
emit closed();
}
+73
View File
@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <deque>
#include <QWidget>
namespace VideoCore {
class ShaderNotify;
}
namespace Core {
struct PerfStatsResults;
}
namespace Ui {
class PerformanceOverlay;
}
class QLineSeries;
class QChart;
class QChartView;
class QValueAxis;
class MainWindow;
class PerformanceOverlay : public QWidget {
Q_OBJECT
public:
explicit PerformanceOverlay(MainWindow* parent = nullptr);
~PerformanceOverlay();
protected:
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void closeEvent(QCloseEvent *event) override;
private:
void resetPosition(const QPoint& pos);
void updateStats(const Core::PerfStatsResults &results, const VideoCore::ShaderNotify &shaders);
MainWindow *m_mainWindow = nullptr;
Ui::PerformanceOverlay* ui;
// colors
QColor m_background{127, 127, 127, 190};
QPoint m_offset{25, 75};
// frametime
const size_t NUM_FRAMETIME_SAMPLES = 300;
std::deque<double> m_frametimeSamples;
// fps
const size_t NUM_FPS_SAMPLES = 120;
qreal m_xPos = 0;
std::deque<double> m_fpsSamples;
std::deque<QPointF> m_fpsPoints;
// drag
QPoint m_drag_start_pos;
// fps chart
QLineSeries *m_fpsSeries = nullptr;
QChart *m_fpsChart = nullptr;
QChartView *m_fpsChartView = nullptr;
QValueAxis *m_fpsX = nullptr;
QValueAxis *m_fpsY = nullptr;
signals:
void closed();
};
+181
View File
@@ -0,0 +1,181 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PerformanceOverlay</class>
<widget class="QWidget" name="PerformanceOverlay">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>225</width>
<height>250</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="ft_label_layout">
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>12</pointsize>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: #0000ff;</string>
</property>
<property name="text">
<string>Frametime</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="frametime">
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>0 ms</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="ft_layout">
<item>
<widget class="QLabel" name="ft_min">
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>10</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Min: 0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="ft_max">
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>10</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Max: 0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="ft_avg">
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>10</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Avg: 0</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="fps_label_layout">
<item>
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>12</pointsize>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: #ff0000;</string>
</property>
<property name="text">
<string>FPS</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fps">
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>0 fps</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="fps_min">
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>10</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Min: 0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fps_max">
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>10</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Max: 0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fps_avg">
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>10</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Avg: 0</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>