if (result != null) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt
new file mode 100644
index 0000000000..c181656d99
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// Copyright 2023 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+package org.yuzu.yuzu_emu.utils
+
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.activities.EmulationActivity
+
+/**
+ * A service that shows a permanent notification in the background to avoid the app getting
+ * cleared from memory by the system.
+ */
+class ForegroundService : Service() {
+ companion object {
+ const val EMULATION_RUNNING_NOTIFICATION = 0x1000
+
+ const val ACTION_STOP = "stop"
+ }
+
+ private fun showRunningNotification() {
+ // Intent is used to resume emulation if the notification is clicked
+ val contentIntent = PendingIntent.getActivity(
+ this,
+ 0,
+ Intent(this, EmulationActivity::class.java),
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+ )
+ val builder =
+ NotificationCompat.Builder(this, getString(R.string.app_notification_channel_id))
+ .setSmallIcon(R.drawable.ic_stat_notification_logo)
+ .setContentTitle(getString(R.string.app_name))
+ .setContentText(getString(R.string.app_notification_running))
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setOngoing(true)
+ .setVibrate(null)
+ .setSound(null)
+ .setContentIntent(contentIntent)
+ startForeground(EMULATION_RUNNING_NOTIFICATION, builder.build())
+ }
+
+ override fun onBind(intent: Intent): IBinder? {
+ return null
+ }
+
+ override fun onCreate() {
+ showRunningNotification()
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ if (intent?.action == ACTION_STOP) {
+ try {
+ NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)
+ stopForeground(STOP_FOREGROUND_REMOVE)
+ } catch (e: Exception) {
+ Log.error("Failed to stop foreground service")
+ }
+ stopSelfResult(startId)
+ return START_NOT_STICKY
+ }
+
+ if (intent != null) {
+ showRunningNotification()
+ }
+ return START_STICKY
+ }
+
+ override fun onDestroy() =
+ NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)
+}
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 9872d69d18..b8f59de880 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -8,6 +8,11 @@
noticesAndErrors
Shows notifications when something goes wrong.
Notification permission not granted!
+ Eden
+ Eden
+ Eden Switch emulator notifications
+ Eden is Running
+
(Enhanced)
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 94ef1a48df..eb18a4bd66 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -792,6 +792,11 @@ void BufferCache::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
const u32 size = (std::min)(binding.size, (*channel_state->uniform_buffer_sizes)[stage][index]);
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
+ const bool sync_buffer = SynchronizeBuffer(buffer, device_addr, size);
+ if (sync_buffer) {
+ ++channel_state->uniform_cache_hits[0];
+ }
+ ++channel_state->uniform_cache_shots[0];
const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID &&
size <= channel_state->uniform_buffer_skip_cache_size &&
!memory_tracker.IsRegionGpuModified(device_addr, size);
@@ -822,12 +827,6 @@ void BufferCache
::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
device_memory.ReadBlockUnsafe(device_addr, span.data(), size);
return;
}
- // Classic cached path
- const bool sync_cached = SynchronizeBuffer(buffer, device_addr, size);
- if (sync_cached) {
- ++channel_state->uniform_cache_hits[0];
- }
- ++channel_state->uniform_cache_shots[0];
// Skip binding if it's not needed and if the bound buffer is not the fast version
// This exists to avoid instances where the fast buffer is bound and a GPU write happens
diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
index 600003953d..3af9758a31 100644
--- a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
@@ -31,7 +31,7 @@ struct DescriptorBank {
bool DescriptorBankInfo::IsSuperset(const DescriptorBankInfo& subset) const noexcept {
return uniform_buffers >= subset.uniform_buffers && storage_buffers >= subset.storage_buffers &&
texture_buffers >= subset.texture_buffers && image_buffers >= subset.image_buffers &&
- textures >= subset.textures && images >= subset.image_buffers;
+ textures >= subset.textures && images >= subset.images;
}
template