Compare commits

..

18 Commits

Author SHA1 Message Date
lizzie ca9dc2da35 Fix license headers 2026-09-28 06:16:38 +00:00
lizzie 33e2e0cf57 2026-09-23 07:42:01
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-28 06:16:25 +00:00
lizzie 737535dc2b 2026-09-23 02:07:39
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-28 06:16:25 +00:00
lizzie ccb257c729 2026-09-23 01:28:59
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-28 06:16:25 +00:00
lizzie f9292fa4ac 2026-09-23 01:18:14
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-28 06:16:25 +00:00
lizzie b5b59f2659 2026-09-22 04:33:30
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-28 06:16:25 +00:00
lizzie e18e8b5995 2026-09-22 04:25:09
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-28 06:16:25 +00:00
lizzie 214d7772b4 2026-09-21 02:08:29
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-28 06:16:25 +00:00
lizzie 08585f03de 2026-09-21 02:07:30
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-28 06:16:25 +00:00
lizzie 4bb5ab4d83 Fix license headers 2026-09-28 06:16:25 +00:00
lizzie 6acf3c496a Fix license headers 2026-09-28 06:16:25 +00:00
lizzie c9c5e5c677 2026-09-21 00:39:13
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-28 06:16:25 +00:00
lizzie 86761a1892 Fix license headers 2026-09-28 06:16:25 +00:00
lizzie 261cf66e58 2026-09-20 13:20:40
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-28 06:16:25 +00:00
lizzie 2a991fafe9 2026-09-20 13:02:52
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-28 06:16:25 +00:00
lizzie 338cf2a532 2026-09-20 12:52:02
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-28 06:16:25 +00:00
lizzie e85ce56b11 Fix license headers 2026-09-28 06:16:25 +00:00
lizzie e853a577e3 2026-09-20 07:03:36
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-28 06:16:25 +00:00
103 changed files with 13762 additions and 13236 deletions
+1 -1
View File
@@ -79,7 +79,7 @@
"name": "ffmpeg",
"package": "FFmpeg",
"repo": "crueter-ci/FFmpeg",
"version": "9.0.2-1790726865-946fcce07b"
"version": "9.0.1-1788120736-bf1b838f2a"
},
"fmt": {
"hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2",
+587 -558
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
+477 -443
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
+470 -442
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
+470 -442
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
+470 -443
View File
File diff suppressed because it is too large Load Diff
+470 -442
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
+477 -443
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
+470 -442
View File
File diff suppressed because it is too large Load Diff
+470 -442
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
+477 -443
View File
File diff suppressed because it is too large Load Diff
+474 -444
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
+474 -447
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
+477 -443
View File
File diff suppressed because it is too large Load Diff
+469 -442
View File
File diff suppressed because it is too large Load Diff
@@ -16,8 +16,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_USE_SPEED_LIMIT("use_speed_limit"),
USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"),
SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"),
NCE_INVALIDATION_GPU_READBACK("nce_invalidation_gpu_readback"),
NCE_RUNTIME_NRO_PATCH("nce_runtime_nro_patch"),
FIX_BLOOM_EFFECTS("fix_bloom_effects"),
EMULATE_BGR565("emulate_bgr565"),
RESCALE_HACK("rescale_hack"),
@@ -866,20 +866,6 @@ abstract class SettingsItem(
descriptionId = R.string.skip_cpu_inner_invalidation_description
)
)
put(
SwitchSetting(
BooleanSetting.NCE_INVALIDATION_GPU_READBACK,
titleId = R.string.nce_invalidation_gpu_readback,
descriptionId = R.string.nce_invalidation_gpu_readback_description
)
)
put(
SwitchSetting(
BooleanSetting.NCE_RUNTIME_NRO_PATCH,
titleId = R.string.nce_runtime_nro_patch,
descriptionId = R.string.nce_runtime_nro_patch_description
)
)
put(
SwitchSetting(
BooleanSetting.FIX_BLOOM_EFFECTS,
@@ -554,8 +554,6 @@ class SettingsFragmentPresenter(
add(HeaderSetting(R.string.hacks))
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
add(BooleanSetting.NCE_INVALIDATION_GPU_READBACK.key)
add(BooleanSetting.NCE_RUNTIME_NRO_PATCH.key)
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
add(BooleanSetting.EMULATE_BGR565.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
@@ -23,7 +23,6 @@ import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.NativeConfig
import java.io.File
@@ -147,11 +146,11 @@ class DriverViewModel : ViewModel() {
private fun wipeGameShaders(game: Game) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val userDirectory = DirectoryInitialization.userDirectory
val externalFilesDir = YuzuApplication.appContext.getExternalFilesDir(null)
?: return@withContext
val shaderDir = File(
userDirectory +
"/cache/shader/" + game.settingsName.lowercase()
externalFilesDir.absolutePath +
"/shader/" + game.settingsName.lowercase()
)
if (shaderDir.exists()) {
shaderDir.deleteRecursively()
@@ -28,7 +28,7 @@
<string name="input_overlay_behavior">السلوك</string>
<string name="overlay_auto_hide">إخفاء الطبقة تلقائيًا</string>
<string name="overlay_auto_hide_description">إخفاء طبقة ذراع التحكم باللمس تلقائيًا بعد مرور الوقت المحدد من عدم النشاط.</string>
<string name="enable_input_overlay_auto_hide">تفعيل إخفاء الطبقة تلقائيًا</string>
<string name="enable_input_overlay_auto_hide">تمكين إخفاء الطبقة تلقائيًا</string>
<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>
@@ -51,7 +51,7 @@
<string name="stats_overlay_customization">التخصيص</string>
<string name="stats_overlay_items">الرؤية</string>
<string name="stats_overlay_options">طبقة الأداء</string>
<string name="enable_stats_overlay_">تفعيل طبقة إحصائيات الأداء</string>
<string name="enable_stats_overlay_">تمكين طبقة إحصائيات الأداء</string>
<string name="stats_overlay_options_description">ضبط المعلومات التي يتم عرضها في طبقة إحصائيات الأداء</string>
<string name="show_fps">عرض معدل الإطارات</string>
<string name="show_fps_description">عرض معدل الإطارات الحالي</string>
@@ -81,7 +81,7 @@
<!-- Device Overlay settings -->
<string name="show_soc_overlay">عرض طبقة معلومات الجهاز</string>
<string name="enable_soc_overlay">تفعيل طبقة الجهاز</string>
<string name="enable_soc_overlay">تمكين طبقة الجهاز</string>
<string name="soc_overlay_options">طبقة الجهاز</string>
<string name="soc_overlay_options_description">ضبط المعلومات التي يتم عرضها في طبقة الجهاز</string>
@@ -98,8 +98,8 @@
<string name="use_sync_core">مزامنة سرعة النواة</string>
<string name="use_sync_core_description">قم بمزامنة سرعة النواة مع النسبة المئوية للسرعة القصوى لتحسين الأداء دون تغيير السرعة الفعلية للعبة.</string>
<string name="cpuopt_unsafe_host_mmu">تفعيل محاكاة MMU المضيف</string>
<string name="cpuopt_unsafe_host_mmu_description">يعمل هذا التحسين على تسريع وصول الذاكرة بواسطة البرنامج الضيف. يؤدي تفعيله إلى إجراء عمليات قراءة/كتابة ذاكرة الضيف مباشرة في الذاكرة والاستفادة من MMU المضيف. يؤدي تعطيل هذا إلى إجبار جميع عمليات الوصول إلى الذاكرة على استخدام محاكاة MMU البرمجية.</string>
<string name="cpuopt_unsafe_host_mmu">تمكين محاكاة MMU المضيف</string>
<string name="cpuopt_unsafe_host_mmu_description">يعمل هذا التحسين على تسريع وصول الذاكرة بواسطة البرنامج الضيف. يؤدي تمكينه إلى إجراء عمليات قراءة/كتابة ذاكرة الضيف مباشرة في الذاكرة والاستفادة من MMU المضيف. يؤدي تعطيل هذا إلى إجبار جميع عمليات الوصول إلى الذاكرة على استخدام محاكاة MMU البرمجية.</string>
<string name="debug_knobs">مقابض تصحيح الأخطاء</string>
<string name="debug_knobs_description">للإستخدام في التطوير فقط.</string>
<string name="debug_knobs_hint">0 إلى 65535</string>
@@ -342,7 +342,7 @@
<string name="frame_gen_flow_scale_description">دقة مسار التدفق البصري، كجزء من الناتج. ويُعد خفض هذه القيمة أرخص طريقة لاستعادة الأداء.</string>
<string name="frame_gen_unsupported">توليد الإطار غير متاح</string>
<string name="frame_gen_unsupported_description">يفتقر برنامج تشغيل وحدة معالجة الرسومات هذا إلى نموذج ذاكرة Vulkan أو دعم الدقة النصفية (float16) التي تتطلبها برامج التظليل الخاصة بـ «Lossless Scaling».</string>
<string name="lossless_scaling_setup_description">اختياري. قم بتوفير ملف Lossless.dll الخاص بك لتفعيل إنشاء الإطارات لاحقًا</string>
<string name="lossless_scaling_setup_description">اختياري. قم بتوفير ملف Lossless.dll الخاص بك لتمكين إنشاء الإطارات لاحقًا</string>
<string name="lossless_scaling_install">تثبيت ملف Lossless.dll</string>
<string name="lossless_scaling_install_description">يتطلب إنشاء الإطارات أن تكون لديك نسخة قانونية خاصة بك من ملف Lossless.dll المأخوذ من برنامج Lossless Scaling</string>
<string name="lossless_scaling_replace_description">اختر نسخة أخرى من ملف Lossless.dll</string>
@@ -350,7 +350,7 @@
<string name="frame_generation_supported">مدعوم</string>
<string name="frame_generation_unsupported">غير مدعوم (لا يتوفر نموذج ذاكرة Vulkan أو float16)</string>
<string name="lossless_scaling">Lossless Scaling</string>
<string name="lossless_scaling_description">قم بتوفير نسختك الخاصة من ملف Lossless.dll لتفعيل توليد الإطارات</string>
<string name="lossless_scaling_description">قم بتوفير نسختك الخاصة من ملف Lossless.dll لتمكين توليد الإطارات</string>
<string name="lossless_scaling_installed">مثبت</string>
<string name="lossless_scaling_not_installed">غير مثبت</string>
<string name="lossless_scaling_replace">استبدل</string>
@@ -502,9 +502,9 @@
<string name="frame_limit_slider">نسبة الحد الأقصى للسرعة</string>
<string name="frame_limit_slider_description">يحدد النسبة المئوية للحد من سرعة المحاكاة. %100 هي السرعة الطبيعية. القيم الأعلى أو الأقل ستزيد أو تقلل من الحد الأقصى للسرعة.</string>
<string name="turbo_speed_limit">سرعة تيربو</string>
<string name="turbo_speed_limit_description">عند تفعيل وضع التيربو، سيتم تشغيل المحاكاة بهذه السرعة.</string>
<string name="turbo_speed_limit_description">عند تمكين وضع التيربو، سيتم تشغيل المحاكاة بهذه السرعة.</string>
<string name="slow_speed_limit">سرعة بطيئة</string>
<string name="slow_speed_limit_description">عند تفعيل الوضع البطيء، سيتم تشغيل المحاكاة بهذه السرعة.</string>
<string name="slow_speed_limit_description">عند تمكين الوضع البطيء، سيتم تشغيل المحاكاة بهذه السرعة.</string>
<string name="cpu_backend">خلفية وحدة المعالجة المركزية</string>
<string name="cpu_accuracy">دقة وحدة المعالجة المركزية</string>
<string name="value_with_units">%1$s%2$s</string>
@@ -578,7 +578,7 @@
<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>
<string name="enable_buffer_history">تمكين سجل التخزين المؤقت</string>
<string name="enable_buffer_history_description">يُتيح هذا الخيار الوصول إلى حالات التخزين المؤقت السابقة. وقد يُحسّن جودة العرض وثبات الأداء في بعض الألعاب.</string>
<string name="use_optimized_vertex_buffers">مخازن الرؤوس المُحسّنة</string>
<string name="use_optimized_vertex_buffers_description">يُتيح ربطًا مُحسَّنًا لمخازن الرؤوس لتحسين الأداء. يتطلب برامج تشغيل Mesa 26.0+ Turnip/ برامج تشغيل QCOM. قد يتعطل على برامج تشغيل Turnip القديمة (25.3 وما دون).</string>
@@ -616,7 +616,7 @@
<string name="dyna_state_description">يتحكم هذا الخيار في عدد الميزات التي يمكن استخدامها في ExtendedDynamicState (EDS). كلما زادت القيمة، قلّ عدد عمليات تجميع خط الأنابيب بناءً على الحالة الديناميكية التي يدعمها برنامج التشغيل.</string>
<string name="disabled">معطل</string>
<string name="vertex_input_dynamic_state">حالة ديناميكية لإدخال الرأس</string>
<string name="vertex_input_dynamic_state_description">يتيح تفعيل هذه الميزة معالجة أكثر مرونة لمدخلات الرؤوس، مما قد يقلل من وقت تجميع خط الأنابيب في vertex/buffer.</string>
<string name="vertex_input_dynamic_state_description">يتيح تمكين هذه الميزة معالجة أكثر مرونة لمدخلات الرؤوس، مما قد يقلل من وقت تجميع خط الأنابيب في vertex/buffer.</string>
<string name="sample_shading_fraction">تظليل العينة</string>
<string name="sample_shading_fraction_description">يسمح هذا الخيار بتنفيذ مُظلل الأجزاء لكل عينة في جزء متعدد العينات بدلاً من تنفيذه مرة واحدة لكل جزء. يُحسّن هذا من جودة الرسومات على حساب بعض الأداء.</string>
@@ -825,9 +825,9 @@
<string name="preferences_system">النظام</string>
<string name="preferences_system_description">وضع الإرساء، المنطقة، اللغة</string>
<string name="preferences_graphics">الرسومات</string>
<string name="preferences_graphics_description">مستوى دقة المحاكاة، الدقة، ذاكرة التخزين المؤقتة للتظليل</string>
<string name="preferences_graphics_description">مستوى الدقة، الدقة، ذاكرة التخزين المؤقتة للتظليل</string>
<string name="quick_settings">الإعدادات السريعة</string>
<string name="enable_quick_settings">تفعيل الإعدادات السريعة</string>
<string name="enable_quick_settings">تمكين الإعدادات السريعة</string>
<string name="enable_quick_settings_description">السماح بالوصول إلى قائمة الإعدادات السريعة عن طريق التمرير و زر القائمة</string>
<string name="preferences_audio">الصوت</string>
<string name="preferences_audio_description">محرك الإخراج، مستوى الصوت</string>
@@ -1223,8 +1223,8 @@
<string name="airplane_mode">وضع الطيران</string>
<string name="airplane_mode_description">يمرر وضع الطيران إلى نظام تشغيل Switch</string>
<string name="enable_overlay">تفعيل التطبيق الصغير للطبقة</string>
<string name="enable_overlay_description">تفعيل التطبيق الصغير المدمج في Horizon. اضغط مع الاستمرار على زر الشاشة الرئيسية لمدة 1 ثانية لإظهاره.</string>
<string name="enable_overlay">تمكين التطبيق الصغير للطبقة</string>
<string name="enable_overlay_description">تمكين التطبيق الصغير المدمج في Horizon. اضغط مع الاستمرار على زر الشاشة الرئيسية لمدة 1 ثانية لإظهاره.</string>
<!-- Profile Management -->
<string name="profile_manager">إدارة ملف التعريف</string>
@@ -16,7 +16,7 @@
<string name="value_too_high">範圍最大必須為%1$d</string>
<string name="invalid_value">無效的範圍</string>
<string name="using_per_game_config">使用個別遊戲設定中</string>
<string name="using_per_game_config">使用個別設定中</string>
<!-- Input Overlay -->
<string name="show_input_overlay">顯示虛擬按鍵</string>
@@ -67,7 +67,7 @@
<string name="show_shaders_building">當著色器在編譯時顯示</string>
<string name="show_shaders_building_description">顯示正在編譯的著色器數量</string>
<string name="pipeline_worker_cores">管線工作執行緒</string>
<string name="pipeline_worker_cores_description">設定用於建置Vulkan管線的CPU核心數量,設定的數值越大效能越好,但溫度也會上升更快</string>
<string name="pipeline_worker_cores_description">設定用於建置 Vulkan 管線的 CPU 核心數量,設定的數值越大效能越好,但溫度也會上升更快</string>
<string name="overlay_position">疊加層位置</string>
<string name="overlay_position_description">選擇疊加層在畫面上的顯示位置</string>
<string name="overlay_position_top_left">左上</string>
@@ -98,15 +98,15 @@
<string name="use_sync_core">同步核心速度</string>
<string name="use_sync_core_description">將核心速度與最大速度百分比同步,在不改變遊戲實際速度的情況下提高效能</string>
<string name="cpuopt_unsafe_host_mmu">啟用主機MMU模擬</string>
<string name="cpuopt_unsafe_host_mmu_description">此最佳化可加速客體程式的記憶體存取。啟用後,客體記憶體讀取/寫入將直接在記憶體中執行並利用主機的MMU。停用此功能將強制所有記憶體存取使用軟體MMU模擬</string>
<string name="cpuopt_unsafe_host_mmu">啟用主機 MMU 模擬</string>
<string name="cpuopt_unsafe_host_mmu_description">此最佳化可加速客體程式的記憶體存取。啟用後,客體記憶體讀取/寫入將直接在記憶體中執行並利用主機的 MMU。停用此功能將強制所有記憶體存取使用軟體 MMU 模擬。</string>
<string name="debug_knobs">偵錯開關</string>
<string name="debug_knobs_description">僅限開發用途</string>
<string name="debug_knobs_hint">0到65535</string>
<!-- NVDEC Emulation -->
<string name="nvdec_emulation">NVDEC模擬</string>
<string name="nvdec_emulation_description">若在過場動畫中當機請切換成CPU</string>
<string name="nvdec_emulation_description">若在過場動畫中當機請切換成 CPU</string>
<string name="nvdec_emulation_none">無</string>
<!-- Optimize SPIRV output -->
@@ -151,7 +151,7 @@
<string name="multiplayer_member_leave">%1$s 離開</string>
<string name="multiplayer_member_kicked">%1$s 被踢出</string>
<string name="multiplayer_member_banned">%1$s 被封鎖</string>
<string name="multiplayer_address_unbanned">IP位址已解封</string>
<string name="multiplayer_address_unbanned">位址已解封</string>
<string name="multiplayer_kick_member">踢出成員</string>
<string name="multiplayer_password">密碼</string>
<string name="multiplayer_joining">正在加入…</string>
@@ -173,7 +173,7 @@
<string name="multiplayer_ban">封鎖使用者</string>
<string name="multiplayer_room_browser">公開房間</string>
<string name="multiplayer_no_rooms_found">未找到公開房間</string>
<string name="multiplayer_password_required">請輸入密碼</string>
<string name="multiplayer_password_required">需要輸入密碼</string>
<string name="multiplayer_player_count">:%1$d/%2$d</string>
<string name="multiplayer_game">遊戲</string>
<string name="multiplayer_no_game_info">任意遊戲</string>
@@ -189,7 +189,7 @@
<string name="multiplayer_token_required">需要Web令牌,請前往進階設定 -> 系統 -> 網路</string>
<string name="multiplayer_ip_error">IP格式無效</string>
<string name="multiplayer_username_error">必須介於4-20個字元之間,並且僅能使用英文字母、數字、句點、破折號、底線和空格(標點符號須為英文格式)</string>
<string name="multiplayer_nickname_invalid">使用者名稱無效,請在系統 -&gt; 網路中檢查設定</string>
<string name="multiplayer_nickname_invalid">使用者名稱無效,請在系統→網路中檢查設定</string>
<string name="multiplayer_token_error">必須為48個字元,且僅包含小寫字母a-z</string>
<string name="multiplayer_port_error">埠號需為1-65535</string>
<string name="cancel">取消</string>
@@ -201,16 +201,16 @@
<!-- Setup strings -->
<string name="welcome">歡迎!</string>
<string name="welcome_description">瞭解如何設定 <b>Eden</b> 並開始模擬</string>
<string name="welcome_description">瞭解如何設定 <b>Eden</b> 並開始模擬。</string>
<string name="get_started">開始使用</string>
<string name="keys">金鑰</string>
<string name="keys_description">使用下方的按鈕安裝您的 <b>prod.keys</b> 檔案</string>
<string name="keys_description">使用下方的按鈕安裝您的 <b>prod.keys</b> 檔案。</string>
<string name="firmware">韌體</string>
<string name="firmware_description">使用下方的按鈕安裝您的<b>firmware.zip</b>檔案</string>
<string name="games">遊戲資料夾</string>
<string name="games_description">使用下方的按鈕選擇您的<b>遊戲</b>資料夾。</string>
<string name="done">完成</string>
<string name="done_description">您已準備就緒\n盡情遊玩您的遊戲!</string>
<string name="done_description">您已準備就緒。\n盡情遊玩您的遊戲!</string>
<string name="next">下一步</string>
<string name="back">上一步</string>
<string name="add_games">新增遊戲</string>
@@ -237,7 +237,7 @@
<string name="update_install_failed">更新安裝失敗:%1$s</string>
<string name="home_search">搜尋</string>
<string name="home_settings">設定</string>
<string name="empty_gamelist">找不到遊戲檔案,或者尚未選取遊戲目錄</string>
<string name="empty_gamelist">找不到檔案,或者尚未選取遊戲目錄</string>
<string name="manage_game_folders">管理遊戲資料夾</string>
<string name="select_games_folder_description">允許 Eden 尋找您的遊戲檔案</string>
<string name="add_games_warning">跳過選擇遊戲資料夾?</string>
@@ -287,9 +287,9 @@
<string name="warning_skip">跳過</string>
<string name="warning_cancel">取消</string>
<string name="install_amiibo_keys">安裝 Amiibo 金鑰</string>
<string name="install_amiibo_keys_description">需要先安裝才能在遊戲中使用Amiibo</string>
<string name="install_amiibo_keys_description">若要在遊戲中使用 Amiibo 得先安裝</string>
<string name="gpu_driver_fetcher">GPU驅動程式下載器</string>
<string name="gpu_driver_manager">GPU驅動程式管理員</string>
<string name="gpu_driver_manager">GPU 驅動程式管理員</string>
<string name="install_gpu_driver_description">安裝替代驅動程式以取得潛在的更佳效能或準確度</string>
<string name="post_processing">後製處理效果</string>
<string name="post_processing_description">在渲染完成後套用 ReShade FX 效果</string>
@@ -299,7 +299,7 @@
<string name="post_processing_open_list">開啟清單</string>
<string name="post_processing_close_list">收起清單</string>
<string name="post_processing_remove_all">移除效果</string>
<string name="post_processing_preset_locked">濾鏡已啟用。請在開啟遊戲前於\"後製處理效果\"中調整</string>
<string name="post_processing_preset_locked">濾鏡已啟用。請在開啟遊戲前於 後製處理效果 中調整</string>
<string name="post_processing_preset_modified">數值已變更,與原始濾鏡設定不同</string>
<string name="post_processing_presets">濾鏡</string>
<string name="post_processing_preset_new">新增濾鏡</string>
@@ -312,102 +312,93 @@
<string name="post_processing_empty">找不到設定檔。請將濾鏡效果的 .fx 檔案放置於此資料夾:</string>
<string name="frame_gen">影格生成</string>
<string name="frame_gen_per_game_description">調整此遊戲的影格生成設定</string>
<string name="frame_gen_description">使用Lossless Scaling在已渲染的影格之間插入補間影格。啟用此功能時,會強制採用FIFO垂直同步</string>
<string name="frame_gen_description">使用 Lossless Scaling 在已渲染的影格之間插入補間影格。啟用此功能時,會強制採用 FIFO 垂直同步</string>
<string name="frame_gen_multiplier">影格倍率</string>
<string name="frame_gen_multiplier_description">設定在每個已渲染的影格中要顯示的影格數,數值越高所需的GPU運算時間也會按比例增加。若要求的影格數超過裝置顯示器能呈現的數量將會降低模擬速度</string>
<string name="frame_gen_multiplier_description">設定在每個已渲染的影格中要顯示的影格數。數值越高所需的 GPU 運算時間也會按比例增加。若要求的影格數超過裝置顯示器能呈現的數量將會降低模擬速度</string>
<string name="frame_gen_multiplier_2x">2x</string>
<string name="frame_gen_multiplier_3x">3x</string>
<string name="frame_gen_multiplier_4x">4x</string>
<string name="frame_gen_target_rate">目標影格率</string>
<string name="frame_gen_target_rate_description">選擇裝置顯示器能實際呈現的影格率,之後倍率器會自動升高或降低以維持影格率。如果某個調整導致遊戲本身運作變慢,則會自動復原該設定</string>
<string name="frame_gen_target_rate_description">選擇裝置顯示器能實際呈現的影格率。之後倍率器會自動升高或降低以維持影格率。如果某個調整導致遊戲本身運作變慢,則會自動復原該設定</string>
<string name="frame_gen_target_rate_off">使用固定倍率</string>
<string name="frame_gen_target_rate_60">60 FPS</string>
<string name="frame_gen_target_rate_90">90 FPS</string>
<string name="frame_gen_target_rate_120">120 FPS</string>
<string name="frame_gen_on">開啟</string>
<string name="frame_gen_off">關閉</string>
<string name="frame_gen_fixed">固定</string>
<string name="frame_gen_flow_auto">自動</string>
<string name="frame_gen_quick_description">開啟或關閉影格生成後選擇影格倍率</string>
<string name="frame_gen_target_rate_quick_description">選擇要生成的目標影格率,倍率器會自動調整</string>
<string name="frame_gen_flow_scale_quick_description">用來預測影格之間動態的解析度,較低的數值可以減少GPU運算時間</string>
<string name="frame_gen_queue_target">影格佇列目標</string>
<string name="frame_gen_queue_target_description">最多允許多少個已完成的影格在顯示器前等待,較大的佇列可以吸收 GPU 突發負載,但會增加輸入延遲</string>
<string name="frame_gen_queue_target_0">最低延遲(無緩衝)</string>
<string name="frame_gen_queue_target_1">平衡(1影格)</string>
<string name="frame_gen_queue_target_2">最流暢(2影格)</string>
<string name="frame_gen_flow_scale_auto">配合遊戲調整運動預測</string>
<string name="frame_gen_flow_scale_auto_description">以遊戲實際渲染的解析度而非升頻後的輸出進行運動預測,由於升頻後不會增加任何動態細節,因此不會影響準確度</string>
<string name="frame_gen_flow_scale_auto_description">以遊戲實際渲染的解析度而非升頻後的輸出進行運動預測。由於升頻後不會增加任何動態細節,因此不會影響準確度</string>
<string name="frame_gen_flow_scale">運動預測解析度</string>
<string name="frame_gen_flow_scale_description">光流處理的解析度,以輸出解析度的比例表示,降低此設定值是減少效能負載最有效的方法</string>
<string name="frame_gen_flow_scale_description">光流處理的解析度,以輸出解析度的比例表示。降低此設定值是減少效能負載最有效的方法</string>
<string name="frame_gen_unsupported">無法使用影格生成</string>
<string name="frame_gen_unsupported_description">此GPU驅動程式缺少Lossless Scaling著色器所需的Vulkan記憶體模型或半精度浮點數(float16)支援</string>
<string name="lossless_scaling_setup_description">可選擇安裝自己擁有的 Lossless.dll 以在之後啟用影格生成功能</string>
<string name="lossless_scaling_install">安裝 Lossless.dll</string>
<string name="lossless_scaling_install_description">您需要擁有從合法管道獲得的 Lossless Scaling 並從該軟體的資料夾中取得 Lossless.dll 檔案才能使用影格生成功能</string>
<string name="lossless_scaling_replace_description">安裝不同的 Lossless.dll 檔案</string>
<string name="frame_generation_support">影格生成</string>
<string name="frame_generation_supported">支援</string>
<string name="frame_generation_unsupported">不支援(無Vulkan記憶體模型或float16)</string>
<string name="lossless_scaling">Lossless Scaling</string>
<string name="lossless_scaling_description">安裝您擁有的Lossless.dll檔案以使用影格生成</string>
<string name="lossless_scaling_description">安裝您擁有的 Lossless.dll 檔案以使用影格生成</string>
<string name="lossless_scaling_installed">已安裝</string>
<string name="lossless_scaling_not_installed">未安裝</string>
<string name="lossless_scaling_replace">更換</string>
<string name="lossless_scaling_replace">替換</string>
<string name="lossless_scaling_remove">刪除</string>
<string name="lossless_scaling_remove_description">刪除已安裝的Lossless.dll及預先編譯的著色器</string>
<string name="lossless_scaling_remove_confirmation">影格生成功能會停止運作直到您重新安裝Lossless.dll,您的Lossless.dll原始檔不會被 Eden 修改</string>
<string name="lossless_scaling_remove_description">刪除已安裝的 Lossless.dll 及預先編譯的著色器</string>
<string name="lossless_scaling_remove_confirmation">影格生成功能會停止運作直到您重新安裝 Lossless.dll 。您的 Lossless.dll 原始檔並不會被 Eden 修改</string>
<string name="lossless_scaling_missing">Lossless.dll 未安裝</string>
<string name="lossless_scaling_missing_description">前往 設定 -&gt; Lossless Scaling 安裝以使用影格生成功能</string>
<string name="lossless_scaling_locked">請先關閉遊戲</string>
<string name="lossless_scaling_locked_description">Lossless.dll無法在遊戲執行時進行更換</string>
<string name="lossless_scaling_locked_description">Lossless.dll 無法在遊戲執行時進行更換</string>
<string name="lossless_scaling_remove_unavailable">沒有東西可以刪除</string>
<string name="lossless_scaling_remove_unavailable_description">尚未安裝Lossless.dll</string>
<string name="lossless_scaling_remove_unavailable_description">尚未安裝 Lossless.dll</string>
<string name="lossless_scaling_installing">準備影格生成著色器中...</string>
<string name="lossless_scaling_install_success">成功安裝Lossless.dll</string>
<string name="lossless_scaling_install_failed">無法安裝Lossless.dll</string>
<string name="lossless_scaling_install_success">成功安裝 Lossless.dll</string>
<string name="lossless_scaling_install_failed">無法安裝 Lossless.dll</string>
<string name="error_lossless_copy_failed">無法複製選取的檔案</string>
<string name="error_lossless_unreadable">無法讀取選取的檔案</string>
<string name="error_lossless_not_pe">選取的檔案並不是Windows程式庫。請在Lossless Scaling的安裝位置(資料夾)中選擇Lossless.dll</string>
<string name="error_lossless_missing_shaders">這個Lossless.dll的副本並未包含影格生成著色器。請更新Lossless Scaling後重試</string>
<string name="error_lossless_translation_failed">無法轉譯影格生成著色器,尚不支援此Lossless Scaling版本</string>
<string name="error_lossless_cache_failed">轉譯的著色器無法寫入到儲存空間,請確認手機容量是否充足</string>
<string name="error_lossless_not_pe">選取的檔案並不是 Windows 程式庫。請在 Lossless Scaling 的安裝位置(資料夾)中選擇 Lossless.dll</string>
<string name="error_lossless_missing_shaders">這個 Lossless.dll 的副本並未包含影格生成著色器。請更新 Lossless Scaling 後再試一次</string>
<string name="error_lossless_translation_failed">無法轉譯影格生成著色器。尚不支援此 Lossless Scaling 版本</string>
<string name="error_lossless_cache_failed">轉譯的著色器無法寫入到儲存空間。請確認手機容量是否充足</string>
<string name="advanced_settings">進階設定</string>
<string name="settings_description">調整模擬器設定</string>
<string name="search_recently_played">最近遊玩</string>
<string name="search_recently_added">最近新增</string>
<string name="open_user_folder">開啟Eden資料夾</string>
<string name="open_user_folder_description">管理Eden的內部檔案</string>
<string name="open_user_folder">開啟 Eden 資料夾</string>
<string name="open_user_folder_description">管理 Eden 的內部檔案</string>
<string name="app_settings_description">自訂Eden的主題與介面</string>
<string name="no_file_manager">找不到檔案管理員</string>
<string name="notification_no_directory_link">無法開啟Eden目錄</string>
<string name="notification_no_directory_link">無法開啟 Eden 目錄</string>
<string name="notification_no_directory_link_description">請使用檔案管理員的側邊面板手動定位到使用者資料夾</string>
<string name="manage_save_data">管理儲存資料</string>
<string name="manage_save_data_description">導入/導出遊戲的儲存資料\n壓縮檔必須為.zip格式\n壓縮檔中首個子資料夾名稱必須為遊戲ID</string>
<string name="manage_save_data_description">導入/導出遊戲的儲存資料\n壓縮檔必須為.zip格式\n壓縮檔中首個子資料夾名稱必須為遊戲 ID</string>
<string name="import_save_warning">導入儲存資料</string>
<string name="import_save_warning_description">這將會以提供的檔案覆寫現有的該遊戲儲存資料,您確定要繼續嗎?</string>
<string name="save_files_importing">正在導入儲存資料…</string>
<string name="save_files_exporting">正在導出儲存資料…</string>
<string name="save_file_imported_success">已成功導入</string>
<string name="save_file_invalid_zip_structure">無效的儲存目錄結構</string>
<string name="save_file_invalid_zip_structure_description">首個子資料夾名稱必須為遊戲ID</string>
<string name="save_file_invalid_zip_structure_description">首個子資料夾名稱必須為遊戲 ID</string>
<string name="install_firmware">安裝韌體</string>
<string name="install_firmware_description">韌體必須為ZIP壓縮檔,將會用於部分遊戲的啟動</string>
<string name="install_firmware_description">韌體必須為 ZIP 壓縮檔,將會用於部分遊戲的啟動</string>
<string name="firmware_installing">正在安裝韌體</string>
<string name="firmware_installed_failure">韌體安裝失敗</string>
<string name="firmware_installed_failure_description">請確保韌體nca檔案位於zip壓縮檔的根目錄後再試一次</string>
<string name="firmware_installed_failure_description">請確保韌體 nca 檔案位於 zip 壓縮檔的根目錄,然後再試一次。</string>
<string name="firmware_uninstalled_failure">韌體解除安裝失敗</string>
<string name="share_log">分享偵錯記錄</string>
<string name="share_log_description">分享Eden的記錄檔以便對相關問題進行偵錯</string>
<string name="share_log_description">分享 Eden 的記錄檔以便對相關問題進行偵錯</string>
<string name="share_log_missing">找不到日誌檔案</string>
<string name="share_gpu_log">分享GPU日誌</string>
<string name="share_gpu_log_description">分享Eden的GPU日誌以對圖形問題進行偵錯</string>
<string name="share_gpu_log_missing">找不到GPU日誌檔案</string>
<string name="share_gpu_log">分享 GPU 日誌</string>
<string name="share_gpu_log_description">分享 Eden 的 GPU 日誌以對圖形問題進行偵錯</string>
<string name="share_gpu_log_missing">找不到 GPU 日誌檔案</string>
<string name="install_game_content">安裝遊戲內容</string>
<string name="install_game_content_description">安裝遊戲更新或DLC</string>
<string name="install_game_content_description">安裝遊戲更新或 DLC</string>
<string name="installing_game_content">正在安裝內容…</string>
<string name="install_game_content_failure">安裝檔案至NAND時發生錯誤</string>
<string name="install_game_content_failure_description">請確保內容有效並且prod.keys檔案已安裝</string>
<string name="install_game_content_failure">安裝檔案至 NAND 時發生錯誤</string>
<string name="install_game_content_failure_description">請確保內容有效並且 prod.keys 檔案已安裝</string>
<string name="install_game_content_failure_base">為避免產生衝突,無法直接安裝遊戲本體</string>
<string name="install_game_content_failed_count">%1$d 安裝錯誤</string>
<string name="install_game_content_success">遊戲內容已成功安裝</string>
@@ -415,8 +406,8 @@
<string name="install_game_content_success_overwrite">%1$d 覆寫成功</string>
<string name="install_game_content_help_link">https://yuzu-mirror.github.io/help/quickstart/#dumping-installed-updates</string>
<string name="custom_driver_not_supported">您的裝置不支援自訂驅動程式</string>
<string name="custom_driver_not_supported_description">此裝置不支援注入自訂驅動程式\n請以後再來查看是否已新增支援!</string>
<string name="manage_yuzu_data">管理Eden資料</string>
<string name="custom_driver_not_supported_description">此裝置不支援注入自訂驅動程式。\n請以後再來查看是否已新增支援!</string>
<string name="manage_yuzu_data">管理 Eden 資料</string>
<string name="manage_yuzu_data_description">安裝韌體、金鑰,導入/導出使用者資料及安裝其它項目!</string>
<string name="game_folders">遊戲資料夾</string>
<string name="deep_scan">深度掃描</string>
@@ -433,7 +424,7 @@
<string name="verify_installed_content">驗證已安裝內容的完整性</string>
<string name="verify_installed_content_description">檢查所有已安裝的內容是否有損壞</string>
<string name="keys_missing">缺少prod.keys</string>
<string name="keys_missing">缺少 prod.keys</string>
<string name="keys_missing_description">無法解密韌體和零售遊戲</string>
<string name="keys_missing_help">https://yuzu-mirror.github.io/help/quickstart/#dumping-decryption-keys</string>
@@ -446,8 +437,8 @@
<string name="keys_failed">金鑰安裝失敗</string>
<string name="keys_install_success">金鑰安裝成功</string>
<string name="error_keys_copy_failed">一個或多個金鑰安裝失敗</string>
<string name="error_keys_invalid_filename">請確保金鑰檔案具有.keys副檔名後重試</string>
<string name="error_keys_failed_init">金鑰初始化失敗,請檢查您的轉儲工具是否為最新版本並重新轉儲金鑰</string>
<string name="error_keys_invalid_filename">請確保金鑰檔案具有.keys副檔名後重試。</string>
<string name="error_keys_failed_init">金鑰初始化失敗。請檢查您的轉儲工具是否為最新版本並重新轉儲金鑰。</string>
<!-- Applet launcher strings -->
<string name="qlaunch_applet">Qlaunch</string>
@@ -459,48 +450,48 @@
<string name="applets_error_description"><![CDATA[請確定您的<a href=\"https://yuzu-mirror.github.io/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a>檔案和<a href=\"https://yuzu-mirror.github.io/help/quickstart/#dumping-system-firmware\">韌體</a>已安裝並重試]]></string>
<string name="album_applet">相簿</string>
<string name="album_applet_description">使用系統相片檢視器查看儲存在使用者螢幕截圖資料夾中的影像</string>
<string name="mii_edit_applet">Mii編輯</string>
<string name="mii_edit_applet_description">使用系統編輯器來檢視並編輯Mii</string>
<string name="mii_edit_applet">Mii 編輯</string>
<string name="mii_edit_applet_description">使用系統編輯器來檢視並編輯 Mii</string>
<string name="cabinet_applet">Cabinet</string>
<string name="cabinet_applet_description">編輯、刪除儲存在amiibo上的資料</string>
<string name="cabinet_launcher">Cabinet啟動器</string>
<string name="cabinet_applet_description">編輯、刪除儲存在 amiibo 上的資料</string>
<string name="cabinet_launcher">Cabinet 啟動器</string>
<string name="cabinet_nickname_and_owner">暱稱和擁有者設定</string>
<string name="cabinet_game_data_eraser">刪除遊戲資料</string>
<string name="cabinet_restorer">還原程式</string>
<string name="cabinet_formatter">格式化程式</string>
<!-- About screen strings -->
<string name="gaia_is_not_real">Gaia不存在</string>
<string name="gaia_is_not_real">Gaia 不存在</string>
<string name="copied_to_clipboard">已複製到剪貼簿</string>
<string name="about_app_description">一個開放原始碼的Switch模擬器</string>
<string name="about_app_description">一個開放原始碼的 Switch 模擬器</string>
<string name="contributors">參與者</string>
<string name="contributors_description">這些人讓Eden Android版成為可能</string>
<string name="licenses_description">這些專案使Eden Android版成為可能</string>
<string name="contributors_description">這些人讓 Eden Android 版成為可能</string>
<string name="licenses_description">這些專案使 Eden Android 版成為可能</string>
<string name="build">組建版本</string>
<string name="user_data">使用者資料</string>
<string name="user_data_description">導入/導出所有應用程式資料\n\n導入使用者資料時,現有的使用者資料將被取代!\n\n直接從 Citron 導入使用者資料可能會出現問題,建議手動導入所有所需資料</string>
<string name="user_data_description">導入/導出所有應用程式資料。\n\n導入使用者資料時,現有的使用者資料將被取代!\n\n直接從 Citron 導入使用者資料可能會出現問題,建議手動導入所有所需資料。</string>
<string name="exporting_user_data">正在導出使用者資料…</string>
<string name="importing_user_data">正在導入使用者資料…</string>
<string name="invalid_yuzu_backup">無效的Eden備份</string>
<string name="invalid_yuzu_backup">無效的 Eden 備份</string>
<string name="user_data_export_success">使用者資料導出成功</string>
<string name="user_data_import_success">使用者資料導入成功</string>
<string name="user_data_export_cancelled">導出已取消</string>
<string name="user_data_import_failed_description">請確保使用者資料夾位於zip壓縮檔的根目錄,並在 config/config.ini 路徑中包含組態檔案後重試</string>
<string name="user_data_import_failed_description">請確保使用者資料夾位於 zip 壓縮檔的根目錄,並在 config/config.ini 路徑中包含組態檔案,並再試一次</string>
<!-- General settings strings -->
<string name="frame_limit_enable">限制模擬速度</string>
<string name="frame_limit_enable_description">將模擬速度限制在標準速度的指定百分比</string>
<string name="frame_limit_slider">限制速度百分比</string>
<string name="frame_limit_slider_description">指定模擬速度的百分比,100%為標準速度,更高或更低的值將會減少或增加速度限制</string>
<string name="frame_limit_slider_description">指定模擬速度的百分比。100% 為標準速度,更高或更低的值將會減少或增加速度限制</string>
<string name="turbo_speed_limit">加速模式</string>
<string name="turbo_speed_limit_description">當開啟加速模式時,模擬器將以此速度執行</string>
<string name="slow_speed_limit">慢速模式</string>
<string name="slow_speed_limit_description">當慢速模式開啟時,模擬器將會以此速度執行</string>
<string name="cpu_backend">CPU後端</string>
<string name="cpu_accuracy">CPU準確度</string>
<string name="cpu_backend">CPU 後端</string>
<string name="cpu_accuracy">CPU 準確度</string>
<string name="value_with_units">%1$s%2$s</string>
<string name="program_args">Homebrew參數</string>
<string name="program_args_description">在啟動時傳遞給Homebrew的命令列參數(例如:-noglsl)</string>
<string name="program_args">Homebrew 參數</string>
<string name="program_args_description">在啟動時傳遞給 Homebrew 的命令列參數(例如:-noglsl)</string>
<!-- System settings strings -->
<string name="device_name">裝置名稱</string>
@@ -508,26 +499,26 @@
<string name="use_docked_mode_description">提高解析度,降低效能。停用後將會使用手提模式,會降低解析度並提高效能</string>
<string name="emulated_region">模擬區域</string>
<string name="emulated_language">模擬語言</string>
<string name="select_rtc_date">選擇RTC日期</string>
<string name="select_rtc_time">選擇RTC時間</string>
<string name="use_custom_rtc">自訂RTC</string>
<string name="select_rtc_date">選擇 RTC 日期</string>
<string name="select_rtc_time">選擇 RTC 時間</string>
<string name="use_custom_rtc">自訂 RTC</string>
<string name="use_custom_rtc_description">允許您設定與您的目前系統時間相互獨立的自訂時間</string>
<string name="set_custom_rtc">設定自訂RTC</string>
<string name="set_custom_rtc">設定自訂 RTC</string>
<!-- CPU -->
<string name="fast_cpu_time">CPU時脈</string>
<string name="fast_cpu_time_description">提高模擬CPU回報的時脈,可以減少某些FPS限制。較弱的CPU可能會出現效能下降的情況,部分遊戲可能會出現異常</string>
<string name="fast_cpu_time">CPU 時脈</string>
<string name="fast_cpu_time_description">提高模擬 CPU 回報的時脈,可以減少某些 FPS 限制。較弱的 CPU 可能會出現效能下降的情況,部分遊戲可能會出現異常</string>
<string name="custom_cpu_ticks">自訂CPU時脈</string>
<string name="custom_cpu_ticks_description">自訂CPU時脈,更高的值可能提高效能,但也可能導致遊戲卡死。建議範圍為77-21000</string>
<string name="custom_cpu_ticks_description">自訂CPU時脈。更高的值可能提高效能,但也可能導致遊戲卡死。建議範圍為77-21000。</string>
<string name="cpu_ticks">時脈</string>
<string name="memory_layout">記憶體布局</string>
<string name="memory_layout_description">(實驗性)更改模擬記憶體布局,此設定不會提高效能,但可能有助於使用模組來提高解析度的遊戲的載入。不要在RAM為8GB或更少的手機上使用。若遊戲崩潰請將進階設定裡偵錯選項中的CPU後端改為Dynarmic (JIT)</string>
<string name="memory_layout">記憶體佈局</string>
<string name="memory_layout_description">(實驗性)更改模擬記憶體佈局。此設定不會提高效能,但可能有助於使用模組來提高解析度的遊戲的載入。不要在 RAM 為 8GB 或更少的手機上使用。若遊戲崩潰請將進階設定裡偵錯選項中的CPU後端改為 Dynarmic (JIT)</string>
<string name="generate">生成</string>
<!-- Network settings strings -->
<string name="web_token">網路令牌</string>
<string name="web_token_description">用於建立公開大廳的網路令牌,由48個小寫字母a-z組成</string>
<string name="web_token_description">用於建立公開大廳的網路令牌。由48個小寫字母a-z組成</string>
<string name="web_username">網路使用者名稱</string>
<string name="web_username_description">多人遊戲房間中顯示的使用者名稱。必須為4-20個字元,僅能使用英文字母、數字、句點、破折號、底線和空格(標點符號須為英文格式)</string>
<string name="network">網路</string>
@@ -536,19 +527,19 @@
<string name="renderer_resolution">解析度 (手提/底座)</string>
<string name="renderer_vsync">垂直同步</string>
<string name="renderer_scaling_filter">視窗自適應濾波器</string>
<string name="fsr_sharpness">FSR/SGSR銳化度</string>
<string name="fsr_sharpness_description">使用FSR/SGSR時圖片的銳化程度</string>
<string name="fsr_sharpness">FSR/SGSR 銳化度</string>
<string name="fsr_sharpness_description">使用 FSR/SGSR 時圖片的銳化程度</string>
<string name="renderer_anti_aliasing">抗鋸齒</string>
<string name="advanced">進階</string>
<string name="renderer_accuracy">GPU模式</string>
<string name="renderer_accuracy_description">設定GPU模擬的準確度,大多數遊戲在設定為快速時即可正常渲染,但有些需要渲染粒子的遊戲仍需設為準確來避免圖形錯誤</string>
<string name="dma_accuracy">DMA準確度</string>
<string name="dma_accuracy_description">控制DMA準確度,將準確度設為穩定可以修復某些遊戲中的問題,但在某些情況下也可能影響效能。如果不確定,請保留為預設</string>
<string name="renderer_accuracy">GPU 模式</string>
<string name="renderer_accuracy_description">設定 GPU 模擬的準確度。大多數遊戲在設定為快速時即可正常渲染,但有些需要渲染粒子的遊戲仍需設為準確來避免圖形錯誤</string>
<string name="dma_accuracy">DMA 準確度</string>
<string name="dma_accuracy_description">控制 DMA 準確度。將準確度設為穩定可以修復某些遊戲中的問題,但在某些情況下也可能影響效能。如果不確定,請保留為預設</string>
<string name="gpu_fence_behavior">GPU 同步柵欄模式</string>
<string name="gpu_fence_behavior_description">控制 GPU 同步柵欄模式,即時是速度最快的選項,但可能會導致一些問題;使用平衡可以提供更好的相容性並修復某些遊戲中的錯誤;準確則是在犧牲部分效能的情況下進一步提升相容性;嚴格是最慢的選項,但可以修復那些對同步要求更嚴格的遊戲中的問題;預設則會依照 GPU 準確度設定</string>
<string name="gpu_fence_behavior_description">控制 GPU 同步柵欄模式。即時是速度最快的選項,但可能會導致一些問題。使用平衡可以提供更好的相容性並修復某些遊戲中的錯誤。準確則是在犧牲部分效能的情況下進一步提升相容性。嚴格是最慢的選項,但可以修復那些對同步要求更嚴格的遊戲中的問題。預設則會依照 GPU 準確度設定</string>
<string name="anisotropic_filtering">各向異性過濾</string>
<string name="anisotropic_filtering_description">改善斜角檢視時的紋理品質</string>
<string name="vram_usage_mode">VRAM使用模式</string>
@@ -557,53 +548,53 @@
<string name="accelerate_astc_description">選擇ASTC壓縮紋理的解碼方式:CPU(慢速、穩定)、GPU(快速、推薦)或CPU非同步(無卡頓,可能導致問題)</string>
<string name="sync_memory_operations">同步記憶體操作</string>
<string name="sync_memory_operations_description">確保計算和記憶體操作之間的資料一致性,此選項應能修復某些遊戲中的問題,但在某些情況下可能會降低效能。使用Unreal Engine 4的遊戲似乎受影響最大</string>
<string name="sync_memory_operations_description">確保計算和記憶體操作之間的資料一致性。 此選項應能修復某些遊戲中的問題,但在某些情況下可能會降低效能。 使用Unreal Engine 4的遊戲似乎受影響最大</string>
<string name="use_disk_shader_cache">磁碟著色器快取</string>
<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">GPU非同步模擬</string>
<string name="renderer_asynchronous_gpu_emulation_description">此修改可透過使用GPU非同步模擬來提升性能,不過可能會導致圖形問題且會提高在進行時序相關操作時當機的機率</string>
<string name="renderer_force_max_clock">強制使用最大時脈 (僅限Adreno)</string>
<string name="renderer_force_max_clock_description">強制 GPU 以可能的最大時脈執行 (熱溫限制仍會被套用)</string>
<string name="renderer_asynchronous_gpu_emulation">GPU 非同步模擬</string>
<string name="renderer_asynchronous_gpu_emulation_description">此修改可透過使用 GPU 非同步模擬來提升性能,不過可能會導致圖形問題且會提高在進行時序相關操作時當機的機率</string>
<string name="renderer_async_presentation">非同步呈現</string>
<string name="renderer_async_presentation_description">此修改可以透過將渲染移至單獨的CPU執行緒來提升性能,但可能會導致圖形問題</string>
<string name="renderer_async_presentation_description">此修改可以透過將渲染移至單獨的 CPU 執行緒來提升性能,但可能會導致圖形問題</string>
<string name="renderer_reactive_flushing">使用重新啟用排清</string>
<string name="renderer_reactive_flushing_description">犧牲效能以改善部分遊戲的轉譯準確度</string>
<string name="renderer_reactive_flushing_description">犧牲效能,以改善部分遊戲的轉譯準確度</string>
<string name="enable_buffer_history">啟用緩衝區歷史</string>
<string name="enable_buffer_history_description">允許存取先前的緩衝區狀態,此選項可能會改善部分遊戲的渲染品質與效能穩定性</string>
<string name="enable_buffer_history_description">允許存取先前的緩衝區狀態。此選項可能會改善部分遊戲的渲染品質與效能穩定性</string>
<string name="use_optimized_vertex_buffers">最佳化頂點緩衝區</string>
<string name="use_optimized_vertex_buffers_description">啟用最佳化的頂點緩衝區綁定,需要安裝 Mesa 26.0+ Turnip drivers/Qualcomm drivers。使用舊版 Turnip drivers 會導致當機(25.3版和更低的版本)</string>
<string name="use_optimized_vertex_buffers_description">啟用最佳化的頂點緩衝區綁定。需要安裝 Mesa 26.0+ Turnip drivers/Qualcomm drivers。使用舊版 Turnip drivers 會導致當機 (25.3版和更低的版本)</string>
<string name="hacks">修改</string>
<string name="fast_gpu_time">GPU時脈</string>
<string name="fast_gpu_time_description">讓遊戲誤以為GPU工作能更快完成,避免遊戲為了配合Switch的時脈而降低解析度與渲染距離</string>
<string name="fast_gpu_time">GPU 時脈</string>
<string name="fast_gpu_time_description">讓遊戲誤以為 GPU 工作能更快完成,避免遊戲為了配合 Switch 的時脈而降低解析度與渲染距離</string>
<string name="skip_cpu_inner_invalidation">跳過CPU內部失效處理</string>
<string name="skip_cpu_inner_invalidation_description">在記憶體更新期間跳過某些CPU端快取的失效處理,減少CPU使用率並提高其性能。可能會導致某些遊戲出現故障或當機</string>
<string name="fix_bloom_effects">修正光暈特效</string>
<string name="fix_bloom_effects_description">在 \"薩爾達傳說:織夢島\" 和 \"薩爾達傳說:智慧的再現\" 中減少光暈模糊的問題並移除 \"橫衝直撞:狂飆樂園重製版\" 中的光暈效果(適用於Adreno A6XX - A7XX/ Turnip)。警告:可能會在其它遊戲中導致圖形異常</string>
<string name="emulate_bgr565">模擬BGR565</string>
<string name="emulate_bgr565_description">修正遊戲中出現的色彩反轉、圖形異常或陰影異常</string>
<string name="fix_bloom_effects_description">在 薩爾達傳說:織夢島 和 薩爾達傳說:智慧的再現 中 減少光暈模糊的問題並移除 橫衝直撞:狂飆樂園重製版 中的光暈效果 (Adreno A6XX - A7XX/ Turnip)。警告:可能會在其它遊戲中導致圖形異常</string>
<string name="emulate_bgr565">模擬 BGR565</string>
<string name="emulate_bgr565_description">修正遊戲中出現的顏色反轉、圖形異常或陰影異常</string>
<string name="rescale_hack">啟用舊版重新縮放處理</string>
<string name="rescale_hack_description">透過快速重新縮放處理來啟用遊戲重新縮放設定階段的舊版處理方式</string>
<string name="renderer_asynchronous_shaders">使用非同步著色器</string>
<string name="renderer_asynchronous_shaders_description">使用非同步編譯著色器,這可能會減少卡頓,但也可能導致圖形錯誤</string>
<string name="gpu_unswizzle_settings">GPU Unswizzle設定</string>
<string name="gpu_unswizzle_settings_description">設定基於GPU的Unswizzling參數或完全停用此功能,調整此設定以在性能與紋理載入品質中取得平衡</string>
<string name="gpu_unswizzle_enable">啟用GPU Unswizzle</string>
<string name="renderer_asynchronous_shaders_description">使用非同步編譯著色器。這可能會減少卡頓,但也可能導致圖形錯誤。</string>
<string name="gpu_unswizzle_settings">GPU Unswizzle 設定</string>
<string name="gpu_unswizzle_settings_description">設定基於 GPU 的 Unswizzling 參數或完全停用此功能。調整此設定以在性能與紋理載入品質中取得平衡</string>
<string name="gpu_unswizzle_enable">啟用 GPU Unswizzle</string>
<string name="gpu_unswizzle_disabled">停用</string>
<string name="gpu_unswizzle_texture_size">GPU Unswizzle最大紋理尺寸</string>
<string name="gpu_unswizzle_texture_size_description">設定 GPU 紋理 Unswizzling 的最大值(MB),雖然 GPU 處理中型和大型紋理的速度較快,但 CPU 處理非常小的紋理可能更有效率。調整此設定以在GPU加速與CPU負載之間取得平衡</string>
<string name="gpu_unswizzle_stream_size">GPU Unswizzle Stream大小</string>
<string name="gpu_unswizzle_stream_size_description">設定每個影格處理大型紋理Unswizzling的資料上限,數值越高載入紋理的速度越快,但會增加畫面延遲;數值較低則能降低 GPU 負載,不過有可能導致至紋理載入不完全甚至突然出現</string>
<string name="gpu_unswizzle_chunk_size">GPU Unswizzle Chunk大小</string>
<string name="gpu_unswizzle_chunk_size_description">設定 3D 紋理每批次處理的深度切片數量,增加此數值可提升規格較佳GPU的處理效率,但在較低規格的硬體上可能導致卡頓或驅動程式逾時</string>
<string name="gpu_unswizzle_texture_size">GPU Unswizzle 最大紋理尺寸</string>
<string name="gpu_unswizzle_texture_size_description">設定 GPU 紋理 Unswizzling 的最大值 (MB)。雖然 GPU 處理中型和大型紋理的速度較快,但 CPU 處理非常小的紋理可能更有效率。整此設定以在 GPU 加速與 CPU 負載之間取得平衡</string>
<string name="gpu_unswizzle_stream_size">GPU Unswizzle 流大小</string>
<string name="gpu_unswizzle_stream_size_description">設定每個影格處理大型紋理 Unswizzling 的資料上限。數值越高載入紋理的速度越快,但會增加畫面延遲;數值較低則能降低 GPU 負載,不過有可能導致至紋理載入不完全甚至突然出現</string>
<string name="gpu_unswizzle_chunk_size">GPU Unswizzle 塊大小</string>
<string name="gpu_unswizzle_chunk_size_description">設定 3D 紋理每批次處理的深度切片數量。增加此數值可提升規格較佳 GPU 的處理效率,但在較低規格的硬體上可能導致卡頓或驅動程式逾時</string>
<string name="gpu_unswizzle_default_button">預設</string>
<string name="extensions">擴充功能</string>
<string name="dyna_state">擴展動態狀態</string>
<string name="dyna_state_description">控制擴展動態狀態中可使用的功能數量,較高的數值可根據驅動程式支援的動態狀態減少管線編譯的次數</string>
<string name="dyna_state_description">控制擴展動態狀態中可使用的功能數量。較高的數值可根據驅動程式支援的動態狀態減少管線編譯的次數。</string>
<string name="disabled">停用</string>
<string name="vertex_input_dynamic_state">頂點輸入動態狀態</string>
<string name="vertex_input_dynamic_state_description">啟用此功能可提供更彈性的頂點輸入處理方式,並可能減少頂點/緩衝區的管線編譯時間</string>
@@ -625,43 +616,43 @@
<string name="cpu">CPU</string>
<string name="clocks">時脈</string>
<string name="use_auto_stub">使用自動存根</string>
<string name="use_auto_stub_description">自動補上缺少的服務和功能,可提高相容性但可能導致當機和穩定性問題</string>
<string name="use_auto_stub_description">自動補上缺少的服務和功能。可提高相容性但可能導致當機和穩定性問題。</string>
<string name="gpu">GPU</string>
<string name="renderer_api">API</string>
<string name="renderer_debug">圖形偵錯</string>
<string name="renderer_debug_description">將圖形API設為慢速偵錯模式</string>
<string name="patch_old_qcom_drivers">BCn區塊壓縮紋理修補</string>
<string name="patch_old_qcom_drivers_description">覆寫Adreno GPU的BCn紋理格式自動偵測。通常會根據Android版本自動偵測(會在API 28以上的裝置自動啟用)</string>
<string name="renderer_debug_description">將圖形 API 設為慢速偵錯模式</string>
<string name="patch_old_qcom_drivers">BCn 區塊壓縮紋理修補</string>
<string name="patch_old_qcom_drivers_description">覆寫 Adreno GPU 的 BCn 紋理格式自動偵測。通常會根據 Android 版本自動偵測(會在API 28 以上自動啟用)</string>
<string name="fastmem">Fastmem</string>
<string name="log">日誌</string>
<string name="flush_by_line">按行寫入偵錯日誌</string>
<string name="flush_by_line_description">在寫入一行日誌就立刻更新偵錯記錄,讓程式在當機或閃退時更容易偵錯</string>
<string name="flush_by_line_description">在每行寫入時重新整理偵錯日誌,讓程式在當機或閃退時更容易偵錯。</string>
<string name="extended_logging">啟用更詳細的日誌記錄</string>
<string name="extended_logging_description">將日誌檔的最大檔案限制從100MB提高到1GB</string>
<string name="extended_logging_description">將日誌檔的最大檔案限制從 100MB 提高到 1GB</string>
<string name="log_filter">日誌過濾器</string>
<string name="log_filter_description">控制 Eden 日誌的紀錄類別。例如: *:Info Service.LM:Debug</string>
<!-- GPU Logging strings -->
<string name="gpu_logging_header">GPU日誌</string>
<string name="gpu_logging_header">GPU 日誌</string>
<string name="gpu_log_level">記錄層級</string>
<string name="gpu_log_level_description">GPU日誌的詳細層級(越高日誌越詳細,不過會更容易過熱)</string>
<string name="gpu_log_vulkan_calls">記錄Vulkan API呼叫</string>
<string name="gpu_log_vulkan_calls_description">在環形緩衝區中記錄所有Vulkan API呼叫</string>
<string name="gpu_log_shader_dumps">傾印SPIR-V著色器</string>
<string name="gpu_log_shader_dumps_description">將重新編譯後的SPIR-V二進位檔案(.spv)儲存至Eden資料夾裡的dump資料夾中。可使用 spirv-dis/spirv-cross/spirv-val 進行檢查</string>
<string name="gpu_log_level_description">GPU 日誌的詳細層級 (越高日誌越詳細,不過會更容易過熱)</string>
<string name="gpu_log_vulkan_calls">記錄 Vulkan API 呼叫</string>
<string name="gpu_log_vulkan_calls_description">在環形緩衝區中記錄所有 Vulkan API 呼叫</string>
<string name="gpu_log_shader_dumps">傾印 SPIR-V 著色器</string>
<string name="gpu_log_shader_dumps_description">將重新編譯後的 SPIR-V 二進位檔案 (.spv) 儲存至 Eden 資料夾裡的 dump 資料夾中。可使用 spirv-dis/spirv-cross/spirv-val 進行檢查</string>
<string name="dump_guest_shaders">傾印客體(Maxwell)著色器</string>
<string name="dump_guest_shaders_description">將Maxwell客體著色器位元組碼檔案(.ash)儲存至Eden資料夾裡的dump資料夾中。可使用 nvdisasm 進行檢查</string>
<string name="dump_macros">傾印Maxwell巨集</string>
<string name="dump_macros_description">將Maxwell巨集程式檔案(.macro)儲存至Eden資料夾裡的dump資料夾中。可使用 envydis 進行檢查</string>
<string name="gpu_log_memory_tracking">追蹤GPU記憶體</string>
<string name="gpu_log_memory_tracking_description">監控GPU記憶體的分配和釋放</string>
<string name="dump_guest_shaders_description">將 Maxwell 客體著色器位元組碼檔案 (.ash) 儲存至 Eden 資料夾裡的 dump 資料夾中。可使用 nvdisasm 進行檢查</string>
<string name="dump_macros">傾印 Maxwell 巨集</string>
<string name="dump_macros_description">將 Maxwell 巨集程式檔案 (.macro) 儲存至 Eden 資料夾裡的 dump 資料夾中。可使用 envydis 進行檢查</string>
<string name="gpu_log_memory_tracking">追蹤 GPU 記憶體</string>
<string name="gpu_log_memory_tracking_description">監控 GPU 記憶體的分配和釋放</string>
<string name="gpu_log_driver_debug">驅動程式偵錯資料</string>
<string name="gpu_log_driver_debug_description">擷取驅動程式的特定偵錯資料(例如 Turnip breadcrumbs)</string>
<string name="gpu_log_ring_buffer_size">環形緩衝區大小</string>
<string name="gpu_log_ring_buffer_size_description">追蹤的近期Vulkan呼叫數量(預設為512)</string>
<string name="gpu_log_ring_buffer_size_hint">64至4096個項目</string>
<string name="gpu_log_ring_buffer_size_description">追蹤的近期 Vulkan 呼叫數量(預設為 512 )</string>
<string name="gpu_log_ring_buffer_size_hint">64 至 4096 個項目</string>
<string name="general">一般</string>
@@ -685,17 +676,17 @@
<string name="left_stick">左搖桿</string>
<string name="control_stick">控制搖桿</string>
<string name="right_stick">右搖桿</string>
<string name="c_stick">C搖桿</string>
<string name="c_stick">C 搖桿</string>
<string name="pressed">按壓</string>
<string name="range">靈敏度</string>
<string name="deadzone">無感帶</string>
<string name="modifier">用按鈕、按鍵來模擬搖桿</string>
<string name="modifier_range">修改按鈕模擬左/右搖桿的值,視遊戲不同而有所差異\n(模擬左搖桿時,若將此數值設為50,則按下按鈕時遊戲裡的人物可能只會走動而不是奔跑;模擬右搖桿時所使用的值則是會影響到轉視角的速度,數值越高代表使用的模擬值越大)</string>
<string name="triggers">肩部按鍵</string>
<string name="button_sl_left">左SL</string>
<string name="button_sr_left">左SR</string>
<string name="button_sl_right">右SL</string>
<string name="button_sr_right">右SR</string>
<string name="button_sl_left">左 SL</string>
<string name="button_sr_left">左 SR</string>
<string name="button_sl_right">右 SL</string>
<string name="button_sr_right">右 SR</string>
<string name="invalid">無效</string>
<string name="not_set">無</string>
<string name="unknown">未知</string>
@@ -710,12 +701,12 @@
<string name="auto_map_description">選擇一個控制器以嘗試自動配對按鈕、搖桿</string>
<string name="attempted_auto_map">嘗試為 %1$s 自動配對</string>
<string name="controller_type">控制器類型</string>
<string name="pro_controller">Pro手把</string>
<string name="pro_controller">Pro 手把</string>
<string name="handheld">掌機模式</string>
<string name="dual_joycons">雙Joycon手把</string>
<string name="left_joycon">左Joycon手把</string>
<string name="right_joycon">右Joycon手把</string>
<string name="gamecube_controller">GameCube手把</string>
<string name="dual_joycons">雙 Joycon 手把</string>
<string name="left_joycon">左 Joycon 手把</string>
<string name="right_joycon">右 Joycon 手把</string>
<string name="gamecube_controller">GameCube 手把</string>
<string name="invert_axis">方向反轉</string>
<string name="invert_button">反轉按鈕</string>
<string name="toggle_button">切換按鍵</string>
@@ -808,7 +799,7 @@
<string name="recommended_driver">推薦驅動程式:</string>
<string name="gpu_model">GPU型號</string>
<string name="unsupported_gpu">不支援的GPU</string>
<string name="unsupported_gpu_warning">您的GPU不支援驅動程式注入,不建議嘗試設定自訂驅動程式</string>
<string name="unsupported_gpu_warning">您的GPU不支援驅動程式注入。不建議嘗試設定自訂驅動程式。</string>
<!-- Preferences Screen -->
<string name="preferences_settings">設定</string>
@@ -595,10 +595,6 @@
<string name="fast_gpu_time_description">Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch\'s clocks.</string>
<string name="skip_cpu_inner_invalidation">Skip CPU Inner Invalidation</string>
<string name="skip_cpu_inner_invalidation_description">Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games.</string>
<string name="nce_invalidation_gpu_readback">GPU Readback Before NCE Invalidation</string>
<string name="nce_invalidation_gpu_readback_description">Temporary. Fix Ender Magnolia (and other UE titles) crashes. May reduce performance. Improvement in progress.</string>
<string name="nce_runtime_nro_patch">Patch Runtime NROs for NCE</string>
<string name="nce_runtime_nro_patch_description">Temporary. Fix Biomutant boot. May impact performance. Improvement in progress.</string>
<string name="fix_bloom_effects">Fix Bloom Effects</string>
<string name="fix_bloom_effects_description">Reduces bloom blur in LA/EOW (Adreno A6XX - A7XX/ Turnip), removes bloom in Burnout. Warning: may cause graphical artifacts in other games.</string>
<string name="emulate_bgr565">Emulate BGR565</string>
+6 -1
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 2023 yuzu Emulator Project
@@ -19,6 +19,11 @@ ADSP::ADSP(Core::System& system, Sink::Sink& sink) {
}
}
void ADSP::Shutdown() {
audio_renderer->Stop();
opus_decoder->Shutdown();
}
AudioRenderer::AudioRenderer& ADSP::AudioRenderer() {
return *audio_renderer;
}
+2 -1
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 2023 yuzu Emulator Project
@@ -45,6 +45,7 @@ public:
explicit ADSP(Core::System& system, Sink::Sink& sink);
~ADSP() = default;
void Shutdown();
AudioRenderer::AudioRenderer& AudioRenderer();
OpusDecoder::OpusDecoder& OpusDecoder();
@@ -34,26 +34,19 @@ void AudioRenderer::Start() {
mailbox.Send(Direction::DSP, Message::InitializeOK);
if (mailbox.Receive(Direction::Host) != Message::InitializeOK) {
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
"message response from ADSP!");
return;
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown message response from ADSP!");
}
running = true;
}
void AudioRenderer::Stop() {
if (!running) {
return;
if (main_thread.joinable()) {
main_thread.request_stop();
mailbox.Send(Direction::DSP, Message::Shutdown);
if (mailbox.Receive(Direction::Host, main_thread.get_stop_token()) != Message::Shutdown) {
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown message response from ADSP!");
}
main_thread.join();
}
mailbox.Send(Direction::DSP, Message::Shutdown);
if (mailbox.Receive(Direction::Host) != Message::Shutdown) {
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
"message response from ADSP!");
}
main_thread.request_stop();
main_thread.join();
for (auto& stream : streams) {
if (stream) {
stream->Stop();
@@ -61,7 +54,6 @@ void AudioRenderer::Stop() {
stream = nullptr;
}
}
running = false;
}
void AudioRenderer::Signal() {
@@ -69,13 +61,12 @@ void AudioRenderer::Signal() {
Send(Direction::DSP, Message::Render);
}
void AudioRenderer::Wait() {
auto msg = Receive(Direction::Host);
void AudioRenderer::Wait(std::stop_token stop_token) {
auto msg = mailbox.Receive(Direction::Host, stop_token);
if (msg != Message::RenderResponse) {
LOG_ERROR(Service_Audio,
"Did not receive the expected render response from the AudioRenderer! Expected "
"{}, got {}",
Message::RenderResponse, msg);
"Did not receive the expected render response from the AudioRenderer! Expected "
"{}, got {}", Message::RenderResponse, msg);
}
PostDSPClearCommandBuffer();
}
@@ -147,7 +138,9 @@ void AudioRenderer::Main(std::stop_token stop_token) {
constexpr u64 max_process_time{2'304'000ULL};
while (!stop_token.stop_requested()) {
auto msg{mailbox.Receive(Direction::DSP)};
auto msg = mailbox.Receive(Direction::DSP, stop_token);
if (stop_token.stop_requested())
break;
switch (msg) {
case Message::Shutdown:
mailbox.Send(Direction::Host, Message::Shutdown);
@@ -63,13 +63,15 @@ public:
*/
void Start();
void NotifyShutdown();
/**
* Stop the AudioRenderer.
*/
void Stop();
void Signal();
void Wait();
void Wait(std::stop_token stop_token);
void Send(Direction dir, u32 message);
u32 Receive(Direction dir);
@@ -102,8 +104,6 @@ private:
Mailbox mailbox;
/// Main thread
std::jthread main_thread{};
/// The current state
std::atomic<bool> running{};
/// Shared memory of input command buffers, set by host, read by DSP
std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
/// The command lists to process
+33 -23
View File
@@ -41,19 +41,16 @@ OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
}
OpusDecoder::~OpusDecoder() {
if (!running) {
if (main_thread.joinable()) {
// Shutdown the thread
Send(Direction::DSP, Message::Shutdown);
main_thread.request_stop();
auto msg = Receive(Direction::Host, main_thread.get_stop_token());
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", Message::ShutdownOK, msg);
main_thread.join();
} else {
init_thread.request_stop();
return;
}
// Shutdown the thread
Send(Direction::DSP, Message::Shutdown);
auto msg = Receive(Direction::Host);
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
Message::ShutdownOK, msg);
main_thread.request_stop();
main_thread.join();
running = false;
}
void OpusDecoder::Send(Direction dir, u32 message) {
@@ -73,7 +70,6 @@ void OpusDecoder::Init(std::stop_token stop_token) {
return;
}
main_thread = std::jthread([this](std::stop_token st) { Main(st); });
running = true;
Send(Direction::Host, Message::StartOK);
}
@@ -82,12 +78,15 @@ void OpusDecoder::Main(std::stop_token stop_token) {
while (!stop_token.stop_requested()) {
auto msg = Receive(Direction::DSP, stop_token);
if (stop_token.stop_requested())
break;
switch (msg) {
case Shutdown:
case Message::Shutdown:
Send(Direction::Host, Message::ShutdownOK);
return;
case GetWorkBufferSize: {
case Message::GetWorkBufferSize: {
auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
ASSERT(IsValidChannelCount(channel_count));
@@ -96,7 +95,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::GetWorkBufferSizeOK);
} break;
case InitializeDecodeObject: {
case Message::InitializeDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
auto buffer_size = shared_memory->host_send_data[1];
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
@@ -113,7 +112,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::InitializeDecodeObjectOK);
} break;
case ShutdownDecodeObject: {
case Message::ShutdownDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
@@ -123,7 +122,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
} break;
case DecodeInterleaved: {
case Message::DecodeInterleaved: {
auto start_time = system.CoreTiming().GetGlobalTimeUs();
auto buffer = shared_memory->host_send_data[0];
@@ -161,19 +160,19 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::DecodeInterleavedOK);
} break;
case MapMemory: {
case Message::MapMemory: {
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
Send(Direction::Host, Message::MapMemoryOK);
} break;
case UnmapMemory: {
case Message::UnmapMemory: {
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
Send(Direction::Host, Message::UnmapMemoryOK);
} break;
case GetWorkBufferSizeForMultiStream: {
case Message::GetWorkBufferSizeForMultiStream: {
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]);
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]);
@@ -184,7 +183,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
} break;
case InitializeMultiStreamDecodeObject: {
case Message::InitializeMultiStreamDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
auto buffer_size = shared_memory->host_send_data[1];
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
@@ -211,7 +210,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
} break;
case ShutdownMultiStreamDecodeObject: {
case Message::ShutdownMultiStreamDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
@@ -221,7 +220,7 @@ void OpusDecoder::Main(std::stop_token stop_token) {
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
} break;
case DecodeInterleavedForMultiStream: {
case Message::DecodeInterleavedForMultiStream: {
auto start_time = system.CoreTiming().GetGlobalTimeUs();
auto buffer = shared_memory->host_send_data[0];
@@ -266,4 +265,15 @@ void OpusDecoder::Main(std::stop_token stop_token) {
}
}
void OpusDecoder::Shutdown() {
if (init_thread.joinable()) {
init_thread.request_stop();
init_thread.join(); //must explicitly wait for init to join
}
if (main_thread.joinable()) {
main_thread.request_stop();
Send(Direction::DSP, Message::Shutdown);
}
}
} // namespace AudioCore::ADSP::OpusDecoder
+3 -3
View File
@@ -57,7 +57,7 @@ public:
~OpusDecoder();
bool IsRunning() const noexcept {
return running;
return main_thread.joinable();
}
void Send(Direction dir, u32 message);
@@ -67,6 +67,8 @@ public:
shared_memory = &shared_memory_;
}
void Shutdown();
private:
/**
* Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
@@ -85,8 +87,6 @@ private:
std::jthread init_thread{};
/// Main thread
std::jthread main_thread{};
/// The current state
bool running{};
/// Structure shared with the host, input data set by the host before sending a mailbox message,
/// and the responses are written back by the OpusDecoder.
SharedMemory* shared_memory{};
+1
View File
@@ -33,6 +33,7 @@ void AudioCore::CreateSinks() {
void AudioCore::Shutdown() {
audio_manager->Shutdown();
adsp->Shutdown();
}
AudioManager& AudioCore::GetAudioManager() {
+2 -2
View File
@@ -17,7 +17,7 @@ AudioManager::AudioManager() {
std::unique_lock l{events.GetAudioEventLock()};
events.ClearEvents();
while (!stop_token.stop_requested()) {
const auto timed_out{events.Wait(l, std::chrono::seconds(2))};
const auto timed_out = events.Wait(l, std::chrono::seconds{2});
if (events.CheckAudioEventSet(Event::Type::Max)) {
break;
}
@@ -35,9 +35,9 @@ AudioManager::AudioManager() {
}
void AudioManager::Shutdown() {
events.SetAudioEvent(Event::Type::Max, true);
if (thread.joinable()) {
thread.request_stop();
events.SetAudioEvent(Event::Type::Max, true);
thread.join();
}
}
+1 -3
View File
@@ -39,9 +39,7 @@ class AudioManager {
public:
explicit AudioManager();
/**
* Shutdown the audio manager.
*/
/// @brief Shutdown the audio manager.
void Shutdown();
/**
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -35,7 +38,7 @@ void CircularBufferSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStat
auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
auto current_state{reinterpret_cast<CircularBufferState*>(state.data())};
if (in_use == buffer_params->in_use && !buffer_unmapped) {
if (in_use == bool(buffer_params->in_use) && !buffer_unmapped) {
error_info.error_code = ResultSuccess;
error_info.address = CpuAddr(0);
out_status.writeOffset = current_state->last_pos2;
+19 -20
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 2022 yuzu Emulator Project
@@ -31,10 +31,10 @@ public:
};
struct DeviceInParameter {
/* 0x000 */ char name[0x100];
/* 0x000 */ u8 name[0x100];
/* 0x100 */ u32 input_count;
/* 0x104 */ std::array<s8, MaxChannels> inputs;
/* 0x10A */ char unk10A[0x1];
/* 0x10A */ u8 unk10A[0x1];
/* 0x10B */ bool downmix_enabled;
/* 0x10C */ std::array<f32, 4> downmix_coeff;
};
@@ -43,7 +43,7 @@ public:
struct DeviceState {
/* 0x00 */ UpsamplerInfo* upsampler_info;
/* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff;
/* 0x18 */ char unk18[0x18];
/* 0x18 */ u8 unk18[0x18];
};
static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!");
@@ -55,8 +55,8 @@ public:
/* 0x14 */ u32 previous_pos;
/* 0x18 */ SampleFormat format;
/* 0x1C */ std::array<s8, MaxChannels> inputs;
/* 0x22 */ bool in_use;
/* 0x23 */ char unk23[0x5];
/* 0x22 */ u8 in_use;
/* 0x23 */ u8 unk23[0x5];
};
static_assert(sizeof(CircularBufferInParameter) == 0x28,
"CircularBufferInParameter has the wrong size!");
@@ -65,16 +65,16 @@ public:
/* 0x00 */ u32 last_pos2;
/* 0x04 */ s32 current_pos;
/* 0x08 */ u32 last_pos;
/* 0x0C */ char unk0C[0x4];
/* 0x0C */ u8 unk0C[0x4];
/* 0x10 */ AddressInfo address_info;
};
static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!");
struct InParameter {
/* 0x000 */ Type type;
/* 0x001 */ bool in_use;
/* 0x001 */ u8 in_use;
/* 0x004 */ u32 node_id;
/* 0x008 */ char unk08[0x18];
/* 0x008 */ u8 unk08[0x18];
union {
/* 0x020 */ DeviceInParameter device;
/* 0x020 */ CircularBufferInParameter circular_buffer;
@@ -84,7 +84,7 @@ public:
struct OutStatus {
/* 0x00 */ u32 writeOffset;
/* 0x04 */ char unk04[0x1C];
/* 0x04 */ u8 unk04[0x1C];
}; // size == 0x20
static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!");
@@ -162,19 +162,18 @@ public:
u8* GetParameter();
protected:
/// Type of this sink
Type type{Type::Invalid};
/// Is this sink in use?
bool in_use{};
/// Is this sink's buffer unmapped? Circular only
bool buffer_unmapped{};
/// Node id for this sink
u32 node_id{};
/// State buffer for this sink
std::array<u8, (std::max)(sizeof(DeviceState), sizeof(CircularBufferState))> state{};
/// Parameter buffer for this sink
std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))>
parameter{};
std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))> parameter{};
/// Type of this sink
Type type{Type::Invalid};
/// Node id for this sink
u32 node_id{};
/// Is this sink in use?
bool in_use : 1 = false;
/// Is this sink's buffer unmapped? Circular only
bool buffer_unmapped : 1 = false;
};
} // namespace AudioCore::Renderer
+14 -14
View File
@@ -100,8 +100,9 @@ u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) {
}
System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_)
: core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()},
adsp_rendered_event{adsp_rendered_event_} {}
: core{core_}
, adsp_rendered_event{adsp_rendered_event_}
{}
Result System::Initialize(const AudioRendererParameterInternal& params,
Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
@@ -406,7 +407,7 @@ void System::Finalize() {
return;
}
if (active) {
if (IsActive()) {
Stop();
}
@@ -433,14 +434,12 @@ void System::Start() {
std::scoped_lock l{lock};
frames_elapsed = 0;
state = State::Started;
active = true;
}
void System::Stop() {
{
std::scoped_lock l{lock};
state = State::Stopped;
active = false;
}
if (execution_mode == ExecutionMode::Auto) {
@@ -480,7 +479,7 @@ Result System::Update(std::span<const u8> input, std::span<u8> performance, std:
return result;
}
result = info_updater.UpdateEffects(effect_context, active, memory_pool_workbuffer,
result = info_updater.UpdateEffects(effect_context, IsActive(), memory_pool_workbuffer,
memory_pool_count);
if (result.IsError()) {
LOG_ERROR(Service_Audio, "Failed to update Effects!");
@@ -582,16 +581,16 @@ u32 System::GetRenderingDevice() const {
}
bool System::IsActive() const {
return active;
return state == State::Started;
}
void System::SendCommandToDsp() {
std::scoped_lock l{lock};
if (initialized) {
if (active) {
if (IsActive()) {
terminate_event.Reset();
const auto remaining_command_count{audio_renderer.GetRemainCommandCount(session_id)};
const auto remaining_command_count = core.AudioCore().ADSP().AudioRenderer().GetRemainCommandCount(session_id);
u64 command_size{0};
if (remaining_command_count) {
@@ -618,16 +617,17 @@ void System::SendCommandToDsp() {
auto time_limit{
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
(static_cast<f32>(render_time_limit_percent) / 100.0f))};
audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit,
applet_resource_user_id, process_handle,
reset_command_buffers);
core.AudioCore().ADSP().AudioRenderer().SetCommandBuffer(session_id,
translated_addr, command_size,
time_limit, applet_resource_user_id,
process_handle, reset_command_buffers);
reset_command_buffers = false;
command_buffer_size = command_size;
if (remaining_command_count == 0) {
adsp_rendered_event->Signal(core.Kernel());
}
} else {
audio_renderer.ClearRemainCommandCount(session_id);
core.AudioCore().ADSP().AudioRenderer().ClearRemainCommandCount(session_id);
terminate_event.Set();
}
}
@@ -738,7 +738,7 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer,
const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()};
total_ticks_elapsed += end_time - start_time;
num_command_lists_generated++;
render_start_tick = audio_renderer.GetRenderingStartTick(session_id);
render_start_tick = core.AudioCore().ADSP().AudioRenderer().GetRenderingStartTick(session_id);
frames_elapsed++;
return command_buffer.size;
+3 -2
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -219,8 +222,6 @@ public:
private:
/// Core system
Core::System& core;
/// Reference to the ADSP's AudioRenderer for communication
::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer;
/// Is this system initialized?
bool initialized{};
/// Is this system currently active?
+27 -30
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 2022 yuzu Emulator Project
@@ -11,73 +11,70 @@
#include "audio_core/renderer/system_manager.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/core_timing.h"
namespace AudioCore::Renderer {
SystemManager::SystemManager(Core::System& core_)
: core{core_}, audio_renderer{core.AudioCore().ADSP().AudioRenderer()} {}
: core{core_}
{
}
SystemManager::~SystemManager() {
Stop();
}
void SystemManager::InitializeUnsafe() {
if (!active) {
active = true;
audio_renderer.Start();
thread = std::jthread([this](std::stop_token stop_token) {
Common::SetCurrentThreadName("AudioRenderSystemManager");
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
while (active && !stop_token.stop_requested()) {
if (!thread.joinable()) {
core.AudioCore().ADSP().AudioRenderer().Start();
thread = core.Kernel().RunOnHostCoreThread("AudioRenderSystemManager", [this]() {
auto& audio_renderer = core.AudioCore().ADSP().AudioRenderer();
auto const stop_token = thread.get_stop_token();
while (!stop_token.stop_requested()) {
{
std::scoped_lock l{mutex1};
std::scoped_lock lk{mutex};
for (auto system : systems)
system->SendCommandToDsp();
}
audio_renderer.Signal();
audio_renderer.Wait();
audio_renderer.Wait(stop_token);
}
});
}
}
void SystemManager::Stop() {
if (active) {
active = false;
core.AudioCore().ADSP().AudioRenderer().Stop();
if (thread.joinable()) {
thread.request_stop();
thread.join();
audio_renderer.Stop();
}
}
bool SystemManager::Add(System& system_) {
std::scoped_lock l2{mutex2};
if (systems.size() + 1 > MaxRendererSessions) {
std::scoped_lock lk{mutex};
if (systems.size() >= MaxRendererSessions) {
LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!");
return false;
}
{
std::scoped_lock l{mutex1};
if (systems.empty())
InitializeUnsafe();
}
systems.push_back(&system_);
if (!systems.empty()) {
InitializeUnsafe();
}
return true;
}
bool SystemManager::Remove(System& system_) {
std::scoped_lock l2{mutex2};
{
std::scoped_lock l{mutex1};
if (systems.remove(&system_) == 0) {
LOG_ERROR(Service_Audio, "Failed to remove a render system, it was not found in the list!");
return false;
}
std::scoped_lock lk{mutex};
if (systems.remove(&system_) == 0) {
LOG_ERROR(Service_Audio, "Failed to remove a render system, it was not found in the list!");
return false;
}
if (systems.empty())
if (systems.empty()) {
Stop();
}
return true;
}
+3 -9
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 2022 yuzu Emulator Project
@@ -75,14 +75,8 @@ private:
std::list<System*> systems{};
/// Main worker thread for generating command lists
std::jthread thread;
/// Mutex for the systems
std::mutex mutex1{};
/// Mutex for adding/removing systems
std::mutex mutex2{};
/// Is the system manager thread active?
std::atomic<bool> active{};
/// Reference to the ADSP's AudioRenderer for communication
::AudioCore::ADSP::AudioRenderer::AudioRenderer& audio_renderer;
/// Mutex for the reading and adding/removing systems
std::mutex mutex{};
};
} // namespace AudioCore::Renderer
+7 -7
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 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -109,12 +109,6 @@ private:
if (read_index == producer.index.load(std::memory_order::acquire)) {
return false;
}
} else if constexpr (Mode == PopMode::Wait) {
// Wait until the queue is not empty.
std::unique_lock lock{consumer.cv_mutex};
consumer.cv.wait(lock, [this, read_index] {
return read_index != producer.index.load(std::memory_order::acquire);
});
} else if constexpr (Mode == PopMode::WaitWithStopToken) {
// Wait until the queue is not empty.
std::unique_lock lock{consumer.cv_mutex};
@@ -124,6 +118,12 @@ private:
if (stop_token.stop_requested()) {
return false;
}
} else if constexpr (Mode == PopMode::Wait) {
// Wait until the queue is not empty.
std::unique_lock lock{consumer.cv_mutex};
consumer.cv.wait(lock, [this, read_index] {
return read_index != producer.index.load(std::memory_order::acquire);
});
} else {
static_assert(Mode < PopMode::Count, "Invalid PopMode.");
}
-4
View File
@@ -639,10 +639,6 @@ struct Values {
Specialization::Default,
true,
true};
SwitchableSetting<bool> nce_invalidation_gpu_readback{
linkage, false, "nce_invalidation_gpu_readback", Category::RendererHacks};
SwitchableSetting<bool> nce_runtime_nro_patch{
linkage, false, "nce_runtime_nro_patch", Category::RendererHacks};
SwitchableSetting<bool> async_presentation{linkage,
#ifdef __ANDROID__
false,
+1 -1
View File
@@ -150,7 +150,7 @@ ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, Lanczos, Scale
ENUM(AntiAliasing, None, Fxaa, Smaa);
ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
ENUM(ConsoleMode, Handheld, Docked);
ENUM(AppletMode, HLE, LLE, Disabled);
ENUM(AppletMode, HLE, LLE);
ENUM(SpirvOptimizeMode, Never, OnLoad, Always);
ENUM(GpuClock, Normal, Boost, Overclock)
ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge)
+45 -23
View File
@@ -110,8 +110,13 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
struct System::Impl {
explicit Impl(System& system)
: kernel{system}, fs_controller{system}, hid_core{kernel}, cpu_manager{system},
reporter{system}, applet_manager{system}, frontend_applets{system}, profile_manager{} {}
: kernel{system}
, fs_controller{system}
, hid_core{kernel}
, cpu_manager{system}
, reporter{system}
, profile_manager{}
{}
u64 program_id;
@@ -124,6 +129,12 @@ struct System::Impl {
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
applet_manager.emplace(system);
frontend_applets.emplace(system);
apm_controller.emplace(core_timing);
arp_manager.emplace();
profile_manager.emplace();
// Create a default fs if one doesn't already exist.
if (virtual_filesystem == nullptr) {
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
@@ -133,7 +144,7 @@ struct System::Impl {
}
// Create default implementations of applets if one is not provided.
frontend_applets.SetDefaultAppletsIfMissing();
frontend_applets->SetDefaultAppletsIfMissing();
auto const is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue();
@@ -376,7 +387,7 @@ struct System::Impl {
// Register with applet manager
// All threads are started, begin main process execution, now that we're in the clear
applet_manager.CreateAndInsertByFrontendAppletParameters(std::move(process), params);
applet_manager->CreateAndInsertByFrontendAppletParameters(std::move(process), params);
if (Settings::values.gamecard_inserted) {
if (Settings::values.gamecard_current_game) {
@@ -428,13 +439,27 @@ struct System::Impl {
core_timing.SyncPause(false);
Network::CancelPendingSocketOperations();
kernel.SuspendEmulation(true);
kernel.CloseServices();
kernel.ShutdownCores();
// Notify services helpers of shutdown
audio_core->Shutdown();
// Wait for threads/services to join
kernel.CloseServices();
// service shutdown
services.reset();
service_manager.reset();
frontend_applets.reset();
applet_manager.reset();
apm_controller.reset();
arp_manager.reset();
profile_manager.reset();
fs_controller.Reset();
cheat_engine.reset();
core_timing.ClearPendingEvents();
core_timing.Reset();
app_loader.reset();
audio_core.reset();
gpu_core.reset();
@@ -451,9 +476,6 @@ struct System::Impl {
room_member->SendGameInfo(game_info);
}
// Reset all glue registrations
arp_manager.ResetAll();
LOG_DEBUG(Core, "Shutdown OK");
}
@@ -482,13 +504,13 @@ struct System::Impl {
CpuManager cpu_manager;
Reporter reporter;
/// Applets
Service::AM::AppletManager applet_manager;
Service::AM::Frontend::FrontendAppletHolder frontend_applets;
std::optional<Service::AM::AppletManager> applet_manager;
std::optional<Service::AM::Frontend::FrontendAppletHolder> frontend_applets;
/// APM (Performance) services
Service::APM::Controller apm_controller{core_timing};
std::optional<Service::APM::Controller> apm_controller;
/// Service State
Service::Glue::ARPManager arp_manager;
Service::Account::ProfileManager profile_manager;
std::optional<Service::Glue::ARPManager> arp_manager;
std::optional<Service::Account::ProfileManager> profile_manager;
/// Network instance
Network::NetworkInstance network_instance;
Core::SpeedLimiter speed_limiter;
@@ -815,19 +837,19 @@ void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
}
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
impl->frontend_applets.SetFrontendAppletSet(std::move(set));
impl->frontend_applets->SetFrontendAppletSet(std::move(set));
}
Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() {
return impl->frontend_applets;
return *impl->frontend_applets;
}
const Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() const {
return impl->frontend_applets;
return *impl->frontend_applets;
}
Service::AM::AppletManager& System::GetAppletManager() {
return impl->applet_manager;
return *impl->applet_manager;
}
void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) {
@@ -868,27 +890,27 @@ const Reporter& System::GetReporter() const {
}
Service::Glue::ARPManager& System::GetARPManager() {
return impl->arp_manager;
return *impl->arp_manager;
}
const Service::Glue::ARPManager& System::GetARPManager() const {
return impl->arp_manager;
return *impl->arp_manager;
}
Service::APM::Controller& System::GetAPMController() {
return impl->apm_controller;
return *impl->apm_controller;
}
const Service::APM::Controller& System::GetAPMController() const {
return impl->apm_controller;
return *impl->apm_controller;
}
Service::Account::ProfileManager& System::GetProfileManager() {
return impl->profile_manager;
return *impl->profile_manager;
}
const Service::Account::ProfileManager& System::GetProfileManager() const {
return impl->profile_manager;
return *impl->profile_manager;
}
void System::SetExitLocked(bool locked) {
+2 -2
View File
@@ -304,10 +304,10 @@ std::optional<s64> CoreTiming::Advance() {
void CoreTiming::Reset() {
paused = true;
pause_event.Set();
event.Set();
if (timer_thread.joinable()) {
timer_thread.request_stop();
pause_event.Set();
event.Set();
timer_thread.join();
}
has_started = false;
@@ -72,11 +72,9 @@ void GlobalSchedulerContext::UnregisterDummyThreadForWakeup(KThread* thread) noe
void GlobalSchedulerContext::WakeupWaitingDummyThreads(KernelCore& kernel) noexcept {
ASSERT(this->IsLocked());
if (m_woken_dummy_threads.size() > 0) {
for (auto* thread : m_woken_dummy_threads)
thread->DummyThreadEndWait(kernel);
m_woken_dummy_threads.clear();
}
for (auto* thread : m_woken_dummy_threads)
thread->DummyThreadEndWait(kernel);
m_woken_dummy_threads.clear();
}
} // namespace Kernel
-1
View File
@@ -59,7 +59,6 @@ Result TerminateChildren(KernelCore& kernel, KProcess* process, const KThread* t
KThread* cur_child = nullptr;
{
KScopedLightLock proc_lk(process->GetListLock());
auto& thread_list = process->GetThreadList();
for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) {
+4 -10
View File
@@ -1236,18 +1236,13 @@ namespace Kernel {
KScopedSchedulerLock sl{kernel};
// Determine if this is the first termination request.
const bool first_request = [&]() -> bool {
// Perform an atomic compare-and-swap from false to true.
bool expected = false;
return m_termination_requested.compare_exchange_strong(expected, true);
}();
// Perform an atomic compare-and-swap from false to true.
bool expected = false;
// If this is the first request, start termination procedure.
if (first_request) {
if (m_termination_requested.compare_exchange_strong(expected, true)) {
// If the thread is in initialized state, just change state to terminated.
if (this->GetState() == ThreadState::Initialized) {
m_thread_state = ThreadState::Terminated;
return ThreadState::Terminated;
return m_thread_state = ThreadState::Terminated;
}
// Register the terminating dpc.
@@ -1281,7 +1276,6 @@ namespace Kernel {
m_wait_queue->CancelWait(kernel, this, ResultTerminationRequested, true);
}
}
return this->GetState();
}
+34 -5
View File
@@ -4,7 +4,9 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include "common/assert.h"
#include "common/thread.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_worker_task.h"
@@ -26,7 +28,33 @@ void KWorkerTask::DoWorkerTask(KernelCore& kernel) {
}
}
KWorkerTaskManager::KWorkerTaskManager() : m_waiting_thread(1, "KWorkerTaskManager") {}
KWorkerTaskManager::KWorkerTaskManager(KernelCore& kernel) {
m_waiting_thread = std::jthread([&kernel, this](std::stop_token stop_token) {
Common::SetCurrentThreadName("KWorkerManager");
while (!stop_token.stop_requested()) {
KWorkerTask* t;
{
std::unique_lock lk{m_task_mutex};
m_task_cv.wait(lk, m_waiting_thread.get_stop_token(), [&]() {
return !m_task_queue.empty();
});
if (stop_token.stop_requested())
break;
t = m_task_queue.back();
m_task_queue.pop_back();
}
t->DoWorkerTask(kernel);
}
});
}
KWorkerTaskManager::~KWorkerTaskManager() {
if (m_waiting_thread.joinable()) {
m_waiting_thread.request_stop();
m_task_cv.notify_one();
m_waiting_thread.join();
}
}
void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task) {
ASSERT(type <= WorkerType::Count);
@@ -35,10 +63,11 @@ void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTas
void KWorkerTaskManager::AddTask(KernelCore& kernel, KWorkerTask* task) {
KScopedSchedulerLock sl(kernel);
m_waiting_thread.QueueWork([&kernel, task]() {
// Do the task.
task->DoWorkerTask(kernel);
});
{
std::scoped_lock lk{m_task_mutex};
m_task_queue.emplace_back(task);
}
m_task_cv.notify_one();
}
} // namespace Kernel
+11 -4
View File
@@ -1,8 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <thread>
#include "common/common_types.h"
#include "common/thread_worker.h"
@@ -18,15 +23,17 @@ public:
Count,
};
KWorkerTaskManager();
KWorkerTaskManager(KernelCore& kernel);
~KWorkerTaskManager();
static void AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task);
private:
void AddTask(KernelCore& kernel, KWorkerTask* task);
std::jthread m_waiting_thread;
private:
Common::ThreadWorker m_waiting_thread;
std::mutex m_task_mutex;
std::condition_variable_any m_task_cv;
std::vector<KWorkerTask*> m_task_queue;
};
} // namespace Kernel
+17 -12
View File
@@ -80,7 +80,10 @@ struct KernelCore::Impl {
// so it will be statically given a TLS slot anyways.
static inline thread_local ThreadLocalData tls_data = {};
explicit Impl(Core::System& system_, KernelCore& kernel_) : system{system_} {
explicit Impl(Core::System& system_, KernelCore& kernel_)
: worker_task_manager{kernel_}
, system{system_}
{
tls_data.lock = true;
}
@@ -967,12 +970,16 @@ Kernel::KHardwareTimer& KernelCore::HardwareTimer() {
return *impl->hardware_timer;
}
KAutoObjectWithListContainer& KernelCore::ObjectListContainer() {
return *impl->global_object_list_container;
KAutoObjectWithListContainer* KernelCore::ObjectListContainer() {
if (!impl->global_object_list_container)
return nullptr;
return std::addressof(*impl->global_object_list_container);
}
const KAutoObjectWithListContainer& KernelCore::ObjectListContainer() const {
return *impl->global_object_list_container;
const KAutoObjectWithListContainer* KernelCore::ObjectListContainer() const {
if (!impl->global_object_list_container)
return nullptr;
return std::addressof(*impl->global_object_list_container);
}
void KernelCore::PrepareReschedule(std::size_t id) {
@@ -1001,16 +1008,10 @@ void KernelCore::UnregisterInUseObject(KAutoObject* object) {
void KernelCore::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
auto* manager = server_manager.get();
{
if (!impl->is_shutting_down) {
std::scoped_lock lk{impl->server_lock};
if (impl->is_shutting_down) {
return;
}
impl->server_managers.emplace_back(std::move(server_manager));
}
manager->LoopProcess();
}
@@ -1255,6 +1256,10 @@ void KernelCore::SuspendEmulation(bool suspended) {
}
void KernelCore::ShutdownCores() {
// Notify shutdown (pre-emptively)
for (auto& sm : impl->server_managers) {
sm->NotifyShutdown();
}
impl->TerminateAllProcesses();
KScopedSchedulerLock lk{*this};
for (auto* thread : impl->shutdown_threads)
+2 -3
View File
@@ -175,9 +175,8 @@ public:
/// Stops execution of 'id' core, in order to reschedule a new thread.
void PrepareReschedule(std::size_t id);
KAutoObjectWithListContainer& ObjectListContainer();
const KAutoObjectWithListContainer& ObjectListContainer() const;
KAutoObjectWithListContainer* ObjectListContainer();
const KAutoObjectWithListContainer* ObjectListContainer() const;
/// Registers all kernel objects with the global emulation state, this is purely for tracking
/// leaks after emulation has been shutdown.
+4 -3
View File
@@ -151,7 +151,8 @@ public:
const bool is_initialized = this->IsInitialized();
uintptr_t arg = 0;
if (is_initialized) {
kernel.ObjectListContainer().Unregister(this);
if (auto const olc = kernel.ObjectListContainer(); olc)
olc->Unregister(this);
arg = this->GetPostDestroyArgument();
this->Finalize(kernel);
}
@@ -175,7 +176,7 @@ public:
public:
static void InitializeSlabHeap(KernelCore& kernel, void* memory, size_t memory_size) {
kernel.SlabHeap<Derived>().Initialize(memory, memory_size);
kernel.ObjectListContainer().Initialize();
kernel.ObjectListContainer()->Initialize();
}
static Derived* Create(KernelCore& kernel) {
@@ -186,7 +187,7 @@ public:
}
static void Register(KernelCore& kernel, Derived* obj) {
return kernel.ObjectListContainer().Register(obj);
return kernel.ObjectListContainer()->Register(obj);
}
static size_t GetObjectSize(KernelCore& kernel) {
+5 -6
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -15,13 +18,9 @@ void LoopProcess(Core::System& system) {
WindowSystem window_system(system);
ButtonPoller button_poller(system, window_system);
EventObserver event_observer(system, window_system);
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService(
"appletAE", std::make_shared<IAllSystemAppletProxiesService>(system, window_system));
server_manager->RegisterNamedService(
"appletOE", std::make_shared<IApplicationProxyService>(system, window_system));
server_manager->RegisterNamedService("appletAE", std::make_shared<IAllSystemAppletProxiesService>(system, window_system));
server_manager->RegisterNamedService("appletOE", std::make_shared<IApplicationProxyService>(system, window_system));
ServerManager::RunServer(std::move(server_manager));
}
+9 -8
View File
@@ -26,24 +26,25 @@ EventObserver::EventObserver(Core::System& system, WindowSystem& window_system)
m_window_system.SetEventObserver(this);
m_wakeup_holder.SetUserData(static_cast<uintptr_t>(UserDataTag::WakeupEvent));
m_wakeup_holder.LinkToMultiWait(std::addressof(m_multi_wait));
m_thread = std::jthread([this](std::stop_token stop_token) {
Common::SetCurrentThreadName("am:EventObserver");
system.Kernel().RunOnGuestCoreProcess("am:EventObserver", [this]() {
auto const stop_token = m_stop_source.get_token();
while (!stop_token.stop_requested()) {
auto* signaled_holder = this->WaitSignaled(stop_token);
if (!signaled_holder)
if (stop_token.stop_requested() || !signaled_holder)
break;
this->Process(signaled_holder);
}
m_process_cv.notify_one();
});
}
EventObserver::~EventObserver() {
// Signal thread and wait for processing to finish.
if (m_thread.joinable()) {
// Signal thread and wait for processing to finish.
m_thread.request_stop();
m_wakeup_event.Signal(m_system.Kernel());
m_thread.join();
m_stop_source.request_stop();
m_wakeup_event.Signal(m_system.Kernel());
{
std::unique_lock lk{m_process_mutex};
m_process_cv.wait(lk);
}
// Free remaining owned sessions.
+5 -1
View File
@@ -6,6 +6,8 @@
#pragma once
#include <condition_variable>
#include <stop_token>
#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "core/hle/service/kernel_helpers.h"
@@ -62,7 +64,9 @@ private:
MultiWait m_deferred_wait_list;
// Processing thread.
std::jthread m_thread{};
std::stop_source m_stop_source{};
std::mutex m_process_mutex;
std::condition_variable m_process_cv{};
};
} // namespace Service::AM
+2 -3
View File
@@ -250,10 +250,9 @@ std::shared_ptr<FrontendApplet> FrontendAppletHolder::GetApplet(std::shared_ptr<
return std::make_shared<PhotoViewer>(system, applet, mode, *frontend.photo_viewer);
case AppletId::NetConnect:
return std::make_shared<NetConnect>(system, applet, mode, *frontend.net_connect);
case AppletId::None:
return nullptr;
default:
UNIMPLEMENTED_MSG("No frontend implementation exists for applet_id={:02X} program_id={:016X}! Using stub applet.", static_cast<u8>(id), applet->program_id);
LOG_ERROR(Service_AM, "No backend implementation exists for applet_id={:02X} program_id={:016X}"
"Falling back to stub applet", static_cast<u8>(id), applet->program_id);
return std::make_shared<StubApplet>(system, applet, id, mode);
}
}
+4 -5
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 2023 yuzu Emulator Project
@@ -90,10 +90,9 @@ static Service::PSC::Time::LocationName GetTimeZoneString(
}
TimeManager::TimeManager(Core::System& system)
: m_steady_clock_resource{system}, m_time_zone_binary{system}, m_worker{
system,
m_steady_clock_resource,
m_file_timestamp_worker} {
: m_steady_clock_resource{system}, m_time_zone_binary{system}
, m_worker{system, m_steady_clock_resource, m_file_timestamp_worker}
{
m_time_m =
system.ServiceManager().GetService<Service::PSC::Time::ServiceManager>("time:m", true);
+151 -151
View File
@@ -20,15 +20,17 @@
namespace Service::Glue::Time {
TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource,
FileTimestampWorker& file_timestamp_worker)
: m_system{system}, m_ctx{m_system, "Glue:TimeWorker"}, m_event{m_ctx.CreateEvent(
"Glue:TimeWorker:Event")},
m_steady_clock_resource{steady_clock_resource},
m_file_timestamp_worker{file_timestamp_worker}, m_timer_steady_clock{m_ctx.CreateEvent(
"Glue:TimeWorker:SteadyClockTimerEvent")},
m_timer_file_system{m_ctx.CreateEvent("Glue:TimeWorker:FileTimeTimerEvent")},
m_alarm_worker{m_system, m_steady_clock_resource}, m_pm_state_change_handler{m_alarm_worker} {
TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource, FileTimestampWorker& file_timestamp_worker)
: m_system{system}
, m_ctx{m_system, "Glue:TimeWorker"}
, m_event{m_ctx.CreateEvent("Glue:TimeWorker:Event")}
, m_steady_clock_resource{steady_clock_resource}
, m_file_timestamp_worker{file_timestamp_worker}
, m_timer_steady_clock{m_ctx.CreateEvent("Glue:TimeWorker:SteadyClockTimerEvent")}
, m_timer_file_system{m_ctx.CreateEvent("Glue:TimeWorker:FileTimeTimerEvent")}
, m_alarm_worker{m_system, m_steady_clock_resource}
, m_pm_state_change_handler{m_alarm_worker}
{
m_timer_steady_clock_timing_event = Core::Timing::CreateEvent(
"Time::SteadyClockEvent",
[this](s64 time,
@@ -47,14 +49,16 @@ TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady
}
TimeWorker::~TimeWorker() {
// Wait for processing to stop
m_stop_source.request_stop();
m_local_clock_event->Signal(m_system.Kernel());
m_network_clock_event->Signal(m_system.Kernel());
m_ephemeral_clock_event->Signal(m_system.Kernel());
std::this_thread::sleep_for(std::chrono::milliseconds(16));
m_thread.request_stop();
m_event->Signal(m_system.Kernel());
m_thread.join();
{
std::unique_lock lk{m_process_mutex};
m_process_cv.wait(lk);
}
m_ctx.CloseEvent(m_event);
m_system.CoreTiming().UnscheduleEvent(m_timer_steady_clock_timing_event);
@@ -122,166 +126,162 @@ void TimeWorker::Initialize(std::shared_ptr<Service::PSC::Time::StaticService> t
}
void TimeWorker::StartThread() {
m_thread = std::jthread(std::bind_front(&TimeWorker::ThreadFunc, this));
}
void TimeWorker::ThreadFunc(std::stop_token stop_token) {
Common::SetCurrentThreadName("TimeWorker");
Common::SetCurrentThreadPriority(Common::ThreadPriority::Low);
while (!stop_token.stop_requested()) {
enum class EventType : s32 {
Exit = 0,
PowerStateChange = 1,
SignalAlarms = 2,
UpdateLocalSystemClock = 3,
UpdateNetworkSystemClock = 4,
UpdateEphemeralSystemClock = 5,
UpdateSteadyClock = 6,
UpdateFileTimestamp = 7,
AutoCorrect = 8,
};
s32 index{};
if (m_pm_state_change_handler.m_priority != 0) {
// TODO: gIPmModuleService::GetEvent() 1
index = WaitAny(m_system.Kernel(),
&m_event->GetReadableEvent(), // 0
&m_alarm_worker.GetEvent() // 1
);
} else {
// TODO: gIPmModuleService::GetEvent() 1
index = WaitAny(m_system.Kernel(),
&m_event->GetReadableEvent(), // 0
&m_alarm_worker.GetEvent(), // 1
&m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2
m_local_clock_event, // 3
m_network_clock_event, // 4
m_ephemeral_clock_event, // 5
&m_timer_steady_clock->GetReadableEvent(), // 6
&m_timer_file_system->GetReadableEvent(), // 7
m_standard_user_auto_correct_clock_event // 8
);
}
switch (static_cast<EventType>(index)) {
case EventType::Exit:
return;
case EventType::PowerStateChange:
m_alarm_worker.GetEvent().Clear(m_system.Kernel());
if (m_pm_state_change_handler.m_priority <= 1) {
m_alarm_worker.OnPowerStateChanged();
m_system.Kernel().RunOnGuestCoreProcess("TimeWorker", [this]() {
auto const stop_token = m_stop_source.get_token();
while (!stop_token.stop_requested()) {
enum class EventType : s32 {
Exit = 0,
PowerStateChange = 1,
SignalAlarms = 2,
UpdateLocalSystemClock = 3,
UpdateNetworkSystemClock = 4,
UpdateEphemeralSystemClock = 5,
UpdateSteadyClock = 6,
UpdateFileTimestamp = 7,
AutoCorrect = 8,
};
s32 index{};
if (m_pm_state_change_handler.m_priority != 0) {
// TODO: gIPmModuleService::GetEvent() 1
index = WaitAny(
m_system.Kernel(),
&m_event->GetReadableEvent(), // 0
&m_alarm_worker.GetEvent() // 1
);
} else {
// TODO: gIPmModuleService::GetEvent() 1
index = WaitAny(
m_system.Kernel(),
&m_event->GetReadableEvent(), // 0
&m_alarm_worker.GetEvent(), // 1
&m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2
m_local_clock_event, // 3
m_network_clock_event, // 4
m_ephemeral_clock_event, // 5
&m_timer_steady_clock->GetReadableEvent(), // 6
&m_timer_file_system->GetReadableEvent(), // 7
m_standard_user_auto_correct_clock_event // 8
);
}
break;
case EventType::SignalAlarms:
m_alarm_worker.GetTimerEvent().Clear(m_system.Kernel());
m_time_m->CheckAndSignalAlarms();
break;
if (stop_token.stop_requested())
break;
case EventType::UpdateLocalSystemClock: {
m_local_clock_event->Clear(m_system.Kernel());
switch (EventType(index)) {
case EventType::Exit:
return;
Service::PSC::Time::SystemClockContext context{};
R_ASSERT(m_local_clock->GetSystemClockContext(&context));
case EventType::PowerStateChange:
m_alarm_worker.GetEvent().Clear(m_system.Kernel());
if (m_pm_state_change_handler.m_priority <= 1) {
m_alarm_worker.OnPowerStateChanged();
}
break;
m_set_sys->SetUserSystemClockContext(context);
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
}
case EventType::SignalAlarms:
m_alarm_worker.GetTimerEvent().Clear(m_system.Kernel());
m_time_m->CheckAndSignalAlarms();
break;
case EventType::UpdateNetworkSystemClock: {
m_network_clock_event->Clear(m_system.Kernel());
case EventType::UpdateLocalSystemClock: {
m_local_clock_event->Clear(m_system.Kernel());
Service::PSC::Time::SystemClockContext context{};
R_ASSERT(m_network_clock->GetSystemClockContext(&context));
Service::PSC::Time::SystemClockContext context{};
R_ASSERT(m_local_clock->GetSystemClockContext(&context));
m_set_sys->SetNetworkSystemClockContext(context);
s64 time{};
if (m_network_clock->GetCurrentTime(&time) != ResultSuccess) {
m_set_sys->SetUserSystemClockContext(context);
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
}
[[maybe_unused]] auto offset_before{
m_ig_report_network_clock_context_set ? m_report_network_clock_context.offset : 0};
// TODO system report "standard_netclock_operation"
// "clock_time" = time
// "context_offset_before" = offset_before
// "context_offset_after" = context.offset
m_report_network_clock_context = context;
if (!m_ig_report_network_clock_context_set) {
m_ig_report_network_clock_context_set = true;
}
case EventType::UpdateNetworkSystemClock: {
m_network_clock_event->Clear(m_system.Kernel());
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
}
Service::PSC::Time::SystemClockContext context{};
R_ASSERT(m_network_clock->GetSystemClockContext(&context));
case EventType::UpdateEphemeralSystemClock: {
m_ephemeral_clock_event->Clear(m_system.Kernel());
m_set_sys->SetNetworkSystemClockContext(context);
Service::PSC::Time::SystemClockContext context{};
auto res = m_ephemeral_clock->GetSystemClockContext(&context);
if (res != ResultSuccess) {
s64 time{};
if (m_network_clock->GetCurrentTime(&time) != ResultSuccess) {
break;
}
[[maybe_unused]] auto offset_before{
m_ig_report_network_clock_context_set ? m_report_network_clock_context.offset : 0};
// TODO system report "standard_netclock_operation"
// "clock_time" = time
// "context_offset_before" = offset_before
// "context_offset_after" = context.offset
m_report_network_clock_context = context;
if (!m_ig_report_network_clock_context_set) {
m_ig_report_network_clock_context_set = true;
}
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
}
s64 time{};
res = m_ephemeral_clock->GetCurrentTime(&time);
if (res != ResultSuccess) {
case EventType::UpdateEphemeralSystemClock: {
m_ephemeral_clock_event->Clear(m_system.Kernel());
Service::PSC::Time::SystemClockContext context{};
auto res = m_ephemeral_clock->GetSystemClockContext(&context);
if (res != ResultSuccess) {
break;
}
s64 time{};
res = m_ephemeral_clock->GetCurrentTime(&time);
if (res != ResultSuccess) {
break;
}
[[maybe_unused]] auto offset_before{m_ig_report_ephemeral_clock_context_set
? m_report_ephemeral_clock_context.offset
: 0};
// TODO system report "ephemeral_netclock_operation"
// "clock_time" = time
// "context_offset_before" = offset_before
// "context_offset_after" = context.offset
m_report_ephemeral_clock_context = context;
if (!m_ig_report_ephemeral_clock_context_set) {
m_ig_report_ephemeral_clock_context_set = true;
}
break;
}
[[maybe_unused]] auto offset_before{m_ig_report_ephemeral_clock_context_set
? m_report_ephemeral_clock_context.offset
: 0};
// TODO system report "ephemeral_netclock_operation"
// "clock_time" = time
// "context_offset_before" = offset_before
// "context_offset_after" = context.offset
m_report_ephemeral_clock_context = context;
if (!m_ig_report_ephemeral_clock_context_set) {
m_ig_report_ephemeral_clock_context_set = true;
case EventType::UpdateSteadyClock:
m_timer_steady_clock->Clear(m_system.Kernel());
m_steady_clock_resource.UpdateTime();
m_time_m->SetStandardSteadyClockBaseTime(m_steady_clock_resource.GetTime());
break;
case EventType::UpdateFileTimestamp:
m_timer_file_system->Clear(m_system.Kernel());
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
case EventType::AutoCorrect: {
m_standard_user_auto_correct_clock_event->Clear(m_system.Kernel());
bool automatic_correction{};
R_ASSERT(m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(&automatic_correction));
Service::PSC::Time::SteadyClockTimePoint time_point{};
R_ASSERT(m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point));
m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
break;
}
default:
UNREACHABLE();
}
break;
}
case EventType::UpdateSteadyClock:
m_timer_steady_clock->Clear(m_system.Kernel());
m_steady_clock_resource.UpdateTime();
m_time_m->SetStandardSteadyClockBaseTime(m_steady_clock_resource.GetTime());
break;
case EventType::UpdateFileTimestamp:
m_timer_file_system->Clear(m_system.Kernel());
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
case EventType::AutoCorrect: {
m_standard_user_auto_correct_clock_event->Clear(m_system.Kernel());
bool automatic_correction{};
R_ASSERT(m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(
&automatic_correction));
Service::PSC::Time::SteadyClockTimePoint time_point{};
R_ASSERT(
m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point));
m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
break;
}
default:
UNREACHABLE();
}
}
m_process_cv.notify_one();
});
}
} // namespace Service::Glue::Time
+8 -4
View File
@@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2026 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
#pragma once
#include <condition_variable>
#include "common/common_types.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/glue/time/alarm_worker.h"
@@ -33,17 +37,17 @@ public:
void StartThread();
private:
template <typename T>
T GetSettingsItemValue(const std::string& category, const std::string& name);
void ThreadFunc(std::stop_token stop_token);
Core::System& m_system;
KernelHelpers::ServiceContext m_ctx;
std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
std::jthread m_thread;
std::mutex m_process_mutex;
std::condition_variable m_process_cv;
std::stop_source m_stop_source;
Kernel::KEvent* m_event{};
std::shared_ptr<Service::PSC::Time::ServiceManager> m_time_m;
std::shared_ptr<Service::PSC::Time::StaticService> m_time_sm;
+2 -32
View File
@@ -16,10 +16,6 @@
#include "core/hle/service/ro/ro_types.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
#ifdef HAS_NCE
#include "core/arm/nce/patcher.h"
#include "core/hle/kernel/k_shared_memory.h"
#endif
namespace Service::RO {
@@ -390,7 +386,7 @@ public:
R_SUCCEED();
}
Result MapManualLoadModuleMemory(Kernel::KernelCore& kernel, u64* out_address, size_t context_id, u64 nro_address,
Result MapManualLoadModuleMemory(u64* out_address, size_t context_id, u64 nro_address,
u64 nro_size, u64 bss_address, u64 bss_size) {
// Get context.
ProcessContext* context = this->GetContextById(context_id);
@@ -425,33 +421,7 @@ public:
R_TRY(context->ValidateNro(std::addressof(nro_info->module_id), std::addressof(rx_size),
std::addressof(ro_size), std::addressof(rw_size),
nro_info->base_address, nro_size, bss_size));
#ifdef HAS_NCE
if (Settings::values.nce_runtime_nro_patch.GetValue()) {
if (Settings::IsNceEnabled()) {
auto* process = context->GetProcess();
auto& memory = process->GetMemory();
std::vector<u8> image(total_size);
memory.ReadBlock(nro_info->base_address, image.data(), rx_size);
Kernel::CodeSet::Segment code{.size = static_cast<u32>(rx_size)};
Core::NCE::Patcher patch;
patch.PatchText(image, code);
patch.RelocateAndCopy(nro_info->base_address, code, image, nullptr);
const u64 patch_address = nro_info->base_address + total_size;
const size_t patch_size = patch.GetSectionSize();
constexpr auto permission = Kernel::Svc::MemoryPermission::ReadExecute;
auto* patch_memory = Kernel::KSharedMemory::Create(kernel);
R_TRY(patch_memory->Initialize(kernel, kernel.System().DeviceMemory(), process, permission, permission, patch_size));
std::memcpy(patch_memory->GetPointer(), image.data() + total_size, patch_size);
R_TRY(process->AddSharedMemory(kernel, patch_memory, patch_address, patch_size));
R_TRY(patch_memory->Map(*process, patch_address, patch_size, permission));
memory.WriteBlock(nro_info->base_address, image.data(), rx_size);
}
}
#endif
// Set NRO perms.
R_TRY(SetNroPerms(context->GetProcess(), nro_info->base_address, rx_size, ro_size,
rw_size + bss_size));
@@ -561,7 +531,7 @@ public:
Result MapManualLoadModuleMemory(Out<u64> out_load_address, ClientProcessId client_pid,
u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) {
R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid));
R_RETURN(m_ro->MapManualLoadModuleMemory(system.Kernel(), out_load_address.Get(), m_context_id, nro_address,
R_RETURN(m_ro->MapManualLoadModuleMemory(out_load_address.Get(), m_context_id, nro_address,
nro_size, bss_address, bss_size));
}
+12 -3
View File
@@ -98,8 +98,10 @@ ServerManager::~ServerManager() {
// Signal stop.
m_stop_source.request_stop();
m_wakeup_event->Signal(m_system.Kernel());
if (m_deferral_event) m_deferral_event->Signal(m_system.Kernel()); //for sm: and ports
// Wait for processing to stop.
// Wait for processing to stop. Schedule
m_stopped.Wait();
m_threads.clear();
@@ -253,6 +255,14 @@ void ServerManager::StartAdditionalHostThreads(const char* name, size_t num_thre
}
}
/// @brief Notifies that the system is shutting down (pre-emptively terminate threads)
void ServerManager::NotifyShutdown() {
m_stop_source.request_stop();
// Wake them up regardless
m_wakeup_event->Signal(m_system.Kernel());
if (m_deferral_event) m_deferral_event->Signal(m_system.Kernel()); //for sm: and ports
}
Result ServerManager::LoopProcess() {
SCOPE_EXIT {
m_stopped.Set();
@@ -285,9 +295,8 @@ MultiWaitHolder* ServerManager::WaitSignaled() {
this->LinkDeferred();
// If we're done, return before we start waiting.
if (m_stop_source.stop_requested()) {
if (m_stop_source.stop_requested())
return nullptr;
}
auto* selected = m_multi_wait.WaitAny(m_system.Kernel());
if (selected == std::addressof(*m_wakeup_holder)) {
+4
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 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
@@ -49,6 +52,7 @@ public:
Result LoopProcess();
void StartAdditionalHostThreads(const char* name, size_t num_threads);
void NotifyShutdown();
static void RunServer(std::unique_ptr<ServerManager>&& server);
+1 -1
View File
@@ -117,7 +117,6 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
{"friends", &Friend::LoopProcess},
{"settings", &Set::LoopProcess},
{"psc", &PSC::LoopProcess},
{"glue", &Glue::LoopProcess},
{"grc", &GRC::LoopProcess},
{"hid", &HID::LoopProcess},
{"jit", &JIT::LoopProcess},
@@ -153,6 +152,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
{"usb", &USB::LoopProcess},
{"i2c", &I2C::LoopProcess},
{"gpio", &GPIO::LoopProcess},
{"glue", &Glue::LoopProcess},
})
kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); });
}
+4 -6
View File
@@ -305,16 +305,14 @@ SM::SM(ServiceManager& service_manager_, Core::System& system_)
SM::~SM() = default;
void LoopProcess(Core::System& system) {
auto& service_manager = system.ServiceManager();
auto server_manager = std::make_unique<ServerManager>(system);
Kernel::KEvent* deferral_event{};
server_manager->ManageDeferral(&deferral_event);
service_manager.SetDeferralEvent(deferral_event);
system.ServiceManager().SetDeferralEvent(deferral_event);
auto sm_service = std::make_shared<SM>(system.ServiceManager(), system);
server_manager->ManageNamedPort("sm:", [sm_service] { return sm_service; });
server_manager->ManageNamedPort("sm:", [sm_service] {
return sm_service;
});
ServerManager::RunServer(std::move(server_manager));
}
+23 -33
View File
@@ -11,6 +11,7 @@
#include "common/thread.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/vi/conductor.h"
#include "core/hle/service/vi/container.h"
#include "core/hle/service/vi/display_list.h"
@@ -29,24 +30,29 @@ Conductor::Conductor(Core::System& system, Container& container, DisplayList& di
});
if (system.IsMulticore()) {
m_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
m_signal.Set();
return std::chrono::nanoseconds(this->GetNextTicks());
});
m_event = Core::Timing::CreateEvent("ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
m_signal.Set();
return std::chrono::nanoseconds(this->GetNextTicks());
});
system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
m_thread = std::jthread([this](std::stop_token token) { this->VsyncThread(token); });
} else {
m_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
m_thread = system.Kernel().RunOnHostCoreThread("VSyncThread", [this]() {
auto const stop_token = m_thread.get_stop_token();
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
Common::SetCurrentThreadToPerformanceCores();
while (!stop_token.stop_requested()) {
m_signal.Wait();
if (stop_token.stop_requested() || m_system.IsShuttingDown()) {
break;
}
this->ProcessVsync();
return std::chrono::nanoseconds(this->GetNextTicks());
});
}
});
} else {
m_event = Core::Timing::CreateEvent("ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
this->ProcessVsync();
return std::chrono::nanoseconds(this->GetNextTicks());
});
system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
}
@@ -54,10 +60,10 @@ Conductor::Conductor(Core::System& system, Container& container, DisplayList& di
Conductor::~Conductor() {
m_system.CoreTiming().UnscheduleEvent(m_event);
if (m_system.IsMulticore()) {
if (m_thread.joinable()) {
m_thread.request_stop();
m_signal.Set();
m_thread.join();
}
}
@@ -83,22 +89,6 @@ void Conductor::ProcessVsync() {
}
}
void Conductor::VsyncThread(std::stop_token token) {
Common::SetCurrentThreadName("VSyncThread");
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
Common::SetCurrentThreadToPerformanceCores();
while (!token.stop_requested()) {
m_signal.Wait();
if (m_system.IsShuttingDown()) {
return;
}
this->ProcessVsync();
}
}
s64 Conductor::GetNextTicks() const {
const auto& settings = Settings::values;
auto speed_scale = 1.f;
+1 -1
View File
@@ -757,7 +757,7 @@ struct Memory::Impl {
};
auto& gpu = system.GPU();
gpu_device_memory->ApplyOpOnPointer(
p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size, true); });
p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size); });
}
Core::System& system;
+14
View File
@@ -1,6 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2026 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
// gcc14 bug
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#include "common/assert.h"
#include "common/scope_exit.h"
#include "core/memory/dmnt_cheat_types.h"
@@ -1266,3 +1275,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
}
} // namespace Core::Memory
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
+11 -9
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 2019 yuzu Emulator Project
@@ -254,14 +254,16 @@ struct UnrecognizedInstruction {
struct CheatVmOpcode {
bool begin_conditional_block{};
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction>
opcode{};
std::variant<
std::monostate,
StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction
> opcode{};
};
class DmntCheatVm {
@@ -170,13 +170,19 @@ template<>
// check for marked bit, use as unmapped if marked
if (ctx.conf.page_table_marked_bit) {
code.bt(page, *ctx.conf.page_table_marked_bit);
code.jc(abort, code.T_NEAR);
auto const marked_bit = *ctx.conf.page_table_marked_bit;
if (s64(s32(1 << marked_bit)) == s64(1 << marked_bit)) {
code.test(page, s32(1 << marked_bit));
code.jnz(abort, code.T_NEAR);
} else {
code.bt(page, marked_bit);
code.jc(abort, code.T_NEAR);
}
}
// mask away attributes
if (ctx.conf.page_table_pointer_mask == 0) {
code.test(page, page);
} else if (std::in_range<s32>(ctx.conf.page_table_pointer_mask)) {
} else if (s64(s32(ctx.conf.page_table_pointer_mask)) == s64(ctx.conf.page_table_pointer_mask)) {
code.and_(page, ctx.conf.page_table_pointer_mask);
} else {
code.mov(tmp, ctx.conf.page_table_pointer_mask);
@@ -79,8 +79,10 @@ void NpadAbstractButtonHandler::UpdateAllButtonLifo() {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
for (std::size_t i = 0; i < AruidIndexMax; i++) {
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
if (auto const shfmt = data->shared_memory_format; shfmt) {
auto& npad_entry = shfmt->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
}
}
}
@@ -88,19 +90,19 @@ void NpadAbstractButtonHandler::UpdateCoreBatteryState() {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
for (std::size_t i = 0; i < AruidIndexMax; i++) {
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
if (auto const shfmt = data->shared_memory_format; shfmt) {
auto& npad_entry = shfmt->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
}
}
}
void NpadAbstractButtonHandler::UpdateButtonState(u64 aruid) {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid);
if (data == nullptr) {
return;
if (auto data = applet_resource_holder->applet_resource->GetAruidData(aruid); data) {
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, aruid);
}
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, aruid);
}
Result NpadAbstractButtonHandler::SetHomeProtection(bool is_enabled, u64 aruid) {
+3 -9
View File
@@ -194,14 +194,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
INSERT(Settings, skip_cpu_inner_invalidation, tr("Skip CPU Inner Invalidation"),
tr("Skips certain cache invalidations during memory updates, reducing CPU usage and "
"improving latency. This may cause soft-crashes."));
INSERT(Settings, nce_invalidation_gpu_readback,
tr("GPU Readback Before NCE Invalidation"),
tr("Flushes GPU buffer data back to guest memory before NCE invalidates the whole "
"guest page, preserving it while the CPU store proceeds."));
INSERT(Settings, nce_runtime_nro_patch,
tr("Patch Runtime NROs for NCE"),
tr("Applies NCE code patches to runtime-loaded NRO modules."
"This is required for some games to run correctly."));
INSERT(Settings, vsync_mode, tr("VSync Mode:"),
tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
"refresh rate.\nFIFO Relaxed allows tearing as it recovers from a slow down.\n"
@@ -350,6 +342,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
"it bypasses such prompts and directly exits the emulation."));
INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"),
tr("Hides the mouse after 2.5s of inactivity."));
INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"),
tr("Forcibly disables the use of the controller applet in emulated programs.\n"
"When a program attempts to open the controller applet, it is immediately closed."));
INSERT(UISettings, check_for_updates, tr("Check for updates"),
tr("Whether or not to check for updates upon startup."));
@@ -385,7 +380,6 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
{
PAIR(AppletMode, HLE, tr("Custom frontend")),
PAIR(AppletMode, LLE, tr("Real applet")),
PAIR(AppletMode, Disabled, tr("Disabled")),
}});
translations->insert({Settings::EnumMetadata<Settings::SpirvOptimizeMode>::Index(),
+2
View File
@@ -127,6 +127,8 @@ struct Values {
Setting<bool> hide_mouse{
linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
true, true};
Setting<bool> controller_applet_disabled{linkage, false, "disableControllerApplet",
Category::UiGeneral};
// Set when Vulkan is known to crash the application
bool has_broken_vulkan = false;
+5 -2
View File
@@ -66,14 +66,17 @@ void EmuThread::run() {
} else {
QtCommon::system->Pause();
m_stopped.Set();
EmulationPaused(lk);
m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; });
m_should_run_cv.wait(lk, stop_token, [&] {
return m_should_run;
});
EmulationResumed(lk);
}
}
// Shutdown the main emulated process
// Needs to pause first, we may not have paused before shutting down.
QtCommon::system->Pause();
QtCommon::system->DetachDebugger();
QtCommon::system->ShutdownMainProcess();
}
-1
View File
@@ -172,7 +172,6 @@ add_library(shader_recompiler STATIC
ir_opt/constant_propagation_pass.cpp
ir_opt/dead_code_elimination_pass.cpp
ir_opt/dual_vertex_pass.cpp
ir_opt/geometry_compaction_pass.cpp
ir_opt/global_memory_to_storage_buffer_pass.cpp
ir_opt/identity_removal_pass.cpp
ir_opt/layer_pass.cpp
@@ -4,7 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <span>
#include <tuple>
#include <type_traits>
@@ -440,13 +439,10 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
ctx.AddCapability(spv::Capability::DrawParameters);
}
if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id ||
info.uses_subgroup_shuffles || info.uses_subgroup_mask) &&
info.uses_subgroup_shuffles) &&
profile.support_vote && profile.SupportsSubgroupStage(ctx.stage)) {
ctx.AddCapability(spv::Capability::GroupNonUniformBallot);
ctx.AddCapability(spv::Capability::GroupNonUniformShuffle);
if (info.uses_subgroup_shuffles && profile.support_shuffle_relative) {
ctx.AddCapability(spv::Capability::GroupNonUniformShuffleRelative);
}
if (!profile.warp_size_potentially_larger_than_guest) {
// vote ops are only used when not taking the long path
ctx.AddCapability(spv::Capability::GroupNonUniformVote);
@@ -500,53 +496,29 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
}
void PatchPhiNodes(IR::Program& program, EmitContext& ctx) {
// Flatten all leading PHIs from each block into a vector
std::vector<IR::Inst*> phi_instructions;
for (IR::Block* block : program.blocks) {
for (auto it = block->begin(); it != block->end(); ++it) {
if (it->GetOpcode() != IR::Opcode::Phi)
break;
phi_instructions.push_back(&*it);
}
}
if (phi_instructions.empty()) {
return; // nothing to patch
}
// Start "before" first PHI; advance on phi_arg == 0
size_t phi_index = static_cast<size_t>(-1);
ctx.PatchDeferredPhi([&](size_t phi_arg, Id parent) -> std::pair<Id, Id> {
if (phi_arg == 0) {
++phi_index;
}
IR::Inst* phi = phi_instructions[phi_index];
return { ctx.Def(phi->Arg(phi_arg)), parent };
});
// Flatten all leading PHIs from each block into a vector
std::vector<IR::Inst*> phi_instructions;
for (IR::Block* block : program.blocks) {
for (auto it = block->begin(); it != block->end(); ++it) {
if (it->GetOpcode() != IR::Opcode::Phi)
break;
phi_instructions.push_back(&*it);
}
void RewriteOpcodes(std::vector<u32>& code, std::span<const std::pair<u32, spv::Op>> rewrites) {
if (rewrites.empty()) {
return;
}
size_t offset = 5;
while (offset + 2 < code.size()) {
const u32 word_count{code[offset] >> 16};
const auto opcode{static_cast<spv::Op>(code[offset] & 0xFFFFu)};
if (word_count == 0) {
return;
}
if (opcode == spv::Op::OpGroupNonUniformShuffleXor ||
opcode == spv::Op::OpGroupNonUniformQuadBroadcast) {
const auto it{std::ranges::find(rewrites, code[offset + 2],
&std::pair<u32, spv::Op>::first)};
if (it != rewrites.end()) {
code[offset] = (code[offset] & 0xFFFF0000u) | static_cast<u32>(it->second);
}
}
offset += word_count;
if (phi_instructions.empty()) {
return; // nothing to patch
}
// Start "before" first PHI; advance on phi_arg == 0
size_t phi_index = size_t(-1);
ctx.PatchDeferredPhi([&ctx, &phi_index, phi_insts = std::move(phi_instructions)](size_t phi_arg, Id parent) -> std::pair<Id, Id> {
if (phi_arg == 0) {
++phi_index;
}
IR::Inst* phi = phi_insts[phi_index];
return { ctx.Def(phi->Arg(phi_arg)), parent };
});
}
} // Anonymous namespace
@@ -562,9 +534,7 @@ std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_in
SetupCapabilities(profile, program.info, ctx);
SetupTransformFeedbackCapabilities(ctx, main);
PatchPhiNodes(program, ctx);
std::vector<u32> code{ctx.Assemble()};
RewriteOpcodes(code, ctx.opcode_rewrites);
return code;
return ctx.Assemble();
}
Id EmitPhi(EmitContext& ctx, IR::Inst* inst) {
@@ -77,57 +77,20 @@ Id GetMaxThreadId(EmitContext& ctx, Id thread_id, Id clamp, Id segmentation_mask
return ComputeMaxThreadId(ctx, min_thread_id, clamp, not_seg_mask);
}
Id HostThreadId(EmitContext& ctx, Id thread_id) {
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return thread_id;
Id SelectValue(EmitContext& ctx, Id in_range, Id value, Id src_thread_id) {
if (!StageSupportsSubgroups(ctx)) {
return value;
}
return ctx.OpSelect(
ctx.U32[1], in_range,
ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id), value);
}
Id AddPartitionBase(EmitContext& ctx, Id thread_id) {
const Id partition_idx{ctx.OpShiftRightLogical(ctx.U32[1], GetThreadId(ctx), ctx.Const(5u))};
const Id partition_base{ctx.OpShiftLeftLogical(ctx.U32[1], partition_idx, ctx.Const(5u))};
return ctx.OpIAdd(ctx.U32[1], thread_id, partition_base);
}
Id GuestLane(EmitContext& ctx, Id index) {
return ctx.OpBitwiseAnd(ctx.U32[1], index, ctx.Const(31U));
}
Id ShuffleAbsolute(EmitContext& ctx, Id value, Id src_thread_id) {
if (!ctx.profile.has_broken_spirv_subgroup_shuffle) {
return ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id);
}
Id result{ctx.u32_zero_value};
for (u32 lane = 0; lane < ctx.profile.max_subgroup_size; ++lane) {
const Id read{
ctx.OpGroupNonUniformBroadcast(ctx.U32[1], SubgroupScope(ctx), value, ctx.Const(lane))};
const Id matches{ctx.OpIEqual(ctx.U1, src_thread_id, ctx.Const(lane))};
result = ctx.OpSelect(ctx.U32[1], matches, read, result);
}
return result;
}
Id ShuffleRelative(EmitContext& ctx, Id value, Id delta, Id src_thread_id, spv::Op op) {
if (!ctx.profile.support_shuffle_relative) {
return ShuffleAbsolute(ctx, value, HostThreadId(ctx, src_thread_id));
}
const Id result{ctx.OpGroupNonUniformShuffleXor(ctx.U32[1], SubgroupScope(ctx), value, delta)};
ctx.opcode_rewrites.emplace_back(result.value, op);
return result;
}
Id BroadcastLane(EmitContext& ctx, Id value, u32 lane) {
Id result{
ctx.OpGroupNonUniformBroadcast(ctx.U32[1], SubgroupScope(ctx), value, ctx.Const(lane))};
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return result;
}
const Id partition_idx{ctx.OpShiftRightLogical(ctx.U32[1], GetThreadId(ctx), ctx.Const(5u))};
for (u32 base = 32; base < ctx.profile.max_subgroup_size; base += 32) {
const Id read{ctx.OpGroupNonUniformBroadcast(ctx.U32[1], SubgroupScope(ctx), value,
ctx.Const(base + lane))};
const Id matches{ctx.OpIEqual(ctx.U1, partition_idx, ctx.Const(base >> 5))};
result = ctx.OpSelect(ctx.U32[1], matches, read, result);
}
return result;
}
} // Anonymous namespace
Id EmitLaneId(EmitContext& ctx) {
@@ -240,75 +203,61 @@ Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id cla
const Id min_thread_id{ComputeMinThreadId(ctx, thread_id, segmentation_mask)};
const Id max_thread_id{ComputeMaxThreadId(ctx, min_thread_id, clamp, not_seg_mask)};
const Id lhs{ctx.OpBitwiseAnd(ctx.U32[1], GuestLane(ctx, index), not_seg_mask)};
const Id src_thread_id{ctx.OpBitwiseOr(ctx.U32[1], lhs, min_thread_id)};
const Id lhs{ctx.OpBitwiseAnd(ctx.U32[1], index, not_seg_mask)};
Id src_thread_id{ctx.OpBitwiseOr(ctx.U32[1], lhs, min_thread_id)};
const Id in_range{ctx.OpSLessThanEqual(ctx.U1, src_thread_id, max_thread_id)};
if (ctx.profile.warp_size_potentially_larger_than_guest) {
src_thread_id = AddPartitionBase(ctx, src_thread_id);
}
SetInBoundsFlag(inst, in_range);
if (!StageSupportsSubgroups(ctx)) {
return value;
}
const IR::Value lane{inst->Arg(1).Resolve()};
const IR::Value segment{inst->Arg(3).Resolve()};
if (lane.IsImmediate() && segment.IsImmediate() && segment.U32() == 0) {
return ctx.OpSelect(ctx.U32[1], in_range, BroadcastLane(ctx, value, lane.U32() & 31),
value);
}
const Id shuffled{ShuffleAbsolute(ctx, value, HostThreadId(ctx, src_thread_id))};
return ctx.OpSelect(ctx.U32[1], in_range, shuffled, value);
return SelectValue(ctx, in_range, value, src_thread_id);
}
Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
if (!StageSupportsSubgroups(ctx)) {
SetInBoundsFlag(inst, ctx.false_value);
return value;
}
const Id delta{GuestLane(ctx, index)};
const Id thread_id{EmitLaneId(ctx)};
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
const Id src_thread_id{ctx.OpISub(ctx.U32[1], thread_id, delta)};
Id src_thread_id{ctx.OpISub(ctx.U32[1], thread_id, index)};
const Id in_range{ctx.OpSGreaterThanEqual(ctx.U1, src_thread_id, max_thread_id)};
if (ctx.profile.warp_size_potentially_larger_than_guest) {
src_thread_id = AddPartitionBase(ctx, src_thread_id);
}
SetInBoundsFlag(inst, in_range);
const Id shuffled{ShuffleRelative(ctx, value, delta, src_thread_id,
spv::Op::OpGroupNonUniformShuffleUp)};
return ctx.OpSelect(ctx.U32[1], in_range, shuffled, value);
return SelectValue(ctx, in_range, value, src_thread_id);
}
Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
if (!StageSupportsSubgroups(ctx)) {
SetInBoundsFlag(inst, ctx.false_value);
return value;
}
const Id delta{GuestLane(ctx, index)};
const Id thread_id{EmitLaneId(ctx)};
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
const Id src_thread_id{ctx.OpIAdd(ctx.U32[1], thread_id, delta)};
Id src_thread_id{ctx.OpIAdd(ctx.U32[1], thread_id, index)};
const Id in_range{ctx.OpSLessThanEqual(ctx.U1, src_thread_id, max_thread_id)};
if (ctx.profile.warp_size_potentially_larger_than_guest) {
src_thread_id = AddPartitionBase(ctx, src_thread_id);
}
SetInBoundsFlag(inst, in_range);
const Id shuffled{ShuffleRelative(ctx, value, delta, src_thread_id,
spv::Op::OpGroupNonUniformShuffleDown)};
return ctx.OpSelect(ctx.U32[1], in_range, shuffled, value);
return SelectValue(ctx, in_range, value, src_thread_id);
}
Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
if (!StageSupportsSubgroups(ctx)) {
SetInBoundsFlag(inst, ctx.false_value);
return value;
}
const Id mask{GuestLane(ctx, index)};
const Id thread_id{EmitLaneId(ctx)};
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
const Id src_thread_id{ctx.OpBitwiseXor(ctx.U32[1], thread_id, mask)};
Id src_thread_id{ctx.OpBitwiseXor(ctx.U32[1], thread_id, index)};
const Id in_range{ctx.OpSLessThanEqual(ctx.U1, src_thread_id, max_thread_id)};
if (ctx.profile.warp_size_potentially_larger_than_guest) {
src_thread_id = AddPartitionBase(ctx, src_thread_id);
}
SetInBoundsFlag(inst, in_range);
const Id shuffled{ctx.OpGroupNonUniformShuffleXor(ctx.U32[1], SubgroupScope(ctx), value, mask)};
return ctx.OpSelect(ctx.U32[1], in_range, shuffled, value);
return SelectValue(ctx, in_range, value, src_thread_id);
}
Id EmitQuadBroadcast(EmitContext& ctx, Id value, Id lane) {
@@ -318,16 +267,10 @@ Id EmitQuadBroadcast(EmitContext& ctx, Id value, Id lane) {
const Id base{ctx.OpBitwiseAnd(ctx.U32[1], GetThreadId(ctx), ctx.Const(~3u))};
const Id local_lane{ctx.OpBitwiseAnd(ctx.U32[1], lane, ctx.Const(3u))};
const Id src_thread_id{ctx.OpBitwiseOr(ctx.U32[1], base, local_lane)};
return ShuffleAbsolute(ctx, value, src_thread_id);
return ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id);
}
Id EmitQuadSwap(EmitContext& ctx, Id value, Id direction) {
if (ctx.profile.support_quad_shuffles) {
const Id result{
ctx.OpGroupNonUniformQuadBroadcast(ctx.U32[1], SubgroupScope(ctx), value, direction)};
ctx.opcode_rewrites.emplace_back(result.value, spv::Op::OpGroupNonUniformQuadSwap);
return result;
}
const Id xor_mask{ctx.OpIAdd(ctx.U32[1], direction, ctx.Const(1u))};
return ctx.OpGroupNonUniformShuffleXor(ctx.U32[1], SubgroupScope(ctx), value, xor_mask);
}
@@ -361,7 +361,6 @@ public:
Id frag_depth{};
std::vector<Id> interfaces;
std::vector<std::pair<u32, spv::Op>> opcode_rewrites;
Id load_const_func_u8{};
Id load_const_func_u16{};
@@ -5,10 +5,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <bitset>
#include <memory>
#include <optional>
#include <vector>
#include <queue>
@@ -175,17 +172,7 @@ std::map<IR::Attribute, IR::Attribute> GenerateLegacyToGenericMappings(
void EmitGeometryPassthrough(IR::IREmitter& ir, const IR::Program& program,
const Shader::VaryingState& passthrough_mask,
bool passthrough_position,
std::optional<IR::Attribute> passthrough_layer_attr,
std::optional<IR::Reg> viewport_mask_reg) {
constexpr std::array CULLED_POSITION{2.0f, 2.0f, 2.0f, 1.0f};
IR::U1 culled{ir.Imm1(false)};
IR::U32 viewport{ir.Imm32(0)};
if (viewport_mask_reg) {
const IR::U32 mask{ir.GetReg(*viewport_mask_reg)};
const IR::U32 lowest_bit{ir.BitwiseAnd(mask, IR::U32{ir.INeg(mask)})};
culled = ir.IEqual(mask, ir.Imm32(0));
viewport = IR::U32{ir.Select(culled, ir.Imm32(0), ir.FindUMsb(lowest_bit))};
}
std::optional<IR::Attribute> passthrough_layer_attr) {
for (u32 i = 0; i < program.output_vertices; i++) {
// Assign generics from input
for (u32 j = 0; j < 32; j++) {
@@ -203,19 +190,10 @@ void EmitGeometryPassthrough(IR::IREmitter& ir, const IR::Program& program,
if (passthrough_position) {
// Assign position from input
const IR::Attribute attr = IR::Attribute::PositionX;
for (u32 component = 0; component < 4; ++component) {
IR::F32 value{ir.GetAttribute(attr + component, ir.Imm32(i))};
if (viewport_mask_reg) {
value = IR::F32{
ir.Select(culled, ir.Imm32(CULLED_POSITION[component]), value)};
}
ir.SetAttribute(attr + component, value, ir.Imm32(0));
}
}
if (viewport_mask_reg) {
ir.SetAttribute(IR::Attribute::ViewportIndex, ir.BitCast<IR::F32>(viewport),
ir.Imm32(0));
ir.SetAttribute(attr + 0, ir.GetAttribute(attr + 0, ir.Imm32(i)), ir.Imm32(0));
ir.SetAttribute(attr + 1, ir.GetAttribute(attr + 1, ir.Imm32(i)), ir.Imm32(0));
ir.SetAttribute(attr + 2, ir.GetAttribute(attr + 2, ir.Imm32(i)), ir.Imm32(0));
ir.SetAttribute(attr + 3, ir.GetAttribute(attr + 3, ir.Imm32(i)), ir.Imm32(0));
}
if (passthrough_layer_attr) {
@@ -241,91 +219,19 @@ u32 GetOutputTopologyVertices(OutputTopology output_topology) {
}
}
std::optional<IR::Reg> FindFreeRegister(const IR::Program& program) {
std::bitset<IR::NUM_REGS> used;
for (IR::Block* const block : program.blocks) {
for (const IR::Inst& inst : block->Instructions()) {
const IR::Opcode opcode{inst.GetOpcode()};
if (opcode == IR::Opcode::GetRegister || opcode == IR::Opcode::SetRegister) {
used.set(IR::RegIndex(inst.Arg(0).Reg()));
}
}
}
for (size_t index = IR::NUM_USER_REGS; index-- > 0;) {
if (!used.test(index)) {
return static_cast<IR::Reg>(index);
}
}
return std::nullopt;
}
std::optional<IR::Reg> LowerViewportMask(const IR::Program& program) {
const std::optional<IR::Reg> reg{FindFreeRegister(program)};
if (!reg || program.blocks.empty()) {
return std::nullopt;
}
bool stores_mask{};
for (IR::Block* const block : program.blocks) {
for (IR::Inst& inst : block->Instructions()) {
if (inst.GetOpcode() != IR::Opcode::SetAttribute ||
inst.Arg(0).Attribute() != IR::Attribute::ViewportMask) {
continue;
}
IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)};
ir.SetReg(*reg, ir.BitCast<IR::U32>(IR::F32{inst.Arg(1)}));
inst.Invalidate();
stores_mask = true;
}
}
if (!stores_mask) {
return std::nullopt;
}
IR::Block& entry{*program.blocks.front()};
IR::IREmitter ir{entry, entry.begin()};
ir.SetReg(*reg, ir.Imm32(1));
return reg;
}
void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInfo& host_info) {
std::optional<IR::Reg> viewport_mask_reg;
if (!host_info.support_viewport_mask) {
viewport_mask_reg = LowerViewportMask(program);
}
for (IR::Block* const block : program.blocks) {
for (IR::Inst& inst : block->Instructions()) {
if (inst.GetOpcode() == IR::Opcode::Epilogue) {
IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)};
EmitGeometryPassthrough(
ir, program, program.info.passthrough,
program.info.passthrough.AnyComponent(IR::Attribute::PositionX), {},
viewport_mask_reg);
program.info.passthrough.AnyComponent(IR::Attribute::PositionX), {});
}
}
}
}
void TightenOutputVertices(IR::Program& program) {
if (program.stage != Stage::Geometry || program.is_geometry_passthrough) {
return;
}
const bool has_loops = std::ranges::any_of(program.syntax_list, [](const auto& node) {
return node.type == IR::AbstractSyntaxNode::Type::Loop;
});
if (has_loops) {
return;
}
u32 num_emits = 0;
for (IR::Block* const block : program.blocks) {
for (const IR::Inst& inst : block->Instructions()) {
if (inst.GetOpcode() == IR::Opcode::EmitVertex) {
++num_emits;
}
}
}
if (num_emits != 0) {
program.output_vertices = (std::min)(program.output_vertices, num_emits);
}
}
} // Anonymous namespace
IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool,
@@ -393,14 +299,12 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
Optimization::PositionPass(env, program);
Optimization::GlobalMemoryToStorageBufferPass(program, normalized_host_info);
Optimization::GeometryCompactionPass(program, normalized_host_info);
Optimization::TexturePass(env, program, normalized_host_info);
if (Settings::values.resolution_info.active || Settings::values.rescale_hack.GetValue()) {
Optimization::RescalingPass(program);
}
Optimization::DeadCodeEliminationPass(program);
TightenOutputVertices(program);
if (Settings::values.renderer_debug) {
Optimization::VerificationPass(program);
}
@@ -529,7 +433,7 @@ IR::Program GenerateGeometryPassthrough(ObjectPool<IR::Inst>& inst_pool,
IR::IREmitter ir{*current_block};
EmitGeometryPassthrough(ir, program, program.info.stores, true,
source_program.info.emulated_layer, std::nullopt);
source_program.info.emulated_layer);
IR::Block* return_block{block_pool.Create(inst_pool)};
IR::IREmitter{*return_block}.Epilogue();
@@ -34,12 +34,10 @@ struct HostTranslateInfo {
bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered
bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers
bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS
bool support_viewport_mask{};
bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry
///< passthrough shaders
bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional
///< control flow
bool single_lane_geometry_subgroups{};
void ApplyDescriptorLimitPolicy() noexcept {
if (min_ssbo_alignment == 0) {
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -651,29 +651,6 @@ IR::Value GetThroughCast(IR::Value value, IR::Opcode expected_cast) {
return value;
}
u32 QuadButterflyMask(const IR::Inst& inst) {
if (inst.GetOpcode() == IR::Opcode::QuadSwap) {
const IR::Value direction{inst.Arg(1)};
if (!direction.IsImmediate()) {
return 0;
}
return direction.U32() + 1;
}
if (inst.GetOpcode() != IR::Opcode::ShuffleButterfly) {
return 0;
}
const IR::Value index{inst.Arg(1)};
const IR::Value clamp{inst.Arg(2)};
const IR::Value segmentation_mask{inst.Arg(3)};
if (!index.IsImmediate() || !clamp.IsImmediate() || !segmentation_mask.IsImmediate()) {
return 0;
}
if (clamp.U32() != 3 || segmentation_mask.U32() != 28) {
return 0;
}
return index.U32();
}
void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) {
const IR::Value swizzle{inst.Arg(2)};
if (!swizzle.IsImmediate()) {
@@ -689,8 +666,7 @@ void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) {
return;
}
IR::Inst* const inst2{value_1.InstRecursive()};
const u32 lane_mask{QuadButterflyMask(*inst2)};
if (lane_mask == 0) {
if (inst2->GetOpcode() != IR::Opcode::ShuffleButterfly) {
return;
}
const IR::Value value_3{GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32)};
@@ -702,15 +678,24 @@ void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) {
return;
}
}
const IR::Value index{inst2->Arg(1)};
const IR::Value clamp{inst2->Arg(2)};
const IR::Value segmentation_mask{inst2->Arg(3)};
if (!index.IsImmediate() || !clamp.IsImmediate() || !segmentation_mask.IsImmediate()) {
return;
}
if (clamp.U32() != 3 || segmentation_mask.U32() != 28) {
return;
}
if (swizzle_value == 0x99) {
// DPdxFine
if (lane_mask == 1) {
if (index.U32() == 1) {
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
inst.ReplaceUsesWith(ir.DPdxFine(IR::F32{inst.Arg(1)}));
}
} else if (swizzle_value == 0xA5) {
// DPdyFine
if (lane_mask == 2) {
if (index.U32() == 2) {
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
inst.ReplaceUsesWith(ir.DPdyFine(IR::F32{inst.Arg(1)}));
}
@@ -724,13 +709,6 @@ bool FindGradient3DDerivatives(std::array<IR::Value, 3>& results, IR::Value coor
const auto check_through_shuffle = [](IR::Value input, IR::Value& result) {
const IR::Value value_1{GetThroughCast(input.Resolve(), IR::Opcode::BitCastF32U32)};
IR::Inst* const inst2{value_1.InstRecursive()};
if (inst2->GetOpcode() == IR::Opcode::QuadBroadcast) {
if (!inst2->Arg(1).Resolve().IsImmediate()) {
return false;
}
result = GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32);
return true;
}
if (inst2->GetOpcode() != IR::Opcode::ShuffleIndex) {
return false;
}
@@ -1,129 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <boost/container/small_vector.hpp>
#include "shader_recompiler/frontend/ir/ir_emitter.h"
#include "shader_recompiler/host_translate_info.h"
#include "shader_recompiler/ir_opt/passes.h"
namespace Shader::Optimization {
namespace {
constexpr int MAX_BALLOT_DEPTH = 8;
struct SlotAtomic {
IR::Block* block;
IR::Inst* atomic;
IR::Inst* ballot;
};
IR::Inst* FindBallot(const IR::Value& value, int depth) {
if (depth > MAX_BALLOT_DEPTH || value.IsImmediate()) {
return nullptr;
}
IR::Inst* const inst{value.InstRecursive()};
if (inst->GetOpcode() == IR::Opcode::SubgroupBallot) {
return inst;
}
if (inst->GetOpcode() == IR::Opcode::Phi) {
return nullptr;
}
for (size_t index = 0; index < inst->NumArgs(); ++index) {
if (IR::Inst* const ballot{FindBallot(inst->Arg(index), depth + 1)}) {
return ballot;
}
}
return nullptr;
}
bool IsShuffle(IR::Opcode opcode) {
switch (opcode) {
case IR::Opcode::ShuffleIndex:
case IR::Opcode::ShuffleUp:
case IR::Opcode::ShuffleDown:
case IR::Opcode::ShuffleButterfly:
return true;
default:
return false;
}
}
IR::U32 PrimitiveSlot(IR::IREmitter& ir, u32 invocations, const IR::U32& amount) {
IR::U32 key{ir.GetAttributeU32(IR::Attribute::PrimitiveId)};
if (invocations > 1) {
key = ir.IAdd(ir.IMul(key, ir.Imm32(invocations)), ir.InvocationId());
}
return ir.IMul(key, amount);
}
bool MergesAtomic(const IR::Inst& phi, const IR::Block& block, const IR::Inst& atomic) {
for (size_t index = 0; index < phi.NumArgs(); ++index) {
const IR::Value arg{phi.Arg(index)};
if (phi.PhiBlock(index) == &block && !arg.IsImmediate() &&
arg.InstRecursive() == &atomic) {
return true;
}
}
return false;
}
void RewriteMergedSlots(IR::Block& block, IR::Inst& atomic, u32 invocations) {
for (IR::Block* const successor : block.ImmSuccessors()) {
for (IR::Inst& phi : successor->Instructions()) {
if (phi.GetOpcode() != IR::Opcode::Phi || !MergesAtomic(phi, block, atomic)) {
continue;
}
for (size_t index = 0; index < phi.NumArgs(); ++index) {
const IR::Value arg{phi.Arg(index)};
if (arg.IsImmediate() || !IsShuffle(arg.InstRecursive()->GetOpcode())) {
continue;
}
IR::IREmitter ir{*phi.PhiBlock(index)};
const IR::U32 amount{arg.InstRecursive()->Arg(0)};
phi.SetArg(index, PrimitiveSlot(ir, invocations, amount));
}
}
}
}
void RewriteAtomic(IR::Block& block, IR::Inst& atomic, u32 invocations) {
const auto insert_point{IR::Block::InstructionList::s_iterator_to(atomic)};
IR::IREmitter ir{block, insert_point};
const IR::U32 amount{atomic.Arg(2)};
const IR::U32 slot{PrimitiveSlot(ir, invocations, amount)};
block.PrependNewInst(insert_point, IR::Opcode::StorageAtomicUMax32,
{atomic.Arg(0), atomic.Arg(1), ir.IAdd(slot, amount)});
atomic.ReplaceUsesWith(slot);
}
void NeutralizePredicate(IR::Inst& ballot) {
const IR::Value pred{ballot.Arg(0)};
if (!pred.IsImmediate()) {
pred.InstRecursive()->ReplaceUsesWith(IR::Value{true});
}
}
}
void GeometryCompactionPass(IR::Program& program, const HostTranslateInfo& host_info) {
if (program.stage != Stage::Geometry || !host_info.single_lane_geometry_subgroups) {
return;
}
boost::container::small_vector<SlotAtomic, 4> slot_atomics;
for (IR::Block* const block : program.post_order_blocks) {
for (IR::Inst& inst : block->Instructions()) {
if (inst.GetOpcode() != IR::Opcode::StorageAtomicIAdd32) {
continue;
}
if (IR::Inst* const ballot{FindBallot(inst.Arg(2), 0)}) {
slot_atomics.push_back({block, &inst, ballot});
}
}
}
for (const SlotAtomic& slot_atomic : slot_atomics) {
RewriteMergedSlots(*slot_atomic.block, *slot_atomic.atomic, program.invocations);
RewriteAtomic(*slot_atomic.block, *slot_atomic.atomic, program.invocations);
NeutralizePredicate(*slot_atomic.ballot);
}
}
}
-4
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -19,7 +16,6 @@ void CollectShaderInfoPass(Environment& env, IR::Program& program);
void ConditionalBarrierPass(IR::Program& program);
void ConstantPropagationPass(Environment& env, IR::Program& program);
void DeadCodeEliminationPass(IR::Program& program);
void GeometryCompactionPass(IR::Program& program, const HostTranslateInfo& host_info);
void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateInfo& host_info);
void IdentityRemovalPass(IR::Program& program);
void LowerFp64ToFp32(IR::Program& program);
-3
View File
@@ -40,7 +40,6 @@ struct Profile {
bool support_shader_quad_control{};
bool support_quad_shuffles{};
bool support_vote{};
bool support_shuffle_relative{};
u32 supported_subgroup_stages{0x7F};
bool support_viewport_index_layer_non_geometry{};
bool support_viewport_mask{};
@@ -103,8 +102,6 @@ struct Profile {
bool ignore_nan_fp_comparisons{};
/// Some drivers have broken support for OpVectorExtractDynamic on subgroup mask inputs
bool has_broken_spirv_subgroup_mask_vector_extract_dynamic{};
bool has_broken_spirv_subgroup_shuffle{};
u32 max_subgroup_size{};
u32 gl_max_compute_smem_size{};
+3 -15
View File
@@ -226,19 +226,7 @@ struct GPU::Impl {
}
/// Notify rasterizer that any caches of the specified region should be invalidated
void InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes) {
if (Settings::values.nce_invalidation_gpu_readback.GetValue()) {
VideoCore::RasterizerInterface* rasterizer = renderer->ReadRasterizer();
if (preserve_gpu_writes && rasterizer->MustFlushRegion(addr, size, VideoCommon::CacheType::BufferCache)) {
const u64 fence = RequestSyncOperation([rasterizer, addr, size] {
rasterizer->FlushRegion(addr, size, VideoCommon::CacheType::BufferCache);
rasterizer->OnCacheInvalidation(addr, size);
});
gpu_thread.TickGPU(is_async);
WaitForSyncOperation(fence);
return;
}
}
void InvalidateRegion(DAddr addr, u64 size) {
gpu_thread.InvalidateRegion(addr, size);
}
@@ -530,8 +518,8 @@ void GPU::FlushRegion(DAddr addr, u64 size) {
impl->FlushRegion(addr, size);
}
void GPU::InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes) {
impl->InvalidateRegion(addr, size, preserve_gpu_writes);
void GPU::InvalidateRegion(DAddr addr, u64 size) {
impl->InvalidateRegion(addr, size);
}
bool GPU::OnCPUWrite(DAddr addr, u64 size) {
+1 -1
View File
@@ -248,7 +248,7 @@ public:
void FlushRegion(DAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be invalidated
void InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes = false);
void InvalidateRegion(DAddr addr, u64 size);
/// Notify rasterizer that CPU is trying to write this area. It returns true if the area is
/// sensible, false otherwise, addr and size must be a valid combination
@@ -362,7 +362,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
const auto subgroup_stage_bit{[subgroup_stages](VkShaderStageFlags flag, Shader::Stage stage) {
return (subgroup_stages & flag) != 0 ? (1u << static_cast<u32>(stage)) : 0u;
}};
u32 supported_subgroup_stages{
const u32 supported_subgroup_stages{
subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexA) |
subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexB) |
subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT,
@@ -372,9 +372,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
subgroup_stage_bit(VK_SHADER_STAGE_GEOMETRY_BIT, Shader::Stage::Geometry) |
subgroup_stage_bit(VK_SHADER_STAGE_FRAGMENT_BIT, Shader::Stage::Fragment) |
subgroup_stage_bit(VK_SHADER_STAGE_COMPUTE_BIT, Shader::Stage::Compute)};
if (driver_id == VK_DRIVER_ID_MESA_TURNIP) {
supported_subgroup_stages &= ~(1u << static_cast<u32>(Shader::Stage::Geometry));
}
profile = Shader::Profile{
.supported_spirv = device.SupportedSpirvVersion(),
.unified_descriptor_binding = true,
@@ -412,8 +409,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.support_shader_quad_control = device.IsKhrShaderQuadControlSupported(),
.support_quad_shuffles = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_QUAD_BIT),
.support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT),
.support_shuffle_relative =
device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT),
.supported_subgroup_stages = supported_subgroup_stages,
.support_viewport_index_layer_non_geometry =
device.IsExtShaderViewportIndexLayerSupported(),
@@ -449,17 +444,13 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA,
.has_broken_spirv_clamp = driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS,
.has_broken_spirv_position_input = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
.has_broken_spirv_position_input = driver_id == false,
.has_broken_unsigned_image_offsets = false,
.has_broken_signed_operations = false,
.has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY,
.has_broken_fp32_denorm_flush = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
.ignore_nan_fp_comparisons = false,
.has_broken_spirv_subgroup_mask_vector_extract_dynamic =
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY &&
device.GetDriverVersion() < VK_MAKE_VERSION(512, 672, 0),
.has_broken_spirv_subgroup_shuffle = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
.max_subgroup_size = device.GetMaxSubgroupSize(),
.has_broken_spirv_subgroup_mask_vector_extract_dynamic = false,
.has_broken_robust =
device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal,
.min_ssbo_alignment = device.GetStorageBufferAlignment(),
@@ -486,10 +477,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
driver_id == VK_DRIVER_ID_SAMSUNG_PROPRIETARY,
.support_snorm_render_buffer = true,
.support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(),
.support_viewport_mask = device.IsNvViewportArray2Supported(),
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
.support_conditional_barrier = device.SupportsConditionalBarriers(),
.single_lane_geometry_subgroups = !profile.SupportsSubgroupStage(Shader::Stage::Geometry),
};
host_info.ApplyDescriptorLimitPolicy();
@@ -483,11 +483,6 @@ FN_MAX_LIMIT_LIST
return properties.subgroup_properties.supportedStages;
}
u32 GetMaxSubgroupSize() const {
return (std::max)(properties.subgroup_properties.subgroupSize,
properties.subgroup_size_control.maxSubgroupSize);
}
/// Returns the maximum number of push descriptors.
u32 MaxPushDescriptors() const {
return properties.push_descriptor.maxPushDescriptors;
+1 -3
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -11,6 +8,7 @@
class QCheckBox;
class QLineEdit;
class QComboBox;
class QDateTimeEdit;
namespace Core {
class System;
+4 -8
View File
@@ -140,12 +140,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
if (combobox_enumerations.contains(type)) {
enumeration = &combobox_enumerations.at(type);
for (const auto& [id, name] : *enumeration) {
if (type == Settings::EnumMetadata<Settings::AppletMode>::Index() &&
id == static_cast<u32>(Settings::AppletMode::Disabled) &&
setting.Id() != Settings::values.controller_applet_mode.Id()) {
continue;
}
combobox->addItem(name, id);
combobox->addItem(name);
}
} else {
return combobox;
@@ -163,8 +158,9 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
combobox->setCurrentIndex(find_index(setting_value));
serializer = [this]() {
return std::to_string(combobox->currentData().toUInt());
serializer = [this, enumeration]() {
int current = combobox->currentIndex();
return std::to_string(enumeration->at(current).first);
};
restore_func = [this, find_index]() {

Some files were not shown because too many files have changed in this diff Show More