Compare commits

..

12 Commits

Author SHA1 Message Date
CamilleLaVey 53060c9a91 Small experiment 2026-04-08 23:47:15 +02:00
CamilleLaVey 8200a7564c [texture_cache] Removal of lowmemorydevice dead code 2026-04-08 23:47:15 +02:00
CamilleLaVey 1637a2cb30 fix license headers+ 2026-04-08 23:47:15 +02:00
CamilleLaVey cb851cf091 [texture_cache] Adjusted GC logic for the iterations with older or obsolete textures 2026-04-08 23:47:15 +02:00
CamilleLaVey 90fd089e58 [texture_cache] Reduce garbage collection logic by simplifying conditions and thresholds 2026-04-08 23:47:15 +02:00
CamilleLaVey 29e77b5e2d small fix for the softlock after lru cache removal 2026-04-08 23:47:15 +02:00
CamilleLaVey c515a6bf83 Gido MEOW 2026-04-08 23:47:15 +02:00
CamilleLaVey 2e90822686 [texture_cache] Replace LRU index with frame tick in ImageBase + update garbage collection logic 2026-04-08 23:47:15 +02:00
CamilleLaVey 9bef0b35ba I got meowed by Gidoly 2026-04-08 23:47:15 +02:00
CamilleLaVey ba1e137bb6 [buffer_cache] Removal of LRU inside buffer cache and replaced with tick operations inside frames. 2026-04-08 23:47:15 +02:00
CamilleLaVey febddbd00c [maxwell] Removed prefetching for ProcessCommands 2026-04-08 23:47:15 +02:00
CamilleLaVey ad2225b5d0 [maxwell] Refactor execution mask initialization to use fill() instead of reset() 2026-04-08 23:47:15 +02:00
56 changed files with 8363 additions and 8799 deletions
+318 -330
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+311 -323
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+478 -517
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -301
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
+289 -300
View File
File diff suppressed because it is too large Load Diff
@@ -284,6 +284,8 @@ class SettingsFragmentPresenter(
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
add(BooleanSetting.ENABLE_BUFFER_HISTORY.key)
add(BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS.key)
@@ -294,8 +296,6 @@ class SettingsFragmentPresenter(
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
add(SettingsItem.GPU_UNSWIZZLE_COMBINED)
add(HeaderSetting(R.string.extensions))
@@ -291,23 +291,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
// Game launched via intent (check for existing custom config)
intentGame != null -> {
game?.let { gameInstance ->
runCatching { GameHelper.restoreContentForGame(gameInstance) }
.onFailure {
Log.warning(
"[EmulationFragment] Failed to restore content for intent launch: ${it.message}"
)
}
val customConfigFile = SettingsFile.getCustomSettingsFile(gameInstance)
if (customConfigFile.exists()) {
shouldUseCustom = true
Log.info(
"[EmulationFragment] Found existing custom settings for ${gameInstance.title}, loading them"
)
SettingsFile.loadCustomConfig(gameInstance)
NativeConfig.unloadPerGameConfig()
} else {
shouldUseCustom = false
Log.info(
"[EmulationFragment] No custom settings found for ${gameInstance.title}, using global settings"
)
@@ -881,7 +871,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (drawerView == binding.quickSettingsSheet) {
isQuickSettingsMenuOpen = true
if (shouldUseCustom) {
SettingsFile.loadCustomConfig(game!!)
SettingsFile.loadCustomConfig(args.game!!)
}
}
}
@@ -2509,6 +2499,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
fun toggleOverlay(enable: Boolean) {
if (!isAdded || _binding == null) return
if (enable && hasPhysicalControllerConnected && !args.overlayGamelessEditMode) return
if (enable == !BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) {
// Reset controller input flag so controller can hide overlay again
if (!enable) {
@@ -2546,8 +2537,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (binding.surfaceInputOverlay.isGamelessMode()) return
if (hasConnectedControllers) {
if (BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() &&
BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) {
if (BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) {
overlayHiddenByPhysicalController = true
toggleOverlay(false)
}
@@ -95,7 +95,7 @@ namespace AndroidSettings {
Settings::Setting<u32> input_overlay_auto_hide{linkage, 5, "input_overlay_auto_hide",
Settings::Category::Overlay,
Settings::Specialization::Default, true, true, &enable_input_overlay_auto_hide};
Settings::Setting<bool> hide_overlay_on_controller_input{linkage, true,
Settings::Setting<bool> hide_overlay_on_controller_input{linkage, false,
"hide_overlay_on_controller_input",
Settings::Category::Overlay,
Settings::Specialization::Default, true,
@@ -32,7 +32,7 @@
<string name="hide_overlay_on_controller_input">إخفاء الطبقة عند إدخال ذراع التحكم</string>
<string name="hide_overlay_on_controller_input_description">إخفاء تلقائي لطبقة عناصر التحكم باللمس عند استخدام ذراع تحكم فعلية. تظهر الطبقة مرة أخرى عند فصل ذراع التحكم.</string>
<string name="invert_confirm_back_controller_buttons">عكس أزرار التأكيد/الرجوع في وحدة التحكم</string>
<string name="invert_confirm_back_controller_buttons_description">تبديل طريقة التعامل مع زري «تأكيد» و«رجوع» في نظام Android لتتوافق مع أنماط أجهزة Switch وXbox أثناء استخدام واجهة المستخدم الخاصة بالتطبيق.</string>
<string name="invert_confirm_back_controller_buttons_description">قم بتبديل طريقة عمل زري التأكيد والرجوع في نظام Android لتتوافق مع أنماط Switch و Xbox أثناء استخدام واجهة المستخدم الخاصة بالتطبيق.</string>
<string name="input_overlay_options">طبقة الإدخال</string>
<string name="input_overlay_options_description">ضبط ذراع التحكم على الشاشة</string>
@@ -268,7 +268,7 @@
<string name="product">المنتج</string>
<string name="android_version">إصدار Android</string>
<string name="android_security_patch">تصحيح الأمان</string>
<string name="build_id">معرف الإصدار</string>
<string name="build_id">معرف البناء</string>
<string name="general_information">معلومات عامة</string>
<string name="hardware">الأجهزة</string>
<string name="supported_abis">واجهات برمجة التطبيقات المدعومة</string>
@@ -287,7 +287,7 @@
<string name="install_amiibo_keys">تثبيت مفاتيح أميبو</string>
<string name="install_amiibo_keys_description">مطلوب لاستخدام أميبو في اللعبة</string>
<string name="gpu_driver_fetcher">أداة جلب برامج تشغيل وحدة المعالجة الرسومية</string>
<string name="gpu_driver_manager">إدارة برامج تشغيل وحدة معالجة الرسومات</string>
<string name="gpu_driver_manager">مدير برامج تشغيل وحدة معالجة الرسومات</string>
<string name="install_gpu_driver_description">تثبيت برامج تشغيل بديلة لأداء أو دقة أفضل</string>
<string name="advanced_settings">الإعدادات المتقدمة</string>
<string name="settings_description">ضبط إعدادات المحاكي</string>
@@ -403,7 +403,7 @@
<string name="contributors">المساهمين</string>
<string name="contributors_description">الأشخاص الذين جعلوا تطبيق Eden لنظام Android ممكنًا</string>
<string name="licenses_description">المشاريع التي تجعل Eden لأجهزة Android ممكنة</string>
<string name="build">الإصدار</string>
<string name="build">البناء</string>
<string name="user_data">بيانات المستخدم</string>
<string name="user_data_description">استيراد/تصدير جميع بيانات التطبيق.\nعند استيراد بيانات المستخدم، سيتم حذف جميع بيانات المستخدم الموجودة!\nقد يُسبب استيراد البيانات من Citron بعض المشاكل. يُنصح باستيراد جميع البيانات المطلوبة يدويًا.</string>
<string name="exporting_user_data">جارٍ تصدير بيانات المستخدم...</string>
@@ -486,10 +486,6 @@
<string name="use_disk_shader_cache_description">يقلل من التأتأة عن طريق تخزين وتحميل التظليلات التي تم إنشاؤها محليًا.</string>
<string name="renderer_force_max_clock">إجبار السرعة القصوى (لأجهزة Adreno فقط)</string>
<string name="renderer_force_max_clock_description">يجبر وحدة معالجة الرسومات على العمل بأقصى سرعة ممكنة (سيظل يتم تطبيق القيود الحرارية).</string>
<string name="renderer_asynchronous_gpu_emulation">محاكاة غير متزامنة لوحدة معالجة الرسومات</string>
<string name="renderer_asynchronous_gpu_emulation_description">يُشغّل هذا الخيار محاكاة وحدة معالجة الرسومات بشكل غير متزامن لتقليل توقف وحدة المعالجة المركزية وتحسين الإنتاجية. عطّل هذا الخيار فقط في حال واجهت مشاكل متعلقة بالتوقيت.</string>
<string name="renderer_async_presentation">عرض غير متزامن</string>
<string name="renderer_async_presentation_description">يحسّن الأداء بشكل طفيف عن طريق نقل عملية العرض إلى خيط معالجة منفصل لوحدة المعالجة المركزية.</string>
<string name="renderer_reactive_flushing">استخدم التنظيف التفاعلي</string>
<string name="renderer_reactive_flushing_description">يحسن دقة العرض في بعض الألعاب على حساب الأداء.</string>
<string name="enable_buffer_history">تمكين سجل التخزين المؤقت</string>
@@ -631,10 +627,10 @@
<string name="gamecube_controller">ذراع تحكم GameCube</string>
<string name="invert_axis">عكس المحور</string>
<string name="invert_button">عكس الزر</string>
<string name="toggle_button">زر تشغيل/إيقاف</string>
<string name="toggle_button">زر التبديل</string>
<string name="turbo_button">زر التوربو</string>
<string name="set_threshold">تعيين الحد الفاصل</string>
<string name="toggle_axis">تشغيل/إيقاف المحور</string>
<string name="toggle_axis">تبديل المحور</string>
<string name="connected">متصل</string>
<string name="use_system_vibrator">استخدم هزاز النظام</string>
<string name="input_overlay">طبقة الإدخال</string>
@@ -660,7 +656,7 @@
<!-- Miscellaneous -->
<string name="slider_default">افتراضي</string>
<string name="default_string">افتراضي</string>
<string name="default_string">الافتراضي</string>
<string name="loading">جارٍ التحميل…</string>
<string name="shutting_down">جارٍ إيقاف التشغيل...</string>
<string name="reset_setting_confirmation">هل تريد إعادة تعيين هذا الإعداد إلى قيمته الافتراضية؟</string>
@@ -686,8 +682,8 @@
<string name="import_success">تم الاستيراد بنجاح</string>
<string name="export_success">تم التصدير بنجاح</string>
<string name="start">بدء</string>
<string name="global">الإعدادات العامة</string>
<string name="custom">الإعدادات المخصصة للعبة</string>
<string name="global">المتغيرات العامة</string>
<string name="custom">مخصص</string>
<string name="import_complete">اكتمل الاستيراد</string>
<string name="use_global_setting">استخدام الإعداد العام</string>
<string name="operation_completed_successfully">اكتملت العملية بنجاح</string>
@@ -766,7 +762,7 @@
<string name="info_description">معرف العنوان، المطور، الإصدار</string>
<string name="per_game_settings">الإعدادات الخاصة للعبة</string>
<string name="per_game_settings_description">تعديل الإعدادات الخاصة بهذه اللعبة</string>
<string name="launch_options">إعدادات التشغيل</string>
<string name="launch_options">تشغيل الإعدادات</string>
<string name="path">المسار</string>
<string name="program_id">معرف العنوان</string>
<string name="developer">المطور</string>
@@ -878,13 +874,13 @@
<!-- Emulation Menu -->
<string name="emulation_exit">خروج من المحاكاة</string>
<string name="emulation_done">إنهاء</string>
<string name="emulation_toggle_controls">تشغيل/إيقاف أزرار التحكم</string>
<string name="emulation_toggle_controls">تبديل أزرار التحكم</string>
<string name="emulation_rel_stick_center">مركز العصا النسبي</string>
<string name="emulation_dpad_slide">انزلاق الأسهم</string>
<string name="emulation_haptics">الاهتزازات الديناميكية</string>
<string name="emulation_show_overlay">عرض ذراع التحكم</string>
<string name="emulation_hide_overlay">إخفاء ذراع التحكم</string>
<string name="emulation_toggle_all">تشغيل/إيقاف الكل</string>
<string name="emulation_toggle_all">تبديل الكل</string>
<string name="emulation_control_adjust">ضبط الطبقة</string>
<string name="emulation_control_scale">الحجم</string>
<string name="emulation_control_opacity">الشفافية</string>
@@ -1126,7 +1122,7 @@
<string name="enable_overlay_description">تمكين التطبيق الصغير المدمج في Horizon. اضغط مع الاستمرار على زر الشاشة الرئيسية لمدة 1 ثانية لإظهاره.</string>
<!-- Profile Management -->
<string name="profile_manager">إدارة ملف التعريف</string>
<string name="profile_manager">مدير الملف الشخصي</string>
<string name="profile_manager_description">إدارة ملفات تعريف المستخدمين</string>
<string name="profile_add_user">إضافة مستخدم</string>
<string name="profile_new_user">مستخدم جديد</string>
@@ -272,7 +272,7 @@
<string name="general_information">Información general</string>
<string name="hardware">Hardware</string>
<string name="supported_abis">ABIs soportadas</string>
<string name="cpu_info">Información del procesador</string>
<string name="cpu_info">Información de la CPU</string>
<string name="gpu_information">Información de la GPU</string>
<string name="vulkan_driver_version">Versión del controlador de Vulkan</string>
<string name="error_getting_emulator_info">Error al obtener la información del emulador</string>
@@ -351,7 +351,7 @@
<item quantity="other">%d archivos de guardado importados con éxito.</item>
</plurals>
<string name="no_save_data_found">No se encontraron datos de guardado</string>
<string name="verify_installed_content">Verificar los contenidos instalados</string>
<string name="verify_installed_content">Verificar contenido instalado</string>
<string name="verify_installed_content_description">Comprueba todo el contenido instalado por si hubiese alguno corrupto</string>
<string name="keys_missing">Faltan las claves de encriptación</string>
@@ -416,8 +416,8 @@
<string name="turbo_speed_limit_description">Cuando el modo turbo esté activado, la emulación se ejecutará a esta velocidad.</string>
<string name="slow_speed_limit">Velocidad lenta</string>
<string name="slow_speed_limit_description">Cuando el modo lento esté activado, la emulación se ejecutará a esta velocidad.</string>
<string name="cpu_backend">Motor del procesador</string>
<string name="cpu_accuracy">Precisión del procesador</string>
<string name="cpu_backend">Motor de la CPU</string>
<string name="cpu_accuracy">Precisión de la CPU</string>
<string name="value_with_units">%1$s%2$s</string>
<!-- System settings strings -->
@@ -433,7 +433,7 @@
<string name="set_custom_rtc">Configurar RTC personalizado</string>
<!-- CPU -->
<string name="fast_cpu_time">Overclock del procesador</string>
<string name="fast_cpu_time">Overclock de la CPU</string>
<string name="fast_cpu_time_description">Fuerza a la CPU emulada a ejecutarser a una velocidad de reloj más alta, lo cual reduce ciertos limitadores de fotogramas por segundo. Usa Boost (1700 MHz) para ejecutar a la velocidad de reloj nativa más alta de la Switch, o Fast (2000 MHz) para ejecutar a una velocidad doble de reloj.</string>
<string name="custom_cpu_ticks">Ticks de CPU personalizados</string>
<string name="custom_cpu_ticks_description">Establezca un valor personalizado de los ciclos de la CPU. Los valores más altos pueden aumentar el rendimiento, pero también pueden hacer que el juego se congele. Se recomienda un rango de 77–21000.</string>
@@ -480,10 +480,6 @@
<string name="use_disk_shader_cache_description">Reduce los tirones almacenando y cargando los sombreadores generados.</string>
<string name="renderer_force_max_clock">Forzar velocidad al máximo (solo Adreno)</string>
<string name="renderer_force_max_clock_description">Fuerza a la GPU a ejecutarse a la velocidad máxima de reloj posible (se seguirán aplicando restricciones térmicas).</string>
<string name="renderer_asynchronous_gpu_emulation">Emulación de GPU asíncrona</string>
<string name="renderer_asynchronous_gpu_emulation_description">Ejecuta la emulación de la GPU de forma asíncrona para reducir los bloqueos de la CPU y mejorar el rendimiento. Desactiva esta opción solo si experimentas problemas de sincronización.</string>
<string name="renderer_async_presentation">Presentación asíncrona</string>
<string name="renderer_async_presentation_description">Mejora ligeramente el rendimiento al mover la presentación a un hilo independiente de la CPU.</string>
<string name="renderer_reactive_flushing">Usar limpieza reactiva</string>
<string name="renderer_reactive_flushing_description">Mejora la precisión de renderizado en algunos juegos, pero reduce el rendimiento.</string>
<string name="enable_buffer_history">Activar el historial del búfer</string>
@@ -536,7 +532,7 @@
<string name="warning_resolution">Escalar la resolución a 2x o más puede causar problemas y ralentizar significativamente su dispositivo.</string>
<!-- Debug settings strings -->
<string name="cpu">Procesador</string>
<string name="cpu">CPU</string>
<string name="use_auto_stub">Usar Auto Stub</string>
<string name="use_auto_stub_description">Rellena automáticamente servicios y funciones ausentes. Puede mejorar la compatibilidad pero puede causar cierres inesperados.</string>
@@ -482,10 +482,6 @@
<string name="use_disk_shader_cache_description">Уменьшение зависаний за счет хранения и загрузки сгенерированных шейдеров.</string>
<string name="renderer_force_max_clock">Принудительная максимальная тактовая частота (только для Adreno)</string>
<string name="renderer_force_max_clock_description">Заставляет ГПУ работать на максимально возможных тактовых частотах (тепловые ограничения все равно будут применяться).</string>
<string name="renderer_asynchronous_gpu_emulation">Асинхронная эмуляция ГПУ</string>
<string name="renderer_asynchronous_gpu_emulation_description">Выполняет эмуляцию ГПУ асинхронно для снижения задержек ЦП и увеличения производительности. Отключайте только при возникновении проблем с таймингами.</string>
<string name="renderer_async_presentation">Асинхронная презентация</string>
<string name="renderer_async_presentation_description">Немного улучшает производительность, перемещая презентацию в отдельный поток ЦП.</string>
<string name="renderer_reactive_flushing">Реактивная очистка</string>
<string name="renderer_reactive_flushing_description">Повышение точности рендеринга в некоторых играх за счет снижения производительности.</string>
<string name="enable_buffer_history">Включить историю буфера</string>
@@ -1084,7 +1080,6 @@
<string name="app_language_system">Следовать системе</string>
<!-- Static Themes -->
<string name="static_theme_color">Цвет темы</string>
<string name="eden_theme">Eden</string>
<string name="violet">Фиолетовый </string>
<string name="blue">Синий</string>
<string name="cyan">Циановый</string>
@@ -482,10 +482,6 @@
<string name="use_disk_shader_cache_description">Зменшує затримки шляхом збереження шейдерів.</string>
<string name="renderer_force_max_clock">Максимальна тактова частота (тільки Adreno)</string>
<string name="renderer_force_max_clock_description">Змушує GPU працювати на максимальній тактовій частоті.</string>
<string name="renderer_asynchronous_gpu_emulation">Асинхронна емуляція ГП</string>
<string name="renderer_asynchronous_gpu_emulation_description">Емуляція ГП виконується асинхронно для зменшення затримок ЦП й покращення пропускної здатності. Вимкніть лише у випадку виникнення проблем із таймінгами.</string>
<string name="renderer_async_presentation">Асинхронне подання</string>
<string name="renderer_async_presentation_description">Трохи покращує продуктивність завдяки переміщенню подання на окремий потік ЦП.</string>
<string name="renderer_reactive_flushing">Реактивне очищення</string>
<string name="renderer_reactive_flushing_description">Покращує точність рендерингу в деяких іграх.</string>
<string name="enable_buffer_history">Увімкнути історію буфера</string>
@@ -55,7 +55,7 @@
<string name="stats_overlay_options_description">配置性能统计叠加层中显示的信息</string>
<string name="show_fps">显示帧率</string>
<string name="show_fps_description">显示当前帧率</string>
<string name="show_frametime">显示 Frametime</string>
<string name="show_frametime">显示帧时间</string>
<string name="show_app_ram_usage">显示应用内存使用情况</string>
<string name="show_app_ram_usage_description">显示模拟器内存用量</string>
<string name="show_system_ram_usage">显示系统内存使用情况</string>
-139
View File
@@ -1,139 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <deque>
#include <memory>
#include <type_traits>
#include "common/common_types.h"
namespace Common {
template <class Traits>
class LeastRecentlyUsedCache {
using ObjectType = typename Traits::ObjectType;
using TickType = typename Traits::TickType;
struct Item {
ObjectType obj;
TickType tick;
Item* next{};
Item* prev{};
};
public:
LeastRecentlyUsedCache() : first_item{}, last_item{} {}
~LeastRecentlyUsedCache() = default;
size_t Insert(ObjectType obj, TickType tick) {
const auto new_id = Build();
auto& item = item_pool[new_id];
item.obj = obj;
item.tick = tick;
Attach(item);
return new_id;
}
void Touch(size_t id, TickType tick) {
auto& item = item_pool[id];
if (item.tick >= tick) {
return;
}
item.tick = tick;
if (&item == last_item) {
return;
}
Detach(item);
Attach(item);
}
void Free(size_t id) {
auto& item = item_pool[id];
Detach(item);
item.prev = nullptr;
item.next = nullptr;
free_items.push_back(id);
}
template <typename Func>
void ForEachItemBelow(TickType tick, Func&& func) {
static constexpr bool RETURNS_BOOL =
std::is_same_v<std::invoke_result<Func, ObjectType>, bool>;
Item* iterator = first_item;
while (iterator) {
if (static_cast<s64>(tick) - static_cast<s64>(iterator->tick) < 0) {
return;
}
Item* next = iterator->next;
if constexpr (RETURNS_BOOL) {
if (func(iterator->obj)) {
return;
}
} else {
func(iterator->obj);
}
iterator = next;
}
}
private:
size_t Build() {
if (free_items.empty()) {
const size_t item_id = item_pool.size();
auto& item = item_pool.emplace_back();
item.next = nullptr;
item.prev = nullptr;
return item_id;
}
const size_t item_id = free_items.front();
free_items.pop_front();
auto& item = item_pool[item_id];
item.next = nullptr;
item.prev = nullptr;
return item_id;
}
void Attach(Item& item) {
if (!first_item) {
first_item = &item;
}
if (!last_item) {
last_item = &item;
} else {
item.prev = last_item;
last_item->next = &item;
item.next = nullptr;
last_item = &item;
}
}
void Detach(Item& item) {
if (item.prev) {
item.prev->next = item.next;
}
if (item.next) {
item.next->prev = item.prev;
}
if (&item == first_item) {
first_item = item.next;
if (first_item) {
first_item->prev = nullptr;
}
}
if (&item == last_item) {
last_item = item.prev;
if (last_item) {
last_item->next = nullptr;
}
}
}
std::deque<Item> item_pool;
std::deque<size_t> free_items;
Item* first_item{};
Item* last_item{};
};
} // namespace Common
+3 -3
View File
@@ -636,8 +636,8 @@ struct Values {
Category::System};
SwitchableSetting<Region, true> region_index{linkage, Region::Usa, "region_index", Category::System};
SwitchableSetting<TimeZone, true> time_zone_index{linkage, TimeZone::Auto, "time_zone_index", Category::System};
Setting<u32> serial_battery{linkage, 0, "serial_battery", Category::Debugging};
Setting<u32> serial_unit{linkage, 0, "serial_unit", Category::Debugging};
Setting<u32> serial_battery{linkage, 0, "serial_battery", Category::System};
Setting<u32> serial_unit{linkage, 0, "serial_unit", Category::System};
// Measured in seconds since epoch
SwitchableSetting<bool> custom_rtc_enabled{linkage, false, "custom_rtc_enabled", Category::System, Specialization::Paired, true, true};
SwitchableSetting<s64> custom_rtc{
@@ -807,7 +807,7 @@ struct Values {
0,
65535,
"debug_knobs",
Category::Debugging,
Category::Core,
Specialization::Countable,
true,
true};
@@ -289,6 +289,7 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
break;
// Paranoia mode for debugging optimizations
case Settings::CpuAccuracy::Paranoid:
@@ -340,6 +340,7 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s
config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.fastmem_address_space_bits = 64;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
break;
// Paranoia mode for debugging optimizations
case Settings::CpuAccuracy::Paranoid:
+59 -43
View File
@@ -6,8 +6,6 @@
#include <condition_variable>
#include <mutex>
#include <optional>
#include <stop_token>
#include <thread>
#include "core/core.h"
@@ -20,53 +18,59 @@ namespace Kernel::Svc {
constexpr auto MAX_MSG_TIME = std::chrono::milliseconds(250);
const auto MAX_MSG_SIZE = 0x1000;
static std::string msg_buffer;
static std::mutex msg_mutex;
static std::condition_variable msg_cv;
static std::chrono::steady_clock::time_point last_msg_time;
static bool worker_running = true;
static std::unique_ptr<std::thread> flush_thread;
static std::once_flag start_flag;
static void FlushDbgLoop() {
while (true) {
std::unique_lock lock(msg_mutex);
msg_cv.wait(lock, [] { return !msg_buffer.empty() || !worker_running; });
if (!worker_running && msg_buffer.empty()) break;
auto timeout = last_msg_time + MAX_MSG_TIME;
bool woke_early = msg_cv.wait_until(lock, timeout, [] {
return msg_buffer.size() >= MAX_MSG_SIZE || !worker_running;
});
if (!woke_early || msg_buffer.size() >= MAX_MSG_SIZE || !worker_running) {
if (!msg_buffer.empty()) {
// Remove trailing newline as LOG_INFO adds that anyways
if (msg_buffer.back() == '\n')
msg_buffer.pop_back();
LOG_INFO(Debug_Emulated, "\n{}", msg_buffer);
msg_buffer.clear();
}
if (!worker_running) break;
}
}
}
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
Result OutputDebugString(Core::System& system, u64 address, u64 len) {
static struct DebugFlusher {
std::string msg_buffer;
std::mutex msg_mutex;
std::condition_variable msg_cv;
std::chrono::steady_clock::time_point last_msg_time;
std::optional<std::jthread> thread;
} flusher_data;
R_SUCCEED_IF(len == 0);
// Only start the thread the very first time this function is called
if (!flusher_data.thread) {
flusher_data.thread.emplace([](std::stop_token stop_token) {
while (!stop_token.stop_requested()) {
std::unique_lock lock(flusher_data.msg_mutex);
flusher_data.msg_cv.wait(lock, [&stop_token] {
return !flusher_data.msg_buffer.empty() || stop_token.stop_requested();
});
if (stop_token.stop_requested() && flusher_data.msg_buffer.empty())
break;
auto timeout = flusher_data.last_msg_time + MAX_MSG_TIME;
bool woke_early = flusher_data.msg_cv.wait_until(lock, timeout, [&stop_token] {
return flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested();
});
if (!woke_early || flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested()) {
if (!flusher_data.msg_buffer.empty()) {
// Remove trailing newline as LOG_INFO adds that anyways
if (flusher_data.msg_buffer.back() == '\n')
flusher_data.msg_buffer.pop_back();
LOG_INFO(Debug_Emulated, "\n{}", flusher_data.msg_buffer);
flusher_data.msg_buffer.clear();
}
if (stop_token.stop_requested()) break;
}
}
flusher_data.msg_cv.notify_all();
});
}
// Only start the thread the very first time this function is called
std::call_once(start_flag, [] {
flush_thread = std::make_unique<std::thread>(FlushDbgLoop);
});
{
std::lock_guard lock(flusher_data.msg_mutex);
const auto old_size = flusher_data.msg_buffer.size();
flusher_data.msg_buffer.resize(old_size + len);
GetCurrentMemory(system.Kernel()).ReadBlock(address, flusher_data.msg_buffer.data() + old_size, len);
flusher_data.last_msg_time = std::chrono::steady_clock::now();
std::lock_guard lock(msg_mutex);
const auto old_size = msg_buffer.size();
msg_buffer.resize(old_size + len);
GetCurrentMemory(system.Kernel()).ReadBlock(address, msg_buffer.data() + old_size, len);
last_msg_time = std::chrono::steady_clock::now();
}
flusher_data.msg_cv.notify_one();
msg_cv.notify_one();
R_SUCCEED();
}
@@ -78,4 +82,16 @@ Result OutputDebugString64From32(Core::System& system, uint32_t debug_str, uint3
R_RETURN(OutputDebugString(system, debug_str, len));
}
struct BufferAutoFlush {
~BufferAutoFlush() {
{
std::lock_guard lock(msg_mutex);
worker_running = false;
}
msg_cv.notify_all();
if (flush_thread && flush_thread->joinable()) flush_thread->join();
}
};
static BufferAutoFlush auto_flusher;
} // namespace Kernel::Svc
+3 -2
View File
@@ -169,8 +169,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
tr("Runs an additional optimization pass over generated SPIRV shaders.\n"
"Will increase time required for shader compilation.\nMay slightly improve "
"performance.\nThis feature is experimental."));
INSERT(Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"),
tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled."));
INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"),
tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for "
"decoding, or perform no decoding at all (black screen on videos).\n"
@@ -317,6 +315,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
"their resolution, details and supported controllers and depending on this setting.\n"
"Setting to Handheld can help improve performance for low end systems."));
INSERT(Settings, current_user, QString(), QString());
INSERT(Settings, serial_unit, tr("Unit Serial"), QString());
INSERT(Settings, serial_battery, tr("Battery Serial"), QString());
INSERT(Settings, debug_knobs, tr("Debug knobs"), QString());
// Controls
+5 -5
View File
@@ -109,12 +109,12 @@ public:
return static_cast<u32>(other_cpu_addr - cpu_addr);
}
size_t getLRUID() const noexcept {
return lru_id;
u64 GetFrameTick() const noexcept {
return frame_tick;
}
void setLRUID(size_t lru_id_) {
lru_id = lru_id_;
void SetFrameTick(u64 tick) noexcept {
frame_tick = tick;
}
size_t SizeBytes() const {
@@ -125,7 +125,7 @@ private:
VAddr cpu_addr = 0;
BufferFlagBits flags{};
int stream_score = 0;
size_t lru_id = SIZE_MAX;
u64 frame_tick = 0;
size_t size_bytes = 0;
};
+12 -8
View File
@@ -58,17 +58,22 @@ void BufferCache<P>::RunGarbageCollector() {
const bool aggressive_gc = total_used_memory >= critical_memory;
const u64 ticks_to_destroy = aggressive_gc ? 60 : 120;
int num_iterations = aggressive_gc ? 64 : 32;
const auto clean_up = [this, &num_iterations](BufferId buffer_id) {
const u64 threshold = frame_tick - ticks_to_destroy;
boost::container::small_vector<BufferId, 64> expired;
for (auto [id, buffer] : slot_buffers) {
if (buffer->GetFrameTick() < threshold) {
expired.push_back(id);
}
}
for (const auto buffer_id : expired) {
if (num_iterations == 0) {
return true;
break;
}
--num_iterations;
auto& buffer = slot_buffers[buffer_id];
DownloadBufferMemory(buffer);
DeleteBuffer(buffer_id);
return false;
};
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, clean_up);
}
}
template <class P>
@@ -1595,10 +1600,9 @@ void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
const auto size = buffer.SizeBytes();
if (insert) {
total_used_memory += Common::AlignUp(size, 1024);
buffer.setLRUID(lru_cache.Insert(buffer_id, frame_tick));
buffer.SetFrameTick(frame_tick);
} else {
total_used_memory -= Common::AlignUp(size, 1024);
lru_cache.Free(buffer.getLRUID());
}
const DAddr device_addr_begin = buffer.CpuAddr();
const DAddr device_addr_end = device_addr_begin + size;
@@ -1616,7 +1620,7 @@ void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
template <class P>
void BufferCache<P>::TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept {
if (buffer_id != NULL_BUFFER_ID) {
lru_cache.Touch(buffer.getLRUID(), frame_tick);
buffer.SetFrameTick(frame_tick);
}
}
@@ -23,7 +23,6 @@
#include "common/common_types.h"
#include "common/div_ceil.h"
#include "common/literals.h"
#include "common/lru_cache.h"
#include "common/range_sets.h"
#include "common/scope_exit.h"
#include "common/settings.h"
@@ -506,11 +505,6 @@ private:
size_t immediate_buffer_capacity = 0;
Common::ScratchBuffer<u8> immediate_buffer_alloc;
struct LRUItemParams {
using ObjectType = BufferId;
using TickType = u64;
};
Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache;
u64 frame_tick = 0;
u64 total_used_memory = 0;
u64 minimum_memory = 0;
+35 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -122,7 +122,35 @@ void DmaPusher::ProcessCommands(std::span<const CommandHeader> commands) {
dma_state.is_last_call = true;
index += max_write;
} else if (dma_state.method_count) {
auto const command_header = commands[index]; //can copy
if (!dma_state.non_incrementing && !dma_increment_once &&
dma_state.method >= non_puller_methods) {
auto subchannel = subchannels[dma_state.subchannel];
const u32 available = u32(std::min<size_t>(
index + dma_state.method_count, commands.size()) - index);
u32 batch = 0;
u32 method = dma_state.method;
while (batch < available) {
const bool needs_exec =
(method < Engines::EngineInterface::EXECUTION_MASK_TABLE_SIZE)
? subchannel->execution_mask[method]
: subchannel->execution_mask_default;
if (needs_exec) break;
batch++;
method++;
}
if (batch > 0) {
auto& sink = subchannel->method_sink;
sink.reserve(sink.size() + batch);
for (u32 j = 0; j < batch; j++) {
sink.emplace_back(dma_state.method + j, commands[index + j].argument);
}
dma_state.method += batch;
dma_state.method_count -= batch;
index += batch;
continue;
}
}
auto const command_header = commands[index];
dma_state.dma_word_offset = u32(index * sizeof(u32));
dma_state.is_last_call = dma_state.method_count <= 1;
CallMethod(command_header.argument);
@@ -181,7 +209,11 @@ void DmaPusher::CallMethod(u32 argument) const {
});
} else {
auto subchannel = subchannels[dma_state.subchannel];
if (!subchannel->execution_mask[dma_state.method]) {
const bool needs_execution =
(dma_state.method < Engines::EngineInterface::EXECUTION_MASK_TABLE_SIZE)
? subchannel->execution_mask[dma_state.method]
: subchannel->execution_mask_default;
if (!needs_execution) {
subchannel->method_sink.emplace_back(dma_state.method, argument);
} else {
subchannel->ConsumeSink();
+8 -5
View File
@@ -6,9 +6,9 @@
#pragma once
#include <bitset>
#include <limits>
#include <vector>
#include <array>
#include <boost/container/small_vector.hpp>
#include "common/common_types.h"
@@ -41,8 +41,11 @@ public:
ConsumeSinkImpl();
}
std::bitset<(std::numeric_limits<u16>::max)()> execution_mask{};
std::vector<std::pair<u32, u32>> method_sink{};
static constexpr size_t EXECUTION_MASK_TABLE_SIZE = 0xE00;
std::array<u8, EXECUTION_MASK_TABLE_SIZE> execution_mask{};
bool execution_mask_default{};
boost::container::small_vector<std::pair<u32, u32>, 64> method_sink{};
bool current_dirty{};
GPUVAddr current_dma_segment;
+1 -1
View File
@@ -26,7 +26,7 @@ Fermi2D::Fermi2D(MemoryManager& memory_manager_) : memory_manager{memory_manager
regs.src.depth = 1;
regs.dst.depth = 1;
execution_mask.reset();
execution_mask.fill(0);
execution_mask[FERMI2D_REG_INDEX(pixels_from_memory.src_y0) + 1] = true;
}
+1 -1
View File
@@ -18,7 +18,7 @@ namespace Tegra::Engines {
KeplerCompute::KeplerCompute(Core::System& system_, MemoryManager& memory_manager_)
: system{system_}, memory_manager{memory_manager_}, upload_state{memory_manager, regs.upload} {
execution_mask.reset();
execution_mask.fill(0);
execution_mask[KEPLER_COMPUTE_REG_INDEX(exec_upload)] = true;
execution_mask[KEPLER_COMPUTE_REG_INDEX(data_upload)] = true;
execution_mask[KEPLER_COMPUTE_REG_INDEX(launch)] = true;
+1 -1
View File
@@ -22,7 +22,7 @@ KeplerMemory::~KeplerMemory() = default;
void KeplerMemory::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
upload_state.BindRasterizer(rasterizer_);
execution_mask.reset();
execution_mask.fill(0);
execution_mask[KEPLERMEMORY_REG_INDEX(exec)] = true;
execution_mask[KEPLERMEMORY_REG_INDEX(data)] = true;
}
+17 -5
View File
@@ -4,8 +4,10 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
#include <optional>
#include "common/assert.h"
#include "common/bit_util.h"
#include "common/scope_exit.h"
@@ -37,9 +39,10 @@ Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_)
{
dirty.flags.flip();
InitializeRegisterDefaults();
execution_mask.reset();
for (size_t i = 0; i < execution_mask.size(); i++)
execution_mask.fill(0);
for (size_t i = 0; i < EXECUTION_MASK_TABLE_SIZE; i++)
execution_mask[i] = IsMethodExecutable(u32(i));
execution_mask_default = true;
}
Maxwell3D::~Maxwell3D() = default;
@@ -298,18 +301,27 @@ u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) {
}
void Maxwell3D::ConsumeSinkImpl() {
std::stable_sort(method_sink.begin(), method_sink.end(),
[](const auto& a, const auto& b) { return a.first < b.first; });
const auto sink_size = method_sink.size();
const auto control = shadow_state.shadow_ram_control;
if (control == Regs::ShadowRamControl::Track || control == Regs::ShadowRamControl::TrackWithFilter) {
for (auto [method, value] : method_sink) {
for (size_t i = 0; i < sink_size; ++i) {
const auto [method, value] = method_sink[i];
shadow_state.reg_array[method] = value;
ProcessDirtyRegisters(method, value);
}
} else if (control == Regs::ShadowRamControl::Replay) {
for (auto [method, value] : method_sink)
for (size_t i = 0; i < sink_size; ++i) {
const auto [method, value] = method_sink[i];
ProcessDirtyRegisters(method, shadow_state.reg_array[method]);
}
} else {
for (auto [method, value] : method_sink)
for (size_t i = 0; i < sink_size; ++i) {
const auto [method, value] = method_sink[i];
ProcessDirtyRegisters(method, value);
}
}
method_sink.clear();
}
+1 -1
View File
@@ -23,7 +23,7 @@ using namespace Texture;
MaxwellDMA::MaxwellDMA(Core::System& system_, MemoryManager& memory_manager_)
: system{system_}, memory_manager{memory_manager_} {
execution_mask.reset();
execution_mask.fill(0);
execution_mask[offsetof(Regs, launch_dma) / sizeof(u32)] = true;
}
@@ -65,10 +65,8 @@ static VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox,
return mode;
}
}();
if (setting == Settings::VSyncMode::Immediate && !has_imm) {
setting = Settings::VSyncMode::Mailbox;
}
if ((setting == Settings::VSyncMode::Mailbox && !has_mailbox) ||
(setting == Settings::VSyncMode::Immediate && !has_imm) ||
(setting == Settings::VSyncMode::FifoRelaxed && !has_fifo_relaxed)) {
setting = Settings::VSyncMode::Fifo;
}
+4 -1
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -102,7 +105,7 @@ struct ImageBase {
VAddr cpu_addr_end = 0;
u64 modification_tick = 0;
size_t lru_index = SIZE_MAX;
u64 last_use_tick = 0;
std::array<u32, MAX_MIP_LEVELS> mip_level_offsets{};
+107 -109
View File
@@ -6,6 +6,7 @@
#pragma once
#include <algorithm>
#include <limits>
#include <optional>
#include <bit>
@@ -70,14 +71,10 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
(std::max)((std::min)(device_local_memory - min_vacancy_critical, min_spacing_critical),
DEFAULT_CRITICAL_MEMORY));
minimum_memory = static_cast<u64>((device_local_memory - mem_threshold) / 2);
lowmemorydevice = false;
} else {
expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB;
critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB;
minimum_memory = 0;
lowmemorydevice = true;
}
const bool gpu_unswizzle_enabled = Settings::values.gpu_unswizzle_enabled.GetValue();
@@ -117,25 +114,66 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
}
template <class P>
void TextureCache<P>::RunGarbageCollector() {
bool high_priority_mode = false;
bool aggressive_mode = false;
u64 ticks_to_destroy = 0;
size_t num_iterations = 0;
void TextureCache<P>::RunAllocationGarbageCollector(size_t requested_bytes) {
if (requested_bytes == 0) {
return;
}
const auto Configure = [&](bool allow_aggressive) {
high_priority_mode = total_used_memory >= expected_memory;
aggressive_mode = allow_aggressive && total_used_memory >= critical_memory;
ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL;
num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10);
};
if (allocation_gc_frame != frame_tick) {
allocation_gc_frame = frame_tick;
allocation_gc_passes = 0;
}
if (allocation_gc_passes >= MAX_ALLOCATION_GC_PASSES_PER_FRAME) {
return;
}
if (runtime.CanReportMemoryUsage()) {
total_used_memory = runtime.GetDeviceMemoryUsage();
}
const u64 request = static_cast<u64>(requested_bytes);
const u64 max_u64 = (std::numeric_limits<u64>::max)();
const u64 projected_usage = request > (max_u64 - total_used_memory)
? max_u64
: total_used_memory + request;
if (projected_usage < expected_memory) {
return;
}
RunGarbageCollector();
++allocation_gc_passes;
if (runtime.CanReportMemoryUsage()) {
total_used_memory = runtime.GetDeviceMemoryUsage();
}
const u64 projected_after_gc = request > (max_u64 - total_used_memory)
? max_u64
: total_used_memory + request;
if (projected_after_gc >= critical_memory &&
allocation_gc_passes < MAX_ALLOCATION_GC_PASSES_PER_FRAME) {
RunGarbageCollector();
++allocation_gc_passes;
if (runtime.CanReportMemoryUsage()) {
total_used_memory = runtime.GetDeviceMemoryUsage();
}
}
}
template <class P>
void TextureCache<P>::RunGarbageCollector() {
bool high_priority_mode = total_used_memory >= expected_memory;
bool aggressive_mode = false;
u64 ticks_to_destroy = high_priority_mode ? 25ULL : 50ULL;
size_t num_iterations = high_priority_mode ? 20 : 10;
const auto Cleanup = [this, &num_iterations, &high_priority_mode,
&aggressive_mode](ImageId image_id) {
if (num_iterations == 0) {
return true;
}
--num_iterations;
auto& image = slot_images[image_id];
// Never delete recently allocated sparse textures (within 3 frames)
@@ -145,28 +183,22 @@ void TextureCache<P>::RunGarbageCollector() {
}
if (True(image.flags & ImageFlagBits::IsDecoding)) {
// This image is still being decoded, deleting it will invalidate the slot
// used by the async decoder thread.
return false;
}
// Prioritize large sparse textures for cleanup
const bool is_large_sparse = lowmemorydevice &&
image.info.is_sparse &&
image.guest_size_bytes >= 256_MiB;
if (!aggressive_mode && !is_large_sparse &&
True(image.flags & ImageFlagBits::CostlyLoad)) {
if (!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) {
return false;
}
const bool must_download =
image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap);
if (!high_priority_mode && !is_large_sparse && must_download) {
if (!high_priority_mode && must_download) {
return false;
}
if (must_download && !is_large_sparse) {
--num_iterations;
if (must_download) {
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
image.DownloadMemory(map, copies);
@@ -183,7 +215,6 @@ void TextureCache<P>::RunGarbageCollector() {
if (total_used_memory < critical_memory) {
if (aggressive_mode) {
// Sink the aggresiveness.
num_iterations >>= 2;
aggressive_mode = false;
return false;
@@ -196,31 +227,49 @@ void TextureCache<P>::RunGarbageCollector() {
return false;
};
// Aggressively clear massive sparse textures
if (total_used_memory >= expected_memory) {
lru_cache.ForEachItemBelow(frame_tick, [&](ImageId image_id) {
auto& image = slot_images[image_id];
// Only target sparse textures that are old enough
if (lowmemorydevice &&
image.info.is_sparse &&
image.guest_size_bytes >= 256_MiB &&
image.allocation_tick < frame_tick - 3) {
LOG_DEBUG(HW_GPU, "GC targeting old sparse texture at 0x{:X} ({} MiB, age: {} frames)",
image.gpu_addr, image.guest_size_bytes / (1024 * 1024),
frame_tick - image.allocation_tick);
return Cleanup(image_id);
}
return false;
const auto SortByAge = [this](auto& vec) {
std::sort(vec.begin(), vec.end(), [this](ImageId a, ImageId b) {
return slot_images[a].last_use_tick < slot_images[b].last_use_tick;
});
};
// Single pass: collect all candidates, classified by tier
const u64 normal_threshold = frame_tick > ticks_to_destroy ? frame_tick - ticks_to_destroy : 0;
const u64 aggressive_threshold = frame_tick > 10 ? frame_tick - 10 : 0;
boost::container::small_vector<ImageId, 64> expired;
boost::container::small_vector<ImageId, 64> aggressive_expired;
for (auto [id, image] : slot_images) {
if (False(image->flags & ImageFlagBits::Registered)) {
continue;
}
const u64 tick = image->last_use_tick;
if (tick < normal_threshold) {
expired.push_back(id);
} else if (tick < aggressive_threshold) {
aggressive_expired.push_back(id);
}
}
Configure(false);
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
SortByAge(expired);
SortByAge(aggressive_expired);
// If pressure is still too high, prune aggressively.
// Tier 1: normal expiration
for (const auto image_id : expired) {
if (Cleanup(image_id)) {
break;
}
}
// Tier 2: if still critical, use aggressive threshold with more iterations
if (total_used_memory >= critical_memory) {
Configure(true);
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
aggressive_mode = true;
num_iterations = 40;
for (const auto image_id : aggressive_expired) {
if (Cleanup(image_id)) {
break;
}
}
}
}
@@ -1196,9 +1245,6 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
}
image.flags &= ~ImageFlagBits::CpuModified;
if( lowmemorydevice && image.info.format == PixelFormat::BC1_RGBA_UNORM && MapSizeBytes(image) >= 256_MiB ) {
return;
}
TrackImage(image, image_id);
@@ -1608,49 +1654,19 @@ bool TextureCache<P>::ScaleDown(Image& image) {
template <class P>
ImageId TextureCache<P>::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
RelaxedOptions options) {
const size_t requested_size = CalculateGuestSizeInBytes(info);
std::optional<DAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
const auto size = CalculateGuestSizeInBytes(info);
cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr, size);
cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr, requested_size);
if (!cpu_addr) {
const DAddr fake_addr = ~(1ULL << 40ULL) + virtual_invalid_space;
virtual_invalid_space += Common::AlignUp(size, 32);
virtual_invalid_space += Common::AlignUp(requested_size, 32);
cpu_addr = std::optional<DAddr>(fake_addr);
}
}
ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr=0x{:x}", gpu_addr);
// For large sparse textures, aggressively clean up old allocations at same address
if (lowmemorydevice && info.is_sparse && CalculateGuestSizeInBytes(info) >= 256_MiB) {
const auto alloc_it = image_allocs_table.find(gpu_addr);
if (alloc_it != image_allocs_table.end()) {
const ImageAllocId alloc_id = alloc_it->second;
auto& alloc_images = slot_image_allocs[alloc_id].images;
// Collect old images at this address that were created more than 2 frames ago
boost::container::small_vector<ImageId, 4> to_delete;
for (ImageId old_image_id : alloc_images) {
Image& old_image = slot_images[old_image_id];
if (old_image.info.is_sparse &&
old_image.gpu_addr == gpu_addr &&
old_image.allocation_tick < frame_tick - 2) { // Try not to delete fresh textures
to_delete.push_back(old_image_id);
}
}
// Delete old images immediately
for (ImageId old_id : to_delete) {
Image& old_image = slot_images[old_id];
LOG_DEBUG(HW_GPU, "Immediately deleting old sparse texture at 0x{:X} ({} MiB)",
gpu_addr, old_image.guest_size_bytes / (1024 * 1024));
if (True(old_image.flags & ImageFlagBits::Tracked)) {
UntrackImage(old_image, old_id);
}
UnregisterImage(old_id);
DeleteImage(old_id, true);
}
}
}
RunAllocationGarbageCollector(requested_size);
const ImageId image_id = JoinImages(info, gpu_addr, *cpu_addr);
const Image& image = slot_images[image_id];
@@ -1668,25 +1684,7 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DA
ImageInfo new_info = info;
const size_t size_bytes = CalculateGuestSizeInBytes(new_info);
// Proactive cleanup for large sparse texture allocations
if (lowmemorydevice && new_info.is_sparse && size_bytes >= 256_MiB) {
const u64 estimated_alloc_size = size_bytes;
if (total_used_memory + estimated_alloc_size >= critical_memory) {
LOG_DEBUG(HW_GPU, "Large sparse texture allocation ({} MiB) - running aggressive GC. "
"Current memory: {} MiB, Critical: {} MiB",
size_bytes / (1024 * 1024),
total_used_memory / (1024 * 1024),
critical_memory / (1024 * 1024));
RunGarbageCollector();
// If still over threshold after GC, try one more aggressive pass
if (total_used_memory + estimated_alloc_size >= critical_memory) {
LOG_DEBUG(HW_GPU, "Still critically low on memory, running second GC pass");
RunGarbageCollector();
}
}
}
RunAllocationGarbageCollector(size_bytes);
const bool broken_views = runtime.HasBrokenTextureViewFormats();
const bool native_bgr = runtime.HasNativeBgr();
@@ -2027,8 +2025,8 @@ std::pair<u32, u32> TextureCache<P>::PrepareDmaImage(ImageId dst_id, GPUVAddr ba
const auto& image = slot_images[dst_id];
const auto base = image.TryFindBase(base_addr);
PrepareImage(dst_id, mark_as_modified, false);
const auto& new_image = slot_images[dst_id];
lru_cache.Touch(new_image.lru_index, frame_tick);
auto& new_image = slot_images[dst_id];
new_image.last_use_tick = frame_tick;
return std::make_pair(base->level, base->layer);
}
@@ -2377,7 +2375,7 @@ void TextureCache<P>::RegisterImage(ImageId image_id) {
tentative_size = TranscodedAstcSize(tentative_size, image.info.format);
}
total_used_memory += Common::AlignUp(tentative_size, 1024);
image.lru_index = lru_cache.Insert(image_id, frame_tick);
image.last_use_tick = frame_tick;
ForEachGPUPage(image.gpu_addr, image.guest_size_bytes, [this, image_id](u64 page) {
(*channel_state->gpu_page_table)[page].push_back(image_id);
@@ -2411,7 +2409,7 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) {
"Trying to unregister an already registered image");
image.flags &= ~ImageFlagBits::Registered;
image.flags &= ~ImageFlagBits::BadOverlap;
lru_cache.Free(image.lru_index);
const auto& clear_page_table =
[image_id](u64 page, ankerl::unordered_dense::map<u64, std::vector<ImageId>, Common::IdentityHash<u64>>& selected_page_table) {
const auto page_it = selected_page_table.find(page);
@@ -2740,7 +2738,7 @@ void TextureCache<P>::PrepareImage(ImageId image_id, bool is_modification, bool
if (is_modification) {
MarkModification(image);
}
lru_cache.Touch(image.lru_index, frame_tick);
image.last_use_tick = frame_tick;
}
template <class P>
@@ -22,7 +22,7 @@
#include "common/common_types.h"
#include "common/hash.h"
#include "common/literals.h"
#include "common/lru_cache.h"
#include <ranges>
#include "common/scratch_buffer.h"
#include "common/slot_vector.h"
@@ -120,7 +120,7 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
static constexpr s64 DEFAULT_EXPECTED_MEMORY = 1_GiB + 125_MiB;
static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB + 625_MiB;
static constexpr size_t GC_EMERGENCY_COUNTS = 2;
static constexpr u32 MAX_ALLOCATION_GC_PASSES_PER_FRAME = 2;
using Runtime = typename P::Runtime;
using Image = typename P::Image;
@@ -310,6 +310,8 @@ private:
/// Runs the Garbage Collector.
void RunGarbageCollector();
void RunAllocationGarbageCollector(size_t requested_bytes);
/// Fills image_view_ids in the image views in indices
template <bool has_blacklists>
void FillImageViews(DescriptorTable<TICEntry>& table,
@@ -478,7 +480,6 @@ private:
u64 minimum_memory;
u64 expected_memory;
u64 critical_memory;
bool lowmemorydevice = false;
size_t gpu_unswizzle_maxsize = 0;
size_t swizzle_chunk_size = 0;
u32 swizzle_slices_per_batch = 0;
@@ -510,11 +511,7 @@ private:
std::deque<std::vector<AsyncBuffer>> async_buffers;
std::deque<AsyncBuffer> async_buffers_death_ring;
struct LRUItemParams {
using ObjectType = ImageId;
using TickType = u64;
};
Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache;
#ifdef YUZU_LEGACY
static constexpr size_t TICKS_TO_DESTROY = 6;
@@ -532,6 +529,8 @@ private:
u64 modification_tick = 0;
u64 frame_tick = 0;
u64 allocation_gc_frame = (std::numeric_limits<u64>::max)();
u32 allocation_gc_passes = 0;
u64 last_sampler_gc_frame = (std::numeric_limits<u64>::max)();
Common::ThreadWorker texture_decode_worker{1, "TextureDecoder"};
@@ -51,8 +51,6 @@ void ConfigureDebug::SetConfiguration() {
ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue());
ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue());
ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.GetValue());
ui->serial_battery_edit->setText(QString::fromStdString(std::to_string(Settings::values.serial_battery.GetValue())));
ui->serial_board_edit->setText(QString::fromStdString(std::to_string(Settings::values.serial_unit.GetValue())));
#ifdef YUZU_USE_QT_WEB_ENGINE
ui->disable_web_applet->setChecked(Settings::values.disable_web_applet.GetValue());
#else
@@ -128,8 +126,6 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.extended_logging = ui->extended_logging->isChecked();
Settings::values.perform_vulkan_check = ui->perform_vulkan_check->isChecked();
Settings::values.disable_web_applet = ui->disable_web_applet->isChecked();
Settings::values.serial_battery = ui->serial_battery_edit->text().toUInt();
Settings::values.serial_unit = ui->serial_board_edit->text().toUInt();
Settings::values.debug_knobs = ui->debug_knobs_spinbox->value();
Debugger::ToggleConsole();
Common::Log::Filter filter;