From 7f965a65921ccd01b67000925655e217eecb8822 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 3 Oct 2025 02:12:43 +0000 Subject: [PATCH 01/15] [android] Android 7.0 support Signed-off-by: lizzie --- src/android/app/build.gradle.kts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index c85da039cb..5f33b71ead 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -58,8 +58,7 @@ android { defaultConfig { applicationId = "dev.eden.eden_emulator" - - minSdk = 28 + minSdk = 24 targetSdk = 36 versionName = getGitVersion() From 1cdafdd7a840d19f661c2560cd262ec2593b5825 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 3 Oct 2025 03:09:19 +0000 Subject: [PATCH 02/15] fix drawable Signed-off-by: lizzie --- .../{drawable => drawable-v26}/ic_launcher.xml | 0 .../app/src/main/res/drawable/ic_launcher.png | Bin 0 -> 2210 bytes 2 files changed, 0 insertions(+), 0 deletions(-) rename src/android/app/src/main/res/{drawable => drawable-v26}/ic_launcher.xml (100%) create mode 100644 src/android/app/src/main/res/drawable/ic_launcher.png diff --git a/src/android/app/src/main/res/drawable/ic_launcher.xml b/src/android/app/src/main/res/drawable-v26/ic_launcher.xml similarity index 100% rename from src/android/app/src/main/res/drawable/ic_launcher.xml rename to src/android/app/src/main/res/drawable-v26/ic_launcher.xml diff --git a/src/android/app/src/main/res/drawable/ic_launcher.png b/src/android/app/src/main/res/drawable/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..7ea5c713802a62e149605cae9216a9fd65aacfbb GIT binary patch literal 2210 zcmV;T2wnGyP)Px-TuDShRCwCuoM((xMHI)kFRQVFyO!06Xxv~#jjq8Gv3@YI5ercX#E1nuilVV& z?1?QF;#vq13pUiKhzcSoD(EV@DkZ3hB1l`dvA%ut^TRt=XXnlB_rAB{B>(Jt@6Mb# z_kYivIdf+2mX!370+a(4KqXLK@_#Hs!U4cNKr^rr=)t}80GoiXrOkmr*_KAiw!ke4 z{Yu%|1vSB1FMNutW0%l#3L4V$$!GKfKp&QkO^ZBAVZA03R{8^(A7q0hU@6%>cgvUjPe%RXz?mNO@iQoFAZW8?aBY$vFn- z5X2@zHU56JXiZNySKDxCI(AZ8~~&h!`Ez+&J|V6PDJ&ImY>4F&cS zQbs$@SjcM%FdJlnebXjquMpEx7We>Y2q5V_B{& z2i^s)jG;$aQpA)>RD5kh1UdLlZnV_s?+M@!@pILdiD8|P7q|cMCizhb`X$d|lMRkn zSV+3ZoE^m(uT3~A-yF-5fJqdltnizLfge)pdtOv>c9-Zk4wPnbJu*fA2z(FxY+#Cq zE=^ic`Mb1VCKhEz=U;|J29AqDP#v(dhTVXA7wjT+>LgA&TwUy6HUN=8*pIX>wV~tUNI#pF4pivaV||+8q}B87qJ#L6oTviNVst z5f)kffKjp_?FK#v&KE~JB!VxhHBhJd%0q#v($C|L-;;;3n^w2ZLr$%q62pN{1q*!e zspMs{zzK#AxWGuac1m7oo;dDvV-BBJfsb4;N1wuloD&1w&jJ(+fCs!f?u;<3E|RuG zfo}rTzuKe!6UEwUfE|F59(A7!q2q5M@2T8vr5?7-0H%b<6i)MSs9jFbD8D@{-%0|q~-`)XKzN88?bV#%S}FQ)H_FhUvGpBE+nbB$#0yn(k)tjDkWHxRpK)~ z1Ktu#agy?7^0^6^EVp4LN$M+E+%_Zy+iWur_{uf@LO$LTmpA%d8bzS+W~Yam(Mw<4y{eAmdpMb*iY|ZOv;PV`hdP; zlw3d0L)H=tK{YAp{Jh@5ocKf6H6OCxxS63IV*B+DR$cwt8y1o_h4G02-0PpZ@LR=9 zV;rsSia)t$KbqdCAV1G9mj31y`GlYwLkEvSs5O z9Jn56)OK3ktl}&qqfnrt@UhU*CBUAs#CiLsR|76&chv}bIT`my$({R|7(-@Ks4)rh%MgA|@9d(EMIp0| zEudOtwSTNZ&|7lw;UD?Rz27u3z>KI@6={ey8hX5vjMI%Jp~tr57?+$*p_Dt&&|_+m zmKS=N>9%ja!B}5y2!V}(+NxgP0S>Zk1E~OQeV{gk)Zf86V4&C0kt!clSWp!KmEw=g zA@(TnNesEM;kB|KT7>EJI75d!3Okze%1wSbmR42RVQ)MjY6Tepjk`Hso z8U623M1pP)bIPorpgJ{VxdG$wrd(6fcNI&iUh2|*X?z`m`5p^FgC#?lHadatE&V&M zHRVCMeD^z+`-AU5(u`X0Duak->V$!iVhW|-WuTeq+V#Tqa@(Aiv4$IlW>61-#~}BsM|QP zwt6BEG(Ge<3g-xpvOJKq%c7(=0;i|sg8(L4`dby-CA(i*M;h<2x@G%RV9i`L{?gLd zZGm|SXRsZ_cy}ZSNpH}5msCIX9t8lKT@H=|DCOR^#|7EEr=jQo?xRp|KPO1|cD9*J zuC)PUW6Ccp){Qk>idMNV`4>`875*W#r@PEyKeaM}MZc`Cf*5uAzuLnLR_s^WFO19J@i_jtD{uRY+ kawlNdkb6hs)V`$a-=DRfGw8>|X#fBK07*qoM6N<$g1WRQ$N&HU literal 0 HcmV?d00001 From 61d1614abd1a699d09f5bd01df66ef01f1ecfff6 Mon Sep 17 00:00:00 2001 From: crueter Date: Fri, 3 Oct 2025 00:19:05 -0400 Subject: [PATCH 03/15] script to make <26 sdk icon Signed-off-by: crueter --- .../app/src/main/res/drawable/ic_launcher.png | Bin 2210 -> 49032 bytes tools/VectorDrawable2Svg.py | 86 ++++++++++++++++++ tools/generate-legacy-icons.sh | 26 ++++++ 3 files changed, 112 insertions(+) create mode 100644 tools/VectorDrawable2Svg.py create mode 100755 tools/generate-legacy-icons.sh diff --git a/src/android/app/src/main/res/drawable/ic_launcher.png b/src/android/app/src/main/res/drawable/ic_launcher.png index 7ea5c713802a62e149605cae9216a9fd65aacfbb..6b586fc75761f49f0a7a67cb1ebdf58edcafe745 100644 GIT binary patch literal 49032 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 aH+6LR-^<8R@ya2}0Lt=j<-W;4L;epW>FkjJ literal 2210 zcmV;T2wnGyP)Px-TuDShRCwCuoM((xMHI)kFRQVFyO!06Xxv~#jjq8Gv3@YI5ercX#E1nuilVV& z?1?QF;#vq13pUiKhzcSoD(EV@DkZ3hB1l`dvA%ut^TRt=XXnlB_rAB{B>(Jt@6Mb# z_kYivIdf+2mX!370+a(4KqXLK@_#Hs!U4cNKr^rr=)t}80GoiXrOkmr*_KAiw!ke4 z{Yu%|1vSB1FMNutW0%l#3L4V$$!GKfKp&QkO^ZBAVZA03R{8^(A7q0hU@6%>cgvUjPe%RXz?mNO@iQoFAZW8?aBY$vFn- z5X2@zHU56JXiZNySKDxCI(AZ8~~&h!`Ez+&J|V6PDJ&ImY>4F&cS zQbs$@SjcM%FdJlnebXjquMpEx7We>Y2q5V_B{& z2i^s)jG;$aQpA)>RD5kh1UdLlZnV_s?+M@!@pILdiD8|P7q|cMCizhb`X$d|lMRkn zSV+3ZoE^m(uT3~A-yF-5fJqdltnizLfge)pdtOv>c9-Zk4wPnbJu*fA2z(FxY+#Cq zE=^ic`Mb1VCKhEz=U;|J29AqDP#v(dhTVXA7wjT+>LgA&TwUy6HUN=8*pIX>wV~tUNI#pF4pivaV||+8q}B87qJ#L6oTviNVst z5f)kffKjp_?FK#v&KE~JB!VxhHBhJd%0q#v($C|L-;;;3n^w2ZLr$%q62pN{1q*!e zspMs{zzK#AxWGuac1m7oo;dDvV-BBJfsb4;N1wuloD&1w&jJ(+fCs!f?u;<3E|RuG zfo}rTzuKe!6UEwUfE|F59(A7!q2q5M@2T8vr5?7-0H%b<6i)MSs9jFbD8D@{-%0|q~-`)XKzN88?bV#%S}FQ)H_FhUvGpBE+nbB$#0yn(k)tjDkWHxRpK)~ z1Ktu#agy?7^0^6^EVp4LN$M+E+%_Zy+iWur_{uf@LO$LTmpA%d8bzS+W~Yam(Mw<4y{eAmdpMb*iY|ZOv;PV`hdP; zlw3d0L)H=tK{YAp{Jh@5ocKf6H6OCxxS63IV*B+DR$cwt8y1o_h4G02-0PpZ@LR=9 zV;rsSia)t$KbqdCAV1G9mj31y`GlYwLkEvSs5O z9Jn56)OK3ktl}&qqfnrt@UhU*CBUAs#CiLsR|76&chv}bIT`my$({R|7(-@Ks4)rh%MgA|@9d(EMIp0| zEudOtwSTNZ&|7lw;UD?Rz27u3z>KI@6={ey8hX5vjMI%Jp~tr57?+$*p_Dt&&|_+m zmKS=N>9%ja!B}5y2!V}(+NxgP0S>Zk1E~OQeV{gk)Zf86V4&C0kt!clSWp!KmEw=g zA@(TnNesEM;kB|KT7>EJI75d!3Okze%1wSbmR42RVQ)MjY6Tepjk`Hso z8U623M1pP)bIPorpgJ{VxdG$wrd(6fcNI&iUh2|*X?z`m`5p^FgC#?lHadatE&V&M zHRVCMeD^z+`-AU5(u`X0Duak->V$!iVhW|-WuTeq+V#Tqa@(Aiv4$IlW>61-#~}BsM|QP zwt6BEG(Ge<3g-xpvOJKq%c7(=0;i|sg8(L4`dby-CA(i*M;h<2x@G%RV9i`L{?gLd zZGm|SXRsZ_cy}ZSNpH}5msCIX9t8lKT@H=|DCOR^#|7EEr=jQo?xRp|KPO1|cD9*J zuC)PUW6Ccp){Qk>idMNV`4>`875*W#r@PEyKeaM}MZc`Cf*5uAzuLnLR_s^WFO19J@i_jtD{uRY+ kawlNdkb6hs)V`$a-=DRfGw8>|X#fBK07*qoM6N<$g1WRQ$N&HU diff --git a/tools/VectorDrawable2Svg.py b/tools/VectorDrawable2Svg.py new file mode 100644 index 0000000000..20f168e1aa --- /dev/null +++ b/tools/VectorDrawable2Svg.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python2 +""" +VectorDrawable2Svg +This script convert your VectorDrawable to a Svg +Author: Alessandro Lucchet + +Usage: drop one or more vector drawable onto this script to convert them to svg format +""" + +from xml.dom.minidom import * +import sys + +# extracts all paths inside vdContainer and add them into svgContainer +def convertPaths(vdContainer,svgContainer,svgXml): + vdPaths = vdContainer.getElementsByTagName('path') + for vdPath in vdPaths: + # only iterate in the first level + if vdPath.parentNode == vdContainer: + svgPath = svgXml.createElement('path') + svgPath.attributes['d'] = vdPath.attributes['android:pathData'].value + if vdPath.hasAttribute('android:fillColor'): + svgPath.attributes['fill'] = vdPath.attributes['android:fillColor'].value + else: + svgPath.attributes['fill'] = 'none' + if vdPath.hasAttribute('android:strokeLineJoin'): + svgPath.attributes['stroke-linejoin'] = vdPath.attributes['android:strokeLineJoin'].value + if vdPath.hasAttribute('android:strokeLineCap'): + svgPath.attributes['stroke-linecap'] = vdPath.attributes['android:strokeLineCap'].value + if vdPath.hasAttribute('android:strokeMiterLimit'): + svgPath.attributes['stroke-miterlimit'] = vdPath.attributes['android:strokeMiterLimit'].value + if vdPath.hasAttribute('android:strokeWidth'): + svgPath.attributes['stroke-width'] = vdPath.attributes['android:strokeWidth'].value + if vdPath.hasAttribute('android:strokeColor'): + svgPath.attributes['stroke'] = vdPath.attributes['android:strokeColor'].value + svgContainer.appendChild(svgPath); + +# define the function which converts a vector drawable to a svg +def convertVd(vdFilePath): + + # create svg xml + svgXml = Document() + svgNode = svgXml.createElement('svg') + svgXml.appendChild(svgNode); + + # open vector drawable + vdXml = parse(vdFilePath) + vdNode = vdXml.getElementsByTagName('vector')[0] + + # setup basic svg info + svgNode.attributes['xmlns'] = 'http://www.w3.org/2000/svg' + svgNode.attributes['width'] = vdNode.attributes['android:viewportWidth'].value + svgNode.attributes['height'] = vdNode.attributes['android:viewportHeight'].value + svgNode.attributes['viewBox'] = '0 0 {} {}'.format(vdNode.attributes['android:viewportWidth'].value, vdNode.attributes['android:viewportHeight'].value) + + # iterate through all groups + vdGroups = vdXml.getElementsByTagName('group') + for vdGroup in vdGroups: + + # create the group + svgGroup = svgXml.createElement('g') + + # setup attributes of the group + if vdGroup.hasAttribute('android:translateX'): + svgGroup.attributes['transform'] = 'translate({},{})'.format(vdGroup.attributes['android:translateX'].value,vdGroup.attributes['android:translateY'].value) + + # iterate through all paths inside the group + convertPaths(vdGroup,svgGroup,svgXml) + + # append the group to the svg node + svgNode.appendChild(svgGroup); + + # iterate through all svg-level paths + convertPaths(vdNode,svgNode,svgXml) + + # write xml to file + svgXml.writexml(open(vdFilePath + '.svg', 'w'),indent="",addindent=" ",newl='\n') + +# script begin +if len(sys.argv)>1: + iterArgs = iter(sys.argv) + next(iterArgs) #skip the first entry (it's the name of the script) + for arg in iterArgs: + convertVd(arg) +else: + print("You have to pass me something") + sys.exit() diff --git a/tools/generate-legacy-icons.sh b/tools/generate-legacy-icons.sh new file mode 100755 index 0000000000..9bf14e8093 --- /dev/null +++ b/tools/generate-legacy-icons.sh @@ -0,0 +1,26 @@ +#!/bin/sh -e + +# Generate SDK <26 icons for android +# requires imagemagick, inkscape + +ROOTDIR=$PWD + +cd src/android/app/src/main + +pushd res/drawable +# convert vector to svg--needed to generate launcher png +cp ic_yuzu_icon.xml tmp + +python3 $ROOTDIR/tools/VectorDrawable2Svg.py tmp + +inkscape -w 768 -h 768 tmp.svg -o ic_tmp.png +magick ic_icon_bg_orig.png -resize 512x512 bg_tmp.png + +magick bg_tmp.png -strip -type TrueColor -depth 8 -colorspace sRGB -color-matrix "1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0" bg_tmp_rgb.png +magick -verbose bg_tmp_rgb.png ic_tmp.png -gravity center -composite -colorspace sRGB ic_launcher.png +echo + +rm *tmp* +popd + +# Add legacy here when legacy gets merged From 9acb6006b8586303304102deb42b4f7dc462fe53 Mon Sep 17 00:00:00 2001 From: Caio Oliveira Date: Wed, 8 Oct 2025 01:04:18 +0200 Subject: [PATCH 04/15] [ci] improve ccache and add support on Android (#2673) * disable PCH * fix missing headers after disabling PCH * add support to extra cmake flags on Android building * remove debug symbols on Release building (also fixing ccache on windows) Signed-off-by: Caio Oliveira Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2673 Reviewed-by: crueter Co-authored-by: Caio Oliveira Co-committed-by: Caio Oliveira --- CMakeLists.txt | 7 +++++++ docs/build/Android.md | 1 + externals/CMakeLists.txt | 14 ++++++++------ src/CMakeLists.txt | 12 +++--------- src/android/app/build.gradle.kts | 5 ++++- src/dynarmic/CMakeLists.txt | 12 ++++++------ src/dynarmic/src/dynarmic/backend/arm64/abi.h | 1 + src/dynarmic/src/dynarmic/common/assert.h | 6 ++++++ src/dynarmic/src/dynarmic/common/memory_pool.h | 4 ++++ src/dynarmic/src/dynarmic/ir/opt_passes.cpp | 1 + 10 files changed, 41 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f436c0a183..1b69782a23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -287,6 +287,13 @@ if (ANDROID) set(CMAKE_POLICY_VERSION_MINIMUM 3.5) # Workaround for Oboe endif() +# We need to downgrade debug info (/Zi -> /Z7) to use an older but more cacheable format +# See https://github.com/nanoant/CMakePCHCompiler/issues/21 +if(WIN32 AND (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")) + string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") +endif() + # Default to a Release build get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE) diff --git a/docs/build/Android.md b/docs/build/Android.md index c8ff3a3b1e..f511f71370 100644 --- a/docs/build/Android.md +++ b/docs/build/Android.md @@ -33,6 +33,7 @@ Eden by default will be cloned into - 4. Navigate to `eden/src/android`. 5. Then Build with `./gradlew assembleRelWithDebInfo`. 6. To build the optimised build use `./gradlew assembleGenshinSpoofRelWithDebInfo`. +7. You can pass extra variables to cmake via `-PYUZU_ANDROID_ARGS="-D..."` ### Script A convenience script for building is provided in `.ci/android/build.sh`. The built APK can be put into an `artifacts` directory via `.ci/android/package.sh`. On Windows, these must be done in the Git Bash or MinGW terminal. diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 434e6fb100..2da461fd5c 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -155,12 +155,14 @@ if (YUZU_USE_BUNDLED_SIRIT) AddJsonPackage(sirit-ci) else() AddJsonPackage(sirit) - if(MSVC AND USE_CCACHE AND sirit_ADDED) - get_target_property(_opts sirit COMPILE_OPTIONS) - list(FILTER _opts EXCLUDE REGEX "/Zi") - list(APPEND _opts "/Z7") - set_target_properties(siritobj PROPERTIES COMPILE_OPTIONS "${_opts}") - elseif(MSVC AND CXX_CLANG) + # Change to old-but-more-cacheable debug info on Windows + if (WIN32 AND (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")) + get_target_property(sirit_opts sirit COMPILE_OPTIONS) + list(FILTER sirit_opts EXCLUDE REGEX "/Zi") + list(APPEND sirit_opts "/Z7") + set_target_properties(sirit PROPERTIES COMPILE_OPTIONS "${sirit_opts}") + endif() + if(MSVC AND CXX_CLANG) target_compile_options(siritobj PRIVATE -Wno-error=unused-command-line-argument) endif() endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 88470c4c42..0f3c5cfd4b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -101,15 +101,9 @@ if (MSVC AND NOT CXX_CLANG) ) endif() - if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS) - # when caching, we need to use /Z7 to downgrade debug info to use an older but more cacheable format - # Precompiled headers are deleted if not using /Z7. See https://github.com/nanoant/CMakePCHCompiler/issues/21 - add_compile_options(/Z7) - # Avoid D9025 warning - string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - string(REPLACE "/Zi" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") - else() - add_compile_options(/Zi) + if (WIN32 AND (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")) + string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") endif() if (ARCHITECTURE_x86_64) diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index c85da039cb..31db36199a 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -75,6 +75,8 @@ android { externalNativeBuild { cmake { + val extraCMakeArgs = (project.findProperty("YUZU_ANDROID_ARGS") as String?)?.split("\\s+".toRegex()) ?: emptyList() + arguments.addAll(listOf( "-DENABLE_QT=0", // Don't use QT "-DENABLE_SDL2=0", // Don't use SDL @@ -87,7 +89,8 @@ android { "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", "-DBUILD_TESTING=OFF", "-DYUZU_TESTS=OFF", - "-DDYNARMIC_TESTS=OFF" + "-DDYNARMIC_TESTS=OFF", + *extraCMakeArgs.toTypedArray() )) abiFilters("arm64-v8a") diff --git a/src/dynarmic/CMakeLists.txt b/src/dynarmic/CMakeLists.txt index 6b3308fb54..e5345ef458 100644 --- a/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/CMakeLists.txt @@ -25,11 +25,7 @@ option(DYNARMIC_IGNORE_ASSERTS "Ignore asserts" OFF) option(DYNARMIC_TESTS_USE_UNICORN "Enable fuzzing tests against unicorn" OFF) CMAKE_DEPENDENT_OPTION(DYNARMIC_USE_LLVM "Support disassembly of jitted x86_64 code using LLVM" OFF "NOT YUZU_DISABLE_LLVM" OFF) -if (PLATFORM_OPENBSD) - option(DYNARMIC_USE_PRECOMPILED_HEADERS "Use precompiled headers" OFF) -else() - option(DYNARMIC_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) -endif() +option(DYNARMIC_USE_PRECOMPILED_HEADERS "Use precompiled headers" OFF) option(DYNARMIC_INSTALL "Install dynarmic headers and CMake files" OFF) option(DYNARMIC_USE_BUNDLED_EXTERNALS "Use all bundled externals (useful when e.g. cross-compiling)" OFF) @@ -81,7 +77,6 @@ if (MSVC) /wd4592 # Symbol will be dynamically initialized (implementation limitation) /permissive- # Stricter C++ standards conformance /MP - /Zi /Zo /EHsc /Zc:externConstexpr # Allows external linkage for variables declared "extern constexpr", as the standard permits. @@ -91,6 +86,11 @@ if (MSVC) /bigobj # Increase number of sections in .obj files /DNOMINMAX) + if (WIN32 AND (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")) + string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") + endif() + if (DYNARMIC_WARNINGS_AS_ERRORS) list(APPEND DYNARMIC_CXX_FLAGS /WX) diff --git a/src/dynarmic/src/dynarmic/backend/arm64/abi.h b/src/dynarmic/src/dynarmic/backend/arm64/abi.h index ca7c9187db..635d64f062 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/abi.h +++ b/src/dynarmic/src/dynarmic/backend/arm64/abi.h @@ -14,6 +14,7 @@ #include #include "dynarmic/common/common_types.h" +#include "dynarmic/common/assert.h" #include #include "dynarmic/common/always_false.h" diff --git a/src/dynarmic/src/dynarmic/common/assert.h b/src/dynarmic/src/dynarmic/common/assert.h index 9973b8948d..0a3cb5331d 100644 --- a/src/dynarmic/src/dynarmic/common/assert.h +++ b/src/dynarmic/src/dynarmic/common/assert.h @@ -23,6 +23,12 @@ template } \ }()) #endif +#ifndef ASSERT_FALSE +#define ASSERT_FALSE(...) \ + ([&]() { \ + assert_terminate("false", __VA_ARGS__); \ + }()) +#endif #ifndef ASSERT #define ASSERT(_a_) ASSERT_MSG(_a_, "") diff --git a/src/dynarmic/src/dynarmic/common/memory_pool.h b/src/dynarmic/src/dynarmic/common/memory_pool.h index c99316e107..d0a5223db3 100644 --- a/src/dynarmic/src/dynarmic/common/memory_pool.h +++ b/src/dynarmic/src/dynarmic/common/memory_pool.h @@ -1,3 +1,6 @@ +// 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) 2016 MerryMage * SPDX-License-Identifier: 0BSD @@ -6,6 +9,7 @@ #pragma once #include +#include #include namespace Dynarmic::Common { diff --git a/src/dynarmic/src/dynarmic/ir/opt_passes.cpp b/src/dynarmic/src/dynarmic/ir/opt_passes.cpp index e9175f0e6b..25afde9b5d 100644 --- a/src/dynarmic/src/dynarmic/ir/opt_passes.cpp +++ b/src/dynarmic/src/dynarmic/ir/opt_passes.cpp @@ -6,6 +6,7 @@ * SPDX-License-Identifier: 0BSD */ +#include #include #include From acd7d792a34a809cf4e7f9270b3cee605fb6c35e Mon Sep 17 00:00:00 2001 From: Gamer64 Date: Wed, 8 Oct 2025 02:08:13 +0200 Subject: [PATCH 05/15] [hle] Stubbed QueryLastPlayTime (#389) Ported from Torzu, made by Jarrod Norwell. Co-authored-by: Jarrod Norwell Co-authored-by: Gamer64 <76565986+Gamer64ytb@users.noreply.github.com> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/389 Reviewed-by: crueter Reviewed-by: MaranBr Co-authored-by: Gamer64 Co-committed-by: Gamer64 --- src/core/hle/service/ns/query_service.cpp | 14 +++++++++++++- src/core/hle/service/ns/query_service.h | 8 ++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/ns/query_service.cpp b/src/core/hle/service/ns/query_service.cpp index 1384005415..a4632cb6c8 100644 --- a/src/core/hle/service/ns/query_service.cpp +++ b/src/core/hle/service/ns/query_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -29,7 +32,7 @@ IQueryService::IQueryService(Core::System& system_) : ServiceFramework{system_, {14, nullptr, "QueryRecentlyPlayedApplication"}, {15, nullptr, "GetRecentlyPlayedApplicationUpdateEvent"}, {16, nullptr, "QueryApplicationPlayStatisticsByUserAccountIdForSystemV0"}, - {17, nullptr, "QueryLastPlayTime"}, + {17, D<&IQueryService::QueryLastPlayTime>, "QueryLastPlayTime"}, {18, nullptr, "QueryApplicationPlayStatisticsForSystem"}, {19, nullptr, "QueryApplicationPlayStatisticsByUserAccountIdForSystem"}, }; @@ -53,4 +56,13 @@ Result IQueryService::QueryPlayStatisticsByApplicationIdAndUserAccountId( R_SUCCEED(); } +Result IQueryService::QueryLastPlayTime( + Out out_entries, u8 unknown, + OutArray out_last_play_times, + InArray application_ids) { + *out_entries = 1; + *out_last_play_times = {}; + R_SUCCEED(); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/query_service.h b/src/core/hle/service/ns/query_service.h index c4c82b752e..ba1cddd4ca 100644 --- a/src/core/hle/service/ns/query_service.h +++ b/src/core/hle/service/ns/query_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -23,6 +26,8 @@ struct PlayStatistics { }; static_assert(sizeof(PlayStatistics) == 0x28, "PlayStatistics is an invalid size"); +struct LastPlayTime {}; + class IQueryService final : public ServiceFramework { public: explicit IQueryService(Core::System& system_); @@ -31,6 +36,9 @@ public: private: Result QueryPlayStatisticsByApplicationIdAndUserAccountId( Out out_play_statistics, bool unknown, u64 application_id, Uid account_id); + Result QueryLastPlayTime(Out out_entries, u8 unknown, + OutArray out_last_play_times, + InArray application_ids); }; } // namespace Service::NS From db65f107689f853141fd2cf4cb7b6b55b24aff3e Mon Sep 17 00:00:00 2001 From: Ribbit Date: Wed, 8 Oct 2025 04:01:24 +0200 Subject: [PATCH 06/15] [vk] Unify RAII in Vulkan (#2679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR consolidates Vulkan RAII on video_core/vulkan_common/vulkan_wrapper.h’s vk::Handle and remove the unused duplicate src/video_core/vulkan_common/vulkan_raii.h, reducing confusion and maintenance. Swapchain now uses RAII‑managed per‑image semaphores and clears them in Destroy(), providing correct present synchronization and automatic cleanup. Expected result: simpler lifetimes, fewer leak risks, and more stable presentation with negligible overhead. Co-authored-by: Ribbit Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2679 Reviewed-by: MaranBr Reviewed-by: CamilleLaVey Co-authored-by: Ribbit Co-committed-by: Ribbit --- .../features/settings/model/BooleanSetting.kt | 2 - .../settings/model/view/SettingsItem.kt | 8 +- .../settings/ui/SettingsFragmentPresenter.kt | 1 - .../app/src/main/res/values-ar/strings.xml | 2 - .../app/src/main/res/values-ckb/strings.xml | 2 - .../app/src/main/res/values-cs/strings.xml | 2 - .../app/src/main/res/values-de/strings.xml | 2 - .../app/src/main/res/values-es/strings.xml | 2 - .../app/src/main/res/values-fa/strings.xml | 2 - .../app/src/main/res/values-fr/strings.xml | 2 - .../app/src/main/res/values-he/strings.xml | 2 - .../app/src/main/res/values-hu/strings.xml | 2 - .../app/src/main/res/values-id/strings.xml | 2 - .../app/src/main/res/values-it/strings.xml | 2 - .../app/src/main/res/values-ja/strings.xml | 2 - .../app/src/main/res/values-ko/strings.xml | 2 - .../app/src/main/res/values-nb/strings.xml | 2 - .../app/src/main/res/values-pl/strings.xml | 2 - .../src/main/res/values-pt-rBR/strings.xml | 2 - .../src/main/res/values-pt-rPT/strings.xml | 2 - .../app/src/main/res/values-ru/strings.xml | 2 - .../app/src/main/res/values-sr/strings.xml | 2 - .../app/src/main/res/values-uk/strings.xml | 2 - .../app/src/main/res/values-vi/strings.xml | 2 - .../src/main/res/values-zh-rCN/strings.xml | 2 - .../src/main/res/values-zh-rTW/strings.xml | 2 - .../app/src/main/res/values/strings.xml | 2 - src/common/settings.h | 1 - src/qt_common/shared_translation.cpp | 7 +- .../renderer_vulkan/renderer_vulkan.cpp | 9 - .../renderer_vulkan/renderer_vulkan.h | 10 +- .../renderer_vulkan/vk_swapchain.cpp | 1 + src/video_core/vulkan_common/vulkan_raii.h | 231 ------------------ .../vulkan_common/vulkan_wrapper.cpp | 11 +- src/video_core/vulkan_common/vulkan_wrapper.h | 66 ++--- 35 files changed, 42 insertions(+), 353 deletions(-) delete mode 100644 src/video_core/vulkan_common/vulkan_raii.h diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 638e1101db..b26fb1dec5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -51,7 +51,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { SOC_OVERLAY_BACKGROUND("soc_overlay_background"), - ENABLE_RAII("enable_raii"), FRAME_INTERPOLATION("frame_interpolation"), // FRAME_SKIPPING("frame_skipping"), @@ -71,7 +70,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { DEBUG_FLUSH_BY_LINE("flush_line"), USE_LRU_CACHE("use_lru_cache"); - external fun isRaiiEnabled(): Boolean // external fun isFrameSkippingEnabled(): Boolean external fun isFrameInterpolationEnabled(): Boolean diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 5f7f7a43f9..ebc726225a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -229,13 +229,6 @@ abstract class SettingsItem( override fun reset() = BooleanSetting.USE_DOCKED_MODE.reset() } - put( - SwitchSetting( - BooleanSetting.ENABLE_RAII, - titleId = R.string.enable_raii, - descriptionId = R.string.enable_raii_description - ) - ) put( SwitchSetting( BooleanSetting.FRAME_INTERPOLATION, @@ -833,3 +826,4 @@ abstract class SettingsItem( } } } + diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 715baec72f..0d882a7f01 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -462,7 +462,6 @@ class SettingsFragmentPresenter( add(IntSetting.RENDERER_SAMPLE_SHADING_FRACTION.key) add(HeaderSetting(R.string.veil_renderer)) - add(BooleanSetting.ENABLE_RAII.key) add(BooleanSetting.RENDERER_EARLY_RELEASE_FENCES.key) add(IntSetting.DMA_ACCURACY.key) add(BooleanSetting.BUFFER_REORDER_DISABLE.key) diff --git a/src/android/app/src/main/res/values-ar/strings.xml b/src/android/app/src/main/res/values-ar/strings.xml index 4b58d5f851..388afd88cd 100644 --- a/src/android/app/src/main/res/values-ar/strings.xml +++ b/src/android/app/src/main/res/values-ar/strings.xml @@ -64,8 +64,6 @@ امتدادات GPU العارض - RAII - طريقة لإدارة الموارد تلقائيًا في فولكان تضمن الإفراج الصحيح عن الموارد عندما لا تكون هناك حاجة إليها، ولكن قد تسبب تعطل الألعاب المجمعة. وحدة المعالجة المركزية والذاكرة حجاب عدن إعدادات تجريبية لتحسين الأداء والقدرة. قد تسبب هذه الإعدادات شاشات سوداء أو مشاكل أخرى في اللعبة. diff --git a/src/android/app/src/main/res/values-ckb/strings.xml b/src/android/app/src/main/res/values-ckb/strings.xml index 25fcf6acef..2ab4af16ad 100644 --- a/src/android/app/src/main/res/values-ckb/strings.xml +++ b/src/android/app/src/main/res/values-ckb/strings.xml @@ -65,8 +65,6 @@ پاشکۆکانی GPU رێندرەر - RAII - ڕێگایەکی بەڕێوەبردنی سەرچاوەکان بە خۆکار لە ڤولکان کە دڵنیای دەکاتەوە لە ئازادکردنی گونجاوی سەرچاوەکان کاتێک کە چیتر پێویستیان نییە، بەڵام لەوانەیە ببێتە هۆی کەوتنی یارییە کۆکراوەکان. CPU و بیرگە حجاب عدن ڕێکخستنە تاقیکارییەکان بۆ باشترکردنی کارایی و توانا. ئەم ڕێکخستنانە لەوانەیە ببێتە هۆی شاشە ڕەشەکان یان کێشەیتری یاری. diff --git a/src/android/app/src/main/res/values-cs/strings.xml b/src/android/app/src/main/res/values-cs/strings.xml index 8d7e274464..ad8f89ffc5 100644 --- a/src/android/app/src/main/res/values-cs/strings.xml +++ b/src/android/app/src/main/res/values-cs/strings.xml @@ -64,8 +64,6 @@ Rozšíření GPU Renderer - RAII - Metoda automatické správy prostředků ve Vulkanu, která zajišťuje správné uvolnění prostředků, když již nejsou potřeba, ale může způsobit pády v balených hrách. CPU a paměť Edenův závoj Experimentální nastavení pro zlepšení výkonu a schopností. Tato nastavení mohou způsobit černé obrazovky nebo další herní problémy. diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index 146fa60fed..cf12a2244f 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml @@ -65,8 +65,6 @@ GPU-Erweiterungen Renderer - RAII - Eine Methode zur automatischen Ressourcenverwaltung in Vulkan, die eine ordnungsgemäße Freigabe von Ressourcen gewährleistet, wenn sie nicht mehr benötigt werden, aber bei gebündelten Spielen Abstürze verursachen kann. CPU und Speicher Edens Schleier Experimentelle Einstellungen zur Verbesserung der Leistung und Funktionalität. Diese Einstellungen können schwarze Bildschirme oder andere Spielprobleme verursachen. diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 28a93f005b..eff563b7ea 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -65,8 +65,6 @@ Extensiones de GPU Renderizador - RAII - Un método de gestión automática de recursos en Vulkan que garantiza la liberación adecuada de recursos cuando ya no son necesarios, pero puede causar fallos en juegos empaquetados. CPU y memoria Velo de Edén Configuraciones experimentales para mejorar el rendimiento y la capacidad. Estas configuraciones pueden causar pantallas negras u otros problemas en el juego. diff --git a/src/android/app/src/main/res/values-fa/strings.xml b/src/android/app/src/main/res/values-fa/strings.xml index b30f67292a..205662b182 100644 --- a/src/android/app/src/main/res/values-fa/strings.xml +++ b/src/android/app/src/main/res/values-fa/strings.xml @@ -65,8 +65,6 @@ افزونه‌های GPU رندرر - RAII - روشی برای مدیریت خودکار منابع در ولکان که تضمین می‌کند منابع به درستی آزاد شوند وقتی دیگر مورد نیاز نیستند، اما ممکن است باعث کرش شدن بازی‌های بسته‌بندی شده شود. پردازنده و حافظه پرده عدن تنظیمات آزمایشی برای بهبود عملکرد و قابلیت. این تنظیمات ممکن است باعث نمایش صفحه سیاه یا سایر مشکلات بازی شود. diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml index f4c741aecc..12c93fd76c 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml @@ -65,8 +65,6 @@ Extensions GPU Rendu - RAII - Une méthode de gestion automatique des ressources dans Vulkan qui assure la libération correcte des ressources lorsqu\'elles ne sont plus nécessaires, mais peut provoquer des plantages dans les jeux regroupés. CPU et mémoire Voile d\'Eden Paramètres expérimentaux pour améliorer les performances et les capacités. Ces paramètres peuvent causer des écrans noirs ou d\'autres problèmes de jeu. diff --git a/src/android/app/src/main/res/values-he/strings.xml b/src/android/app/src/main/res/values-he/strings.xml index 6c5a877a66..9efaf05085 100644 --- a/src/android/app/src/main/res/values-he/strings.xml +++ b/src/android/app/src/main/res/values-he/strings.xml @@ -65,8 +65,6 @@ הרחבות GPU רנדרר - RAII - שיטה לניהול אוטומטי של משאבים ב-Vulkan המבטיחה שחרור נכון של משאבים כאשר הם כבר לא נחוצים, אך עלולה לגרום לקריסות במשחקים מאוגדים. מעבד וזיכרון עדן וייל הגדרות ניסיוניות לשיפור ביצועים ויכולות. הגדרות אלו עלולות לגרום למסכים שחורים או לבעיות אחרות במשחק. diff --git a/src/android/app/src/main/res/values-hu/strings.xml b/src/android/app/src/main/res/values-hu/strings.xml index 6c4f428086..20b222818f 100644 --- a/src/android/app/src/main/res/values-hu/strings.xml +++ b/src/android/app/src/main/res/values-hu/strings.xml @@ -65,8 +65,6 @@ GPU kiterjesztések Megjelenítő - RAII - A Vulkan erőforrás-kezelési módszere, amely biztosítja az erőforrások megfelelő felszabadítását, ha már nincs rájuk szükség, de csomagolt játékok összeomlását okozhatja. CPU és memória Eden Fátyla Kísérleti beállítások a teljesítmény és képesség javításához. Ezek a beállítások fekete képernyőket vagy más játékproblémákat okozhatnak. diff --git a/src/android/app/src/main/res/values-id/strings.xml b/src/android/app/src/main/res/values-id/strings.xml index 8e89132815..83db153bec 100644 --- a/src/android/app/src/main/res/values-id/strings.xml +++ b/src/android/app/src/main/res/values-id/strings.xml @@ -65,8 +65,6 @@ Ekstensi GPU Renderer - RAII - Metode manajemen sumber daya otomatis di Vulkan yang memastikan pelepasan sumber daya yang tepat ketika tidak lagi diperlukan, tetapi dapat menyebabkan crash pada game yang dibundel. CPU dan Memori Eden\'s Veil Pengaturan eksperimental untuk meningkatkan kinerja dan kemampuan. Pengaturan ini dapat menyebabkan layar hitam atau masalah game lainnya. diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml index 339bae8883..7d5c118441 100644 --- a/src/android/app/src/main/res/values-it/strings.xml +++ b/src/android/app/src/main/res/values-it/strings.xml @@ -65,8 +65,6 @@ Estensioni GPU Renderer - RAII - Un metodo di gestione automatica delle risorse in Vulkan che garantisce il corretto rilascio delle risorse quando non sono più necessarie, ma può causare crash nei giochi in bundle. CPU e Memoria Velo di Eden Impostazioni sperimentali per migliorare prestazioni e capacità. Queste impostazioni possono causare schermate nere o altri problemi di gioco. diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml index 4fc9f135e4..0d0c37c78f 100644 --- a/src/android/app/src/main/res/values-ja/strings.xml +++ b/src/android/app/src/main/res/values-ja/strings.xml @@ -65,8 +65,6 @@ GPU拡張機能 レンダラー - RAII - Vulkanにおける自動リソース管理の方法で、不要になったリソースを適切に解放しますが、バンドルされたゲームでクラッシュを引き起こす可能性があります。 CPUとメモリ エデンのベール パフォーマンスと機能を向上させる実験的な設定。これらの設定は黒画面やその他のゲームの問題を引き起こす可能性があります。 diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml index ebad3409d7..ff0af4fc43 100644 --- a/src/android/app/src/main/res/values-ko/strings.xml +++ b/src/android/app/src/main/res/values-ko/strings.xml @@ -65,8 +65,6 @@ GPU 확장 기능 렌더러 - RAII - Vulkan에서 자동 리소스 관리를 위한 방법으로, 더 이상 필요하지 않은 리소스를 적절히 해제하지만 번들된 게임에서 충돌을 일으킬 수 있습니다. CPU 및 메모리 에덴의 베일 성능 및 기능을 향상시키기 위한 실험적 설정. 이 설정은 검은 화면 또는 기타 게임 문제를 일으킬 수 있습니다. diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml index 4a5f6f2efb..313d8797c3 100644 --- a/src/android/app/src/main/res/values-nb/strings.xml +++ b/src/android/app/src/main/res/values-nb/strings.xml @@ -65,8 +65,6 @@ GPU-utvidelser Renderer - RAII - En metode for automatisk ressurshåndtering i Vulkan som sikrer riktig frigjøring av ressurser når de ikke lenger trengs, men kan føre til krasj i bundlede spill. CPU og minne Edens slør Eksperimentelle innstillinger for å forbedre ytelse og funksjonalitet. Disse innstillingene kan forårsake svarte skjermer eller andre spillproblemer. diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml index d1bc789aa9..4ebe24e1c9 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml @@ -65,8 +65,6 @@ Rozszerzenia GPU Renderer - RAII - Metoda automatycznego zarządzania zasobami w Vulkanie, która zapewnia prawidłowe zwalnianie zasobów, gdy nie są już potrzebne, ale może powodować awarie w pakietowych grach. Procesor i pamięć Zasłona Edenu Eksperymentalne ustawienia poprawiające wydajność i możliwości. Te ustawienia mogą powodować czarne ekrany lub inne problemy z grą. diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml index bad95a18e5..2f5f4c4b5b 100644 --- a/src/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml @@ -65,8 +65,6 @@ Extensões da GPU Renderizador - RAII - Um método de gerenciamento automático de recursos no Vulkan que garante a liberação adequada de recursos quando não são mais necessários, mas pode causar falhas em jogos empacotados. CPU e Memória Véu do Éden Configurações experimentais para melhorar desempenho e capacidade. Essas configurações podem causar telas pretas ou outros problemas no jogo. diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml index 0b57eebab6..084fe1c82d 100644 --- a/src/android/app/src/main/res/values-pt-rPT/strings.xml +++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml @@ -65,8 +65,6 @@ Extensões da GPU Renderizador - RAII - Um método de gestão automática de recursos no Vulkan que garante a libertação adequada de recursos quando já não são necessários, mas pode causar falhas em jogos empacotados. CPU e Memória Véu do Éden Definições experimentais para melhorar o desempenho e capacidade. Estas definições podem causar ecrãs pretos ou outros problemas no jogo. diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index 53f49b91cb..0938b9c18f 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml @@ -72,8 +72,6 @@ Настройки в Покров Эдема являются экспериментальными и могут вызывать проблемы. Если ваша игра не запускается, отключите все расширения. В разработке: Пропуск кадров Включите или отключите пропуск кадров для повышения производительности за счет уменьшения количества отображаемых кадров. Эта функция находится в разработке и будет включена в будущих версиях. - RAII - Метод автоматического управления ресурсами в Vulkan, который обеспечивает правильное освобождение ресурсов при их ненадобности, но может вызывать сбои в бандл-играх. Улучшенная синхронизация кадров Обеспечивает плавную и стабильную подачу кадров за счет синхронизации их времени, уменьшая подтормаживания и неравномерную анимацию. Идеально для игр с нестабильным временем кадров или микро-подтормаживаниями во время игры. Ранний релиз ограждений diff --git a/src/android/app/src/main/res/values-sr/strings.xml b/src/android/app/src/main/res/values-sr/strings.xml index 3f2de72f8b..35ef07f3a6 100644 --- a/src/android/app/src/main/res/values-sr/strings.xml +++ b/src/android/app/src/main/res/values-sr/strings.xml @@ -81,8 +81,6 @@ Побољшава текстуру и руковање међуспремника, као и преводилачки слој Маквелл. Подржани од стране неких Вулкана 1.1 ГПУ-а и сви Вулкан 1.2+ ГПУ. Рендерер - RAII - Метод аутоматског управљања ресурсима у Vulkan-у који осигурава правилно ослобађање ресурса када више нису потребни, али може изазвати падове у пакованим играма. Побољшани оквирни пејсинг Осигурава глатку и доследан испоруку оквира синхронизацијом времена између оквира, смањење муцања и неуједначене анимације. Идеално за игре које доживљавају временски оквир нестабилност или микро-штитнике током играња. Ranije oslobađanje ograda diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index b5eee4992c..b22c30999b 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml @@ -70,8 +70,6 @@ Експериментальні налаштування для покращення продуктивності та сумісності. Ці налаштування можуть викликати збої, зокрема чорний екран. Експериментальні налаштування Налаштування Завіси Eden є експериментальними та можуть спричинити проблеми. Якщо ваша гра не запускається — вимкніть усі розширення. - RAII - Метод автоматичного керування ресурсами у Vulkan, який забезпечує правильне звільнення ресурсів після завершення їх використання, проте він може спричинити збої в ігрових збірниках. В розробці: Пропуск кадрів Увімкніть або вимкніть пропуск кадрів для покращення продуктивності за рахунок зменшення кількості візуалізованих кадрів. Ця функція ще розробляється та буде доступна у майбутніх версіях. Покращена синхронізація кадрів diff --git a/src/android/app/src/main/res/values-vi/strings.xml b/src/android/app/src/main/res/values-vi/strings.xml index 1a34509f5c..171b4ea116 100644 --- a/src/android/app/src/main/res/values-vi/strings.xml +++ b/src/android/app/src/main/res/values-vi/strings.xml @@ -65,8 +65,6 @@ Tiện ích mở rộng GPU Trình kết xuất - RAII - Phương pháp quản lý tài nguyên tự động trong Vulkan đảm bảo giải phóng tài nguyên đúng cách khi không còn cần thiết, nhưng có thể gây ra sự cố trong các trò chơi được đóng gói. CPU và Bộ nhớ Mành che của Eden Cài đặt thử nghiệm để cải thiện hiệu suất và khả năng. Những cài đặt này có thể gây ra màn hình đen hoặc các vấn đề khác trong trò chơi. diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index 6fa40b8727..daa2143beb 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml @@ -65,8 +65,6 @@ GPU扩展 渲染器 - RAII - Vulkan中的一种自动资源管理方法,确保在不再需要时正确释放资源,但可能导致捆绑游戏崩溃。 CPU和内存 伊甸之幕 实验性设置以提高性能和能力。这些设置可能会导致黑屏或其他游戏问题。 diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml index b73ec8ccaa..67f3f268a8 100644 --- a/src/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml @@ -65,8 +65,6 @@ GPU擴充功能 渲染器 - RAII - Vulkan中的一種自動資源管理方法,確保在不再需要時正確釋放資源,但可能導致捆綁遊戲崩潰。 CPU與記憶體 伊甸之幕 實驗性設定以提高效能和能力。這些設定可能會導致黑屏或其他遊戲問題。 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index cff0bab2b5..00206a5df5 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -109,8 +109,6 @@ The intensity of the sample shading pass. Higher values improve quality more but also reduce performance to a greater extent. Renderer - RAII - A method of automatic resource management in Vulkan that ensures proper release of resources when they are no longer needed, but may cause crashes in bundled games. Enhanced Frame Pacing Ensures smooth and consistent frame delivery by synchronizing the timing between frames, reducing stuttering and uneven animation. Ideal for games that experience frame timing instability or micro-stutters during gameplay. Release Fences Early diff --git a/src/common/settings.h b/src/common/settings.h index 59e75d3ee0..c6b52f7ba3 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -336,7 +336,6 @@ struct Values { "shader_backend", Category::Renderer, Specialization::RuntimeList}; SwitchableSetting vulkan_device{linkage, 0, "vulkan_device", Category::Renderer, Specialization::RuntimeList}; - SwitchableSetting enable_raii{linkage, false, "enable_raii", Category::Renderer}; #ifdef __ANDROID__ SwitchableSetting frame_interpolation{linkage, true, "frame_interpolation", Category::Renderer, Specialization::RuntimeList}; diff --git a/src/qt_common/shared_translation.cpp b/src/qt_common/shared_translation.cpp index f926f506e7..0b40ca9904 100644 --- a/src/qt_common/shared_translation.cpp +++ b/src/qt_common/shared_translation.cpp @@ -320,12 +320,6 @@ std::unique_ptr InitializeTranslations(QObject* parent) tr("Improves rendering of transparency effects in specific games.")); // Renderer (Extensions) - INSERT(Settings, - enable_raii, - tr("RAII"), - tr("A method of automatic resource management in Vulkan " - "that ensures proper release of resources " - "when they are no longer needed, but may cause crashes in bundled games.")); INSERT(Settings, dyna_state, tr("Extended Dynamic State"), @@ -721,3 +715,4 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) return translations; } } // namespace ConfigurationShared + diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 6f3a0e4cd1..e6e72cdca7 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -164,15 +164,6 @@ try PresentFiltersForAppletCapture) , rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker, scheduler) { - // Initialize RAII wrappers after creating the main objects - if (Settings::values.enable_raii.GetValue()) { - managed_instance = MakeManagedInstance(instance, dld); - if (Settings::values.renderer_debug) { - managed_debug_messenger = MakeManagedDebugUtilsMessenger(debug_messenger, instance, dld); - } - managed_surface = MakeManagedSurface(surface, instance, dld); - } - if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) { turbo_mode.emplace(instance, dld); scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); }); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index c1e6d5db7f..4fb88b29de 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -20,7 +23,6 @@ #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/vulkan_wrapper.h" -#include "video_core/vulkan_common/vulkan_raii.h" namespace Core::Memory { class Memory; @@ -78,16 +80,10 @@ private: // Keep original handles for compatibility with existing code vk::Instance instance; - // RAII wrapper for instance - ManagedInstance managed_instance; vk::DebugUtilsMessenger debug_messenger; - // RAII wrapper for debug messenger - ManagedDebugUtilsMessenger managed_debug_messenger; vk::SurfaceKHR surface; - // RAII wrapper for surface - ManagedSurface managed_surface; Device device; MemoryAllocator memory_allocator; diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 3b35e28c05..fdd2de2379 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -351,6 +351,7 @@ void Swapchain::CreateSemaphores() { void Swapchain::Destroy() { frame_index = 0; present_semaphores.clear(); + render_semaphores.clear(); swapchain.reset(); } diff --git a/src/video_core/vulkan_common/vulkan_raii.h b/src/video_core/vulkan_common/vulkan_raii.h deleted file mode 100644 index cf5e268b68..0000000000 --- a/src/video_core/vulkan_common/vulkan_raii.h +++ /dev/null @@ -1,231 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include - -#include "common/logging/log.h" - -#include "video_core/vulkan_common/vulkan_wrapper.h" - -namespace Vulkan { - -/** - * RAII wrapper for Vulkan resources. - * Automatically manages the lifetime of Vulkan objects using RAII principles. - */ -template -class VulkanRaii { -public: - using DeleterFunc = std::function; - - // Default constructor - creates a null handle - VulkanRaii() : handle{}, deleter{}, dispatch{} {} - - // Constructor with handle and deleter - VulkanRaii(T handle_, DeleterFunc deleter_, const Dispatch& dispatch_, const char* resource_name = "Vulkan resource") - : handle{handle_}, deleter{std::move(deleter_)}, dispatch{dispatch_} { - LOG_DEBUG(Render_Vulkan, "RAII wrapper created for {}", resource_name); - } - - // Move constructor - VulkanRaii(VulkanRaii&& other) noexcept - : handle{std::exchange(other.handle, VK_NULL_HANDLE)}, - deleter{std::move(other.deleter)}, - dispatch{other.dispatch} { - } - - // Move assignment - VulkanRaii& operator=(VulkanRaii&& other) noexcept { - if (this != &other) { - cleanup(); - handle = std::exchange(other.handle, VK_NULL_HANDLE); - deleter = std::move(other.deleter); - dispatch = other.dispatch; - } - return *this; - } - - // Destructor - automatically cleans up the resource - ~VulkanRaii() { - cleanup(); - } - - // Disallow copying - VulkanRaii(const VulkanRaii&) = delete; - VulkanRaii& operator=(const VulkanRaii&) = delete; - - // Get the underlying handle - T get() const noexcept { - return handle; - } - - // Check if the handle is valid - bool valid() const noexcept { - return handle != VK_NULL_HANDLE; - } - - // Release ownership of the handle without destroying it - T release() noexcept { - return std::exchange(handle, VK_NULL_HANDLE); - } - - // Reset the handle (destroying the current one if it exists) - void reset(T new_handle = VK_NULL_HANDLE, DeleterFunc new_deleter = {}) { - cleanup(); - handle = new_handle; - deleter = std::move(new_deleter); - } - - // Implicit conversion to handle type - operator T() const noexcept { - return handle; - } - - // Dereference operator for pointer-like access - T operator->() const noexcept { - return handle; - } - -private: - // Optimized cleanup function - void cleanup() noexcept { - if (handle != VK_NULL_HANDLE && deleter) { - deleter(handle, dispatch); - handle = VK_NULL_HANDLE; - } - } - - T handle; - DeleterFunc deleter; - Dispatch dispatch; -}; - -// Common type aliases for Vulkan RAII wrappers with clearer names -using ManagedInstance = VulkanRaii; -using ManagedDevice = VulkanRaii; -using ManagedSurface = VulkanRaii; -using ManagedSwapchain = VulkanRaii; -using ManagedCommandPool = VulkanRaii; -using ManagedBuffer = VulkanRaii; -using ManagedImage = VulkanRaii; -using ManagedImageView = VulkanRaii; -using ManagedSampler = VulkanRaii; -using ManagedShaderModule = VulkanRaii; -using ManagedPipeline = VulkanRaii; -using ManagedPipelineLayout = VulkanRaii; -using ManagedDescriptorSetLayout = VulkanRaii; -using ManagedDescriptorPool = VulkanRaii; -using ManagedSemaphore = VulkanRaii; -using ManagedFence = VulkanRaii; -using ManagedDebugUtilsMessenger = VulkanRaii; - -// Helper functions to create RAII wrappers - -/** - * Creates an RAII wrapper for a Vulkan instance - */ -inline ManagedInstance MakeManagedInstance(const vk::Instance& instance, const vk::InstanceDispatch& dispatch) { - auto deleter = [](VkInstance handle, const vk::InstanceDispatch& dld) { - dld.vkDestroyInstance(handle, nullptr); - }; - return ManagedInstance(*instance, deleter, dispatch, "VkInstance"); -} - -/** - * Creates an RAII wrapper for a Vulkan device - */ -inline ManagedDevice MakeManagedDevice(const vk::Device& device, const vk::DeviceDispatch& dispatch) { - auto deleter = [](VkDevice handle, const vk::DeviceDispatch& dld) { - dld.vkDestroyDevice(handle, nullptr); - }; - return ManagedDevice(*device, deleter, dispatch, "VkDevice"); -} - -/** - * Creates an RAII wrapper for a Vulkan surface - */ -inline ManagedSurface MakeManagedSurface(const vk::SurfaceKHR& surface, const vk::Instance& instance, const vk::InstanceDispatch& dispatch) { - auto deleter = [instance_ptr = *instance](VkSurfaceKHR handle, const vk::InstanceDispatch& dld) { - dld.vkDestroySurfaceKHR(instance_ptr, handle, nullptr); - }; - return ManagedSurface(*surface, deleter, dispatch, "VkSurfaceKHR"); -} - -/** - * Creates an RAII wrapper for a Vulkan debug messenger - */ -inline ManagedDebugUtilsMessenger MakeManagedDebugUtilsMessenger(const vk::DebugUtilsMessenger& messenger, - const vk::Instance& instance, - const vk::InstanceDispatch& dispatch) { - auto deleter = [instance_ptr = *instance](VkDebugUtilsMessengerEXT handle, const vk::InstanceDispatch& dld) { - dld.vkDestroyDebugUtilsMessengerEXT(instance_ptr, handle, nullptr); - }; - return ManagedDebugUtilsMessenger(*messenger, deleter, dispatch, "VkDebugUtilsMessengerEXT"); -} - -/** - * Creates an RAII wrapper for a Vulkan swapchain - */ -inline ManagedSwapchain MakeManagedSwapchain(VkSwapchainKHR swapchain_handle, VkDevice device_handle, const vk::DeviceDispatch& dispatch) { - auto deleter = [device_handle](VkSwapchainKHR handle, const vk::DeviceDispatch& dld) { - dld.vkDestroySwapchainKHR(device_handle, handle, nullptr); - }; - return ManagedSwapchain(swapchain_handle, deleter, dispatch, "VkSwapchainKHR"); -} - -/** - * Creates an RAII wrapper for a Vulkan buffer - */ -inline ManagedBuffer MakeManagedBuffer(VkBuffer buffer_handle, VkDevice device_handle, const vk::DeviceDispatch& dispatch) { - auto deleter = [device_handle](VkBuffer handle, const vk::DeviceDispatch& dld) { - dld.vkDestroyBuffer(device_handle, handle, nullptr); - }; - return ManagedBuffer(buffer_handle, deleter, dispatch, "VkBuffer"); -} - -/** - * Creates an RAII wrapper for a Vulkan image - */ -inline ManagedImage MakeManagedImage(VkImage image_handle, VkDevice device_handle, const vk::DeviceDispatch& dispatch) { - auto deleter = [device_handle](VkImage handle, const vk::DeviceDispatch& dld) { - dld.vkDestroyImage(device_handle, handle, nullptr); - }; - return ManagedImage(image_handle, deleter, dispatch, "VkImage"); -} - -/** - * Creates an RAII wrapper for a Vulkan image view - */ -inline ManagedImageView MakeManagedImageView(VkImageView view_handle, VkDevice device_handle, const vk::DeviceDispatch& dispatch) { - auto deleter = [device_handle](VkImageView handle, const vk::DeviceDispatch& dld) { - dld.vkDestroyImageView(device_handle, handle, nullptr); - }; - return ManagedImageView(view_handle, deleter, dispatch, "VkImageView"); -} - -/** - * Creates an RAII wrapper for a Vulkan semaphore - */ -inline ManagedSemaphore MakeManagedSemaphore(VkSemaphore semaphore_handle, VkDevice device_handle, const vk::DeviceDispatch& dispatch) { - auto deleter = [device_handle](VkSemaphore handle, const vk::DeviceDispatch& dld) { - dld.vkDestroySemaphore(device_handle, handle, nullptr); - }; - return ManagedSemaphore(semaphore_handle, deleter, dispatch, "VkSemaphore"); -} - -/** - * Creates an RAII wrapper for a Vulkan fence - */ -inline ManagedFence MakeManagedFence(VkFence fence_handle, VkDevice device_handle, const vk::DeviceDispatch& dispatch) { - auto deleter = [device_handle](VkFence handle, const vk::DeviceDispatch& dld) { - dld.vkDestroyFence(device_handle, handle, nullptr); - }; - return ManagedFence(fence_handle, deleter, dispatch, "VkFence"); -} - -} // namespace Vulkan \ No newline at end of file diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 949b91499d..b77d01711a 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -12,7 +12,6 @@ #include "common/common_types.h" #include "common/logging/log.h" -#include "common/settings.h" #include "video_core/vulkan_common/vk_enum_string_helper.h" #include "video_core/vulkan_common/vma.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -311,10 +310,7 @@ const char* Exception::what() const noexcept { } void Destroy(VkInstance instance, const InstanceDispatch& dld) noexcept { - // FIXME: A double free occurs here if RAII is enabled. - if (!Settings::values.enable_raii.GetValue()) { - dld.vkDestroyInstance(instance, nullptr); - } + dld.vkDestroyInstance(instance, nullptr); } void Destroy(VkDevice device, const InstanceDispatch& dld) noexcept { @@ -417,10 +413,7 @@ void Destroy(VkInstance instance, VkDebugReportCallbackEXT handle, } void Destroy(VkInstance instance, VkSurfaceKHR handle, const InstanceDispatch& dld) noexcept { - // FIXME: A double free occurs here if RAII is enabled. - if (!Settings::values.enable_raii.GetValue()) { - dld.vkDestroySurfaceKHR(instance, handle, nullptr); - } + dld.vkDestroySurfaceKHR(instance, handle, nullptr); } VkResult Free(VkDevice device, VkDescriptorPool handle, Span sets, diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 6501094f05..39396b3279 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -516,7 +516,7 @@ public: } /// Returns true when there's a held object. - operator bool() const noexcept { + explicit operator bool() const noexcept { return handle != nullptr; } @@ -627,7 +627,7 @@ class Instance : public Handle { public: /// Creates a Vulkan instance. /// @throw Exception on initialization error. - static Instance Create(u32 version, Span layers, Span extensions, + [[nodiscard]] static Instance Create(u32 version, Span layers, Span extensions, InstanceDispatch& dispatch); /// Enumerates physical devices. @@ -637,12 +637,12 @@ public: /// Creates a debug callback messenger. /// @throw Exception on creation failure. - DebugUtilsMessenger CreateDebugUtilsMessenger( + [[nodiscard]] DebugUtilsMessenger CreateDebugUtilsMessenger( const VkDebugUtilsMessengerCreateInfoEXT& create_info) const; /// Creates a debug report callback. /// @throw Exception on creation failure. - DebugReportCallback CreateDebugReportCallback( + [[nodiscard]] DebugReportCallback CreateDebugReportCallback( const VkDebugReportCallbackCreateInfoEXT& create_info) const; /// Returns dispatch table. @@ -986,58 +986,60 @@ class Device : public Handle { using Handle::Handle; public: - static Device Create(VkPhysicalDevice physical_device, Span queues_ci, - Span enabled_extensions, const void* next, - DeviceDispatch& dispatch); + [[nodiscard]] static Device Create(VkPhysicalDevice physical_device, + Span queues_ci, + Span enabled_extensions, const void* next, + DeviceDispatch& dispatch); - Queue GetQueue(u32 family_index) const noexcept; + [[nodiscard]] Queue GetQueue(u32 family_index) const noexcept; - BufferView CreateBufferView(const VkBufferViewCreateInfo& ci) const; + [[nodiscard]] BufferView CreateBufferView(const VkBufferViewCreateInfo& ci) const; - ImageView CreateImageView(const VkImageViewCreateInfo& ci) const; + [[nodiscard]] ImageView CreateImageView(const VkImageViewCreateInfo& ci) const; - Semaphore CreateSemaphore() const; + [[nodiscard]] Semaphore CreateSemaphore() const; - Semaphore CreateSemaphore(const VkSemaphoreCreateInfo& ci) const; + [[nodiscard]] Semaphore CreateSemaphore(const VkSemaphoreCreateInfo& ci) const; - Fence CreateFence(const VkFenceCreateInfo& ci) const; + [[nodiscard]] Fence CreateFence(const VkFenceCreateInfo& ci) const; - DescriptorPool CreateDescriptorPool(const VkDescriptorPoolCreateInfo& ci) const; + [[nodiscard]] DescriptorPool CreateDescriptorPool(const VkDescriptorPoolCreateInfo& ci) const; - RenderPass CreateRenderPass(const VkRenderPassCreateInfo& ci) const; + [[nodiscard]] RenderPass CreateRenderPass(const VkRenderPassCreateInfo& ci) const; - DescriptorSetLayout CreateDescriptorSetLayout(const VkDescriptorSetLayoutCreateInfo& ci) const; + [[nodiscard]] DescriptorSetLayout CreateDescriptorSetLayout( + const VkDescriptorSetLayoutCreateInfo& ci) const; - PipelineCache CreatePipelineCache(const VkPipelineCacheCreateInfo& ci) const; + [[nodiscard]] PipelineCache CreatePipelineCache(const VkPipelineCacheCreateInfo& ci) const; - PipelineLayout CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci) const; + [[nodiscard]] PipelineLayout CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci) const; - Pipeline CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci, - VkPipelineCache cache = nullptr) const; + [[nodiscard]] Pipeline CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci, + VkPipelineCache cache = nullptr) const; - Pipeline CreateComputePipeline(const VkComputePipelineCreateInfo& ci, - VkPipelineCache cache = nullptr) const; + [[nodiscard]] Pipeline CreateComputePipeline(const VkComputePipelineCreateInfo& ci, + VkPipelineCache cache = nullptr) const; - Sampler CreateSampler(const VkSamplerCreateInfo& ci) const; + [[nodiscard]] Sampler CreateSampler(const VkSamplerCreateInfo& ci) const; - Framebuffer CreateFramebuffer(const VkFramebufferCreateInfo& ci) const; + [[nodiscard]] Framebuffer CreateFramebuffer(const VkFramebufferCreateInfo& ci) const; - CommandPool CreateCommandPool(const VkCommandPoolCreateInfo& ci) const; + [[nodiscard]] CommandPool CreateCommandPool(const VkCommandPoolCreateInfo& ci) const; - DescriptorUpdateTemplate CreateDescriptorUpdateTemplate( + [[nodiscard]] DescriptorUpdateTemplate CreateDescriptorUpdateTemplate( const VkDescriptorUpdateTemplateCreateInfo& ci) const; - QueryPool CreateQueryPool(const VkQueryPoolCreateInfo& ci) const; + [[nodiscard]] QueryPool CreateQueryPool(const VkQueryPoolCreateInfo& ci) const; - ShaderModule CreateShaderModule(const VkShaderModuleCreateInfo& ci) const; + [[nodiscard]] ShaderModule CreateShaderModule(const VkShaderModuleCreateInfo& ci) const; - Event CreateEvent() const; + [[nodiscard]] Event CreateEvent() const; - SwapchainKHR CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const; + [[nodiscard]] SwapchainKHR CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const; - DeviceMemory TryAllocateMemory(const VkMemoryAllocateInfo& ai) const noexcept; + [[nodiscard]] DeviceMemory TryAllocateMemory(const VkMemoryAllocateInfo& ai) const noexcept; - DeviceMemory AllocateMemory(const VkMemoryAllocateInfo& ai) const; + [[nodiscard]] DeviceMemory AllocateMemory(const VkMemoryAllocateInfo& ai) const; VkMemoryRequirements GetBufferMemoryRequirements(VkBuffer buffer, void* pnext = nullptr) const noexcept; From 8078990b9b95c5bb2c432abc9012c2738f8a451a Mon Sep 17 00:00:00 2001 From: Ribbit Date: Wed, 8 Oct 2025 05:39:08 +0200 Subject: [PATCH 07/15] [vk] Fast UBO: fix tracking, resize heuristics, add debug guard (#2695) Co-authored-by: Ribbit Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2695 Reviewed-by: CamilleLaVey Co-authored-by: Ribbit Co-committed-by: Ribbit --- src/core/device_memory_manager.h | 6 +++ src/core/device_memory_manager.inc | 26 ++++++++++++ src/video_core/buffer_cache/buffer_cache.h | 42 ++++++++++--------- .../buffer_cache/buffer_cache_base.h | 5 ++- .../vk_staging_buffer_pool.cpp | 26 +++++++----- .../renderer_vulkan/vk_staging_buffer_pool.h | 4 ++ 6 files changed, 76 insertions(+), 33 deletions(-) diff --git a/src/core/device_memory_manager.h b/src/core/device_memory_manager.h index 6dcf7bb228..192c6e5c01 100644 --- a/src/core/device_memory_manager.h +++ b/src/core/device_memory_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -109,6 +112,9 @@ public: void ReadBlock(DAddr address, void* dest_pointer, size_t size); void ReadBlockUnsafe(DAddr address, void* dest_pointer, size_t size); +#ifdef YUZU_DEBUG + bool ReadBlockFastChecked(DAddr address, void* dest_pointer, size_t size); +#endif void WriteBlock(DAddr address, const void* src_pointer, size_t size); void WriteBlockUnsafe(DAddr address, const void* src_pointer, size_t size); diff --git a/src/core/device_memory_manager.inc b/src/core/device_memory_manager.inc index 52dff5df9a..3629579c09 100644 --- a/src/core/device_memory_manager.inc +++ b/src/core/device_memory_manager.inc @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -467,6 +470,29 @@ void DeviceMemoryManager::ReadBlockUnsafe(DAddr address, void* dest_poin }); } +#ifdef YUZU_DEBUG +template +bool DeviceMemoryManager::ReadBlockFastChecked(DAddr address, void* dest_pointer, + size_t size) { + bool success = true; + WalkBlock( + address, size, + [&](size_t copy_amount, DAddr current_vaddr) { + LOG_CRITICAL(Render, "DeviceMemory OOB/unmapped: addr=0x{:x} size={}", current_vaddr, + size); + std::memset(dest_pointer, 0, copy_amount); + success = false; + }, + [&](size_t copy_amount, const u8* const src_ptr) { + std::memcpy(dest_pointer, src_ptr, copy_amount); + }, + [&](const std::size_t copy_amount) { + dest_pointer = static_cast(dest_pointer) + copy_amount; + }); + return success; +} +#endif + template void DeviceMemoryManager::WriteBlockUnsafe(DAddr address, const void* src_pointer, size_t size) { diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 5223afe937..388c8034c5 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -386,11 +386,10 @@ void BufferCache

::BindHostComputeBuffers() { template void BufferCache

::SetUniformBuffersState(const std::array& mask, const UniformBufferSizes* sizes) { - if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { - if (channel_state->enabled_uniform_buffer_masks != mask) { - if constexpr (IS_OPENGL) { - channel_state->fast_bound_uniform_buffers.fill(0); - } + const bool mask_changed = channel_state->enabled_uniform_buffer_masks != mask; + if (mask_changed) { + channel_state->fast_bound_uniform_buffers.fill(0); + if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { channel_state->dirty_uniform_buffers.fill(~u32{0}); channel_state->uniform_buffer_binding_sizes.fill({}); } @@ -806,7 +805,7 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 channel_state->uniform_buffer_binding_sizes[stage][binding_index] != size; if (should_fast_bind) { // We only have to bind when the currently bound buffer is not the fast version - channel_state->fast_bound_uniform_buffers[stage] |= 1U << binding_index; + channel_state->fast_bound_uniform_buffers[stage] |= 1u << binding_index; channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; runtime.BindFastUniformBuffer(stage, binding_index, size); } @@ -815,13 +814,22 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 return; } } - if constexpr (IS_OPENGL) { - channel_state->fast_bound_uniform_buffers[stage] |= 1U << binding_index; - channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; - } + channel_state->fast_bound_uniform_buffers[stage] |= 1u << binding_index; + channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; // Stream buffer path to avoid stalling on non-Nvidia drivers or Vulkan const std::span span = runtime.BindMappedUniformBuffer(stage, binding_index, size); +#ifdef YUZU_DEBUG + ASSERT(binding_index < NUM_GRAPHICS_UNIFORM_BUFFERS); + ASSERT(span.size() >= size && "UBO stream span too small"); + if (!device_memory.ReadBlockFastChecked(device_addr, span.data(), size)) { + LOG_CRITICAL(Render, "DeviceMemory OOB/unmapped: addr=0x{:x} size={}", device_addr, size); + channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index); + ASSERT(false); + return; + } +#else device_memory.ReadBlockUnsafe(device_addr, span.data(), size); +#endif return; } // Classic cached path @@ -830,7 +838,8 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 } // Skip binding if it's not needed and if the bound buffer is not the fast version // This exists to avoid instances where the fast buffer is bound and a GPU write happens - needs_bind |= HasFastUniformBufferBound(stage, binding_index); + const bool was_fast_bound = HasFastUniformBufferBound(stage, binding_index); + needs_bind |= was_fast_bound; if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { needs_bind |= channel_state->uniform_buffer_binding_sizes[stage][binding_index] != size; } @@ -839,9 +848,6 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 } const u32 offset = buffer.Offset(device_addr); if constexpr (IS_OPENGL) { - // Fast buffer will be unbound - channel_state->fast_bound_uniform_buffers[stage] &= ~(1U << binding_index); - // Mark the index as dirty if offset doesn't match const bool is_copy_bind = offset != 0 && !runtime.SupportsNonZeroUniformOffset(); channel_state->dirty_uniform_buffers[stage] |= (is_copy_bind ? 1U : 0U) << index; @@ -855,6 +861,7 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 } else { runtime.BindUniformBuffer(buffer, offset, size); } + channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index); } template @@ -1789,12 +1796,7 @@ std::span BufferCache

::ImmediateBuffer(size_t wanted_capacity) { template bool BufferCache

::HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept { - if constexpr (IS_OPENGL) { - return ((channel_state->fast_bound_uniform_buffers[stage] >> binding_index) & 1) != 0; - } else { - // Only OpenGL has fast uniform buffers - return false; - } + return ((channel_state->fast_bound_uniform_buffers[stage] >> binding_index) & 1u) != 0; } template diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 486d19fb79..09631ffd83 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -53,6 +53,7 @@ constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8; constexpr u32 NUM_STORAGE_BUFFERS = 16; constexpr u32 NUM_TEXTURE_BUFFERS = 32; constexpr u32 NUM_STAGES = 5; +static_assert(NUM_GRAPHICS_UNIFORM_BUFFERS <= 32, "fast bitmask must fit u32"); using UniformBufferSizes = std::array, NUM_STAGES>; using ComputeUniformBufferSizes = std::array; @@ -137,8 +138,8 @@ public: u32 written_compute_texture_buffers = 0; u32 image_compute_texture_buffers = 0; - std::array uniform_cache_hits{}; - std::array uniform_cache_shots{}; + std::array uniform_cache_hits{}; + std::array uniform_cache_shots{}; u32 uniform_buffer_skip_cache_size = DEFAULT_SKIP_CACHE_SIZE; diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index ecc4f77dc7..0fbe707b04 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -25,12 +25,12 @@ namespace { using namespace Common::Literals; -// Maximum potential alignment of a Vulkan buffer -constexpr VkDeviceSize MAX_ALIGNMENT = 256; +// Minimum alignment we want to enforce for the streaming ring +constexpr VkDeviceSize MIN_STREAM_ALIGNMENT = 256; // Stream buffer size in bytes constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB; -size_t GetStreamBufferSize(const Device& device) { +size_t GetStreamBufferSize(const Device& device, VkDeviceSize alignment) { VkDeviceSize size{0}; if (device.HasDebuggingToolAttached()) { bool found_heap = false; @@ -53,8 +53,9 @@ size_t GetStreamBufferSize(const Device& device) { // Clamp to the configured maximum, align up for safety, and ensure a sane minimum so // region_size (stream_buffer_size / NUM_SYNCS) never becomes zero. - const VkDeviceSize aligned = (std::min)(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE); - const VkDeviceSize min_size = MAX_ALIGNMENT * StagingBufferPool::NUM_SYNCS; + const VkDeviceSize aligned = + (std::min)(Common::AlignUp(size, alignment), MAX_STREAM_BUFFER_SIZE); + const VkDeviceSize min_size = alignment * StagingBufferPool::NUM_SYNCS; return static_cast((std::max)(aligned, min_size)); } } // Anonymous namespace @@ -62,8 +63,10 @@ size_t GetStreamBufferSize(const Device& device) { StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_) : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_}, - stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size / - StagingBufferPool::NUM_SYNCS} { + stream_alignment{std::max(device_.GetUniformBufferAlignment(), + MIN_STREAM_ALIGNMENT)}, + stream_buffer_size{GetStreamBufferSize(device_, stream_alignment)}, + region_size{stream_buffer_size / StagingBufferPool::NUM_SYNCS} { VkBufferCreateInfo stream_ci = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, @@ -116,10 +119,11 @@ void StagingBufferPool::TickFrame() { } StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { - const size_t aligned_size = Common::AlignUp(size, MAX_ALIGNMENT); + const size_t alignment = static_cast(stream_alignment); + const size_t aligned_size = Common::AlignUp(size, alignment); const bool wraps = iterator + size >= stream_buffer_size; const size_t new_iterator = - wraps ? aligned_size : Common::AlignUp(iterator + size, MAX_ALIGNMENT); + wraps ? aligned_size : Common::AlignUp(iterator + size, alignment); const size_t begin_region = wraps ? 0 : Region(iterator); const size_t last_byte = new_iterator == 0 ? 0 : new_iterator - 1; const size_t end_region = (std::min)(Region(last_byte) + 1, NUM_SYNCS); @@ -145,7 +149,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { current_tick); used_iterator = 0; iterator = 0; - free_iterator = size; + free_iterator = aligned_size; const size_t head_last_byte = aligned_size == 0 ? 0 : aligned_size - 1; const size_t head_end_region = (std::min)(Region(head_last_byte) + 1, NUM_SYNCS); if (AreRegionsActive(0, head_end_region)) { @@ -160,7 +164,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { iterator = new_iterator; if (!wraps) { - free_iterator = (std::max)(free_iterator, offset + size); + free_iterator = (std::max)(free_iterator, offset + aligned_size); } return StagingBufferRef{ diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h index f63a203272..5c40ca069f 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -102,6 +105,7 @@ private: MemoryAllocator& memory_allocator; Scheduler& scheduler; + VkDeviceSize stream_alignment; vk::Buffer stream_buffer; std::span stream_pointer; VkDeviceSize stream_buffer_size; From 954c17c18a5e6a39c4f7edd80bbb4a1e0507d4e0 Mon Sep 17 00:00:00 2001 From: crueter Date: Wed, 8 Oct 2025 06:39:58 +0200 Subject: [PATCH 08/15] [frontend] change order of filters to match append rules (#2696) otherwise, FSR would change to Gaussian etc. in general, sans resolution, new enum values should always be appended Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2696 Reviewed-by: CamilleLaVey Reviewed-by: Lizzie --- src/android/app/src/main/res/values/arrays.xml | 8 ++++---- src/common/settings_enums.h | 2 +- src/qt_common/shared_translation.cpp | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 7e44750909..2150d401db 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -253,16 +253,16 @@ @string/scaling_filter_nearest_neighbor @string/scaling_filter_bilinear @string/scaling_filter_bicubic - @string/scaling_filter_zero_tangent - @string/scaling_filter_bspline - @string/scaling_filter_mitchell - @string/scaling_filter_spline1 @string/scaling_filter_gaussian @string/scaling_filter_lanczos @string/scaling_filter_scale_force @string/scaling_filter_fsr @string/scaling_filter_area @string/scaling_filter_mmpx + @string/scaling_filter_zero_tangent + @string/scaling_filter_bspline + @string/scaling_filter_mitchell + @string/scaling_filter_spline1 diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 3fcdf08256..ccf6f1cfb2 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -143,7 +143,7 @@ ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never); ENUM(FullscreenMode, Borderless, Exclusive); ENUM(NvdecEmulation, Off, Cpu, Gpu); ENUM(ResolutionSetup, Res1_4X, Res1_2X, Res3_4X, Res1X, Res5_4X, Res3_2X, Res2X, Res3X, Res4X, Res5X, Res6X, Res7X, Res8X); -ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, ZeroTangent, BSpline, Mitchell, Spline1, Gaussian, Lanczos, ScaleForce, Fsr, Area, Mmpx, MaxEnum); +ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, Lanczos, ScaleForce, Fsr, Area, ZeroTangent, BSpline, Mitchell, Spline1, Mmpx, MaxEnum); ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum); ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch); ENUM(ConsoleMode, Handheld, Docked); diff --git a/src/qt_common/shared_translation.cpp b/src/qt_common/shared_translation.cpp index 0b40ca9904..054d28e8e2 100644 --- a/src/qt_common/shared_translation.cpp +++ b/src/qt_common/shared_translation.cpp @@ -549,16 +549,16 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) PAIR(ScalingFilter, NearestNeighbor, tr("Nearest Neighbor")), PAIR(ScalingFilter, Bilinear, tr("Bilinear")), PAIR(ScalingFilter, Bicubic, tr("Bicubic")), - PAIR(ScalingFilter, ZeroTangent, tr("Zero-Tangent")), - PAIR(ScalingFilter, BSpline, tr("B-Spline")), - PAIR(ScalingFilter, Mitchell, tr("Mitchell")), - PAIR(ScalingFilter, Spline1, tr("Spline-1")), PAIR(ScalingFilter, Gaussian, tr("Gaussian")), PAIR(ScalingFilter, Lanczos, tr("Lanczos")), PAIR(ScalingFilter, ScaleForce, tr("ScaleForce")), PAIR(ScalingFilter, Fsr, tr("AMD FidelityFX™️ Super Resolution")), PAIR(ScalingFilter, Area, tr("Area")), PAIR(ScalingFilter, Mmpx, tr("MMPX")), + PAIR(ScalingFilter, ZeroTangent, tr("Zero-Tangent")), + PAIR(ScalingFilter, BSpline, tr("B-Spline")), + PAIR(ScalingFilter, Mitchell, tr("Mitchell")), + PAIR(ScalingFilter, Spline1, tr("Spline-1")), }}); translations->insert({Settings::EnumMetadata::Index(), { From 3c6ef765af9b946240cedf053e593c0fc86a3f01 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 9 Oct 2025 21:37:27 +0200 Subject: [PATCH 09/15] revert [vk] Fast UBO: fix tracking, resize heuristics, add debug guard (#2695) (#2706) revert [vk] Fast UBO: fix tracking, resize heuristics, add debug guard (#2695) Well, stuff showed up after testing phase, that showed us this change break SMO and some mods after being merged directly into master, we will keep stuying why happens this and add a better handling later. Co-authored-by: Ribbit Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2695 Reviewed-by: CamilleLaVey Co-authored-by: Ribbit Co-committed-by: Ribbit Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2706 Co-authored-by: CamilleLaVey Co-committed-by: CamilleLaVey --- src/core/device_memory_manager.h | 6 --- src/core/device_memory_manager.inc | 26 ------------ src/video_core/buffer_cache/buffer_cache.h | 42 +++++++++---------- .../buffer_cache/buffer_cache_base.h | 5 +-- .../vk_staging_buffer_pool.cpp | 26 +++++------- .../renderer_vulkan/vk_staging_buffer_pool.h | 4 -- 6 files changed, 33 insertions(+), 76 deletions(-) diff --git a/src/core/device_memory_manager.h b/src/core/device_memory_manager.h index 192c6e5c01..6dcf7bb228 100644 --- a/src/core/device_memory_manager.h +++ b/src/core/device_memory_manager.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -112,9 +109,6 @@ public: void ReadBlock(DAddr address, void* dest_pointer, size_t size); void ReadBlockUnsafe(DAddr address, void* dest_pointer, size_t size); -#ifdef YUZU_DEBUG - bool ReadBlockFastChecked(DAddr address, void* dest_pointer, size_t size); -#endif void WriteBlock(DAddr address, const void* src_pointer, size_t size); void WriteBlockUnsafe(DAddr address, const void* src_pointer, size_t size); diff --git a/src/core/device_memory_manager.inc b/src/core/device_memory_manager.inc index 3629579c09..52dff5df9a 100644 --- a/src/core/device_memory_manager.inc +++ b/src/core/device_memory_manager.inc @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -470,29 +467,6 @@ void DeviceMemoryManager::ReadBlockUnsafe(DAddr address, void* dest_poin }); } -#ifdef YUZU_DEBUG -template -bool DeviceMemoryManager::ReadBlockFastChecked(DAddr address, void* dest_pointer, - size_t size) { - bool success = true; - WalkBlock( - address, size, - [&](size_t copy_amount, DAddr current_vaddr) { - LOG_CRITICAL(Render, "DeviceMemory OOB/unmapped: addr=0x{:x} size={}", current_vaddr, - size); - std::memset(dest_pointer, 0, copy_amount); - success = false; - }, - [&](size_t copy_amount, const u8* const src_ptr) { - std::memcpy(dest_pointer, src_ptr, copy_amount); - }, - [&](const std::size_t copy_amount) { - dest_pointer = static_cast(dest_pointer) + copy_amount; - }); - return success; -} -#endif - template void DeviceMemoryManager::WriteBlockUnsafe(DAddr address, const void* src_pointer, size_t size) { diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 388c8034c5..5223afe937 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -386,10 +386,11 @@ void BufferCache

::BindHostComputeBuffers() { template void BufferCache

::SetUniformBuffersState(const std::array& mask, const UniformBufferSizes* sizes) { - const bool mask_changed = channel_state->enabled_uniform_buffer_masks != mask; - if (mask_changed) { - channel_state->fast_bound_uniform_buffers.fill(0); - if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { + if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { + if (channel_state->enabled_uniform_buffer_masks != mask) { + if constexpr (IS_OPENGL) { + channel_state->fast_bound_uniform_buffers.fill(0); + } channel_state->dirty_uniform_buffers.fill(~u32{0}); channel_state->uniform_buffer_binding_sizes.fill({}); } @@ -805,7 +806,7 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 channel_state->uniform_buffer_binding_sizes[stage][binding_index] != size; if (should_fast_bind) { // We only have to bind when the currently bound buffer is not the fast version - channel_state->fast_bound_uniform_buffers[stage] |= 1u << binding_index; + channel_state->fast_bound_uniform_buffers[stage] |= 1U << binding_index; channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; runtime.BindFastUniformBuffer(stage, binding_index, size); } @@ -814,22 +815,13 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 return; } } - channel_state->fast_bound_uniform_buffers[stage] |= 1u << binding_index; - channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; + if constexpr (IS_OPENGL) { + channel_state->fast_bound_uniform_buffers[stage] |= 1U << binding_index; + channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; + } // Stream buffer path to avoid stalling on non-Nvidia drivers or Vulkan const std::span span = runtime.BindMappedUniformBuffer(stage, binding_index, size); -#ifdef YUZU_DEBUG - ASSERT(binding_index < NUM_GRAPHICS_UNIFORM_BUFFERS); - ASSERT(span.size() >= size && "UBO stream span too small"); - if (!device_memory.ReadBlockFastChecked(device_addr, span.data(), size)) { - LOG_CRITICAL(Render, "DeviceMemory OOB/unmapped: addr=0x{:x} size={}", device_addr, size); - channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index); - ASSERT(false); - return; - } -#else device_memory.ReadBlockUnsafe(device_addr, span.data(), size); -#endif return; } // Classic cached path @@ -838,8 +830,7 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 } // Skip binding if it's not needed and if the bound buffer is not the fast version // This exists to avoid instances where the fast buffer is bound and a GPU write happens - const bool was_fast_bound = HasFastUniformBufferBound(stage, binding_index); - needs_bind |= was_fast_bound; + needs_bind |= HasFastUniformBufferBound(stage, binding_index); if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { needs_bind |= channel_state->uniform_buffer_binding_sizes[stage][binding_index] != size; } @@ -848,6 +839,9 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 } const u32 offset = buffer.Offset(device_addr); if constexpr (IS_OPENGL) { + // Fast buffer will be unbound + channel_state->fast_bound_uniform_buffers[stage] &= ~(1U << binding_index); + // Mark the index as dirty if offset doesn't match const bool is_copy_bind = offset != 0 && !runtime.SupportsNonZeroUniformOffset(); channel_state->dirty_uniform_buffers[stage] |= (is_copy_bind ? 1U : 0U) << index; @@ -861,7 +855,6 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 } else { runtime.BindUniformBuffer(buffer, offset, size); } - channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index); } template @@ -1796,7 +1789,12 @@ std::span BufferCache

::ImmediateBuffer(size_t wanted_capacity) { template bool BufferCache

::HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept { - return ((channel_state->fast_bound_uniform_buffers[stage] >> binding_index) & 1u) != 0; + if constexpr (IS_OPENGL) { + return ((channel_state->fast_bound_uniform_buffers[stage] >> binding_index) & 1) != 0; + } else { + // Only OpenGL has fast uniform buffers + return false; + } } template diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 09631ffd83..486d19fb79 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -53,7 +53,6 @@ constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8; constexpr u32 NUM_STORAGE_BUFFERS = 16; constexpr u32 NUM_TEXTURE_BUFFERS = 32; constexpr u32 NUM_STAGES = 5; -static_assert(NUM_GRAPHICS_UNIFORM_BUFFERS <= 32, "fast bitmask must fit u32"); using UniformBufferSizes = std::array, NUM_STAGES>; using ComputeUniformBufferSizes = std::array; @@ -138,8 +137,8 @@ public: u32 written_compute_texture_buffers = 0; u32 image_compute_texture_buffers = 0; - std::array uniform_cache_hits{}; - std::array uniform_cache_shots{}; + std::array uniform_cache_hits{}; + std::array uniform_cache_shots{}; u32 uniform_buffer_skip_cache_size = DEFAULT_SKIP_CACHE_SIZE; diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index 0fbe707b04..ecc4f77dc7 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -25,12 +25,12 @@ namespace { using namespace Common::Literals; -// Minimum alignment we want to enforce for the streaming ring -constexpr VkDeviceSize MIN_STREAM_ALIGNMENT = 256; +// Maximum potential alignment of a Vulkan buffer +constexpr VkDeviceSize MAX_ALIGNMENT = 256; // Stream buffer size in bytes constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB; -size_t GetStreamBufferSize(const Device& device, VkDeviceSize alignment) { +size_t GetStreamBufferSize(const Device& device) { VkDeviceSize size{0}; if (device.HasDebuggingToolAttached()) { bool found_heap = false; @@ -53,9 +53,8 @@ size_t GetStreamBufferSize(const Device& device, VkDeviceSize alignment) { // Clamp to the configured maximum, align up for safety, and ensure a sane minimum so // region_size (stream_buffer_size / NUM_SYNCS) never becomes zero. - const VkDeviceSize aligned = - (std::min)(Common::AlignUp(size, alignment), MAX_STREAM_BUFFER_SIZE); - const VkDeviceSize min_size = alignment * StagingBufferPool::NUM_SYNCS; + const VkDeviceSize aligned = (std::min)(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE); + const VkDeviceSize min_size = MAX_ALIGNMENT * StagingBufferPool::NUM_SYNCS; return static_cast((std::max)(aligned, min_size)); } } // Anonymous namespace @@ -63,10 +62,8 @@ size_t GetStreamBufferSize(const Device& device, VkDeviceSize alignment) { StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_) : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_}, - stream_alignment{std::max(device_.GetUniformBufferAlignment(), - MIN_STREAM_ALIGNMENT)}, - stream_buffer_size{GetStreamBufferSize(device_, stream_alignment)}, - region_size{stream_buffer_size / StagingBufferPool::NUM_SYNCS} { + stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size / + StagingBufferPool::NUM_SYNCS} { VkBufferCreateInfo stream_ci = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, @@ -119,11 +116,10 @@ void StagingBufferPool::TickFrame() { } StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { - const size_t alignment = static_cast(stream_alignment); - const size_t aligned_size = Common::AlignUp(size, alignment); + const size_t aligned_size = Common::AlignUp(size, MAX_ALIGNMENT); const bool wraps = iterator + size >= stream_buffer_size; const size_t new_iterator = - wraps ? aligned_size : Common::AlignUp(iterator + size, alignment); + wraps ? aligned_size : Common::AlignUp(iterator + size, MAX_ALIGNMENT); const size_t begin_region = wraps ? 0 : Region(iterator); const size_t last_byte = new_iterator == 0 ? 0 : new_iterator - 1; const size_t end_region = (std::min)(Region(last_byte) + 1, NUM_SYNCS); @@ -149,7 +145,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { current_tick); used_iterator = 0; iterator = 0; - free_iterator = aligned_size; + free_iterator = size; const size_t head_last_byte = aligned_size == 0 ? 0 : aligned_size - 1; const size_t head_end_region = (std::min)(Region(head_last_byte) + 1, NUM_SYNCS); if (AreRegionsActive(0, head_end_region)) { @@ -164,7 +160,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { iterator = new_iterator; if (!wraps) { - free_iterator = (std::max)(free_iterator, offset + aligned_size); + free_iterator = (std::max)(free_iterator, offset + size); } return StagingBufferRef{ diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h index 5c40ca069f..f63a203272 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -105,7 +102,6 @@ private: MemoryAllocator& memory_allocator; Scheduler& scheduler; - VkDeviceSize stream_alignment; vk::Buffer stream_buffer; std::span stream_pointer; VkDeviceSize stream_buffer_size; From bfffafe68bea923ddc56bb1b7da73d549865dc11 Mon Sep 17 00:00:00 2001 From: MaranBr Date: Fri, 10 Oct 2025 01:36:55 +0200 Subject: [PATCH 10/15] [common] Change web offline applet default setting to HLE (#2705) This prevents some games from ignoring the disable web applet setting. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2705 Co-authored-by: MaranBr Co-committed-by: MaranBr --- src/common/settings.h | 2 +- src/yuzu/configuration/configure_debug.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/common/settings.h b/src/common/settings.h index c6b52f7ba3..dd9b03f28e 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -161,7 +161,7 @@ struct Values { Category::LibraryApplet}; Setting photo_viewer_applet_mode{ linkage, AppletMode::LLE, "photo_viewer_applet_mode", Category::LibraryApplet}; - Setting offline_web_applet_mode{linkage, AppletMode::LLE, "offline_web_applet_mode", + Setting offline_web_applet_mode{linkage, AppletMode::HLE, "offline_web_applet_mode", Category::LibraryApplet}; Setting login_share_applet_mode{linkage, AppletMode::HLE, "login_share_applet_mode", Category::LibraryApplet}; diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 18f629f639..b825348760 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -83,8 +83,7 @@ void ConfigureDebug::SetConfiguration() { #ifdef YUZU_USE_QT_WEB_ENGINE ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue()); #else - ui->disable_web_applet->setEnabled(false); - ui->disable_web_applet->setText(tr("Web applet not compiled")); + ui->disable_web_applet->setVisible(false); #endif } From b6241e4148d4a7bccedd3081707106edc58ff365 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 10 Oct 2025 01:55:43 +0200 Subject: [PATCH 11/15] revert [vk] StreamBuffer Changes (#2684) (#2707) revert [vk] StreamBuffer Changes (#2684) Streambuffer changes did broke stuff in other games that got out of our scope of testing, we're going to study this changes in the future for better graphic stability. Co-authored-by: Ribbit Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2684 Reviewed-by: MaranBr Reviewed-by: Lizzie Reviewed-by: CamilleLaVey Co-authored-by: Ribbit Co-committed-by: Ribbit Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2707 Reviewed-by: MaranBr Co-authored-by: CamilleLaVey Co-committed-by: CamilleLaVey --- .../vk_staging_buffer_pool.cpp | 60 +++++-------------- 1 file changed, 14 insertions(+), 46 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index ecc4f77dc7..08513d1534 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -33,29 +33,19 @@ constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB; size_t GetStreamBufferSize(const Device& device) { VkDeviceSize size{0}; if (device.HasDebuggingToolAttached()) { - bool found_heap = false; - ForEachDeviceLocalHostVisibleHeap(device, [&size, &found_heap](size_t /*index*/, VkMemoryHeap& heap) { + ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) { size = (std::max)(size, heap.size); - found_heap = true; }); - // If no suitable heap was found fall back to the default cap to avoid creating a zero-sized stream buffer. - if (!found_heap) { - size = MAX_STREAM_BUFFER_SIZE; - } else if (size <= 256_MiB) { - // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be - // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue - // as the heap will be much larger. + // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be + // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue + // as the heap will be much larger. + if (size <= 256_MiB) { size = size * 40 / 100; } } else { size = MAX_STREAM_BUFFER_SIZE; } - - // Clamp to the configured maximum, align up for safety, and ensure a sane minimum so - // region_size (stream_buffer_size / NUM_SYNCS) never becomes zero. - const VkDeviceSize aligned = (std::min)(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE); - const VkDeviceSize min_size = MAX_ALIGNMENT * StagingBufferPool::NUM_SYNCS; - return static_cast((std::max)(aligned, min_size)); + return (std::min)(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE); } } // Anonymous namespace @@ -116,53 +106,31 @@ void StagingBufferPool::TickFrame() { } StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { - const size_t aligned_size = Common::AlignUp(size, MAX_ALIGNMENT); - const bool wraps = iterator + size >= stream_buffer_size; - const size_t new_iterator = - wraps ? aligned_size : Common::AlignUp(iterator + size, MAX_ALIGNMENT); - const size_t begin_region = wraps ? 0 : Region(iterator); - const size_t last_byte = new_iterator == 0 ? 0 : new_iterator - 1; - const size_t end_region = (std::min)(Region(last_byte) + 1, NUM_SYNCS); - const size_t guard_begin = (std::min)(Region(free_iterator) + 1, NUM_SYNCS); - - if (!wraps) { - if (guard_begin < end_region && AreRegionsActive(guard_begin, end_region)) { - // Avoid waiting for the previous usages to be free - return GetStagingBuffer(size, MemoryUsage::Upload); - } - } else if (guard_begin < NUM_SYNCS && AreRegionsActive(guard_begin, NUM_SYNCS)) { + if (AreRegionsActive(Region(free_iterator) + 1, + (std::min)(Region(iterator + size) + 1, NUM_SYNCS))) { // Avoid waiting for the previous usages to be free return GetStagingBuffer(size, MemoryUsage::Upload); } - const u64 current_tick = scheduler.CurrentTick(); std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + Region(iterator), current_tick); used_iterator = iterator; + free_iterator = (std::max)(free_iterator, iterator + size); - if (wraps) { + if (iterator + size >= stream_buffer_size) { std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS, current_tick); used_iterator = 0; iterator = 0; free_iterator = size; - const size_t head_last_byte = aligned_size == 0 ? 0 : aligned_size - 1; - const size_t head_end_region = (std::min)(Region(head_last_byte) + 1, NUM_SYNCS); - if (AreRegionsActive(0, head_end_region)) { + + if (AreRegionsActive(0, Region(size) + 1)) { // Avoid waiting for the previous usages to be free return GetStagingBuffer(size, MemoryUsage::Upload); } } - - std::fill(sync_ticks.begin() + begin_region, sync_ticks.begin() + end_region, current_tick); - - const size_t offset = wraps ? 0 : iterator; - iterator = new_iterator; - - if (!wraps) { - free_iterator = (std::max)(free_iterator, offset + size); - } - + const size_t offset = iterator; + iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT); return StagingBufferRef{ .buffer = *stream_buffer, .offset = static_cast(offset), From 3656253262fd1c22e5a1ab43ae373173e571e11d Mon Sep 17 00:00:00 2001 From: crueter Date: Fri, 10 Oct 2025 05:59:31 +0200 Subject: [PATCH 12/15] [acc] do not consider system profile as orphaned (#2708) Profile 00000000000000000000000000000000 is apparently needed for acnh, etc Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2708 --- src/core/hle/service/acc/profile_manager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index 4a892f7c65..12ea5f7aa1 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -509,6 +509,9 @@ std::vector ProfileManager::FindOrphanedProfiles() good_uuids.emplace_back(uuid_string); } + // used for acnh, etc + good_uuids.emplace_back("00000000000000000000000000000000"); + // TODO: fetch save_id programmatically const auto path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "user/save/0000000000000000"; From b7cd32d1de110bd2d608ca51144128f34de51157 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 3 Oct 2025 02:12:43 +0000 Subject: [PATCH 13/15] [android] Android 7.0 support Signed-off-by: lizzie --- src/android/app/build.gradle.kts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 31db36199a..87fbc9a870 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -58,8 +58,7 @@ android { defaultConfig { applicationId = "dev.eden.eden_emulator" - - minSdk = 28 + minSdk = 24 targetSdk = 36 versionName = getGitVersion() From 36f0070f674a4c151ebd682a9e6ab1ac1aafa12b Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 3 Oct 2025 03:09:19 +0000 Subject: [PATCH 14/15] fix drawable Signed-off-by: lizzie --- .../{drawable => drawable-v26}/ic_launcher.xml | 0 .../app/src/main/res/drawable/ic_launcher.png | Bin 0 -> 2210 bytes 2 files changed, 0 insertions(+), 0 deletions(-) rename src/android/app/src/main/res/{drawable => drawable-v26}/ic_launcher.xml (100%) create mode 100644 src/android/app/src/main/res/drawable/ic_launcher.png diff --git a/src/android/app/src/main/res/drawable/ic_launcher.xml b/src/android/app/src/main/res/drawable-v26/ic_launcher.xml similarity index 100% rename from src/android/app/src/main/res/drawable/ic_launcher.xml rename to src/android/app/src/main/res/drawable-v26/ic_launcher.xml diff --git a/src/android/app/src/main/res/drawable/ic_launcher.png b/src/android/app/src/main/res/drawable/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..7ea5c713802a62e149605cae9216a9fd65aacfbb GIT binary patch literal 2210 zcmV;T2wnGyP)Px-TuDShRCwCuoM((xMHI)kFRQVFyO!06Xxv~#jjq8Gv3@YI5ercX#E1nuilVV& z?1?QF;#vq13pUiKhzcSoD(EV@DkZ3hB1l`dvA%ut^TRt=XXnlB_rAB{B>(Jt@6Mb# z_kYivIdf+2mX!370+a(4KqXLK@_#Hs!U4cNKr^rr=)t}80GoiXrOkmr*_KAiw!ke4 z{Yu%|1vSB1FMNutW0%l#3L4V$$!GKfKp&QkO^ZBAVZA03R{8^(A7q0hU@6%>cgvUjPe%RXz?mNO@iQoFAZW8?aBY$vFn- z5X2@zHU56JXiZNySKDxCI(AZ8~~&h!`Ez+&J|V6PDJ&ImY>4F&cS zQbs$@SjcM%FdJlnebXjquMpEx7We>Y2q5V_B{& z2i^s)jG;$aQpA)>RD5kh1UdLlZnV_s?+M@!@pILdiD8|P7q|cMCizhb`X$d|lMRkn zSV+3ZoE^m(uT3~A-yF-5fJqdltnizLfge)pdtOv>c9-Zk4wPnbJu*fA2z(FxY+#Cq zE=^ic`Mb1VCKhEz=U;|J29AqDP#v(dhTVXA7wjT+>LgA&TwUy6HUN=8*pIX>wV~tUNI#pF4pivaV||+8q}B87qJ#L6oTviNVst z5f)kffKjp_?FK#v&KE~JB!VxhHBhJd%0q#v($C|L-;;;3n^w2ZLr$%q62pN{1q*!e zspMs{zzK#AxWGuac1m7oo;dDvV-BBJfsb4;N1wuloD&1w&jJ(+fCs!f?u;<3E|RuG zfo}rTzuKe!6UEwUfE|F59(A7!q2q5M@2T8vr5?7-0H%b<6i)MSs9jFbD8D@{-%0|q~-`)XKzN88?bV#%S}FQ)H_FhUvGpBE+nbB$#0yn(k)tjDkWHxRpK)~ z1Ktu#agy?7^0^6^EVp4LN$M+E+%_Zy+iWur_{uf@LO$LTmpA%d8bzS+W~Yam(Mw<4y{eAmdpMb*iY|ZOv;PV`hdP; zlw3d0L)H=tK{YAp{Jh@5ocKf6H6OCxxS63IV*B+DR$cwt8y1o_h4G02-0PpZ@LR=9 zV;rsSia)t$KbqdCAV1G9mj31y`GlYwLkEvSs5O z9Jn56)OK3ktl}&qqfnrt@UhU*CBUAs#CiLsR|76&chv}bIT`my$({R|7(-@Ks4)rh%MgA|@9d(EMIp0| zEudOtwSTNZ&|7lw;UD?Rz27u3z>KI@6={ey8hX5vjMI%Jp~tr57?+$*p_Dt&&|_+m zmKS=N>9%ja!B}5y2!V}(+NxgP0S>Zk1E~OQeV{gk)Zf86V4&C0kt!clSWp!KmEw=g zA@(TnNesEM;kB|KT7>EJI75d!3Okze%1wSbmR42RVQ)MjY6Tepjk`Hso z8U623M1pP)bIPorpgJ{VxdG$wrd(6fcNI&iUh2|*X?z`m`5p^FgC#?lHadatE&V&M zHRVCMeD^z+`-AU5(u`X0Duak->V$!iVhW|-WuTeq+V#Tqa@(Aiv4$IlW>61-#~}BsM|QP zwt6BEG(Ge<3g-xpvOJKq%c7(=0;i|sg8(L4`dby-CA(i*M;h<2x@G%RV9i`L{?gLd zZGm|SXRsZ_cy}ZSNpH}5msCIX9t8lKT@H=|DCOR^#|7EEr=jQo?xRp|KPO1|cD9*J zuC)PUW6Ccp){Qk>idMNV`4>`875*W#r@PEyKeaM}MZc`Cf*5uAzuLnLR_s^WFO19J@i_jtD{uRY+ kawlNdkb6hs)V`$a-=DRfGw8>|X#fBK07*qoM6N<$g1WRQ$N&HU literal 0 HcmV?d00001 From 1ff9d17b2c332df1ace15833644b7497e0766c87 Mon Sep 17 00:00:00 2001 From: crueter Date: Fri, 3 Oct 2025 00:19:05 -0400 Subject: [PATCH 15/15] script to make <26 sdk icon Signed-off-by: crueter --- .../app/src/main/res/drawable/ic_launcher.png | Bin 2210 -> 49032 bytes tools/VectorDrawable2Svg.py | 86 ++++++++++++++++++ tools/generate-legacy-icons.sh | 26 ++++++ 3 files changed, 112 insertions(+) create mode 100644 tools/VectorDrawable2Svg.py create mode 100755 tools/generate-legacy-icons.sh diff --git a/src/android/app/src/main/res/drawable/ic_launcher.png b/src/android/app/src/main/res/drawable/ic_launcher.png index 7ea5c713802a62e149605cae9216a9fd65aacfbb..6b586fc75761f49f0a7a67cb1ebdf58edcafe745 100644 GIT binary patch literal 49032 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 aH+6LR-^<8R@ya2}0Lt=j<-W;4L;epW>FkjJ literal 2210 zcmV;T2wnGyP)Px-TuDShRCwCuoM((xMHI)kFRQVFyO!06Xxv~#jjq8Gv3@YI5ercX#E1nuilVV& z?1?QF;#vq13pUiKhzcSoD(EV@DkZ3hB1l`dvA%ut^TRt=XXnlB_rAB{B>(Jt@6Mb# z_kYivIdf+2mX!370+a(4KqXLK@_#Hs!U4cNKr^rr=)t}80GoiXrOkmr*_KAiw!ke4 z{Yu%|1vSB1FMNutW0%l#3L4V$$!GKfKp&QkO^ZBAVZA03R{8^(A7q0hU@6%>cgvUjPe%RXz?mNO@iQoFAZW8?aBY$vFn- z5X2@zHU56JXiZNySKDxCI(AZ8~~&h!`Ez+&J|V6PDJ&ImY>4F&cS zQbs$@SjcM%FdJlnebXjquMpEx7We>Y2q5V_B{& z2i^s)jG;$aQpA)>RD5kh1UdLlZnV_s?+M@!@pILdiD8|P7q|cMCizhb`X$d|lMRkn zSV+3ZoE^m(uT3~A-yF-5fJqdltnizLfge)pdtOv>c9-Zk4wPnbJu*fA2z(FxY+#Cq zE=^ic`Mb1VCKhEz=U;|J29AqDP#v(dhTVXA7wjT+>LgA&TwUy6HUN=8*pIX>wV~tUNI#pF4pivaV||+8q}B87qJ#L6oTviNVst z5f)kffKjp_?FK#v&KE~JB!VxhHBhJd%0q#v($C|L-;;;3n^w2ZLr$%q62pN{1q*!e zspMs{zzK#AxWGuac1m7oo;dDvV-BBJfsb4;N1wuloD&1w&jJ(+fCs!f?u;<3E|RuG zfo}rTzuKe!6UEwUfE|F59(A7!q2q5M@2T8vr5?7-0H%b<6i)MSs9jFbD8D@{-%0|q~-`)XKzN88?bV#%S}FQ)H_FhUvGpBE+nbB$#0yn(k)tjDkWHxRpK)~ z1Ktu#agy?7^0^6^EVp4LN$M+E+%_Zy+iWur_{uf@LO$LTmpA%d8bzS+W~Yam(Mw<4y{eAmdpMb*iY|ZOv;PV`hdP; zlw3d0L)H=tK{YAp{Jh@5ocKf6H6OCxxS63IV*B+DR$cwt8y1o_h4G02-0PpZ@LR=9 zV;rsSia)t$KbqdCAV1G9mj31y`GlYwLkEvSs5O z9Jn56)OK3ktl}&qqfnrt@UhU*CBUAs#CiLsR|76&chv}bIT`my$({R|7(-@Ks4)rh%MgA|@9d(EMIp0| zEudOtwSTNZ&|7lw;UD?Rz27u3z>KI@6={ey8hX5vjMI%Jp~tr57?+$*p_Dt&&|_+m zmKS=N>9%ja!B}5y2!V}(+NxgP0S>Zk1E~OQeV{gk)Zf86V4&C0kt!clSWp!KmEw=g zA@(TnNesEM;kB|KT7>EJI75d!3Okze%1wSbmR42RVQ)MjY6Tepjk`Hso z8U623M1pP)bIPorpgJ{VxdG$wrd(6fcNI&iUh2|*X?z`m`5p^FgC#?lHadatE&V&M zHRVCMeD^z+`-AU5(u`X0Duak->V$!iVhW|-WuTeq+V#Tqa@(Aiv4$IlW>61-#~}BsM|QP zwt6BEG(Ge<3g-xpvOJKq%c7(=0;i|sg8(L4`dby-CA(i*M;h<2x@G%RV9i`L{?gLd zZGm|SXRsZ_cy}ZSNpH}5msCIX9t8lKT@H=|DCOR^#|7EEr=jQo?xRp|KPO1|cD9*J zuC)PUW6Ccp){Qk>idMNV`4>`875*W#r@PEyKeaM}MZc`Cf*5uAzuLnLR_s^WFO19J@i_jtD{uRY+ kawlNdkb6hs)V`$a-=DRfGw8>|X#fBK07*qoM6N<$g1WRQ$N&HU diff --git a/tools/VectorDrawable2Svg.py b/tools/VectorDrawable2Svg.py new file mode 100644 index 0000000000..20f168e1aa --- /dev/null +++ b/tools/VectorDrawable2Svg.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python2 +""" +VectorDrawable2Svg +This script convert your VectorDrawable to a Svg +Author: Alessandro Lucchet + +Usage: drop one or more vector drawable onto this script to convert them to svg format +""" + +from xml.dom.minidom import * +import sys + +# extracts all paths inside vdContainer and add them into svgContainer +def convertPaths(vdContainer,svgContainer,svgXml): + vdPaths = vdContainer.getElementsByTagName('path') + for vdPath in vdPaths: + # only iterate in the first level + if vdPath.parentNode == vdContainer: + svgPath = svgXml.createElement('path') + svgPath.attributes['d'] = vdPath.attributes['android:pathData'].value + if vdPath.hasAttribute('android:fillColor'): + svgPath.attributes['fill'] = vdPath.attributes['android:fillColor'].value + else: + svgPath.attributes['fill'] = 'none' + if vdPath.hasAttribute('android:strokeLineJoin'): + svgPath.attributes['stroke-linejoin'] = vdPath.attributes['android:strokeLineJoin'].value + if vdPath.hasAttribute('android:strokeLineCap'): + svgPath.attributes['stroke-linecap'] = vdPath.attributes['android:strokeLineCap'].value + if vdPath.hasAttribute('android:strokeMiterLimit'): + svgPath.attributes['stroke-miterlimit'] = vdPath.attributes['android:strokeMiterLimit'].value + if vdPath.hasAttribute('android:strokeWidth'): + svgPath.attributes['stroke-width'] = vdPath.attributes['android:strokeWidth'].value + if vdPath.hasAttribute('android:strokeColor'): + svgPath.attributes['stroke'] = vdPath.attributes['android:strokeColor'].value + svgContainer.appendChild(svgPath); + +# define the function which converts a vector drawable to a svg +def convertVd(vdFilePath): + + # create svg xml + svgXml = Document() + svgNode = svgXml.createElement('svg') + svgXml.appendChild(svgNode); + + # open vector drawable + vdXml = parse(vdFilePath) + vdNode = vdXml.getElementsByTagName('vector')[0] + + # setup basic svg info + svgNode.attributes['xmlns'] = 'http://www.w3.org/2000/svg' + svgNode.attributes['width'] = vdNode.attributes['android:viewportWidth'].value + svgNode.attributes['height'] = vdNode.attributes['android:viewportHeight'].value + svgNode.attributes['viewBox'] = '0 0 {} {}'.format(vdNode.attributes['android:viewportWidth'].value, vdNode.attributes['android:viewportHeight'].value) + + # iterate through all groups + vdGroups = vdXml.getElementsByTagName('group') + for vdGroup in vdGroups: + + # create the group + svgGroup = svgXml.createElement('g') + + # setup attributes of the group + if vdGroup.hasAttribute('android:translateX'): + svgGroup.attributes['transform'] = 'translate({},{})'.format(vdGroup.attributes['android:translateX'].value,vdGroup.attributes['android:translateY'].value) + + # iterate through all paths inside the group + convertPaths(vdGroup,svgGroup,svgXml) + + # append the group to the svg node + svgNode.appendChild(svgGroup); + + # iterate through all svg-level paths + convertPaths(vdNode,svgNode,svgXml) + + # write xml to file + svgXml.writexml(open(vdFilePath + '.svg', 'w'),indent="",addindent=" ",newl='\n') + +# script begin +if len(sys.argv)>1: + iterArgs = iter(sys.argv) + next(iterArgs) #skip the first entry (it's the name of the script) + for arg in iterArgs: + convertVd(arg) +else: + print("You have to pass me something") + sys.exit() diff --git a/tools/generate-legacy-icons.sh b/tools/generate-legacy-icons.sh new file mode 100755 index 0000000000..9bf14e8093 --- /dev/null +++ b/tools/generate-legacy-icons.sh @@ -0,0 +1,26 @@ +#!/bin/sh -e + +# Generate SDK <26 icons for android +# requires imagemagick, inkscape + +ROOTDIR=$PWD + +cd src/android/app/src/main + +pushd res/drawable +# convert vector to svg--needed to generate launcher png +cp ic_yuzu_icon.xml tmp + +python3 $ROOTDIR/tools/VectorDrawable2Svg.py tmp + +inkscape -w 768 -h 768 tmp.svg -o ic_tmp.png +magick ic_icon_bg_orig.png -resize 512x512 bg_tmp.png + +magick bg_tmp.png -strip -type TrueColor -depth 8 -colorspace sRGB -color-matrix "1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0" bg_tmp_rgb.png +magick -verbose bg_tmp_rgb.png ic_tmp.png -gravity center -composite -colorspace sRGB ic_launcher.png +echo + +rm *tmp* +popd + +# Add legacy here when legacy gets merged