Compare commits

..

5 commits

Author SHA1 Message Date
ba86ecef8c spellchecking
All checks were successful
eden-license / license-header (pull_request) Successful in 26s
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2025-09-26 20:08:18 +02:00
f35289ad6f [dist, docs] Clearer wording for settings, guidelines for new settings
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2025-09-26 20:08:18 +02:00
19eb8272b1
[video_core] Fix a bug in buffer cache that caused flickering in some games when using fast buffering (#2584)
This fixes a bug in the buffer cache that caused flickering in some games when using fast buffering. This fixes Kirby Star Allies, Yoshi's Crafted World, and possibly many others.

Reviewed-on: #2584
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2025-09-26 05:13:08 +02:00
nyx
86ddb51a87
[android] Implement foreground notification service (#480)
A notification is shown when emulation is active. This, in theory should help preventing Eden for getting destroyed in the background. It is also a nice Q.O.L feature to have.

Credits go to t895 for the initial implementation. This was back-ported from older official Citra builds, although with crashes which i fixed myself.

Reviewed-on: #480
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: nyx <contact@innix.space>
Co-committed-by: nyx <contact@innix.space>
2025-09-26 05:01:33 +02:00
10aca2f90c
[Vulkan] Descriptor Pool bug fix (#2564)
a bank could be (incorrectly) considered a superset if it had enough image buffer descriptors but not enough storage image descriptors, causing the allocator to pick a bank that can’t actually satisfy VK_DESCRIPTOR_TYPE_STORAGE_IMAGE demand resulting in sham allocations and creation of new pools.
Note to testers,
please look for any regressions in terms of visuals and most importantly please test the performance and ram usage.

Reviewed-on: #2564
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2025-09-26 04:58:09 +02:00
8 changed files with 136 additions and 7 deletions

View file

@ -27,6 +27,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" android:required="false" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" android:required="false" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
@ -93,6 +95,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
<meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" /> <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" />
</activity> </activity>
<service android:name="org.yuzu.yuzu_emu.utils.ForegroundService" android:foregroundServiceType="specialUse">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="Keep emulation running in background"/>
</service>
<provider <provider
android:name=".features.DocumentProvider" android:name=".features.DocumentProvider"
android:authorities="${applicationId}.user" android:authorities="${applicationId}.user"

View file

@ -22,6 +22,17 @@ fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
class YuzuApplication : Application() { class YuzuApplication : Application() {
private fun createNotificationChannels() { private fun createNotificationChannels() {
val name: CharSequence = getString(R.string.app_notification_channel_name)
val description = getString(R.string.app_notification_channel_description)
val foregroundService = NotificationChannel(
getString(R.string.app_notification_channel_id),
name,
NotificationManager.IMPORTANCE_DEFAULT
)
foregroundService.description = description
foregroundService.setSound(null, null)
foregroundService.vibrationPattern = null
val noticeChannel = NotificationChannel( val noticeChannel = NotificationChannel(
getString(R.string.notice_notification_channel_id), getString(R.string.notice_notification_channel_id),
getString(R.string.notice_notification_channel_name), getString(R.string.notice_notification_channel_name),
@ -34,6 +45,7 @@ class YuzuApplication : Application() {
// or other notification behaviors after this // or other notification behaviors after this
val notificationManager = getSystemService(NotificationManager::class.java) val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(noticeChannel) notificationManager.createNotificationChannel(noticeChannel)
notificationManager.createNotificationChannel(foregroundService)
} }
override fun onCreate() { override fun onCreate() {

View file

@ -7,6 +7,7 @@
package org.yuzu.yuzu_emu.activities package org.yuzu.yuzu_emu.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.app.PendingIntent import android.app.PendingIntent
import android.app.PictureInPictureParams import android.app.PictureInPictureParams
import android.app.RemoteAction import android.app.RemoteAction
@ -59,6 +60,7 @@ import org.yuzu.yuzu_emu.utils.ParamPackage
import org.yuzu.yuzu_emu.utils.ThemeHelper import org.yuzu.yuzu_emu.utils.ThemeHelper
import java.text.NumberFormat import java.text.NumberFormat
import kotlin.math.roundToInt import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.utils.ForegroundService
class EmulationActivity : AppCompatActivity(), SensorEventListener { class EmulationActivity : AppCompatActivity(), SensorEventListener {
private lateinit var binding: ActivityEmulationBinding private lateinit var binding: ActivityEmulationBinding
@ -78,6 +80,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
private val emulationViewModel: EmulationViewModel by viewModels() private val emulationViewModel: EmulationViewModel by viewModels()
private var foregroundService: Intent? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Log.gameLaunched = true Log.gameLaunched = true
ThemeHelper.setTheme(this) ThemeHelper.setTheme(this)
@ -128,6 +132,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
nfcReader = NfcReader(this) nfcReader = NfcReader(this)
nfcReader.initialize() nfcReader.initialize()
foregroundService = Intent(this, ForegroundService::class.java)
startForegroundService(foregroundService)
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) { if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) { if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) {
@ -189,6 +196,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
stopMotionSensorListener() stopMotionSensorListener()
} }
override fun onDestroy() {
super.onDestroy()
stopForegroundService(this)
}
override fun onUserLeaveHint() { override fun onUserLeaveHint() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) { if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) {
@ -511,6 +524,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
companion object { companion object {
const val EXTRA_SELECTED_GAME = "SelectedGame" const val EXTRA_SELECTED_GAME = "SelectedGame"
fun stopForegroundService(activity: Activity) {
val startIntent = Intent(activity, ForegroundService::class.java)
startIntent.action = ForegroundService.ACTION_STOP
activity.startForegroundService(startIntent)
}
fun launch(activity: AppCompatActivity, game: Game) { fun launch(activity: AppCompatActivity, game: Game) {
val launcher = Intent(activity, EmulationActivity::class.java) val launcher = Intent(activity, EmulationActivity::class.java)
launcher.putExtra(EXTRA_SELECTED_GAME, game) launcher.putExtra(EXTRA_SELECTED_GAME, game)

View file

@ -48,6 +48,7 @@ import java.io.BufferedOutputStream
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import androidx.core.content.edit import androidx.core.content.edit
import org.yuzu.yuzu_emu.activities.EmulationActivity
import kotlin.text.compareTo import kotlin.text.compareTo
class MainActivity : AppCompatActivity(), ThemeProvider { class MainActivity : AppCompatActivity(), ThemeProvider {
@ -188,6 +189,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (it) checkKeys() if (it) checkKeys()
} }
// Dismiss previous notifications (should not happen unless a crash occurred)
EmulationActivity.stopForegroundService(this)
setInsets() setInsets()
} }
@ -293,6 +297,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
themeId = resId themeId = resId
} }
override fun onDestroy() {
EmulationActivity.stopForegroundService(this)
super.onDestroy()
}
val getGamesDirectory = val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result != null) { if (result != null) {

View file

@ -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)
}

View file

@ -8,6 +8,11 @@
<string name="notice_notification_channel_id" translatable="false">noticesAndErrors</string> <string name="notice_notification_channel_id" translatable="false">noticesAndErrors</string>
<string name="notice_notification_channel_description">Shows notifications when something goes wrong.</string> <string name="notice_notification_channel_description">Shows notifications when something goes wrong.</string>
<string name="notification_permission_not_granted">Notification permission not granted!</string> <string name="notification_permission_not_granted">Notification permission not granted!</string>
<string name="app_notification_channel_name" translatable="false">Eden</string>
<string name="app_notification_channel_id" translatable="false">Eden</string>
<string name="app_notification_channel_description">Eden Switch emulator notifications</string>
<string name="app_notification_running">Eden is Running</string>
<!-- Stats Overlay settings --> <!-- Stats Overlay settings -->
<string name="enhanced_fps_suffix">(Enhanced)</string> <string name="enhanced_fps_suffix">(Enhanced)</string>

View file

@ -792,6 +792,11 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
const u32 size = (std::min)(binding.size, (*channel_state->uniform_buffer_sizes)[stage][index]); const u32 size = (std::min)(binding.size, (*channel_state->uniform_buffer_sizes)[stage][index]);
Buffer& buffer = slot_buffers[binding.buffer_id]; Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, 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 && const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID &&
size <= channel_state->uniform_buffer_skip_cache_size && size <= channel_state->uniform_buffer_skip_cache_size &&
!memory_tracker.IsRegionGpuModified(device_addr, size); !memory_tracker.IsRegionGpuModified(device_addr, size);
@ -822,12 +827,6 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
device_memory.ReadBlockUnsafe(device_addr, span.data(), size); device_memory.ReadBlockUnsafe(device_addr, span.data(), size);
return; 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 // 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 // This exists to avoid instances where the fast buffer is bound and a GPU write happens

View file

@ -31,7 +31,7 @@ struct DescriptorBank {
bool DescriptorBankInfo::IsSuperset(const DescriptorBankInfo& subset) const noexcept { bool DescriptorBankInfo::IsSuperset(const DescriptorBankInfo& subset) const noexcept {
return uniform_buffers >= subset.uniform_buffers && storage_buffers >= subset.storage_buffers && return uniform_buffers >= subset.uniform_buffers && storage_buffers >= subset.storage_buffers &&
texture_buffers >= subset.texture_buffers && image_buffers >= subset.image_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 <typename Descriptors> template <typename Descriptors>