diff --git a/.ci/linux/eden.dwfsprof b/.ci/linux/eden.dwfsprof
index bc360f0d46..9a3bee6f14 100644
--- a/.ci/linux/eden.dwfsprof
+++ b/.ci/linux/eden.dwfsprof
@@ -1,6 +1,6 @@
AppRun
eden.desktop
-org.eden_emu.eden.desktop
+dev.eden_emu.eden.desktop
shared/bin/eden
shared/lib/lib.path
shared/lib/ld-linux-x86-64.so.2
diff --git a/.ci/linux/package.sh b/.ci/linux/package.sh
index 911fea2f7b..837cfe07ef 100755
--- a/.ci/linux/package.sh
+++ b/.ci/linux/package.sh
@@ -59,15 +59,15 @@ VERSION="$(echo "$EDEN_TAG")"
mkdir -p ./AppDir
cd ./AppDir
-cp ../dist/org.eden_emu.eden.desktop .
-cp ../dist/org.eden_emu.eden.svg .
+cp ../dist/dev.eden_emu.eden.desktop .
+cp ../dist/dev.eden_emu.eden.svg .
-ln -sf ./org.eden_emu.eden.svg ./.DirIcon
+ln -sf ./dev.eden_emu.eden.svg ./.DirIcon
UPINFO='gh-releases-zsync|eden-emulator|Releases|latest|*.AppImage.zsync'
if [ "$DEVEL" = 'true' ]; then
- sed -i 's|Name=Eden|Name=Eden Nightly|' ./org.eden_emu.eden.desktop
+ sed -i 's|Name=Eden|Name=Eden Nightly|' ./dev.eden_emu.eden.desktop
UPINFO="$(echo "$UPINFO" | sed 's|Releases|nightly|')"
fi
diff --git a/.ci/update-icons.sh b/.ci/update-icons.sh
index 99adbfae66..4feb2abd24 100755
--- a/.ci/update-icons.sh
+++ b/.ci/update-icons.sh
@@ -6,7 +6,7 @@
which png2icns || [ which yay && yay libicns ] || exit
which magick || exit
-export EDEN_SVG_ICO="dist/org.eden_emu.eden.svg"
+export EDEN_SVG_ICO="dist/dev.eden_emu.eden.svg"
svgo --multipass $EDEN_SVG_ICO
magick -density 256x256 -background transparent $EDEN_SVG_ICO \
diff --git a/.patch/unordered-dense/0001-cmake.patch b/.patch/unordered-dense/0001-cmake.patch
new file mode 100644
index 0000000000..39e7794b1f
--- /dev/null
+++ b/.patch/unordered-dense/0001-cmake.patch
@@ -0,0 +1,22 @@
+From e59d30b7b12e1d04cc2fc9c6219e35bda447c17e Mon Sep 17 00:00:00 2001
+From: Lizzie <159065448+Lizzie841@users.noreply.github.com>
+Date: Fri, 16 May 2025 04:12:13 +0100
+Subject: [PATCH] Update CMakeLists.txt
+
+---
+ CMakeLists.txt | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index b5f4c4f..c5c6f31 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -24,7 +24,7 @@ target_include_directories(
+
+ target_compile_features(unordered_dense INTERFACE cxx_std_17)
+
+-if(_unordered_dense_is_toplevel_project)
++if(_unordered_dense_is_toplevel_project OR UNORDERED_DENSE_INSTALL)
+ # locations are provided by GNUInstallDirs
+ install(
+ TARGETS unordered_dense
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d11b58bf1f..9abca561f3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -406,8 +406,10 @@ if (YUZU_USE_CPM)
if (NOT MSVC)
# boost sucks
- if (NOT PLATFORM_LINUX AND NOT ANDROID)
- target_compile_definitions(boost_container INTERFACE BOOST_HAS_PTHREADS)
+ # Solaris (and probably other NIXes) need explicit pthread definition
+ if (PLATFORM_SUN)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthreads")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthreads")
endif()
target_compile_options(boost_heap INTERFACE -Wno-shadow)
@@ -856,14 +858,14 @@ endif()
# https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html
# https://www.freedesktop.org/software/appstream/docs/
if(ENABLE_QT AND UNIX AND NOT APPLE)
- install(FILES "dist/org.eden_emu.eden.desktop"
+ install(FILES "dist/dev.eden_emu.eden.desktop"
DESTINATION "share/applications")
- install(FILES "dist/org.eden_emu.eden.svg"
+ install(FILES "dist/dev.eden_emu.eden.svg"
DESTINATION "share/icons/hicolor/scalable/apps")
# TODO: these files need to be updated.
- install(FILES "dist/org.eden_emu.eden.xml"
+ install(FILES "dist/dev.eden_emu.eden.xml"
DESTINATION "share/mime/packages")
- install(FILES "dist/org.eden_emu.eden.metainfo.xml"
+ install(FILES "dist/dev.eden_emu.eden.metainfo.xml"
DESTINATION "share/metainfo")
endif()
diff --git a/CMakeModules/CPMUtil.cmake b/CMakeModules/CPMUtil.cmake
index 4d7db6ed61..9daada47ad 100644
--- a/CMakeModules/CPMUtil.cmake
+++ b/CMakeModules/CPMUtil.cmake
@@ -184,8 +184,6 @@ function(AddJsonPackage)
# system/bundled
if (bundled STREQUAL "unset" AND DEFINED JSON_BUNDLED_PACKAGE)
set(bundled ${JSON_BUNDLED_PACKAGE})
- else()
- set(bundled ON)
endif()
AddPackage(
@@ -259,6 +257,7 @@ function(AddPackage)
KEY
BUNDLED_PACKAGE
+ FIND_PACKAGE_ARGUMENTS
)
set(multiValueArgs OPTIONS PATCHES)
@@ -409,9 +408,9 @@ function(AddPackage)
set_precedence(OFF OFF)
elseif (CPMUTIL_FORCE_SYSTEM)
set_precedence(ON ON)
- elseif(NOT CPMUTIL_FORCE_BUNDLED)
+ elseif(CPMUTIL_FORCE_BUNDLED)
set_precedence(OFF OFF)
- elseif (DEFINED PKG_ARGS_BUNDLED_PACKAGE)
+ elseif (DEFINED PKG_ARGS_BUNDLED_PACKAGE AND NOT PKG_ARGS_BUNDLED_PACKAGE STREQUAL "unset")
if (PKG_ARGS_BUNDLED_PACKAGE)
set(local OFF)
else()
diff --git a/dist/org.eden_emu.eden.desktop b/dist/dev.eden_emu.eden.desktop
similarity index 95%
rename from dist/org.eden_emu.eden.desktop
rename to dist/dev.eden_emu.eden.desktop
index d012ab6d07..5d2d7cd8c5 100644
--- a/dist/org.eden_emu.eden.desktop
+++ b/dist/dev.eden_emu.eden.desktop
@@ -10,7 +10,7 @@ Type=Application
Name=Eden
GenericName=Switch Emulator
Comment=Nintendo Switch video game console emulator
-Icon=org.eden_emu.eden
+Icon=dev.eden_emu.eden
TryExec=eden
Exec=eden %f
Categories=Game;Emulator;Qt;
diff --git a/dist/org.eden_emu.eden.metainfo.xml b/dist/dev.eden_emu.eden.metainfo.xml
similarity index 100%
rename from dist/org.eden_emu.eden.metainfo.xml
rename to dist/dev.eden_emu.eden.metainfo.xml
diff --git a/dist/org.eden_emu.eden.svg b/dist/dev.eden_emu.eden.svg
similarity index 100%
rename from dist/org.eden_emu.eden.svg
rename to dist/dev.eden_emu.eden.svg
diff --git a/dist/org.eden_emu.eden.xml b/dist/dev.eden_emu.eden.xml
similarity index 100%
rename from dist/org.eden_emu.eden.xml
rename to dist/dev.eden_emu.eden.xml
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index b209b48db9..e917e4e7d8 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -96,15 +96,7 @@ if (ENABLE_WEB_SERVICE)
endif()
# unordered_dense
-AddPackage(
- NAME unordered_dense
- REPO "Lizzie841/unordered_dense"
- SHA e59d30b7b1
- HASH 71eff7bd9ba4b9226967bacd56a8ff000946f8813167cb5664bb01e96fb79e4e220684d824fe9c59c4d1cc98c606f13aff05b7940a1ed8ab3c95d6974ee34fa0
- FIND_PACKAGE_ARGUMENTS "CONFIG"
- OPTIONS
- "UNORDERED_DENSE_INSTALL OFF"
-)
+AddJsonPackage(unordered-dense)
# FFMpeg
if (YUZU_USE_BUNDLED_FFMPEG)
@@ -147,6 +139,10 @@ add_subdirectory(nx_tzdb)
# VMA
AddJsonPackage(vulkan-memory-allocator)
+if (VulkanMemoryAllocator_ADDED AND MSVC)
+ target_compile_options(VulkanMemoryAllocator INTERFACE /wd4189)
+endif()
+
if (NOT TARGET LLVM::Demangle)
add_library(demangle demangle/ItaniumDemangle.cpp)
target_include_directories(demangle PUBLIC ./demangle)
diff --git a/externals/cpmfile.json b/externals/cpmfile.json
index effcbcc01f..4bc4a97ca4 100644
--- a/externals/cpmfile.json
+++ b/externals/cpmfile.json
@@ -74,14 +74,14 @@
},
"xbyak_sun": {
"package": "xbyak",
- "repo": "Lizzie841/xbyak",
- "sha": "51f507b0b3",
- "hash": "4a29a3c2f97f7d5adf667a21a008be03c951fb6696b0d7ba27e7e4afa037bc76eb5e059bb84860e01baf741d4d3ac851b840cd54c99d038812fbe0f1fa6d38a4",
+ "repo": "herumi/xbyak",
+ "sha": "9bb219333a",
+ "hash": "303165d45c8c19387ec49d9fda7d7a4e0d86d4c0153898c23f25ce2d58ece567f44c0bbbfe348239b933edb6e1a1e34f4bc1c0ab3a285bee5da0e548879387b0",
"bundled": true
},
"xbyak": {
"package": "xbyak",
- "repo": "Lizzie841/xbyak",
+ "repo": "herumi/xbyak",
"sha": "4e44f4614d",
"hash": "5824e92159e07fa36a774aedd3b3ef3541d0241371d522cffa4ab3e1f215fa5097b1b77865b47b2481376c704fa079875557ea463ca63d0a7fd6a8a20a589e70",
"bundled": true
@@ -105,5 +105,18 @@
"sha": "2bc873e53c",
"hash": "02329058a7f9cf7d5039afaae5ab170d9f42f60f4c01e21eaf4f46073886922b057a9ae30eeac040b3ac182f51b9c1bfe9fe1050a2c9f6ce567a1a9a0ec2c768",
"bundled": true
+ },
+ "unordered-dense": {
+ "package": "unordered_dense",
+ "repo": "martinus/unordered_dense",
+ "sha": "73f3cbb237",
+ "hash": "c08c03063938339d61392b687562909c1a92615b6ef39ec8df19ea472aa6b6478e70d7d5e33d4a27b5d23f7806daf57fe1bacb8124c8a945c918c7663a9e8532",
+ "find_args": "CONFIG",
+ "options": [
+ "UNORDERED_DENSE_INSTALL OFF"
+ ],
+ "patches": [
+ "0001-cmake.patch"
+ ]
}
}
diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt
index 54c852f831..ff35c8dc2c 100644
--- a/externals/ffmpeg/CMakeLists.txt
+++ b/externals/ffmpeg/CMakeLists.txt
@@ -63,20 +63,22 @@ if (NOT WIN32 AND NOT ANDROID)
set(FFmpeg_HWACCEL_INCLUDE_DIRS)
set(FFmpeg_HWACCEL_LDFLAGS)
- # In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so
- if(PLATFORM_SUN)
- list(APPEND FFmpeg_HWACCEL_LIBRARIES
- X11
- "/usr/lib/xorg/amd64/libdrm.so")
- else()
- pkg_check_modules(LIBDRM libdrm REQUIRED)
- list(APPEND FFmpeg_HWACCEL_LIBRARIES
- ${LIBDRM_LIBRARIES})
- list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS
- ${LIBDRM_INCLUDE_DIRS})
+ if (NOT APPLE)
+ # In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so
+ if(PLATFORM_SUN)
+ list(APPEND FFmpeg_HWACCEL_LIBRARIES
+ X11
+ "/usr/lib/xorg/amd64/libdrm.so")
+ else()
+ pkg_check_modules(LIBDRM libdrm REQUIRED)
+ list(APPEND FFmpeg_HWACCEL_LIBRARIES
+ ${LIBDRM_LIBRARIES})
+ list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS
+ ${LIBDRM_INCLUDE_DIRS})
+ endif()
+ list(APPEND FFmpeg_HWACCEL_FLAGS
+ --enable-libdrm)
endif()
- list(APPEND FFmpeg_HWACCEL_FLAGS
- --enable-libdrm)
if(LIBVA_FOUND)
find_package(X11 REQUIRED)
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index d907284bb7..e91d2e8c52 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -173,6 +173,7 @@ android {
"-DENABLE_OPENSSL=ON",
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DYUZU_USE_CPM=ON",
+ "-DCPMUTIL_FORCE_BUNDLED=ON",
"-DYUZU_USE_BUNDLED_FFMPEG=ON",
"-DYUZU_ENABLE_LTO=ON",
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt
index b8d0f2197e..dea762dc17 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverFetcherFragment.kt
@@ -79,7 +79,7 @@ class DriverFetcherFragment : Fragment() {
IntRange(600, 639) to "Mr. Purple EOL-24.3.4",
IntRange(640, 699) to "Mr. Purple T19",
IntRange(700, 710) to "KIMCHI 25.2.0_r5",
- IntRange(711, 799) to "Mr. Purple T21",
+ IntRange(711, 799) to "Mr. Purple T22",
IntRange(800, 899) to "GameHub Adreno 8xx",
IntRange(900, Int.MAX_VALUE) to "Unsupported"
)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 96015e58ec..5cc912fbbe 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -509,6 +509,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
gpuModel = GpuDriverHelper.getGpuModel().toString()
fwVersion = NativeLibrary.firmwareVersion()
+ updateQuickOverlayMenuEntry(BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean())
+
binding.surfaceEmulation.holder.addCallback(this)
binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
@@ -530,6 +532,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
binding.inGameMenu.requestFocus()
emulationViewModel.setDrawerOpen(true)
+ updateQuickOverlayMenuEntry(BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean())
}
override fun onDrawerClosed(drawerView: View) {
@@ -571,25 +574,24 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_pause_emulation -> {
if (emulationState.isPaused) {
emulationState.run(false)
- it.title = resources.getString(R.string.emulation_pause)
- it.icon = ResourcesCompat.getDrawable(
- resources,
- R.drawable.ic_pause,
- requireContext().theme
- )
+ updatePauseMenuEntry(false)
} else {
emulationState.pause()
- it.title = resources.getString(R.string.emulation_unpause)
- it.icon = ResourcesCompat.getDrawable(
- resources,
- R.drawable.ic_play,
- requireContext().theme
- )
+ updatePauseMenuEntry(true)
}
binding.inGameMenu.requestFocus()
true
}
+ R.id.menu_quick_overlay -> {
+ val newState = !BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
+ BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(newState)
+ updateQuickOverlayMenuEntry(newState)
+ binding.surfaceInputOverlay.refreshControls()
+ NativeConfig.saveGlobalConfig()
+ true
+ }
+
R.id.menu_settings -> {
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null,
@@ -844,9 +846,50 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
+ private fun updateQuickOverlayMenuEntry(isVisible: Boolean) {
+ val menu = binding.inGameMenu.menu
+ val item = menu.findItem(R.id.menu_quick_overlay)
+ if (isVisible) {
+ item.title = getString(R.string.emulation_hide_overlay)
+ item.icon = ResourcesCompat.getDrawable(
+ resources,
+ R.drawable.ic_controller_disconnected,
+ requireContext().theme
+ )
+ } else {
+ item.title = getString(R.string.emulation_show_overlay)
+ item.icon = ResourcesCompat.getDrawable(
+ resources,
+ R.drawable.ic_controller,
+ requireContext().theme
+ )
+ }
+ }
+
+ private fun updatePauseMenuEntry(isPaused: Boolean) {
+ val menu = binding.inGameMenu.menu
+ val pauseItem = menu.findItem(R.id.menu_pause_emulation)
+ if (isPaused) {
+ pauseItem.title = getString(R.string.emulation_unpause)
+ pauseItem.icon = ResourcesCompat.getDrawable(
+ resources,
+ R.drawable.ic_play,
+ requireContext().theme
+ )
+ } else {
+ pauseItem.title = getString(R.string.emulation_pause)
+ pauseItem.icon = ResourcesCompat.getDrawable(
+ resources,
+ R.drawable.ic_pause,
+ requireContext().theme
+ )
+ }
+ }
+
override fun onPause() {
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
emulationState.pause()
+ updatePauseMenuEntry(true)
}
super.onPause()
}
@@ -869,6 +912,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val socPosition = IntSetting.SOC_OVERLAY_POSITION.getInt()
updateSocPosition(socPosition)
+
+ binding.inGameMenu.post {
+ emulationState?.isPaused?.let { updatePauseMenuEntry(it) }
+ }
}
private fun resetInputOverlay() {
@@ -1391,6 +1438,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_show_overlay -> {
it.isChecked = !it.isChecked
BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(it.isChecked)
+ updateQuickOverlayMenuEntry(it.isChecked)
binding.surfaceInputOverlay.refreshControls()
true
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index fffaa1e3ba..e8dd566f79 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -38,6 +38,7 @@ import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.InstallResult
+import android.os.Build
import org.yuzu.yuzu_emu.model.TaskState
import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.*
@@ -47,6 +48,7 @@ import java.io.BufferedOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import androidx.core.content.edit
+import kotlin.text.compareTo
class MainActivity : AppCompatActivity(), ThemeProvider {
private lateinit var binding: ActivityMainBinding
@@ -110,6 +112,19 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
binding = ActivityMainBinding.inflate(layoutInflater)
+ // Since Android 15, google automatically forces "games" to be 60 hrz
+ // This ensures the display's max refresh rate is actually used
+ display?.let {
+ val supportedModes = it.supportedModes
+ val maxRefreshRate = supportedModes.maxByOrNull { mode -> mode.refreshRate }
+
+ if (maxRefreshRate != null) {
+ val layoutParams = window.attributes
+ layoutParams.preferredDisplayModeId = maxRefreshRate.modeId
+ window.attributes = layoutParams
+ }
+ }
+
setContentView(binding.root)
checkAndRequestBluetoothPermissions()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt
index a317be14d5..377313d0aa 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt
@@ -124,11 +124,16 @@ object CustomSettingsHandler {
// Check for driver requirements if activity and driverViewModel are provided
if (activity != null && driverViewModel != null) {
- val driverPath = extractDriverPath(customSettings)
- if (driverPath != null) {
- Log.info("[CustomSettingsHandler] Custom settings specify driver: $driverPath")
+ val rawDriverPath = extractDriverPath(customSettings)
+ if (rawDriverPath != null) {
+ // Normalize to local storage path (we only store drivers under driverStoragePath)
+ val driverFilename = rawDriverPath.substringAfterLast('/')
+ .substringAfterLast('\\')
+ val localDriverPath = "${GpuDriverHelper.driverStoragePath}$driverFilename"
+ Log.info("[CustomSettingsHandler] Custom settings specify driver: $rawDriverPath (normalized: $localDriverPath)")
+
// Check if driver exists in the driver storage
- val driverFile = File(driverPath)
+ val driverFile = File(localDriverPath)
if (!driverFile.exists()) {
Log.info("[CustomSettingsHandler] Driver not found locally: ${driverFile.name}")
@@ -182,7 +187,7 @@ object CustomSettingsHandler {
}
// Attempt to download and install the driver
- val driverUri = DriverResolver.ensureDriverAvailable(driverPath, activity) { progress ->
+ val driverUri = DriverResolver.ensureDriverAvailable(driverFilename, activity) { progress ->
progressChannel.trySend(progress.toInt())
}
@@ -209,12 +214,12 @@ object CustomSettingsHandler {
return null
}
- // Verify the downloaded driver
- val installedFile = File(driverPath)
+ // Verify the downloaded driver (from normalized local path)
+ val installedFile = File(localDriverPath)
val metadata = GpuDriverHelper.getMetadataFromZip(installedFile)
if (metadata.name == null) {
Log.error(
- "[CustomSettingsHandler] Downloaded driver is invalid: $driverPath"
+ "[CustomSettingsHandler] Downloaded driver is invalid: $localDriverPath"
)
Toast.makeText(
activity,
@@ -232,7 +237,7 @@ object CustomSettingsHandler {
}
// Add to driver list
- driverViewModel.onDriverAdded(Pair(driverPath, metadata))
+ driverViewModel.onDriverAdded(Pair(localDriverPath, metadata))
Log.info(
"[CustomSettingsHandler] Successfully downloaded and installed driver: ${metadata.name}"
)
@@ -268,7 +273,7 @@ object CustomSettingsHandler {
// Driver exists, verify it's valid
val metadata = GpuDriverHelper.getMetadataFromZip(driverFile)
if (metadata.name == null) {
- Log.error("[CustomSettingsHandler] Invalid driver file: $driverPath")
+ Log.error("[CustomSettingsHandler] Invalid driver file: $localDriverPath")
Toast.makeText(
activity,
activity.getString(
@@ -459,6 +464,8 @@ object CustomSettingsHandler {
if (inGpuDriverSection && trimmed.startsWith("driver_path=")) {
return trimmed.substringAfter("driver_path=")
+ .trim()
+ .removeSurrounding("\"", "\"")
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt
index 74f98ccbd2..2072344bdf 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt
@@ -68,6 +68,48 @@ object DriverResolver {
val filename: String
)
+ // Matching helpers
+ private val KNOWN_SUFFIXES = listOf(
+ ".adpkg.zip",
+ ".zip",
+ ".7z",
+ ".tar.gz",
+ ".tar.xz",
+ ".rar"
+ )
+
+ private fun stripKnownSuffixes(name: String): String {
+ var result = name
+ var changed: Boolean
+ do {
+ changed = false
+ for (s in KNOWN_SUFFIXES) {
+ if (result.endsWith(s, ignoreCase = true)) {
+ result = result.dropLast(s.length)
+ changed = true
+ }
+ }
+ } while (changed)
+ return result
+ }
+
+ private fun normalizeName(name: String): String {
+ val base = stripKnownSuffixes(name.lowercase())
+ // Remove non-alphanumerics to make substring checks resilient
+ return base.replace(Regex("[^a-z0-9]+"), " ").trim()
+ }
+
+ private fun tokenize(name: String): Set =
+ normalizeName(name).split(Regex("\\s+")).filter { it.isNotBlank() }.toSet()
+
+ // Jaccard similarity between two sets
+ private fun jaccard(a: Set, b: Set): Double {
+ if (a.isEmpty() || b.isEmpty()) return 0.0
+ val inter = a.intersect(b).size.toDouble()
+ val uni = a.union(b).size.toDouble()
+ return if (uni == 0.0) 0.0 else inter / uni
+ }
+
/**
* Resolve a driver download URL from its filename
* @param filename The driver filename (e.g., "turnip_mrpurple-T19-toasted.adpkg.zip")
@@ -98,7 +140,7 @@ object DriverResolver {
async {
searchRepository(repoPath, filename)
}
- }.mapNotNull { it.await() }.firstOrNull().also { resolved ->
+ }.firstNotNullOfOrNull { it.await() }.also { resolved ->
// Cache the result if found
resolved?.let {
urlCache[filename] = it
@@ -119,22 +161,56 @@ object DriverResolver {
releaseCache[repoPath] = it
}
- // Search through all releases and artifacts
+ // First pass: exact name (case-insensitive) against asset filenames
+ val target = filename.lowercase()
for (release in releases) {
for (artifact in release.artifacts) {
- if (artifact.name == filename) {
- Log.info(
- "[DriverResolver] Found $filename in $repoPath/${release.tagName}"
- )
+ if (artifact.name.equals(filename, ignoreCase = true) || artifact.name.lowercase() == target) {
+ Log.info("[DriverResolver] Found $filename in $repoPath/${release.tagName}")
return@withContext ResolvedDriver(
downloadUrl = artifact.url.toString(),
repoPath = repoPath,
releaseTag = release.tagName,
- filename = filename
+ filename = artifact.name
)
}
}
}
+
+ // Second pass: fuzzy match by asset filenames only
+ val reqNorm = normalizeName(filename)
+ val reqTokens = tokenize(filename)
+ var best: ResolvedDriver? = null
+ var bestScore = 0.0
+
+ for (release in releases) {
+ for (artifact in release.artifacts) {
+ val artNorm = normalizeName(artifact.name)
+ val artTokens = tokenize(artifact.name)
+
+ var score = jaccard(reqTokens, artTokens)
+ // Boost if one normalized name contains the other
+ if (artNorm.contains(reqNorm) || reqNorm.contains(artNorm)) {
+ score = maxOf(score, 0.92)
+ }
+
+ if (score > bestScore) {
+ bestScore = score
+ best = ResolvedDriver(
+ downloadUrl = artifact.url.toString(),
+ repoPath = repoPath,
+ releaseTag = release.tagName,
+ filename = artifact.name
+ )
+ }
+ }
+ }
+
+ // Threshold to avoid bad guesses, this worked fine in testing but might need tuning
+ if (best != null && bestScore >= 0.6) {
+ Log.info("[DriverResolver] Fuzzy matched $filename -> ${best.filename} in ${best.repoPath} (score=%.2f)".format(bestScore))
+ return@withContext best
+ }
null
} catch (e: Exception) {
Log.error("[DriverResolver] Failed to search $repoPath: ${e.message}")
@@ -296,8 +372,8 @@ object DriverResolver {
context: Context,
onProgress: ((Float) -> Unit)? = null
): Uri? {
- // Extract filename from path
- val filename = driverPath.substringAfterLast('/')
+ // Extract filename from path (support both separators)
+ val filename = driverPath.substringAfterLast('/').substringAfterLast('\\')
// Check if driver already exists locally
val localPath = "${GpuDriverHelper.driverStoragePath}$filename"
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt
index 1e30b16d96..9dbee1fcef 100644
--- a/src/android/app/src/main/jni/CMakeLists.txt
+++ b/src/android/app/src/main/jni/CMakeLists.txt
@@ -17,7 +17,7 @@ add_library(yuzu-android SHARED
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
-target_link_libraries(yuzu-android PRIVATE audio_core common core input_common frontend_common Vulkan::Headers)
+target_link_libraries(yuzu-android PRIVATE audio_core common core input_common frontend_common Vulkan::Headers GPUOpen::VulkanMemoryAllocator)
target_link_libraries(yuzu-android PRIVATE android camera2ndk EGL glad jnigraphics log)
if (ARCHITECTURE_arm64)
target_link_libraries(yuzu-android PRIVATE adrenotools)
diff --git a/src/android/app/src/main/res/menu/menu_in_game.xml b/src/android/app/src/main/res/menu/menu_in_game.xml
index ce3f11f6e6..d45699638c 100644
--- a/src/android/app/src/main/res/menu/menu_in_game.xml
+++ b/src/android/app/src/main/res/menu/menu_in_game.xml
@@ -8,6 +8,11 @@
android:icon="@drawable/ic_pause"
android:title="@string/emulation_pause" />
+
+
- مركز العصا النسبي
مزلاق الأسهم
الاهتزازات الديناميكية
- عرض التراكب
+ إظهار وحدة التحكم
+ إخفاء وحدة التحكم
الكل
ضبط التراكب
الحجم
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 221a197843..34b1ae6252 100644
--- a/src/android/app/src/main/res/values-ckb/strings.xml
+++ b/src/android/app/src/main/res/values-ckb/strings.xml
@@ -710,7 +710,8 @@
ناوەندی گێڕ بەنزیکەیی
خلیسکانی 4 دوگمەکە
لەرینەوەی پەنجەلێدان
- نیشاندانی داپۆشەر
+ نیشاندانی کۆنتڕۆڵەر
+ پیشاندانی کۆنتڕۆڵەر
گۆڕینی سەرجەم
ڕێکخستنی داپۆشەر
پێوەر
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 61e389f9fc..293524271e 100644
--- a/src/android/app/src/main/res/values-cs/strings.xml
+++ b/src/android/app/src/main/res/values-cs/strings.xml
@@ -691,7 +691,8 @@
Relativní střed joysticku
D-pad slide
Haptická odezva
- Zobrazit překryv
+ Zobrazit ovladač
+ Skrýt ovladač
Přepnout vše
Upravit překryv
Měřítko
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 bff0b0379e..46ae9ba7fe 100644
--- a/src/android/app/src/main/res/values-de/strings.xml
+++ b/src/android/app/src/main/res/values-de/strings.xml
@@ -762,6 +762,13 @@ Wirklich fortfahren?
Emulation beenden
Fertig
FPS Zähler
+
+ Steuerung umschalten
+ Relativer Stick-Zentrum
+ D-Pad-Scrollen
+ Haptisches Feedback
+ Controller anzeigen
+ Controller ausblenden
Alle umschalten
Overlay anpassen
Größe
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 888d6d1684..8712f455de 100644
--- a/src/android/app/src/main/res/values-es/strings.xml
+++ b/src/android/app/src/main/res/values-es/strings.xml
@@ -806,7 +806,8 @@
Centro relativo del stick
Deslizamiento de la cruceta
Toques hápticos
- Mostrar overlay
+ Mostrar controlador
+ Ocultar controlador
Alternar todo
Ajustar overlay
Escala
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 60b1626aa5..07ff8ff4e0 100644
--- a/src/android/app/src/main/res/values-fa/strings.xml
+++ b/src/android/app/src/main/res/values-fa/strings.xml
@@ -805,7 +805,8 @@
مرکز نسبی استیک
لغزش دکمههای جهتی
لرزش لمسی
- نشان دادن نمایش روی صفحه
+ نمایش کنترلر
+ پنهان کردن کنترلر
تغییر همه
تنظیم نمایش روی صفحه
مقیاس
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 fde02d1aa8..2e06ac98e1 100644
--- a/src/android/app/src/main/res/values-fr/strings.xml
+++ b/src/android/app/src/main/res/values-fr/strings.xml
@@ -854,7 +854,8 @@
Centre du stick relatif
Glissement du D-pad
Toucher haptique
- Afficher l\'overlay
+ Afficher la manette
+ Masquer la manette
Tout basculer
Ajuster l\'overlay
Échelle
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 59312086e9..c0c835d633 100644
--- a/src/android/app/src/main/res/values-he/strings.xml
+++ b/src/android/app/src/main/res/values-he/strings.xml
@@ -739,7 +739,8 @@
מרכז ג׳ויסטיק יחסי
החלקת D-pad
רטט מגע
- הצג את שכבת-העל
+ הצג בקר
+ הסתר בקר
החלף הכל
התאם את שכבת-העל
קנה מידה
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 f95e2d3f97..46a5ac7cce 100644
--- a/src/android/app/src/main/res/values-hu/strings.xml
+++ b/src/android/app/src/main/res/values-hu/strings.xml
@@ -843,7 +843,8 @@
Irányítás átkapcsolása
D-pad csúsztatása
Érintés haptikája
- Átfedés mutatása
+ Vezérlő megjelenítése
+ Vezérlő elrejtése
Összes átkapcsolása
Átfedés testreszabása
Skálázás
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 dae77d53af..cffb526ad5 100644
--- a/src/android/app/src/main/res/values-id/strings.xml
+++ b/src/android/app/src/main/res/values-id/strings.xml
@@ -798,7 +798,8 @@
Pusat stick relatif
Geser Dpad
Haptik
- Tampilkan Hamparan
+ Tampilkan Kontroler
+ Sembunyikan Kontroler
Alihkan Semua
Menyesuaikan
Skala
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 dd184e9d9a..cb234cf61e 100644
--- a/src/android/app/src/main/res/values-it/strings.xml
+++ b/src/android/app/src/main/res/values-it/strings.xml
@@ -770,7 +770,8 @@
Centro relativo degli Stick
DPad A Scorrimento
Feedback Aptico
- Mostra l\'overlay
+ Mostra l\'controller
+ Nascondi l\'controller
Attiva/Disattiva tutto
Regola l\'overlay
Scala
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 873d433fc0..abedb1e0bc 100644
--- a/src/android/app/src/main/res/values-ja/strings.xml
+++ b/src/android/app/src/main/res/values-ja/strings.xml
@@ -729,7 +729,8 @@
スティックを固定しない
十字キーをスライド操作
タッチ振動
- ボタンを表示
+ コントローラーを表示
+ コントローラーを非表示
すべて切替
見た目を調整
大きさ
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 3f3a4a96c0..c6d9457744 100644
--- a/src/android/app/src/main/res/values-ko/strings.xml
+++ b/src/android/app/src/main/res/values-ko/strings.xml
@@ -798,6 +798,7 @@
십자키 슬라이드
터치 햅틱
컨트롤러 표시
+ 컨트롤러 숨기기
모두 선택
컨트롤러 조정
크기
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 1e898fca79..3cc4c6d12c 100644
--- a/src/android/app/src/main/res/values-nb/strings.xml
+++ b/src/android/app/src/main/res/values-nb/strings.xml
@@ -720,7 +720,8 @@
Relativt pinnesenter
D-pad-skyving
Berøringshaptikk
- Vis overlegg
+ Vis kontroller
+ Skjul kontroller
Veksle mellom alle
Juster overlegg
Skaler
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 724d7608b6..b9858838e8 100644
--- a/src/android/app/src/main/res/values-pl/strings.xml
+++ b/src/android/app/src/main/res/values-pl/strings.xml
@@ -718,7 +718,8 @@
Wycentruj gałki
Ruchomy D-pad
Wibracje haptyczne
- Pokaż przyciski
+ Pokaż kontroler
+ Ukryj kontroler
Włącz wszystkie
Dostosuj nakładkę
Skala
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 a3fd3fe13a..1296fad889 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
@@ -855,7 +855,8 @@ uma tentativa de mapeamento automático
Centro Relativo do Analógico
Deslizamento dos Botões Direcionais
Vibração ao tocar
- Mostrar overlay
+ Mostrar controle
+ Ocultar controle
Marcar/Desmarcar tudo
Ajustar overlay
Escala
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 7adce075cf..a166907877 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
@@ -855,7 +855,8 @@ uma tentativa de mapeamento automático
Centro Relativo de Analógico
Deslizamento dos Botões Direcionais
Vibração ao tocar
- Mostrar overlay
+ Mostrar comando
+ Ocultar comando
Marcar/Desmarcar tudo
Ajustar overlay
Escala
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 8d02ff7b58..dc68c7b817 100644
--- a/src/android/app/src/main/res/values-ru/strings.xml
+++ b/src/android/app/src/main/res/values-ru/strings.xml
@@ -856,7 +856,8 @@
Относительный центр стика
Слайд крестовиной
Обратная связь от нажатий
- Показать оверлей
+ Показать контроллер
+ Скрыть контроллер
Переключить всё
Регулировка оверлея
Масштаб
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 2294033550..c547b3f761 100644
--- a/src/android/app/src/main/res/values-sr/strings.xml
+++ b/src/android/app/src/main/res/values-sr/strings.xml
@@ -812,7 +812,8 @@
Релативни центар за штапић
Д-Пад Слиде
Додирните ХАптицс
- Приказати прекривање
+ Приказати контролер
+ Сакрити контролер
Пребацивати све
Подесити прекривање
Скала
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 ebb5493f12..b48a8a4a58 100644
--- a/src/android/app/src/main/res/values-uk/strings.xml
+++ b/src/android/app/src/main/res/values-uk/strings.xml
@@ -749,7 +749,8 @@
Відносний центр джойстика
Ковзання D-pad
Тактильний відгук
- Показати накладання
+ Показати контролер
+ Сховати контролер
Перемкнути все
Налаштувати накладання
Масштаб
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 102c720835..b19d437ceb 100644
--- a/src/android/app/src/main/res/values-vi/strings.xml
+++ b/src/android/app/src/main/res/values-vi/strings.xml
@@ -723,7 +723,8 @@
Trung tâm nút cần xoay tương đối
Trượt D-pad
Chạm haptics
- Hiện lớp phủ
+ Hiện bộ điều khiển
+ Ẩn bộ điều khiển
Chuyển đổi tất cả
Điều chỉnh lớp phủ
Tỉ lệ thu phóng
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 a0dab375d0..95ab14abd0 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
@@ -848,7 +848,8 @@
相对摇杆中心
十字方向键滑动
触觉反馈
- 显示虚拟按键
+ 显示控制器
+ 隐藏控制器
全部切换
调整虚拟按键
缩放
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 851483668a..8640875f2c 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
@@ -853,7 +853,8 @@
相對搖桿中心
方向鍵滑動
觸覺回饋技術
- 顯示覆疊
+ 顯示控制器
+ 隱藏控制器
全部切換
調整覆疊
縮放
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index f73fc1d9aa..7124ba41b4 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -835,7 +835,8 @@
Relative stick center
D-pad slide
Touch haptics
- Show overlay
+ Show controller
+ Hide controller
Toggle all
Adjust overlay
Scale
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp
index edb64de8ec..15a198e216 100644
--- a/src/common/host_memory.cpp
+++ b/src/common/host_memory.cpp
@@ -12,7 +12,7 @@
#include
#include "common/dynamic_library.h"
-#elif defined(__linux__) || defined(__FreeBSD__) || defined(__sun__) // ^^^ Windows ^^^ vvv Linux vvv
+#elif defined(__linux__) || defined(__FreeBSD__) || defined(__sun__) || defined(__APPLE__) // ^^^ Windows ^^^ vvv POSIX vvv
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
@@ -20,10 +20,18 @@
#include
#include
#include
-#include
#include
#include "common/scope_exit.h"
+#if defined(__linux__)
+#include
+#elif defined(__APPLE__)
+#include
+#include
+#include
+#include
+#endif
+
// FreeBSD
#ifndef MAP_NORESERVE
#define MAP_NORESERVE 0
@@ -32,8 +40,12 @@
#ifndef MAP_ALIGNED_SUPER
#define MAP_ALIGNED_SUPER 0
#endif
+// macOS
+#ifndef MAP_ANONYMOUS
+#define MAP_ANONYMOUS MAP_ANON
+#endif
-#endif // ^^^ Linux ^^^
+#endif // ^^^ POSIX ^^^
#include
#include
@@ -372,7 +384,7 @@ private:
std::unordered_map placeholder_host_pointers; ///< Placeholder backing offset
};
-#elif defined(__linux__) || defined(__FreeBSD__) || defined(__sun__) // ^^^ Windows ^^^ vvv Linux vvv
+#elif defined(__linux__) || defined(__FreeBSD__) || defined(__sun__) || defined(__APPLE__) // ^^^ Windows ^^^ vvv POSIX vvv
#ifdef ARCHITECTURE_arm64
@@ -417,14 +429,11 @@ static void* ChooseVirtualBase(size_t virtual_size) {
#else
static void* ChooseVirtualBase(size_t virtual_size) {
-#if defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__managarm__)
+#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__managarm__) || defined(__AIX__)
void* virtual_base = mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_ALIGNED_SUPER, -1, 0);
-
- if (virtual_base != MAP_FAILED) {
+ if (virtual_base != MAP_FAILED)
return virtual_base;
- }
#endif
-
return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
}
@@ -492,6 +501,13 @@ public:
#elif defined(__FreeBSD__) && __FreeBSD__ < 13
// XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30
fd = shm_open(SHM_ANON, O_RDWR, 0600);
+#elif defined(__APPLE__)
+ // macOS doesn't have memfd_create, use anonymous temporary file
+ char template_path[] = "/tmp/eden_mem_XXXXXX";
+ fd = mkstemp(template_path);
+ if (fd >= 0) {
+ unlink(template_path);
+ }
#else
fd = memfd_create("HostMemory", 0);
#endif
@@ -648,7 +664,7 @@ private:
FreeRegionManager free_manager{};
};
-#else // ^^^ Linux ^^^ vvv Generic vvv
+#else // ^^^ POSIX ^^^ vvv Generic vvv
class HostMemory::Impl {
public:
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 19140bce0d..d4f16f4853 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -163,8 +163,9 @@ bool IsFastmemEnabled() {
}
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun__)
return false;
-#endif
+#else
return true;
+#endif
}
static bool is_nce_enabled = false;
diff --git a/src/common/settings.h b/src/common/settings.h
index 64545d10ff..b657dc8658 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -551,6 +551,8 @@ struct Values {
3,
#elif defined (ANDROID)
0,
+#elif defined (__APPLE__)
+ 0,
#else
2,
#endif
diff --git a/src/common/string_util.h b/src/common/string_util.h
index 8ed87cdadc..4358541b14 100644
--- a/src/common/string_util.h
+++ b/src/common/string_util.h
@@ -7,6 +7,7 @@
#pragma once
+#include
#include
#include
#include
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index 95bf18dbf7..5f754650d9 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -219,28 +219,55 @@ NvResult nvhost_gpu::AllocGPFIFOEx2(IoctlAllocGpfifoEx& params, DeviceFD fd) {
return NvResult::Success;
}
-NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) {
- LOG_DEBUG(Service_NVDRV, "called, class_num={:X}, flags={:X}, obj_id={:X}", params.class_num,
- params.flags, params.obj_id);
+s32_le nvhost_gpu::GetObjectContextClassNumberIndex(CtxClasses class_number) {
+ constexpr s32_le invalid_class_number_index = -1;
+ switch (class_number) {
+ case CtxClasses::Ctx2D: return 0;
+ case CtxClasses::Ctx3D: return 1;
+ case CtxClasses::CtxCompute: return 2;
+ case CtxClasses::CtxKepler: return 3;
+ case CtxClasses::CtxDMA: return 4;
+ case CtxClasses::CtxChannelGPFIFO: return 5;
+ default: return invalid_class_number_index;
+ }
+}
- if (!channel_state->initialized) {
+NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) {
+ LOG_DEBUG(Service_NVDRV, "called, class_num={:#X}, flags={:#X}, obj_id={:#X}", params.class_num,
+ params.flags, params.obj_id);
+
+ if (!channel_state || !channel_state->initialized) {
LOG_CRITICAL(Service_NVDRV, "No address space bound to allocate a object context!");
return NvResult::NotInitialized;
}
- switch (static_cast(params.class_num)) {
- case CtxClasses::Ctx2D:
- case CtxClasses::Ctx3D:
- case CtxClasses::CtxCompute:
- case CtxClasses::CtxKepler:
- case CtxClasses::CtxDMA:
- case CtxClasses::CtxChannelGPFIFO:
- ctxObj_params.push_back(params);
- return NvResult::Success;
- default:
- LOG_ERROR(Service_NVDRV, "Invalid class number for object context: {:X}", params.class_num);
+ std::scoped_lock lk(channel_mutex);
+
+ if (params.flags) {
+ LOG_WARNING(Service_NVDRV, "non-zero flags={:#X} for class={:#X}", params.flags,
+ params.class_num);
+
+ constexpr u32 allowed_mask{};
+ params.flags = allowed_mask;
+ }
+
+ s32_le ctx_class_number_index =
+ GetObjectContextClassNumberIndex(static_cast(params.class_num));
+ if (ctx_class_number_index < 0) {
+ LOG_ERROR(Service_NVDRV, "Invalid class number for object context: {:#X}",
+ params.class_num);
return NvResult::BadParameter;
}
+
+ if (ctxObjs[ctx_class_number_index].has_value()) {
+ LOG_ERROR(Service_NVDRV, "Object context for class {:#X} already allocated on this channel",
+ params.class_num);
+ return NvResult::AlreadyAllocated;
+ }
+
+ ctxObjs[ctx_class_number_index] = params;
+
+ return NvResult::Success;
}
static boost::container::small_vector BuildWaitCommandList(
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index a017cc50d0..fb0a5be959 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -172,7 +172,7 @@ private:
s32_le nvmap_fd{};
u64_le user_data{};
IoctlZCullBind zcull_params{};
- std::vector ctxObj_params{};
+ std::array, 6> ctxObjs{};
u32_le channel_priority{};
u32_le channel_timeslice{};
@@ -184,9 +184,12 @@ private:
NvResult SetChannelPriority(IoctlChannelSetPriority& params);
NvResult AllocGPFIFOEx(IoctlAllocGpfifoEx& params, DeviceFD fd);
NvResult AllocGPFIFOEx2(IoctlAllocGpfifoEx& params, DeviceFD fd);
+
+ s32_le GetObjectContextClassNumberIndex(CtxClasses class_number);
NvResult AllocateObjectContext(IoctlAllocObjCtx& params);
NvResult SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandList&& entries);
+
NvResult SubmitGPFIFOBase1(IoctlSubmitGpfifo& params,
std::span commands, bool kickoff = false);
NvResult SubmitGPFIFOBase2(IoctlSubmitGpfifo& params,
diff --git a/src/dynarmic/externals/CMakeLists.txt b/src/dynarmic/externals/CMakeLists.txt
index 23cfd42236..73c97d8f06 100644
--- a/src/dynarmic/externals/CMakeLists.txt
+++ b/src/dynarmic/externals/CMakeLists.txt
@@ -60,12 +60,12 @@ AddJsonPackage(
# endif()
# endif()
-# unordered_dense
+# unordered_dense - already in root
-AddJsonPackage(
- NAME unordered-dense
- BUNDLED_PACKAGE ${DYNARMIC_USE_BUNDLED_EXTERNALS}
-)
+# AddJsonPackage(
+# NAME unordered-dense
+# BUNDLED_PACKAGE ${DYNARMIC_USE_BUNDLED_EXTERNALS}
+# )
# xbyak
# uncomment if in an independent repo
diff --git a/src/dynarmic/externals/cpmfile.json b/src/dynarmic/externals/cpmfile.json
index b934856af2..e9406cbe81 100644
--- a/src/dynarmic/externals/cpmfile.json
+++ b/src/dynarmic/externals/cpmfile.json
@@ -14,16 +14,6 @@
"MCL_INSTALL OFF"
]
},
- "unordered-dense": {
- "package": "unordered_dense",
- "repo": "Lizzie841/unordered_dense",
- "sha": "e59d30b7b1",
- "hash": "71eff7bd9ba4b9226967bacd56a8ff000946f8813167cb5664bb01e96fb79e4e220684d824fe9c59c4d1cc98c606f13aff05b7940a1ed8ab3c95d6974ee34fa0",
- "find_args": "CONFIG",
- "options": [
- "UNORDERED_DENSE_INSTALL OFF"
- ]
- },
"zycore": {
"package": "Zycore",
"repo": "zyantific/zycore-c",
diff --git a/src/dynarmic/src/dynarmic/common/lut_from_list.h b/src/dynarmic/src/dynarmic/common/lut_from_list.h
index ed9e3dc046..c904e2c041 100644
--- a/src/dynarmic/src/dynarmic/common/lut_from_list.h
+++ b/src/dynarmic/src/dynarmic/common/lut_from_list.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) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
@@ -19,6 +22,16 @@
namespace Dynarmic::Common {
+// prevents this function from printing 56,000 character warning messages
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wno-stack-usage"
+#endif
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wno-stack-usage"
+#endif
+
template
inline auto GenerateLookupTableFromList(Function f, mcl::mp::list) {
#ifdef _MSC_VER
@@ -34,4 +47,11 @@ inline auto GenerateLookupTableFromList(Function f, mcl::mp::list) {
return MapT(pair_array.begin(), pair_array.end());
}
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
} // namespace Dynarmic::Common
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 89fe7a35f9..8131d42aae 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -332,7 +332,8 @@ target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})
add_dependencies(video_core host_shaders)
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
-target_link_libraries(video_core PRIVATE sirit Vulkan::Headers Vulkan::UtilityHeaders GPUOpen::VulkanMemoryAllocator)
+target_link_libraries(video_core PRIVATE sirit Vulkan::Headers Vulkan::UtilityHeaders)
+target_link_libraries(video_core PUBLIC GPUOpen::VulkanMemoryAllocator)
if (ENABLE_NSIGHT_AFTERMATH)
if (NOT DEFINED ENV{NSIGHT_AFTERMATH_SDK})
diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp
index 596323fb32..37213912e3 100644
--- a/src/video_core/renderer_vulkan/blit_image.cpp
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -102,13 +102,16 @@ constexpr VkPipelineVertexInputStateCreateInfo PIPELINE_VERTEX_INPUT_STATE_CREAT
.vertexAttributeDescriptionCount = 0,
.pVertexAttributeDescriptions = nullptr,
};
-constexpr VkPipelineInputAssemblyStateCreateInfo PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO{
- .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
- .primitiveRestartEnable = VK_FALSE,
-};
+
+VkPipelineInputAssemblyStateCreateInfo GetPipelineInputAssemblyStateCreateInfo(const Device& device) {
+ return VkPipelineInputAssemblyStateCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .primitiveRestartEnable = device.IsMoltenVK() ? VK_TRUE : VK_FALSE,
+ };
+}
constexpr VkPipelineViewportStateCreateInfo PIPELINE_VIEWPORT_STATE_CREATE_INFO{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pNext = nullptr,
@@ -802,6 +805,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceColorPipeline(const BlitImagePipelineKe
.pAttachments = &blend_attachment,
.blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
};
+ const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
blit_color_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
@@ -809,7 +813,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceColorPipeline(const BlitImagePipelineKe
.stageCount = static_cast(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
- .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pInputAssemblyState = &input_assembly_ci,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
@@ -833,6 +837,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePip
}
blit_depth_stencil_keys.push_back(key);
const std::array stages = MakeStages(*full_screen_vert, *blit_depth_stencil_frag);
+ const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
blit_depth_stencil_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
@@ -840,7 +845,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePip
.stageCount = static_cast(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
- .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pInputAssemblyState = &input_assembly_ci,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
@@ -885,6 +890,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel
.pAttachments = &color_blend_attachment_state,
.blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
};
+ const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
clear_color_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
@@ -892,7 +898,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel
.stageCount = static_cast(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
- .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pInputAssemblyState = &input_assembly_ci,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
@@ -940,6 +946,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
.minDepthBounds = 0.0f,
.maxDepthBounds = 0.0f,
};
+ const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
clear_stencil_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
@@ -947,7 +954,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
.stageCount = static_cast(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
- .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pInputAssemblyState = &input_assembly_ci,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
@@ -970,6 +977,7 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend
}
VkShaderModule frag_shader = *convert_float_to_depth_frag;
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
+ const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
pipeline = device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
@@ -977,7 +985,7 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend
.stageCount = static_cast(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
- .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pInputAssemblyState = &input_assembly_ci,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
@@ -999,6 +1007,7 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend
}
VkShaderModule frag_shader = *convert_depth_to_float_frag;
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
+ const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
pipeline = device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
@@ -1006,7 +1015,7 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend
.stageCount = static_cast(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
- .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pInputAssemblyState = &input_assembly_ci,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
@@ -1029,6 +1038,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren
return;
}
const std::array stages = MakeStages(*full_screen_vert, *module);
+ const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
pipeline = device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
@@ -1036,7 +1046,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren
.stageCount = static_cast(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
- .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pInputAssemblyState = &input_assembly_ci,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
@@ -1070,6 +1080,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
VkShaderModule frag_shader =
is_target_depth ? *convert_float_to_depth_frag : *convert_depth_to_float_frag;
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
+ const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
pipeline = device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
@@ -1077,7 +1088,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
.stageCount = static_cast(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
- .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pInputAssemblyState = &input_assembly_ci,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
diff --git a/src/video_core/renderer_vulkan/present/util.cpp b/src/video_core/renderer_vulkan/present/util.cpp
index 6874bbae99..07b6a41c5c 100644
--- a/src/video_core/renderer_vulkan/present/util.cpp
+++ b/src/video_core/renderer_vulkan/present/util.cpp
@@ -400,12 +400,12 @@ static vk::Pipeline CreateWrappedPipelineImpl(
.pVertexAttributeDescriptions = nullptr,
};
- constexpr VkPipelineInputAssemblyStateCreateInfo input_assembly_ci{
+ const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
- .primitiveRestartEnable = VK_FALSE,
+ .primitiveRestartEnable = device.IsMoltenVK() ? VK_TRUE : VK_FALSE,
};
constexpr VkPipelineViewportStateCreateInfo viewport_state_ci{
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index 0226eb2c14..dc068c5e52 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -635,14 +635,16 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
.flags = 0,
.topology = input_assembly_topology,
.primitiveRestartEnable =
- dynamic.primitive_restart_enable != 0 &&
+ // MoltenVK/Metal always has primitive restart enabled and cannot disable it
+ device.IsMoltenVK() ? VK_TRUE :
+ (dynamic.primitive_restart_enable != 0 &&
((input_assembly_topology != VK_PRIMITIVE_TOPOLOGY_PATCH_LIST &&
device.IsTopologyListPrimitiveRestartSupported()) ||
SupportsPrimitiveRestart(input_assembly_topology) ||
(input_assembly_topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST &&
device.IsPatchListPrimitiveRestartSupported()))
? VK_TRUE
- : VK_FALSE,
+ : VK_FALSE),
};
const VkPipelineTessellationStateCreateInfo tessellation_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO,
diff --git a/src/video_core/vulkan_common/vma.h b/src/video_core/vulkan_common/vma.h
index 6e25aa1bdf..911c1114b2 100644
--- a/src/video_core/vulkan_common/vma.h
+++ b/src/video_core/vulkan_common/vma.h
@@ -1,3 +1,5 @@
+// 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
@@ -8,4 +10,4 @@
#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
-#include
+#include "vk_mem_alloc.h"
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index cfa88850a0..6fdf1e7874 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -725,6 +725,11 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
dynamic_state3_enables = true;
}
+ if (is_mvk && Settings::values.dyna_state.GetValue() != 0) {
+ LOG_WARNING(Render_Vulkan, "MoltenVK detected: Forcing dynamic state to 0 to prevent black screen issues");
+ Settings::values.dyna_state.SetValue(0);
+ }
+
if (Settings::values.dyna_state.GetValue() == 0) {
must_emulate_scaled_formats = true;
LOG_INFO(Render_Vulkan, "Dynamic state is disabled (dyna_state = 0), forcing scaled format emulation ON");
@@ -753,18 +758,24 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
functions.vkGetInstanceProcAddr = dld.vkGetInstanceProcAddr;
functions.vkGetDeviceProcAddr = dld.vkGetDeviceProcAddr;
- const VmaAllocatorCreateInfo allocator_info = {
- .flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT,
- .physicalDevice = physical,
- .device = *logical,
- .preferredLargeHeapBlockSize = 0,
- .pAllocationCallbacks = nullptr,
- .pDeviceMemoryCallbacks = nullptr,
- .pHeapSizeLimit = nullptr,
- .pVulkanFunctions = &functions,
- .instance = instance,
- .vulkanApiVersion = VK_API_VERSION_1_1,
- .pTypeExternalMemoryHandleTypes = nullptr,
+ VmaAllocatorCreateFlags flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT;
+ if (extensions.memory_budget) {
+ flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
+ }
+ const VmaAllocatorCreateInfo allocator_info{
+ .flags = flags,
+ .physicalDevice = physical,
+ .device = *logical,
+ .preferredLargeHeapBlockSize = is_integrated
+ ? (64u * 1024u * 1024u)
+ : (256u * 1024u * 1024u),
+ .pAllocationCallbacks = nullptr,
+ .pDeviceMemoryCallbacks = nullptr,
+ .pHeapSizeLimit = nullptr,
+ .pVulkanFunctions = &functions,
+ .instance = instance,
+ .vulkanApiVersion = ApiVersion(),
+ .pTypeExternalMemoryHandleTypes = nullptr,
};
vk::Check(vmaCreateAllocator(&allocator_info, &allocator));
@@ -1090,8 +1101,15 @@ bool Device::GetSuitability(bool requires_swapchain) {
// Some features are mandatory. Check those.
#define CHECK_FEATURE(feature, name) \
if (!features.feature.name) { \
- LOG_ERROR(Render_Vulkan, "Missing required feature {}", #name); \
- suitable = false; \
+ if (IsMoltenVK() && (strcmp(#name, "geometryShader") == 0 || \
+ strcmp(#name, "logicOp") == 0 || \
+ strcmp(#name, "shaderCullDistance") == 0 || \
+ strcmp(#name, "wideLines") == 0)) { \
+ LOG_INFO(Render_Vulkan, "MoltenVK missing feature {} - using fallback", #name); \
+ } else { \
+ LOG_ERROR(Render_Vulkan, "Missing required feature {}", #name); \
+ suitable = false; \
+ } \
}
#define LOG_FEATURE(feature, name) \
@@ -1378,13 +1396,13 @@ void Device::CollectPhysicalMemoryInfo() {
device_access_memory += mem_properties.memoryHeaps[element].size;
}
if (!is_integrated) {
- const u64 reserve_memory = std::min(device_access_memory / 4, 2_GiB);
+ const u64 reserve_memory = std::min(device_access_memory / 8, 1_GiB);
device_access_memory -= reserve_memory;
if (Settings::values.vram_usage_mode.GetValue() != Settings::VramUsageMode::Aggressive) {
// Account for resolution scaling in memory limits
- const size_t normal_memory = 8_GiB;
- const size_t scaler_memory = 2_GiB * Settings::values.resolution_info.ScaleUp(1);
+ const size_t normal_memory = 6_GiB;
+ const size_t scaler_memory = 1_GiB * Settings::values.resolution_info.ScaleUp(1);
device_access_memory =
std::min(device_access_memory, normal_memory + scaler_memory);
}
@@ -1393,7 +1411,7 @@ void Device::CollectPhysicalMemoryInfo() {
}
const s64 available_memory = static_cast(device_access_memory - device_initial_usage);
device_access_memory = static_cast(std::max(
- std::min(available_memory - 4_GiB, 6_GiB), std::min(local_memory, 6_GiB)));
+ std::min(available_memory - 8_GiB, 6_GiB), std::min(local_memory, 6_GiB)));
}
void Device::CollectToolingInfo() {
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 9b78f2e599..bd54144480 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -717,6 +717,10 @@ public:
return properties.driver.driverID == VK_DRIVER_ID_NVIDIA_PROPRIETARY;
}
+ bool IsMoltenVK() const noexcept {
+ return properties.driver.driverID == VK_DRIVER_ID_MOLTENVK;
+ }
+
NvidiaArchitecture GetNvidiaArch() const noexcept {
return nvidia_arch;
}
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index 2e37615f99..4ab420afea 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -6,7 +6,10 @@
#include
#include
+#include
#include
+#include
+#include
#include
#include "common/alignment.h"
@@ -21,379 +24,302 @@
#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-namespace {
-struct Range {
- u64 begin;
- u64 end;
+ namespace {
- [[nodiscard]] bool Contains(u64 iterator, u64 size) const noexcept {
- return iterator < end && begin < iterator + size;
- }
-};
+// Helpers translating MemoryUsage to flags/usage
-[[nodiscard]] u64 AllocationChunkSize(u64 required_size) {
- static constexpr std::array sizes{
- 0x1000ULL << 10, 0x1400ULL << 10, 0x1800ULL << 10, 0x1c00ULL << 10, 0x2000ULL << 10,
- 0x3200ULL << 10, 0x4000ULL << 10, 0x6000ULL << 10, 0x8000ULL << 10, 0xA000ULL << 10,
- 0x10000ULL << 10, 0x18000ULL << 10, 0x20000ULL << 10,
- };
- static_assert(std::is_sorted(sizes.begin(), sizes.end()));
-
- const auto it = std::ranges::lower_bound(sizes, required_size);
- return it != sizes.end() ? *it : Common::AlignUp(required_size, 4ULL << 20);
-}
-
-[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) {
- switch (usage) {
- case MemoryUsage::DeviceLocal:
- return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
- case MemoryUsage::Upload:
- return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
- case MemoryUsage::Download:
- return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
- VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
- case MemoryUsage::Stream:
- return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
- VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
- }
- ASSERT_MSG(false, "Invalid memory usage={}", usage);
- return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
-}
-
-[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) {
- return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
- : VkMemoryPropertyFlagBits{};
-}
-
-[[nodiscard]] VmaAllocationCreateFlags MemoryUsageVmaFlags(MemoryUsage usage) {
- switch (usage) {
- case MemoryUsage::Upload:
- case MemoryUsage::Stream:
- return VMA_ALLOCATION_CREATE_MAPPED_BIT |
- VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
- case MemoryUsage::Download:
- return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
- case MemoryUsage::DeviceLocal:
- return {};
- }
- return {};
-}
-
-[[nodiscard]] VmaMemoryUsage MemoryUsageVma(MemoryUsage usage) {
- switch (usage) {
- case MemoryUsage::DeviceLocal:
- case MemoryUsage::Stream:
- return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
- case MemoryUsage::Upload:
- case MemoryUsage::Download:
- return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
- }
- return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
-}
-
-} // Anonymous namespace
-
-class MemoryAllocation {
-public:
- explicit MemoryAllocation(MemoryAllocator* const allocator_, vk::DeviceMemory memory_,
- VkMemoryPropertyFlags properties, u64 allocation_size_, u32 type)
- : allocator{allocator_}, memory{std::move(memory_)}, allocation_size{allocation_size_},
- property_flags{properties}, shifted_memory_type{1U << type} {}
-
- MemoryAllocation& operator=(const MemoryAllocation&) = delete;
- MemoryAllocation(const MemoryAllocation&) = delete;
-
- MemoryAllocation& operator=(MemoryAllocation&&) = delete;
- MemoryAllocation(MemoryAllocation&&) = delete;
-
- [[nodiscard]] std::optional Commit(VkDeviceSize size, VkDeviceSize alignment) {
- const std::optional alloc = FindFreeRegion(size, alignment);
- if (!alloc) {
- // Signal out of memory, it'll try to do more allocations.
- return std::nullopt;
+ [[maybe_unused]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) {
+ switch (usage) {
+ case MemoryUsage::DeviceLocal:
+ return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+ case MemoryUsage::Upload:
+ return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+ case MemoryUsage::Download:
+ return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
+ VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
+ case MemoryUsage::Stream:
+ return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+ }
+ ASSERT_MSG(false, "Invalid memory usage={}", usage);
+ return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
- const Range range{
- .begin = *alloc,
- .end = *alloc + size,
+
+ [[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) {
+ return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+ : VkMemoryPropertyFlagBits{};
+ }
+
+ [[nodiscard]] VmaAllocationCreateFlags MemoryUsageVmaFlags(MemoryUsage usage) {
+ switch (usage) {
+ case MemoryUsage::Upload:
+ case MemoryUsage::Stream:
+ return VMA_ALLOCATION_CREATE_MAPPED_BIT |
+ VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
+ case MemoryUsage::Download:
+ return VMA_ALLOCATION_CREATE_MAPPED_BIT |
+ VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
+ case MemoryUsage::DeviceLocal:
+ return {};
+ }
+ return {};
+ }
+
+ [[nodiscard]] VmaMemoryUsage MemoryUsageVma(MemoryUsage usage) {
+ switch (usage) {
+ case MemoryUsage::DeviceLocal:
+ case MemoryUsage::Stream:
+ return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
+ case MemoryUsage::Upload:
+ case MemoryUsage::Download:
+ return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
+ }
+ return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
+ }
+
+
+// This avoids calling vkGetBufferMemoryRequirements* directly.
+ template
+ static VkBuffer GetVkHandleFromBuffer(const T &buf) {
+ if constexpr (requires { static_cast(buf); }) {
+ return static_cast(buf);
+ } else if constexpr (requires {{ buf.GetHandle() } -> std::convertible_to; }) {
+ return buf.GetHandle();
+ } else if constexpr (requires {{ buf.Handle() } -> std::convertible_to; }) {
+ return buf.Handle();
+ } else if constexpr (requires {{ buf.vk_handle() } -> std::convertible_to; }) {
+ return buf.vk_handle();
+ } else {
+ static_assert(sizeof(T) == 0, "Cannot extract VkBuffer handle from vk::Buffer");
+ return VK_NULL_HANDLE;
+ }
+ }
+
+ } // namespace
+
+//MemoryCommit is now VMA-backed
+ MemoryCommit::MemoryCommit(VmaAllocator alloc, VmaAllocation a,
+ const VmaAllocationInfo &info) noexcept
+ : allocator{alloc}, allocation{a}, memory{info.deviceMemory},
+ offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} {}
+
+ MemoryCommit::~MemoryCommit() { Release(); }
+
+ MemoryCommit::MemoryCommit(MemoryCommit &&rhs) noexcept
+ : allocator{std::exchange(rhs.allocator, nullptr)},
+ allocation{std::exchange(rhs.allocation, nullptr)},
+ memory{std::exchange(rhs.memory, VK_NULL_HANDLE)},
+ offset{std::exchange(rhs.offset, 0)},
+ size{std::exchange(rhs.size, 0)},
+ mapped_ptr{std::exchange(rhs.mapped_ptr, nullptr)} {}
+
+ MemoryCommit &MemoryCommit::operator=(MemoryCommit &&rhs) noexcept {
+ if (this != &rhs) {
+ Release();
+ allocator = std::exchange(rhs.allocator, nullptr);
+ allocation = std::exchange(rhs.allocation, nullptr);
+ memory = std::exchange(rhs.memory, VK_NULL_HANDLE);
+ offset = std::exchange(rhs.offset, 0);
+ size = std::exchange(rhs.size, 0);
+ mapped_ptr = std::exchange(rhs.mapped_ptr, nullptr);
+ }
+ return *this;
+ }
+
+ std::span MemoryCommit::Map()
+ {
+ if (!allocation) return {};
+ if (!mapped_ptr) {
+ if (vmaMapMemory(allocator, allocation, &mapped_ptr) != VK_SUCCESS) return {};
+ }
+ const size_t n = static_cast(std::min(size,
+ std::numeric_limits::max()));
+ return std::span{static_cast(mapped_ptr), n};
+ }
+
+ std::span MemoryCommit::Map() const
+ {
+ if (!allocation) return {};
+ if (!mapped_ptr) {
+ void *p = nullptr;
+ if (vmaMapMemory(allocator, allocation, &p) != VK_SUCCESS) return {};
+ const_cast(this)->mapped_ptr = p;
+ }
+ const size_t n = static_cast(std::min(size,
+ std::numeric_limits::max()));
+ return std::span{static_cast(mapped_ptr), n};
+ }
+
+ void MemoryCommit::Unmap()
+ {
+ if (allocation && mapped_ptr) {
+ vmaUnmapMemory(allocator, allocation);
+ mapped_ptr = nullptr;
+ }
+ }
+
+ void MemoryCommit::Release() {
+ if (allocation && allocator) {
+ if (mapped_ptr) {
+ vmaUnmapMemory(allocator, allocation);
+ mapped_ptr = nullptr;
+ }
+ vmaFreeMemory(allocator, allocation);
+ }
+ allocation = nullptr;
+ allocator = nullptr;
+ memory = VK_NULL_HANDLE;
+ offset = 0;
+ size = 0;
+ }
+
+ MemoryAllocator::MemoryAllocator(const Device &device_)
+ : device{device_}, allocator{device.GetAllocator()},
+ properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
+ buffer_image_granularity{
+ device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
+
+ // Preserve the previous "RenderDoc small heap" trimming behavior that we had in original vma minus the heap bug
+ if (device.HasDebuggingToolAttached())
+ {
+ using namespace Common::Literals;
+ ForEachDeviceLocalHostVisibleHeap(device, [this](size_t heap_idx, VkMemoryHeap &heap) {
+ if (heap.size <= 256_MiB) {
+ for (u32 t = 0; t < properties.memoryTypeCount; ++t) {
+ if (properties.memoryTypes[t].heapIndex == heap_idx) {
+ valid_memory_types &= ~(1u << t);
+ }
+ }
+ }
+ });
+ }
+ }
+
+ MemoryAllocator::~MemoryAllocator() = default;
+
+ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci) const
+ {
+ const VmaAllocationCreateInfo alloc_ci = {
+ .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
+ .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
+ .requiredFlags = 0,
+ .preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+ .memoryTypeBits = 0,
+ .pool = VK_NULL_HANDLE,
+ .pUserData = nullptr,
+ .priority = 0.f,
};
- commits.insert(std::ranges::upper_bound(commits, *alloc, {}, &Range::begin), range);
- return std::make_optional(this, *memory, *alloc, *alloc + size);
+
+ VkImage handle{};
+ VmaAllocation allocation{};
+ vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr));
+ return vk::Image(handle, ci.usage, *device.GetLogical(), allocator, allocation,
+ device.GetDispatchLoader());
}
- void Free(u64 begin) {
- const auto it = std::ranges::find(commits, begin, &Range::begin);
- ASSERT_MSG(it != commits.end(), "Invalid commit");
- commits.erase(it);
- if (commits.empty()) {
- // Do not call any code involving 'this' after this call, the object will be destroyed
- allocator->ReleaseMemory(this);
- }
+ vk::Buffer
+ MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const
+ {
+ const VmaAllocationCreateInfo alloc_ci = {
+ .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
+ .usage = MemoryUsageVma(usage),
+ .requiredFlags = 0,
+ .preferredFlags = MemoryUsagePreferredVmaFlags(usage),
+ .memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
+ .pool = VK_NULL_HANDLE,
+ .pUserData = nullptr,
+ .priority = 0.f,
+ };
+
+ VkBuffer handle{};
+ VmaAllocationInfo alloc_info{};
+ VmaAllocation allocation{};
+ VkMemoryPropertyFlags property_flags{};
+
+ vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
+ vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
+
+ u8 *data = reinterpret_cast(alloc_info.pMappedData);
+ const std::span mapped_data = data ? std::span{data, ci.size} : std::span{};
+ const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
+
+ return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data,
+ is_coherent,
+ device.GetDispatchLoader());
}
- [[nodiscard]] std::span Map() {
- if (memory_mapped_span.empty()) {
- u8* const raw_pointer = memory.Map(0, allocation_size);
- memory_mapped_span = std::span(raw_pointer, allocation_size);
- }
- return memory_mapped_span;
- }
+ MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements &reqs, MemoryUsage usage)
+ {
+ const auto vma_usage = MemoryUsageVma(usage);
+ VmaAllocationCreateInfo ci{};
+ ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage);
+ ci.usage = vma_usage;
+ ci.memoryTypeBits = reqs.memoryTypeBits & valid_memory_types;
+ ci.requiredFlags = 0;
+ ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage);
- /// Returns whether this allocation is compatible with the arguments.
- [[nodiscard]] bool IsCompatible(VkMemoryPropertyFlags flags, u32 type_mask) const {
- return (flags & property_flags) == flags && (type_mask & shifted_memory_type) != 0;
- }
+ VmaAllocation a{};
+ VmaAllocationInfo info{};
+ VkResult res = vmaAllocateMemory(allocator, &reqs, &ci, &a, &info);
-private:
- [[nodiscard]] static constexpr u32 ShiftType(u32 type) {
- return 1U << type;
- }
+ if (res != VK_SUCCESS) {
+ // Relax 1: drop budget constraint
+ auto ci2 = ci;
+ ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
+ res = vmaAllocateMemory(allocator, &reqs, &ci2, &a, &info);
- [[nodiscard]] std::optional FindFreeRegion(u64 size, u64 alignment) noexcept {
- ASSERT(std::has_single_bit(alignment));
- const u64 alignment_log2 = std::countr_zero(alignment);
- std::optional candidate;
- u64 iterator = 0;
- auto commit = commits.begin();
- while (iterator + size <= allocation_size) {
- candidate = candidate.value_or(iterator);
- if (commit == commits.end()) {
- break;
+ // Relax 2: if we preferred DEVICE_LOCAL, drop that preference
+ if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
+ auto ci3 = ci2;
+ ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+ res = vmaAllocateMemory(allocator, &reqs, &ci3, &a, &info);
}
- if (commit->Contains(*candidate, size)) {
- candidate = std::nullopt;
+ }
+
+ vk::Check(res);
+ return MemoryCommit(allocator, a, info);
+ }
+
+ MemoryCommit MemoryAllocator::Commit(const vk::Buffer &buffer, MemoryUsage usage) {
+ // Allocate memory appropriate for this buffer automatically
+ const auto vma_usage = MemoryUsageVma(usage);
+
+ VmaAllocationCreateInfo ci{};
+ ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage);
+ ci.usage = vma_usage;
+ ci.requiredFlags = 0;
+ ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage);
+ ci.pool = VK_NULL_HANDLE;
+ ci.pUserData = nullptr;
+ ci.priority = 0.0f;
+
+ const VkBuffer raw = *buffer;
+
+ VmaAllocation a{};
+ VmaAllocationInfo info{};
+
+ // Let VMA infer memory requirements from the buffer
+ VkResult res = vmaAllocateMemoryForBuffer(allocator, raw, &ci, &a, &info);
+
+ if (res != VK_SUCCESS) {
+ auto ci2 = ci;
+ ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
+ res = vmaAllocateMemoryForBuffer(allocator, raw, &ci2, &a, &info);
+
+ if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
+ auto ci3 = ci2;
+ ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+ res = vmaAllocateMemoryForBuffer(allocator, raw, &ci3, &a, &info);
}
- iterator = Common::AlignUpLog2(commit->end, alignment_log2);
- ++commit;
}
- return candidate;
+
+ vk::Check(res);
+ vk::Check(vmaBindBufferMemory2(allocator, a, 0, raw, nullptr));
+ return MemoryCommit(allocator, a, info);
}
- MemoryAllocator* const allocator; ///< Parent memory allocation.
- const vk::DeviceMemory memory; ///< Vulkan memory allocation handler.
- const u64 allocation_size; ///< Size of this allocation.
- const VkMemoryPropertyFlags property_flags; ///< Vulkan memory property flags.
- const u32 shifted_memory_type; ///< Shifted Vulkan memory type.
- std::vector commits; ///< All commit ranges done from this allocation.
- std::span memory_mapped_span; ///< Memory mapped span. Empty if not queried before.
-};
-
-MemoryCommit::MemoryCommit(MemoryAllocation* allocation_, VkDeviceMemory memory_, u64 begin_,
- u64 end_) noexcept
- : allocation{allocation_}, memory{memory_}, begin{begin_}, end{end_} {}
-
-MemoryCommit::~MemoryCommit() {
- Release();
-}
-
-MemoryCommit& MemoryCommit::operator=(MemoryCommit&& rhs) noexcept {
- Release();
- allocation = std::exchange(rhs.allocation, nullptr);
- memory = rhs.memory;
- begin = rhs.begin;
- end = rhs.end;
- span = std::exchange(rhs.span, std::span{});
- return *this;
-}
-
-MemoryCommit::MemoryCommit(MemoryCommit&& rhs) noexcept
- : allocation{std::exchange(rhs.allocation, nullptr)}, memory{rhs.memory}, begin{rhs.begin},
- end{rhs.end}, span{std::exchange(rhs.span, std::span{})} {}
-
-std::span MemoryCommit::Map() {
- if (span.empty()) {
- span = allocation->Map().subspan(begin, end - begin);
- }
- return span;
-}
-
-void MemoryCommit::Release() {
- if (allocation) {
- allocation->Free(begin);
- }
-}
-
-MemoryAllocator::MemoryAllocator(const Device& device_)
- : device{device_}, allocator{device.GetAllocator()},
- properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
- buffer_image_granularity{
- device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
- // GPUs not supporting rebar may only have a region with less than 256MB host visible/device
- // local memory. In that case, opening 2 RenderDoc captures side-by-side is not possible due to
- // the heap running out of memory. With RenderDoc attached and only a small host/device region,
- // only allow the stream buffer in this memory heap.
- if (device.HasDebuggingToolAttached()) {
- using namespace Common::Literals;
- ForEachDeviceLocalHostVisibleHeap(device, [this](size_t index, VkMemoryHeap& heap) {
- if (heap.size <= 256_MiB) {
- valid_memory_types &= ~(1u << index);
- }
- });
- }
-}
-
-MemoryAllocator::~MemoryAllocator() = default;
-
-vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
- const VmaAllocationCreateInfo alloc_ci = {
- .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
- .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
- .requiredFlags = 0,
- .preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
- .memoryTypeBits = 0,
- .pool = VK_NULL_HANDLE,
- .pUserData = nullptr,
- .priority = 0.f,
- };
-
- VkImage handle{};
- VmaAllocation allocation{};
-
- vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr));
-
- return vk::Image(handle, ci.usage, *device.GetLogical(), allocator, allocation,
- device.GetDispatchLoader());
-}
-
-vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
- const VmaAllocationCreateInfo alloc_ci = {
- .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
- .usage = MemoryUsageVma(usage),
- .requiredFlags = 0,
- .preferredFlags = MemoryUsagePreferredVmaFlags(usage),
- .memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
- .pool = VK_NULL_HANDLE,
- .pUserData = nullptr,
- .priority = 0.f,
- };
-
- VkBuffer handle{};
- VmaAllocationInfo alloc_info{};
- VmaAllocation allocation{};
- VkMemoryPropertyFlags property_flags{};
-
- vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
- vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
-
- u8* data = reinterpret_cast(alloc_info.pMappedData);
- const std::span mapped_data = data ? std::span{data, ci.size} : std::span{};
- const bool is_coherent = property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
-
- return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data, is_coherent,
- device.GetDispatchLoader());
-}
-
-MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, MemoryUsage usage) {
- // Find the fastest memory flags we can afford with the current requirements
- const u32 type_mask = requirements.memoryTypeBits;
- const VkMemoryPropertyFlags usage_flags = MemoryUsagePropertyFlags(usage);
- const VkMemoryPropertyFlags flags = MemoryPropertyFlags(type_mask, usage_flags);
- if (std::optional commit = TryCommit(requirements, flags)) {
- return std::move(*commit);
- }
- // Commit has failed, allocate more memory.
- const u64 chunk_size = AllocationChunkSize(requirements.size);
- if (!TryAllocMemory(flags, type_mask, chunk_size)) {
- // TODO(Rodrigo): Handle out of memory situations in some way like flushing to guest memory.
- throw vk::Exception(VK_ERROR_OUT_OF_DEVICE_MEMORY);
- }
- // Commit again, this time it won't fail since there's a fresh allocation above.
- // If it does, there's a bug.
- return TryCommit(requirements, flags).value();
- }
-
-bool MemoryAllocator::TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size) {
- const auto type_opt = FindType(flags, type_mask);
- if (!type_opt) {
- return false;
- }
-
- // Adreno stands firm
- const u64 aligned_size = (device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) ?
- Common::AlignUp(size, 4096) :
- size;
-
- vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({
- .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
- .pNext = nullptr,
- .allocationSize = aligned_size,
- .memoryTypeIndex = *type_opt,
- });
-
- if (!memory) {
- return false;
- }
-
- allocations.push_back(
- std::make_unique(this, std::move(memory), flags, aligned_size, *type_opt));
- return true;
-}
-
-void MemoryAllocator::ReleaseMemory(MemoryAllocation* alloc) {
- const auto it = std::ranges::find(allocations, alloc, &std::unique_ptr::get);
- ASSERT(it != allocations.end());
- allocations.erase(it);
-}
-
-std::optional MemoryAllocator::TryCommit(const VkMemoryRequirements& requirements,
- VkMemoryPropertyFlags flags) {
- // Conservative, spec-compliant alignment for suballocation
- VkDeviceSize eff_align = requirements.alignment;
- const auto& limits = device.GetPhysical().GetProperties().limits;
- if ((flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
- !(flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
- // Non-coherent memory must be invalidated on atom boundary
- if (limits.nonCoherentAtomSize > eff_align) eff_align = limits.nonCoherentAtomSize;
- }
- // Separate buffers to avoid stalls on tilers
- if (buffer_image_granularity > eff_align) {
- eff_align = buffer_image_granularity;
- }
- eff_align = std::bit_ceil(eff_align);
-
- for (auto& allocation : allocations) {
- if (!allocation->IsCompatible(flags, requirements.memoryTypeBits)) {
- continue;
- }
- if (auto commit = allocation->Commit(requirements.size, eff_align)) {
- return commit;
- }
- }
- if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) {
- // Look for non device local commits on failure
- return TryCommit(requirements, flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
- }
- return std::nullopt;
-}
-
-VkMemoryPropertyFlags MemoryAllocator::MemoryPropertyFlags(u32 type_mask,
- VkMemoryPropertyFlags flags) const {
- if (FindType(flags, type_mask)) {
- // Found a memory type with those requirements
- return flags;
- }
- if ((flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) != 0) {
- // Remove host cached bit in case it's not supported
- return MemoryPropertyFlags(type_mask, flags & ~VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
- }
- if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) {
- // Remove device local, if it's not supported by the requested resource
- return MemoryPropertyFlags(type_mask, flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
- }
- ASSERT_MSG(false, "No compatible memory types found");
- return 0;
-}
-
-std::optional MemoryAllocator::FindType(VkMemoryPropertyFlags flags, u32 type_mask) const {
- for (u32 type_index = 0; type_index < properties.memoryTypeCount; ++type_index) {
- const VkMemoryPropertyFlags type_flags = properties.memoryTypes[type_index].propertyFlags;
- if ((type_mask & (1U << type_index)) != 0 && (type_flags & flags) == flags) {
- // The type matches in type and in the wanted properties.
- return type_index;
- }
- }
- // Failed to find index
- return std::nullopt;
-}
-
} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h
index 38a182bcba..581f2e66d2 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.h
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -6,138 +9,134 @@
#include
#include
#include
+
#include "common/common_types.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
-
-VK_DEFINE_HANDLE(VmaAllocator)
+#include "video_core/vulkan_common/vma.h"
namespace Vulkan {
-class Device;
-class MemoryMap;
-class MemoryAllocation;
+ class Device;
/// Hints and requirements for the backing memory type of a commit
-enum class MemoryUsage {
- DeviceLocal, ///< Requests device local host visible buffer, falling back to device local
- ///< memory.
- Upload, ///< Requires a host visible memory type optimized for CPU to GPU uploads
- Download, ///< Requires a host visible memory type optimized for GPU to CPU readbacks
- Stream, ///< Requests device local host visible buffer, falling back host memory.
-};
+ enum class MemoryUsage {
+ DeviceLocal, ///< Requests device local host visible buffer, falling back to device local memory.
+ Upload, ///< Requires a host visible memory type optimized for CPU to GPU uploads
+ Download, ///< Requires a host visible memory type optimized for GPU to CPU readbacks
+ Stream, ///< Requests device local host visible buffer, falling back host memory.
+ };
-template
-void ForEachDeviceLocalHostVisibleHeap(const Device& device, F&& f) {
- auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
- for (size_t i = 0; i < memory_props.memoryTypeCount; i++) {
- auto& memory_type = memory_props.memoryTypes[i];
- if ((memory_type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
- (memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
- f(memory_type.heapIndex, memory_props.memoryHeaps[memory_type.heapIndex]);
+ template
+ void ForEachDeviceLocalHostVisibleHeap(const Device &device, F &&f) {
+ auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
+ for (size_t i = 0; i < memory_props.memoryTypeCount; i++) {
+ auto &memory_type = memory_props.memoryTypes[i];
+ if ((memory_type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
+ (memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
+ f(memory_type.heapIndex, memory_props.memoryHeaps[memory_type.heapIndex]);
+ }
}
}
-}
-/// Ownership handle of a memory commitment.
-/// Points to a subregion of a memory allocation.
-class MemoryCommit {
-public:
- explicit MemoryCommit() noexcept = default;
- explicit MemoryCommit(MemoryAllocation* allocation_, VkDeviceMemory memory_, u64 begin_,
- u64 end_) noexcept;
- ~MemoryCommit();
+/// Ownership handle of a memory commitment (real VMA allocation).
+ class MemoryCommit {
+ public:
+ MemoryCommit() noexcept = default;
- MemoryCommit& operator=(MemoryCommit&&) noexcept;
- MemoryCommit(MemoryCommit&&) noexcept;
+ MemoryCommit(VmaAllocator allocator, VmaAllocation allocation,
+ const VmaAllocationInfo &info) noexcept;
- MemoryCommit& operator=(const MemoryCommit&) = delete;
- MemoryCommit(const MemoryCommit&) = delete;
+ ~MemoryCommit();
- /// Returns a host visible memory map.
- /// It will map the backing allocation if it hasn't been mapped before.
- std::span Map();
+ MemoryCommit(const MemoryCommit &) = delete;
- /// Returns the Vulkan memory handler.
- VkDeviceMemory Memory() const {
- return memory;
- }
+ MemoryCommit &operator=(const MemoryCommit &) = delete;
- /// Returns the start position of the commit relative to the allocation.
- VkDeviceSize Offset() const {
- return static_cast(begin);
- }
+ MemoryCommit(MemoryCommit &&) noexcept;
-private:
- void Release();
+ MemoryCommit &operator=(MemoryCommit &&) noexcept;
- MemoryAllocation* allocation{}; ///< Pointer to the large memory allocation.
- VkDeviceMemory memory{}; ///< Vulkan device memory handler.
- u64 begin{}; ///< Beginning offset in bytes to where the commit exists.
- u64 end{}; ///< Offset in bytes where the commit ends.
- std::span span; ///< Host visible memory span. Empty if not queried before.
-};
+ [[nodiscard]] std::span Map();
+
+ [[nodiscard]] std::span Map() const;
+
+ void Unmap();
+
+ explicit operator bool() const noexcept { return allocation != nullptr; }
+
+ VkDeviceMemory Memory() const noexcept { return memory; }
+
+ VkDeviceSize Offset() const noexcept { return offset; }
+
+ VkDeviceSize Size() const noexcept { return size; }
+
+ VmaAllocation Allocation() const noexcept { return allocation; }
+
+ private:
+ void Release();
+
+ VmaAllocator allocator{}; ///< VMA allocator
+ VmaAllocation allocation{}; ///< VMA allocation handle
+ VkDeviceMemory memory{}; ///< Underlying VkDeviceMemory chosen by VMA
+ VkDeviceSize offset{}; ///< Offset of this allocation inside VkDeviceMemory
+ VkDeviceSize size{}; ///< Size of the allocation
+ void *mapped_ptr{}; ///< Optional persistent mapped pointer
+ };
/// Memory allocator container.
/// Allocates and releases memory allocations on demand.
-class MemoryAllocator {
- friend MemoryAllocation;
+ class MemoryAllocator {
+ public:
+ /**
+ * Construct memory allocator
+ *
+ * @param device_ Device to allocate from
+ *
+ * @throw vk::Exception on failure
+ */
+ explicit MemoryAllocator(const Device &device_);
-public:
- /**
- * Construct memory allocator
- *
- * @param device_ Device to allocate from
- *
- * @throw vk::Exception on failure
- */
- explicit MemoryAllocator(const Device& device_);
- ~MemoryAllocator();
+ ~MemoryAllocator();
- MemoryAllocator& operator=(const MemoryAllocator&) = delete;
- MemoryAllocator(const MemoryAllocator&) = delete;
+ MemoryAllocator &operator=(const MemoryAllocator &) = delete;
- vk::Image CreateImage(const VkImageCreateInfo& ci) const;
+ MemoryAllocator(const MemoryAllocator &) = delete;
- vk::Buffer CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const;
+ vk::Image CreateImage(const VkImageCreateInfo &ci) const;
- /**
- * Commits a memory with the specified requirements.
- *
- * @param requirements Requirements returned from a Vulkan call.
- * @param usage Indicates how the memory will be used.
- *
- * @returns A memory commit.
- */
- MemoryCommit Commit(const VkMemoryRequirements& requirements, MemoryUsage usage);
+ vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const;
- /// Commits memory required by the buffer and binds it.
- MemoryCommit Commit(const vk::Buffer& buffer, MemoryUsage usage);
+ /**
+ * Commits a memory with the specified requirements.
+ *
+ * @param requirements Requirements returned from a Vulkan call.
+ * @param usage Indicates how the memory will be used.
+ *
+ * @returns A memory commit.
+ */
+ MemoryCommit Commit(const VkMemoryRequirements &requirements, MemoryUsage usage);
-private:
- /// Tries to allocate a chunk of memory.
- bool TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size);
+ /// Commits memory required by the buffer and binds it (for buffers created outside VMA).
+ MemoryCommit Commit(const vk::Buffer &buffer, MemoryUsage usage);
- /// Releases a chunk of memory.
- void ReleaseMemory(MemoryAllocation* alloc);
+ private:
+ static bool IsAutoUsage(VmaMemoryUsage u) noexcept {
+ switch (u) {
+ case VMA_MEMORY_USAGE_AUTO:
+ case VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE:
+ case VMA_MEMORY_USAGE_AUTO_PREFER_HOST:
+ return true;
+ default:
+ return false;
+ }
+ }
- /// Tries to allocate a memory commit.
- std::optional TryCommit(const VkMemoryRequirements& requirements,
- VkMemoryPropertyFlags flags);
-
- /// Returns the fastest compatible memory property flags from the wanted flags.
- VkMemoryPropertyFlags MemoryPropertyFlags(u32 type_mask, VkMemoryPropertyFlags flags) const;
-
- /// Returns index to the fastest memory type compatible with the passed requirements.
- std::optional FindType(VkMemoryPropertyFlags flags, u32 type_mask) const;
-
- const Device& device; ///< Device handle.
- VmaAllocator allocator; ///< Vma allocator.
- const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties.
- std::vector> allocations; ///< Current allocations.
- VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers
- // and optimal images
- u32 valid_memory_types{~0u};
-};
+ const Device &device; ///< Device handle.
+ VmaAllocator allocator; ///< VMA allocator.
+ const VkPhysicalDeviceMemoryProperties properties; ///< Physical device memory properties.
+ VkDeviceSize buffer_image_granularity; ///< Adjacent buffer/image granularity
+ u32 valid_memory_types{~0u};
+ };
} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index 106630182f..949b91499d 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -580,6 +580,7 @@ DescriptorSets DescriptorPool::Allocate(const VkDescriptorSetAllocateInfo& ai) c
case VK_SUCCESS:
return DescriptorSets(std::move(sets), num, owner, handle, *dld);
case VK_ERROR_OUT_OF_POOL_MEMORY:
+ case VK_ERROR_FRAGMENTED_POOL:
return {};
default:
throw Exception(result);
@@ -604,6 +605,7 @@ CommandBuffers CommandPool::Allocate(std::size_t num_buffers, VkCommandBufferLev
case VK_SUCCESS:
return CommandBuffers(std::move(buffers), num_buffers, owner, handle, *dld);
case VK_ERROR_OUT_OF_POOL_MEMORY:
+ case VK_ERROR_FRAGMENTED_POOL:
return {};
default:
throw Exception(result);
diff --git a/src/yuzu/configuration/configure_graphics_extensions.cpp b/src/yuzu/configuration/configure_graphics_extensions.cpp
index c8dee6b073..322fa9ea08 100644
--- a/src/yuzu/configuration/configure_graphics_extensions.cpp
+++ b/src/yuzu/configuration/configure_graphics_extensions.cpp
@@ -60,6 +60,10 @@ void ConfigureGraphicsExtensions::Setup(const ConfigurationShared::Builder& buil
if (setting->Id() == Settings::values.dyna_state.Id()) {
widget->slider->setTickInterval(1);
widget->slider->setTickPosition(QSlider::TicksAbove);
+#ifdef __APPLE__
+ widget->setEnabled(false);
+ widget->setToolTip(tr("Extended Dynamic State is disabled on macOS due to MoltenVK compatibility issues that cause black screens."));
+#endif
}
}
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 2c3c46114d..4c6b176c56 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -553,9 +553,6 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
// Gen keys if necessary
OnCheckFirmwareDecryption();
- // Check firmware
- OnCheckFirmware();
-
game_list->LoadCompatibilityList();
// force reload on first load to ensure add-ons get updated
game_list->PopulateAsync(UISettings::values.game_dirs, false);
@@ -3094,34 +3091,7 @@ bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path,
const std::filesystem::path& command,
const std::string& arguments, const std::string& categories,
const std::string& keywords, const std::string& name) try {
-#if defined(__linux__) || defined(__FreeBSD__) // Linux and FreeBSD
- std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop");
- std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc);
- if (!shortcut_stream.is_open()) {
- LOG_ERROR(Frontend, "Failed to create shortcut");
- return false;
- }
- // TODO: Migrate fmt::print to std::print in futures STD C++ 23.
- fmt::print(shortcut_stream, "[Desktop Entry]\n");
- fmt::print(shortcut_stream, "Type=Application\n");
- fmt::print(shortcut_stream, "Version=1.0\n");
- fmt::print(shortcut_stream, "Name={}\n", name);
- if (!comment.empty()) {
- fmt::print(shortcut_stream, "Comment={}\n", comment);
- }
- if (std::filesystem::is_regular_file(icon_path)) {
- fmt::print(shortcut_stream, "Icon={}\n", icon_path.string());
- }
- fmt::print(shortcut_stream, "TryExec={}\n", command.string());
- fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments);
- if (!categories.empty()) {
- fmt::print(shortcut_stream, "Categories={}\n", categories);
- }
- if (!keywords.empty()) {
- fmt::print(shortcut_stream, "Keywords={}\n", keywords);
- }
- return true;
-#elif defined(_WIN32) // Windows
+#ifdef _WIN32 // Windows
HRESULT hr = CoInitialize(nullptr);
if (FAILED(hr)) {
LOG_ERROR(Frontend, "CoInitialize failed");
@@ -3183,7 +3153,34 @@ bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path,
return false;
}
return true;
-#else // Unsupported platform
+#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) // Any desktop NIX
+ std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop");
+ std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc);
+ if (!shortcut_stream.is_open()) {
+ LOG_ERROR(Frontend, "Failed to create shortcut");
+ return false;
+ }
+ // TODO: Migrate fmt::print to std::print in futures STD C++ 23.
+ fmt::print(shortcut_stream, "[Desktop Entry]\n");
+ fmt::print(shortcut_stream, "Type=Application\n");
+ fmt::print(shortcut_stream, "Version=1.0\n");
+ fmt::print(shortcut_stream, "Name={}\n", name);
+ if (!comment.empty()) {
+ fmt::print(shortcut_stream, "Comment={}\n", comment);
+ }
+ if (std::filesystem::is_regular_file(icon_path)) {
+ fmt::print(shortcut_stream, "Icon={}\n", icon_path.string());
+ }
+ fmt::print(shortcut_stream, "TryExec={}\n", command.string());
+ fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments);
+ if (!categories.empty()) {
+ fmt::print(shortcut_stream, "Categories={}\n", categories);
+ }
+ if (!keywords.empty()) {
+ fmt::print(shortcut_stream, "Keywords={}\n", keywords);
+ }
+ return true;
+#else // Unsupported platform
return false;
#endif
} catch (const std::exception& e) {
@@ -3228,7 +3225,7 @@ bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_vi
#if defined(_WIN32)
out_icon_path = Common::FS::GetEdenPath(Common::FS::EdenPath::IconsDir);
ico_extension = "ico";
-#elif defined(__linux__) || defined(__FreeBSD__)
+#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
out_icon_path = Common::FS::GetDataDirectory("XDG_DATA_HOME") / "icons/hicolor/256x256";
#endif
// Create icons directory if it doesn't exist
@@ -4459,7 +4456,6 @@ void GMainWindow::InstallFirmware(const QString& location, bool recursive) {
progress.close();
OnCheckFirmwareDecryption();
- OnCheckFirmware();
}
void GMainWindow::OnInstallFirmware() {
@@ -4580,7 +4576,6 @@ void GMainWindow::OnInstallDecryptionKeys() {
}
OnCheckFirmwareDecryption();
- OnCheckFirmware();
}
void GMainWindow::OnAbout() {
@@ -4609,6 +4604,7 @@ void GMainWindow::OnToggleStatusBar() {
void GMainWindow::OnGameListRefresh() {
// force reload add-ons etc
game_list->ForceRefreshGameDirectory();
+ SetFirmwareVersion();
}
void GMainWindow::OnAlbum() {
@@ -4707,13 +4703,42 @@ void GMainWindow::OnOpenControllerMenu() {
}
void GMainWindow::OnHomeMenu() {
+ auto result = FirmwareManager::VerifyFirmware(*system.get());
+
+ switch (result) {
+ case FirmwareManager::ErrorFirmwareMissing:
+ QMessageBox::warning(this, tr("No firmware available"),
+ tr("Please install firmware to use the Home Menu."));
+ return;
+ case FirmwareManager::ErrorFirmwareCorrupted:
+ QMessageBox::warning(this, tr("Firmware Corrupted"),
+ tr(FirmwareManager::GetFirmwareCheckString(result)));
+ return;
+ case FirmwareManager::ErrorFirmwareTooNew: {
+ if (!UISettings::values.show_fw_warning.GetValue()) break;
+
+ QMessageBox box(QMessageBox::Warning,
+ tr("Firmware Too New"),
+ tr(FirmwareManager::GetFirmwareCheckString(result)) + tr("\nContinue anyways?"),
+ QMessageBox::Yes | QMessageBox::No,
+ this);
+
+ QCheckBox *checkbox = new QCheckBox(tr("Don't show again"));
+ box.setCheckBox(checkbox);
+
+ int button = box.exec();
+ if (checkbox->isChecked()) {
+ UISettings::values.show_fw_warning.SetValue(false);
+ }
+
+ if (button == static_cast(QMessageBox::No)) return;
+ break;
+ } default:
+ break;
+ }
+
constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch);
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
- if (!bis_system) {
- QMessageBox::warning(this, tr("No firmware available"),
- tr("Please install the firmware to use the Home Menu."));
- return;
- }
auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
if (!qlaunch_applet_nca) {
@@ -4853,7 +4878,7 @@ void GMainWindow::CreateShortcut(const std::string& game_path, const u64 program
}
}
-#if defined(__linux__)
+#if defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
// Special case for AppImages
// Warn once if we are making a shortcut to a volatile AppImage
if (command.string().ends_with(".AppImage") && !UISettings::values.shortcut_already_warned) {
@@ -4863,7 +4888,7 @@ void GMainWindow::CreateShortcut(const std::string& game_path, const u64 program
}
UISettings::values.shortcut_already_warned = true;
}
-#endif // __linux__
+#endif
// Create shortcut
std::string arguments{arguments_};
@@ -5240,19 +5265,6 @@ void GMainWindow::OnCheckFirmwareDecryption() {
UpdateMenuState();
}
-void GMainWindow::OnCheckFirmware() {
- auto result = FirmwareManager::VerifyFirmware(*system.get());
-
- switch (result) {
- case FirmwareManager::FirmwareGood:
- break;
- default:
- QMessageBox::warning(this, tr("Firmware Read Error"),
- tr(FirmwareManager::GetFirmwareCheckString(result)));
- break;
- }
-}
-
bool GMainWindow::CheckFirmwarePresence() {
return FirmwareManager::CheckFirmwarePresence(*system.get());
}
@@ -5730,17 +5742,13 @@ int main(int argc, char* argv[]) {
#ifdef _WIN32
// Increases the maximum open file limit to 8192
_setmaxstdio(8192);
-#endif
-
-#ifdef __APPLE__
+#elif defined(__APPLE__)
// If you start a bundle (binary) on OSX without the Terminal, the working directory is "/".
// But since we require the working directory to be the executable path for the location of
// the user folder in the Qt Frontend, we need to cd into that working directory
const auto bin_path = Common::FS::GetBundleDirectory() / "..";
chdir(Common::FS::PathToUTF8String(bin_path).c_str());
-#endif
-
-#ifdef __linux__
+#elif defined(__unix__) && !defined(__ANDROID__)
// Set the DISPLAY variable in order to open web browsers
// TODO (lat9nq): Find a better solution for AppImages to start external applications
if (QString::fromLocal8Bit(qgetenv("DISPLAY")).isEmpty()) {
@@ -5749,7 +5757,7 @@ int main(int argc, char* argv[]) {
// Fix the Wayland appId. This needs to match the name of the .desktop file without the .desktop
// suffix.
- QGuiApplication::setDesktopFileName(QStringLiteral("org.eden_emu.eden"));
+ QGuiApplication::setDesktopFileName(QStringLiteral("dev.eden_emu.eden"));
#endif
SetHighDPIAttributes();
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 8a34a9f075..b1c5669a41 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -424,7 +424,6 @@ private slots:
void OnCreateHomeMenuShortcut(GameListShortcutTarget target);
void OnCaptureScreenshot();
void OnCheckFirmwareDecryption();
- void OnCheckFirmware();
void OnLanguageChanged(const QString& locale);
void OnMouseActivity();
bool OnShutdownBegin();
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 85de0ae72d..3322b31ca3 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -212,6 +212,9 @@ struct Values {
// Play time
Setting show_play_time{linkage, true, "show_play_time", Category::UiGameList};
+ // misc
+ Setting show_fw_warning{linkage, true, "show_fw_warning", Category::Miscellaneous};
+
bool configuration_applied;
bool reset_to_defaults;
bool shortcut_already_warned{false};
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index e22cf84bf1..551df7b4cd 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -138,7 +141,7 @@ bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image)
icon_file.Close();
return true;
-#elif defined(__linux__) || defined(__FreeBSD__)
+#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
// Convert and write the icon as a PNG
if (!image.save(QString::fromStdString(icon_path.string()))) {
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index ebd8fd7387..a7cf6d204c 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -39,6 +39,7 @@ create_resource("../../dist/yuzu.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR})
target_link_libraries(yuzu-cmd PRIVATE SDL2::SDL2 Vulkan::Headers)
+target_link_libraries(yuzu-cmd PRIVATE GPUOpen::VulkanMemoryAllocator)
if(UNIX AND NOT APPLE)
install(TARGETS yuzu-cmd)