summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt91
-rw-r--r--src/android/app/src/main/res/layout/fragment_search.xml1
-rw-r--r--src/android/app/src/main/res/values/strings.xml2
-rw-r--r--src/core/hid/hid_types.h7
-rw-r--r--src/core/hle/service/am/am.cpp91
-rw-r--r--src/core/hle/service/am/am.h3
-rw-r--r--src/core/hle/service/am/applets/applet_controller.h2
-rw-r--r--src/core/hle/service/btm/btm.cpp56
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp16
-rw-r--r--src/core/hle/service/hid/controllers/npad.h72
-rw-r--r--src/core/hle/service/hid/hid_server.cpp4
-rw-r--r--src/core/hle/service/hid/hid_system_server.cpp293
-rw-r--r--src/core/hle/service/hid/hid_system_server.h23
-rw-r--r--src/core/hle/service/ldn/ldn.cpp10
-rw-r--r--src/core/hle/service/set/set_sys.cpp30
-rw-r--r--src/core/hle/service/set/set_sys.h12
-rw-r--r--src/video_core/CMakeLists.txt4
-rw-r--r--src/video_core/host1x/codecs/codec.cpp329
-rw-r--r--src/video_core/host1x/codecs/codec.h39
-rw-r--r--src/video_core/host1x/codecs/h264.cpp4
-rw-r--r--src/video_core/host1x/codecs/h264.h1
-rw-r--r--src/video_core/host1x/ffmpeg/ffmpeg.cpp419
-rw-r--r--src/video_core/host1x/ffmpeg/ffmpeg.h213
-rw-r--r--src/video_core/host1x/nvdec.cpp2
-rw-r--r--src/video_core/host1x/nvdec.h2
-rw-r--r--src/video_core/host1x/vic.cpp62
-rw-r--r--src/video_core/host1x/vic.h4
-rw-r--r--src/yuzu/CMakeLists.txt51
-rw-r--r--src/yuzu/configuration/shared_translation.cpp517
-rw-r--r--src/yuzu/main.cpp41
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu/main.ui22
35 files changed, 1646 insertions, 796 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index b3b3fc209..6aba69dbe 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -73,7 +73,7 @@ abstract class SettingsItem(
73 R.string.frame_limit_slider, 73 R.string.frame_limit_slider,
74 R.string.frame_limit_slider_description, 74 R.string.frame_limit_slider_description,
75 1, 75 1,
76 200, 76 400,
77 "%" 77 "%"
78 ) 78 )
79 ) 79 )
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
index ec116ab62..6940fc757 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
@@ -21,6 +21,8 @@ import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding
21import org.yuzu.yuzu_emu.model.HomeViewModel 21import org.yuzu.yuzu_emu.model.HomeViewModel
22import org.yuzu.yuzu_emu.model.Installable 22import org.yuzu.yuzu_emu.model.Installable
23import org.yuzu.yuzu_emu.ui.main.MainActivity 23import org.yuzu.yuzu_emu.ui.main.MainActivity
24import java.time.LocalDateTime
25import java.time.format.DateTimeFormatter
24 26
25class InstallableFragment : Fragment() { 27class InstallableFragment : Fragment() {
26 private var _binding: FragmentInstallablesBinding? = null 28 private var _binding: FragmentInstallablesBinding? = null
@@ -78,7 +80,15 @@ class InstallableFragment : Fragment() {
78 R.string.manage_save_data, 80 R.string.manage_save_data,
79 R.string.import_export_saves_description, 81 R.string.import_export_saves_description,
80 install = { mainActivity.importSaves.launch(arrayOf("application/zip")) }, 82 install = { mainActivity.importSaves.launch(arrayOf("application/zip")) },
81 export = { mainActivity.exportSave() } 83 export = {
84 mainActivity.exportSaves.launch(
85 "yuzu saves - ${
86 LocalDateTime.now().format(
87 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
88 )
89 }.zip"
90 )
91 }
82 ) 92 )
83 } else { 93 } else {
84 Installable( 94 Installable(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
index de84b2adb..2fa3ab31b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
@@ -18,8 +18,8 @@ class Game(
18 val version: String = "", 18 val version: String = "",
19 val isHomebrew: Boolean = false 19 val isHomebrew: Boolean = false
20) : Parcelable { 20) : Parcelable {
21 val keyAddedToLibraryTime get() = "${programId}_AddedToLibraryTime" 21 val keyAddedToLibraryTime get() = "${path}_AddedToLibraryTime"
22 val keyLastPlayedTime get() = "${programId}_LastPlayed" 22 val keyLastPlayedTime get() = "${path}_LastPlayed"
23 23
24 override fun equals(other: Any?): Boolean { 24 override fun equals(other: Any?): Boolean {
25 if (other !is Game) { 25 if (other !is Game) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 211b7cf69..ace5dddea 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.ui.main
6import android.content.Intent 6import android.content.Intent
7import android.net.Uri 7import android.net.Uri
8import android.os.Bundle 8import android.os.Bundle
9import android.provider.DocumentsContract
10import android.view.View 9import android.view.View
11import android.view.ViewGroup.MarginLayoutParams 10import android.view.ViewGroup.MarginLayoutParams
12import android.view.WindowManager 11import android.view.WindowManager
@@ -20,7 +19,6 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
20import androidx.core.view.ViewCompat 19import androidx.core.view.ViewCompat
21import androidx.core.view.WindowCompat 20import androidx.core.view.WindowCompat
22import androidx.core.view.WindowInsetsCompat 21import androidx.core.view.WindowInsetsCompat
23import androidx.documentfile.provider.DocumentFile
24import androidx.lifecycle.Lifecycle 22import androidx.lifecycle.Lifecycle
25import androidx.lifecycle.lifecycleScope 23import androidx.lifecycle.lifecycleScope
26import androidx.lifecycle.repeatOnLifecycle 24import androidx.lifecycle.repeatOnLifecycle
@@ -41,7 +39,6 @@ import org.yuzu.yuzu_emu.NativeLibrary
41import org.yuzu.yuzu_emu.R 39import org.yuzu.yuzu_emu.R
42import org.yuzu.yuzu_emu.activities.EmulationActivity 40import org.yuzu.yuzu_emu.activities.EmulationActivity
43import org.yuzu.yuzu_emu.databinding.ActivityMainBinding 41import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
44import org.yuzu.yuzu_emu.features.DocumentProvider
45import org.yuzu.yuzu_emu.features.settings.model.Settings 42import org.yuzu.yuzu_emu.features.settings.model.Settings
46import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment 43import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
47import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 44import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
@@ -53,9 +50,6 @@ import org.yuzu.yuzu_emu.model.TaskViewModel
53import org.yuzu.yuzu_emu.utils.* 50import org.yuzu.yuzu_emu.utils.*
54import java.io.BufferedInputStream 51import java.io.BufferedInputStream
55import java.io.BufferedOutputStream 52import java.io.BufferedOutputStream
56import java.io.FileOutputStream
57import java.time.LocalDateTime
58import java.time.format.DateTimeFormatter
59import java.util.zip.ZipEntry 53import java.util.zip.ZipEntry
60import java.util.zip.ZipInputStream 54import java.util.zip.ZipInputStream
61 55
@@ -73,7 +67,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
73 67
74 // Get first subfolder in saves folder (should be the user folder) 68 // Get first subfolder in saves folder (should be the user folder)
75 val savesFolderRoot get() = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: "" 69 val savesFolderRoot get() = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: ""
76 private var lastZipCreated: File? = null
77 70
78 override fun onCreate(savedInstanceState: Bundle?) { 71 override fun onCreate(savedInstanceState: Bundle?) {
79 val splashScreen = installSplashScreen() 72 val splashScreen = installSplashScreen()
@@ -657,74 +650,30 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
657 } 650 }
658 651
659 /** 652 /**
660 * Zips the save files located in the given folder path and creates a new zip file with the current date and time.
661 * @return true if the zip file is successfully created, false otherwise.
662 */
663 private fun zipSave(): Boolean {
664 try {
665 val tempFolder = File(getPublicFilesDir().canonicalPath, "temp")
666 tempFolder.mkdirs()
667 val saveFolder = File(savesFolderRoot)
668 val outputZipFile = File(
669 tempFolder,
670 "yuzu saves - ${
671 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
672 }.zip"
673 )
674 outputZipFile.createNewFile()
675 val result = FileUtil.zipFromInternalStorage(
676 saveFolder,
677 savesFolderRoot,
678 BufferedOutputStream(FileOutputStream(outputZipFile))
679 )
680 if (result == TaskState.Failed) {
681 return false
682 }
683 lastZipCreated = outputZipFile
684 } catch (e: Exception) {
685 return false
686 }
687 return true
688 }
689
690 /**
691 * Exports the save file located in the given folder path by creating a zip file and sharing it via intent. 653 * Exports the save file located in the given folder path by creating a zip file and sharing it via intent.
692 */ 654 */
693 fun exportSave() { 655 val exportSaves = registerForActivityResult(
694 CoroutineScope(Dispatchers.IO).launch { 656 ActivityResultContracts.CreateDocument("application/zip")
695 val wasZipCreated = zipSave() 657 ) { result ->
696 val lastZipFile = lastZipCreated 658 if (result == null) {
697 if (!wasZipCreated || lastZipFile == null) { 659 return@registerForActivityResult
698 withContext(Dispatchers.Main) { 660 }
699 Toast.makeText(
700 this@MainActivity,
701 getString(R.string.export_save_failed),
702 Toast.LENGTH_LONG
703 ).show()
704 }
705 return@launch
706 }
707 661
708 withContext(Dispatchers.Main) { 662 IndeterminateProgressDialogFragment.newInstance(
709 val file = DocumentFile.fromSingleUri( 663 this,
710 this@MainActivity, 664 R.string.save_files_exporting,
711 DocumentsContract.buildDocumentUri( 665 false
712 DocumentProvider.AUTHORITY, 666 ) {
713 "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}" 667 val zipResult = FileUtil.zipFromInternalStorage(
714 ) 668 File(savesFolderRoot),
715 )!! 669 savesFolderRoot,
716 val intent = Intent(Intent.ACTION_SEND) 670 BufferedOutputStream(contentResolver.openOutputStream(result))
717 .setDataAndType(file.uri, "application/zip") 671 )
718 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 672 return@newInstance when (zipResult) {
719 .putExtra(Intent.EXTRA_STREAM, file.uri) 673 TaskState.Completed -> getString(R.string.export_success)
720 startForResultExportSave.launch( 674 TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed)
721 Intent.createChooser(
722 intent,
723 getString(R.string.share_save_file)
724 )
725 )
726 } 675 }
727 } 676 }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
728 } 677 }
729 678
730 private val startForResultExportSave = 679 private val startForResultExportSave =
diff --git a/src/android/app/src/main/res/layout/fragment_search.xml b/src/android/app/src/main/res/layout/fragment_search.xml
index b8d54d947..efdfd7047 100644
--- a/src/android/app/src/main/res/layout/fragment_search.xml
+++ b/src/android/app/src/main/res/layout/fragment_search.xml
@@ -127,6 +127,7 @@
127 android:layout_height="wrap_content" 127 android:layout_height="wrap_content"
128 android:clipToPadding="false" 128 android:clipToPadding="false"
129 android:paddingVertical="4dp" 129 android:paddingVertical="4dp"
130 app:checkedChip="@id/chip_recently_played"
130 app:chipSpacingHorizontal="12dp" 131 app:chipSpacingHorizontal="12dp"
131 app:singleLine="true" 132 app:singleLine="true"
132 app:singleSelection="true"> 133 app:singleSelection="true">
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 98c3f20f8..471af8795 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -91,6 +91,7 @@
91 <string name="manage_save_data">Manage save data</string> 91 <string name="manage_save_data">Manage save data</string>
92 <string name="manage_save_data_description">Save data found. Please select an option below.</string> 92 <string name="manage_save_data_description">Save data found. Please select an option below.</string>
93 <string name="import_export_saves_description">Import or export save files</string> 93 <string name="import_export_saves_description">Import or export save files</string>
94 <string name="save_files_exporting">Exporting save files…</string>
94 <string name="save_file_imported_success">Imported successfully</string> 95 <string name="save_file_imported_success">Imported successfully</string>
95 <string name="save_file_invalid_zip_structure">Invalid save directory structure</string> 96 <string name="save_file_invalid_zip_structure">Invalid save directory structure</string>
96 <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string> 97 <string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string>
@@ -256,6 +257,7 @@
256 <string name="cancelling">Cancelling</string> 257 <string name="cancelling">Cancelling</string>
257 <string name="install">Install</string> 258 <string name="install">Install</string>
258 <string name="delete">Delete</string> 259 <string name="delete">Delete</string>
260 <string name="export_success">Exported successfully</string>
259 261
260 <!-- GPU driver installation --> 262 <!-- GPU driver installation -->
261 <string name="select_gpu_driver">Select GPU driver</string> 263 <string name="select_gpu_driver">Select GPU driver</string>
diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h
index 9d48cd90e..70fcc6b69 100644
--- a/src/core/hid/hid_types.h
+++ b/src/core/hid/hid_types.h
@@ -218,6 +218,13 @@ enum class NpadIdType : u32 {
218 Invalid = 0xFFFFFFFF, 218 Invalid = 0xFFFFFFFF,
219}; 219};
220 220
221enum class NpadInterfaceType : u8 {
222 Bluetooth = 1,
223 Rail = 2,
224 Usb = 3,
225 Embedded = 4,
226};
227
221// This is nn::hid::NpadStyleIndex 228// This is nn::hid::NpadStyleIndex
222enum class NpadStyleIndex : u8 { 229enum class NpadStyleIndex : u8 {
223 None = 0, 230 None = 0,
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index cc643ea09..a266d7c21 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -13,6 +13,7 @@
13#include "core/file_sys/patch_manager.h" 13#include "core/file_sys/patch_manager.h"
14#include "core/file_sys/registered_cache.h" 14#include "core/file_sys/registered_cache.h"
15#include "core/file_sys/savedata_factory.h" 15#include "core/file_sys/savedata_factory.h"
16#include "core/hid/hid_types.h"
16#include "core/hle/kernel/k_event.h" 17#include "core/hle/kernel/k_event.h"
17#include "core/hle/kernel/k_transfer_memory.h" 18#include "core/hle/kernel/k_transfer_memory.h"
18#include "core/hle/result.h" 19#include "core/hle/result.h"
@@ -21,6 +22,7 @@
21#include "core/hle/service/am/applet_ae.h" 22#include "core/hle/service/am/applet_ae.h"
22#include "core/hle/service/am/applet_oe.h" 23#include "core/hle/service/am/applet_oe.h"
23#include "core/hle/service/am/applets/applet_cabinet.h" 24#include "core/hle/service/am/applets/applet_cabinet.h"
25#include "core/hle/service/am/applets/applet_controller.h"
24#include "core/hle/service/am/applets/applet_mii_edit_types.h" 26#include "core/hle/service/am/applets/applet_mii_edit_types.h"
25#include "core/hle/service/am/applets/applet_profile_select.h" 27#include "core/hle/service/am/applets/applet_profile_select.h"
26#include "core/hle/service/am/applets/applet_software_keyboard_types.h" 28#include "core/hle/service/am/applets/applet_software_keyboard_types.h"
@@ -35,6 +37,7 @@
35#include "core/hle/service/caps/caps_su.h" 37#include "core/hle/service/caps/caps_su.h"
36#include "core/hle/service/caps/caps_types.h" 38#include "core/hle/service/caps/caps_types.h"
37#include "core/hle/service/filesystem/filesystem.h" 39#include "core/hle/service/filesystem/filesystem.h"
40#include "core/hle/service/hid/controllers/npad.h"
38#include "core/hle/service/ipc_helpers.h" 41#include "core/hle/service/ipc_helpers.h"
39#include "core/hle/service/ns/ns.h" 42#include "core/hle/service/ns/ns.h"
40#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h" 43#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
@@ -73,7 +76,7 @@ IWindowController::IWindowController(Core::System& system_)
73 static const FunctionInfo functions[] = { 76 static const FunctionInfo functions[] = {
74 {0, nullptr, "CreateWindow"}, 77 {0, nullptr, "CreateWindow"},
75 {1, &IWindowController::GetAppletResourceUserId, "GetAppletResourceUserId"}, 78 {1, &IWindowController::GetAppletResourceUserId, "GetAppletResourceUserId"},
76 {2, nullptr, "GetAppletResourceUserIdOfCallerApplet"}, 79 {2, &IWindowController::GetAppletResourceUserIdOfCallerApplet, "GetAppletResourceUserIdOfCallerApplet"},
77 {10, &IWindowController::AcquireForegroundRights, "AcquireForegroundRights"}, 80 {10, &IWindowController::AcquireForegroundRights, "AcquireForegroundRights"},
78 {11, nullptr, "ReleaseForegroundRights"}, 81 {11, nullptr, "ReleaseForegroundRights"},
79 {12, nullptr, "RejectToChangeIntoBackground"}, 82 {12, nullptr, "RejectToChangeIntoBackground"},
@@ -97,6 +100,16 @@ void IWindowController::GetAppletResourceUserId(HLERequestContext& ctx) {
97 rb.Push<u64>(process_id); 100 rb.Push<u64>(process_id);
98} 101}
99 102
103void IWindowController::GetAppletResourceUserIdOfCallerApplet(HLERequestContext& ctx) {
104 const u64 process_id = 0;
105
106 LOG_WARNING(Service_AM, "(STUBBED) called");
107
108 IPC::ResponseBuilder rb{ctx, 4};
109 rb.Push(ResultSuccess);
110 rb.Push<u64>(process_id);
111}
112
100void IWindowController::AcquireForegroundRights(HLERequestContext& ctx) { 113void IWindowController::AcquireForegroundRights(HLERequestContext& ctx) {
101 LOG_WARNING(Service_AM, "(STUBBED) called"); 114 LOG_WARNING(Service_AM, "(STUBBED) called");
102 IPC::ResponseBuilder rb{ctx, 2}; 115 IPC::ResponseBuilder rb{ctx, 2};
@@ -1565,7 +1578,7 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1565 {6, nullptr, "GetPopInteractiveInDataEvent"}, 1578 {6, nullptr, "GetPopInteractiveInDataEvent"},
1566 {10, &ILibraryAppletSelfAccessor::ExitProcessAndReturn, "ExitProcessAndReturn"}, 1579 {10, &ILibraryAppletSelfAccessor::ExitProcessAndReturn, "ExitProcessAndReturn"},
1567 {11, &ILibraryAppletSelfAccessor::GetLibraryAppletInfo, "GetLibraryAppletInfo"}, 1580 {11, &ILibraryAppletSelfAccessor::GetLibraryAppletInfo, "GetLibraryAppletInfo"},
1568 {12, nullptr, "GetMainAppletIdentityInfo"}, 1581 {12, &ILibraryAppletSelfAccessor::GetMainAppletIdentityInfo, "GetMainAppletIdentityInfo"},
1569 {13, nullptr, "CanUseApplicationCore"}, 1582 {13, nullptr, "CanUseApplicationCore"},
1570 {14, &ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo, "GetCallerAppletIdentityInfo"}, 1583 {14, &ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo, "GetCallerAppletIdentityInfo"},
1571 {15, nullptr, "GetMainAppletApplicationControlProperty"}, 1584 {15, nullptr, "GetMainAppletApplicationControlProperty"},
@@ -1609,6 +1622,9 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1609 case Applets::AppletId::SoftwareKeyboard: 1622 case Applets::AppletId::SoftwareKeyboard:
1610 PushInShowSoftwareKeyboard(); 1623 PushInShowSoftwareKeyboard();
1611 break; 1624 break;
1625 case Applets::AppletId::Controller:
1626 PushInShowController();
1627 break;
1612 default: 1628 default:
1613 break; 1629 break;
1614 } 1630 }
@@ -1666,13 +1682,33 @@ void ILibraryAppletSelfAccessor::GetLibraryAppletInfo(HLERequestContext& ctx) {
1666 rb.PushRaw(applet_info); 1682 rb.PushRaw(applet_info);
1667} 1683}
1668 1684
1669void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext& ctx) { 1685void ILibraryAppletSelfAccessor::GetMainAppletIdentityInfo(HLERequestContext& ctx) {
1670 struct AppletIdentityInfo { 1686 struct AppletIdentityInfo {
1671 Applets::AppletId applet_id; 1687 Applets::AppletId applet_id;
1672 INSERT_PADDING_BYTES(0x4); 1688 INSERT_PADDING_BYTES(0x4);
1673 u64 application_id; 1689 u64 application_id;
1674 }; 1690 };
1691 static_assert(sizeof(AppletIdentityInfo) == 0x10, "AppletIdentityInfo has incorrect size.");
1692
1693 LOG_WARNING(Service_AM, "(STUBBED) called");
1694
1695 const AppletIdentityInfo applet_info{
1696 .applet_id = Applets::AppletId::QLaunch,
1697 .application_id = 0x0100000000001000ull,
1698 };
1699
1700 IPC::ResponseBuilder rb{ctx, 6};
1701 rb.Push(ResultSuccess);
1702 rb.PushRaw(applet_info);
1703}
1675 1704
1705void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext& ctx) {
1706 struct AppletIdentityInfo {
1707 Applets::AppletId applet_id;
1708 INSERT_PADDING_BYTES(0x4);
1709 u64 application_id;
1710 };
1711 static_assert(sizeof(AppletIdentityInfo) == 0x10, "AppletIdentityInfo has incorrect size.");
1676 LOG_WARNING(Service_AM, "(STUBBED) called"); 1712 LOG_WARNING(Service_AM, "(STUBBED) called");
1677 1713
1678 const AppletIdentityInfo applet_info{ 1714 const AppletIdentityInfo applet_info{
@@ -1737,6 +1773,55 @@ void ILibraryAppletSelfAccessor::PushInShowAlbum() {
1737 queue_data.emplace_back(std::move(settings_data)); 1773 queue_data.emplace_back(std::move(settings_data));
1738} 1774}
1739 1775
1776void ILibraryAppletSelfAccessor::PushInShowController() {
1777 const Applets::CommonArguments common_args = {
1778 .arguments_version = Applets::CommonArgumentVersion::Version3,
1779 .size = Applets::CommonArgumentSize::Version3,
1780 .library_version = static_cast<u32>(Applets::ControllerAppletVersion::Version8),
1781 .theme_color = Applets::ThemeColor::BasicBlack,
1782 .play_startup_sound = true,
1783 .system_tick = system.CoreTiming().GetClockTicks(),
1784 };
1785
1786 Applets::ControllerSupportArgNew user_args = {
1787 .header = {.player_count_min = 1,
1788 .player_count_max = 4,
1789 .enable_take_over_connection = true,
1790 .enable_left_justify = false,
1791 .enable_permit_joy_dual = true,
1792 .enable_single_mode = false,
1793 .enable_identification_color = false},
1794 .identification_colors = {},
1795 .enable_explain_text = false,
1796 .explain_text = {},
1797 };
1798
1799 Applets::ControllerSupportArgPrivate private_args = {
1800 .arg_private_size = sizeof(Applets::ControllerSupportArgPrivate),
1801 .arg_size = sizeof(Applets::ControllerSupportArgNew),
1802 .is_home_menu = true,
1803 .flag_1 = true,
1804 .mode = Applets::ControllerSupportMode::ShowControllerSupport,
1805 .caller = Applets::ControllerSupportCaller::
1806 Application, // switchbrew: Always zero except with
1807 // ShowControllerFirmwareUpdateForSystem/ShowControllerKeyRemappingForSystem,
1808 // which sets this to the input param
1809 .style_set = Core::HID::NpadStyleSet::None,
1810 .joy_hold_type = 0,
1811 };
1812 std::vector<u8> common_args_data(sizeof(common_args));
1813 std::vector<u8> private_args_data(sizeof(private_args));
1814 std::vector<u8> user_args_data(sizeof(user_args));
1815
1816 std::memcpy(common_args_data.data(), &common_args, sizeof(common_args));
1817 std::memcpy(private_args_data.data(), &private_args, sizeof(private_args));
1818 std::memcpy(user_args_data.data(), &user_args, sizeof(user_args));
1819
1820 queue_data.emplace_back(std::move(common_args_data));
1821 queue_data.emplace_back(std::move(private_args_data));
1822 queue_data.emplace_back(std::move(user_args_data));
1823}
1824
1740void ILibraryAppletSelfAccessor::PushInShowCabinetData() { 1825void ILibraryAppletSelfAccessor::PushInShowCabinetData() {
1741 const Applets::CommonArguments arguments{ 1826 const Applets::CommonArguments arguments{
1742 .arguments_version = Applets::CommonArgumentVersion::Version3, 1827 .arguments_version = Applets::CommonArgumentVersion::Version3,
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 8f8cb8a9e..905a71b9f 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -87,6 +87,7 @@ public:
87 87
88private: 88private:
89 void GetAppletResourceUserId(HLERequestContext& ctx); 89 void GetAppletResourceUserId(HLERequestContext& ctx);
90 void GetAppletResourceUserIdOfCallerApplet(HLERequestContext& ctx);
90 void AcquireForegroundRights(HLERequestContext& ctx); 91 void AcquireForegroundRights(HLERequestContext& ctx);
91}; 92};
92 93
@@ -345,6 +346,7 @@ private:
345 void PopInData(HLERequestContext& ctx); 346 void PopInData(HLERequestContext& ctx);
346 void PushOutData(HLERequestContext& ctx); 347 void PushOutData(HLERequestContext& ctx);
347 void GetLibraryAppletInfo(HLERequestContext& ctx); 348 void GetLibraryAppletInfo(HLERequestContext& ctx);
349 void GetMainAppletIdentityInfo(HLERequestContext& ctx);
348 void ExitProcessAndReturn(HLERequestContext& ctx); 350 void ExitProcessAndReturn(HLERequestContext& ctx);
349 void GetCallerAppletIdentityInfo(HLERequestContext& ctx); 351 void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
350 void GetDesirableKeyboardLayout(HLERequestContext& ctx); 352 void GetDesirableKeyboardLayout(HLERequestContext& ctx);
@@ -355,6 +357,7 @@ private:
355 void PushInShowCabinetData(); 357 void PushInShowCabinetData();
356 void PushInShowMiiEditData(); 358 void PushInShowMiiEditData();
357 void PushInShowSoftwareKeyboard(); 359 void PushInShowSoftwareKeyboard();
360 void PushInShowController();
358 361
359 std::deque<std::vector<u8>> queue_data; 362 std::deque<std::vector<u8>> queue_data;
360}; 363};
diff --git a/src/core/hle/service/am/applets/applet_controller.h b/src/core/hle/service/am/applets/applet_controller.h
index f6c64f633..9f839f3d7 100644
--- a/src/core/hle/service/am/applets/applet_controller.h
+++ b/src/core/hle/service/am/applets/applet_controller.h
@@ -56,7 +56,7 @@ enum class ControllerSupportResult : u32 {
56struct ControllerSupportArgPrivate { 56struct ControllerSupportArgPrivate {
57 u32 arg_private_size{}; 57 u32 arg_private_size{};
58 u32 arg_size{}; 58 u32 arg_size{};
59 bool flag_0{}; 59 bool is_home_menu{};
60 bool flag_1{}; 60 bool flag_1{};
61 ControllerSupportMode mode{}; 61 ControllerSupportMode mode{};
62 ControllerSupportCaller caller{}; 62 ControllerSupportCaller caller{};
diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp
index 8069f75b7..c65e32489 100644
--- a/src/core/hle/service/btm/btm.cpp
+++ b/src/core/hle/service/btm/btm.cpp
@@ -127,7 +127,7 @@ public:
127 127
128private: 128private:
129 void GetCore(HLERequestContext& ctx) { 129 void GetCore(HLERequestContext& ctx) {
130 LOG_DEBUG(Service_BTM, "called"); 130 LOG_WARNING(Service_BTM, "called");
131 131
132 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 132 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
133 rb.Push(ResultSuccess); 133 rb.Push(ResultSuccess);
@@ -263,13 +263,13 @@ public:
263 explicit IBtmSystemCore(Core::System& system_) : ServiceFramework{system_, "IBtmSystemCore"} { 263 explicit IBtmSystemCore(Core::System& system_) : ServiceFramework{system_, "IBtmSystemCore"} {
264 // clang-format off 264 // clang-format off
265 static const FunctionInfo functions[] = { 265 static const FunctionInfo functions[] = {
266 {0, nullptr, "StartGamepadPairing"}, 266 {0, &IBtmSystemCore::StartGamepadPairing, "StartGamepadPairing"},
267 {1, nullptr, "CancelGamepadPairing"}, 267 {1, &IBtmSystemCore::CancelGamepadPairing, "CancelGamepadPairing"},
268 {2, nullptr, "ClearGamepadPairingDatabase"}, 268 {2, nullptr, "ClearGamepadPairingDatabase"},
269 {3, nullptr, "GetPairedGamepadCount"}, 269 {3, nullptr, "GetPairedGamepadCount"},
270 {4, nullptr, "EnableRadio"}, 270 {4, nullptr, "EnableRadio"},
271 {5, nullptr, "DisableRadio"}, 271 {5, nullptr, "DisableRadio"},
272 {6, nullptr, "GetRadioOnOff"}, 272 {6, &IBtmSystemCore::IsRadioEnabled, "IsRadioEnabled"},
273 {7, nullptr, "AcquireRadioEvent"}, 273 {7, nullptr, "AcquireRadioEvent"},
274 {8, nullptr, "AcquireGamepadPairingEvent"}, 274 {8, nullptr, "AcquireGamepadPairingEvent"},
275 {9, nullptr, "IsGamepadPairingStarted"}, 275 {9, nullptr, "IsGamepadPairingStarted"},
@@ -280,18 +280,58 @@ public:
280 {14, nullptr, "AcquireAudioDeviceConnectionEvent"}, 280 {14, nullptr, "AcquireAudioDeviceConnectionEvent"},
281 {15, nullptr, "ConnectAudioDevice"}, 281 {15, nullptr, "ConnectAudioDevice"},
282 {16, nullptr, "IsConnectingAudioDevice"}, 282 {16, nullptr, "IsConnectingAudioDevice"},
283 {17, nullptr, "GetConnectedAudioDevices"}, 283 {17, &IBtmSystemCore::GetConnectedAudioDevices, "GetConnectedAudioDevices"},
284 {18, nullptr, "DisconnectAudioDevice"}, 284 {18, nullptr, "DisconnectAudioDevice"},
285 {19, nullptr, "AcquirePairedAudioDeviceInfoChangedEvent"}, 285 {19, nullptr, "AcquirePairedAudioDeviceInfoChangedEvent"},
286 {20, nullptr, "GetPairedAudioDevices"}, 286 {20, nullptr, "GetPairedAudioDevices"},
287 {21, nullptr, "RemoveAudioDevicePairing"}, 287 {21, nullptr, "RemoveAudioDevicePairing"},
288 {22, nullptr, "RequestAudioDeviceConnectionRejection"}, 288 {22, &IBtmSystemCore::RequestAudioDeviceConnectionRejection, "RequestAudioDeviceConnectionRejection"},
289 {23, nullptr, "CancelAudioDeviceConnectionRejection"} 289 {23, &IBtmSystemCore::CancelAudioDeviceConnectionRejection, "CancelAudioDeviceConnectionRejection"}
290 }; 290 };
291 // clang-format on 291 // clang-format on
292 292
293 RegisterHandlers(functions); 293 RegisterHandlers(functions);
294 } 294 }
295
296private:
297 void IsRadioEnabled(HLERequestContext& ctx) {
298 LOG_DEBUG(Service_BTM, "(STUBBED) called"); // Spams a lot when controller applet is running
299
300 IPC::ResponseBuilder rb{ctx, 3};
301 rb.Push(ResultSuccess);
302 rb.Push(true);
303 }
304
305 void StartGamepadPairing(HLERequestContext& ctx) {
306 LOG_WARNING(Service_BTM, "(STUBBED) called");
307 IPC::ResponseBuilder rb{ctx, 2};
308 rb.Push(ResultSuccess);
309 }
310
311 void CancelGamepadPairing(HLERequestContext& ctx) {
312 LOG_WARNING(Service_BTM, "(STUBBED) called");
313 IPC::ResponseBuilder rb{ctx, 2};
314 rb.Push(ResultSuccess);
315 }
316
317 void CancelAudioDeviceConnectionRejection(HLERequestContext& ctx) {
318 LOG_WARNING(Service_BTM, "(STUBBED) called");
319 IPC::ResponseBuilder rb{ctx, 2};
320 rb.Push(ResultSuccess);
321 }
322
323 void GetConnectedAudioDevices(HLERequestContext& ctx) {
324 LOG_WARNING(Service_BTM, "(STUBBED) called");
325 IPC::ResponseBuilder rb{ctx, 3};
326 rb.Push(ResultSuccess);
327 rb.Push<u32>(0);
328 }
329
330 void RequestAudioDeviceConnectionRejection(HLERequestContext& ctx) {
331 LOG_WARNING(Service_BTM, "(STUBBED) called");
332 IPC::ResponseBuilder rb{ctx, 2};
333 rb.Push(ResultSuccess);
334 }
295}; 335};
296 336
297class BTM_SYS final : public ServiceFramework<BTM_SYS> { 337class BTM_SYS final : public ServiceFramework<BTM_SYS> {
@@ -308,7 +348,7 @@ public:
308 348
309private: 349private:
310 void GetCore(HLERequestContext& ctx) { 350 void GetCore(HLERequestContext& ctx) {
311 LOG_DEBUG(Service_BTM, "called"); 351 LOG_WARNING(Service_BTM, "called");
312 352
313 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 353 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
314 rb.Push(ResultSuccess); 354 rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index d46bf917e..127af2b82 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -344,6 +344,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
344 controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices, 344 controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices,
345 Common::Input::PollingMode::Active); 345 Common::Input::PollingMode::Active);
346 } 346 }
347
347 SignalStyleSetChangedEvent(npad_id); 348 SignalStyleSetChangedEvent(npad_id);
348 WriteEmptyEntry(controller.shared_memory); 349 WriteEmptyEntry(controller.shared_memory);
349 hid_core.SetLastActiveController(npad_id); 350 hid_core.SetLastActiveController(npad_id);
@@ -1726,4 +1727,19 @@ const Controller_NPad::SixaxisParameters& Controller_NPad::GetSixaxisState(
1726 } 1727 }
1727} 1728}
1728 1729
1730Controller_NPad::AppletDetailedUiType Controller_NPad::GetAppletDetailedUiType(
1731 Core::HID::NpadIdType npad_id) {
1732
1733 auto controller = GetControllerFromNpadIdType(npad_id);
1734 auto shared_memory = controller.shared_memory;
1735 Service::HID::Controller_NPad::AppletFooterUiType applet_footer_type =
1736 shared_memory->applet_footer_type;
1737
1738 Controller_NPad::AppletDetailedUiType detailed_ui_type{
1739 .ui_variant = 0,
1740 .footer = applet_footer_type,
1741 };
1742 return detailed_ui_type;
1743}
1744
1729} // namespace Service::HID 1745} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index e23b4986c..cd93abdd1 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -78,6 +78,46 @@ public:
78 MaxActivationMode = 3, 78 MaxActivationMode = 3,
79 }; 79 };
80 80
81 // This is nn::hid::system::AppletFooterUiAttributesSet
82 struct AppletFooterUiAttributes {
83 INSERT_PADDING_BYTES(0x4);
84 };
85
86 // This is nn::hid::system::AppletFooterUiType
87 enum class AppletFooterUiType : u8 {
88 None = 0,
89 HandheldNone = 1,
90 HandheldJoyConLeftOnly = 2,
91 HandheldJoyConRightOnly = 3,
92 HandheldJoyConLeftJoyConRight = 4,
93 JoyDual = 5,
94 JoyDualLeftOnly = 6,
95 JoyDualRightOnly = 7,
96 JoyLeftHorizontal = 8,
97 JoyLeftVertical = 9,
98 JoyRightHorizontal = 10,
99 JoyRightVertical = 11,
100 SwitchProController = 12,
101 CompatibleProController = 13,
102 CompatibleJoyCon = 14,
103 LarkHvc1 = 15,
104 LarkHvc2 = 16,
105 LarkNesLeft = 17,
106 LarkNesRight = 18,
107 Lucia = 19,
108 Verification = 20,
109 Lagon = 21,
110 };
111
112 using AppletFooterUiVariant = u8;
113
114 // This is "nn::hid::system::AppletDetailedUiType".
115 struct AppletDetailedUiType {
116 AppletFooterUiVariant ui_variant;
117 INSERT_PADDING_BYTES(0x2);
118 AppletFooterUiType footer;
119 };
120 static_assert(sizeof(AppletDetailedUiType) == 0x4, "AppletDetailedUiType is an invalid size");
81 // This is nn::hid::NpadCommunicationMode 121 // This is nn::hid::NpadCommunicationMode
82 enum class NpadCommunicationMode : u64 { 122 enum class NpadCommunicationMode : u64 {
83 Mode_5ms = 0, 123 Mode_5ms = 0,
@@ -203,6 +243,7 @@ public:
203 static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle); 243 static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle);
204 static Result VerifyValidSixAxisSensorHandle( 244 static Result VerifyValidSixAxisSensorHandle(
205 const Core::HID::SixAxisSensorHandle& device_handle); 245 const Core::HID::SixAxisSensorHandle& device_handle);
246 AppletDetailedUiType GetAppletDetailedUiType(Core::HID::NpadIdType npad_id);
206 247
207private: 248private:
208 static constexpr std::size_t NPAD_COUNT = 10; 249 static constexpr std::size_t NPAD_COUNT = 10;
@@ -360,37 +401,6 @@ private:
360 static_assert(sizeof(NfcXcdDeviceHandleStateImpl) == 0x18, 401 static_assert(sizeof(NfcXcdDeviceHandleStateImpl) == 0x18,
361 "NfcXcdDeviceHandleStateImpl is an invalid size"); 402 "NfcXcdDeviceHandleStateImpl is an invalid size");
362 403
363 // This is nn::hid::system::AppletFooterUiAttributesSet
364 struct AppletFooterUiAttributes {
365 INSERT_PADDING_BYTES(0x4);
366 };
367
368 // This is nn::hid::system::AppletFooterUiType
369 enum class AppletFooterUiType : u8 {
370 None = 0,
371 HandheldNone = 1,
372 HandheldJoyConLeftOnly = 2,
373 HandheldJoyConRightOnly = 3,
374 HandheldJoyConLeftJoyConRight = 4,
375 JoyDual = 5,
376 JoyDualLeftOnly = 6,
377 JoyDualRightOnly = 7,
378 JoyLeftHorizontal = 8,
379 JoyLeftVertical = 9,
380 JoyRightHorizontal = 10,
381 JoyRightVertical = 11,
382 SwitchProController = 12,
383 CompatibleProController = 13,
384 CompatibleJoyCon = 14,
385 LarkHvc1 = 15,
386 LarkHvc2 = 16,
387 LarkNesLeft = 17,
388 LarkNesRight = 18,
389 Lucia = 19,
390 Verification = 20,
391 Lagon = 21,
392 };
393
394 // This is nn::hid::NpadLarkType 404 // This is nn::hid::NpadLarkType
395 enum class NpadLarkType : u32 { 405 enum class NpadLarkType : u32 {
396 Invalid, 406 Invalid,
diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp
index 9094fdcc7..9caed6541 100644
--- a/src/core/hle/service/hid/hid_server.cpp
+++ b/src/core/hle/service/hid/hid_server.cpp
@@ -1222,8 +1222,8 @@ void IHidServer::SetNpadJoyAssignmentModeDual(HLERequestContext& ctx) {
1222 controller.SetNpadMode(new_npad_id, parameters.npad_id, {}, 1222 controller.SetNpadMode(new_npad_id, parameters.npad_id, {},
1223 Controller_NPad::NpadJoyAssignmentMode::Dual); 1223 Controller_NPad::NpadJoyAssignmentMode::Dual);
1224 1224
1225 LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id, 1225 LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id,
1226 parameters.applet_resource_user_id); 1226 parameters.applet_resource_user_id); // Spams a lot when controller applet is open
1227 1227
1228 IPC::ResponseBuilder rb{ctx, 2}; 1228 IPC::ResponseBuilder rb{ctx, 2};
1229 rb.Push(ResultSuccess); 1229 rb.Push(ResultSuccess);
diff --git a/src/core/hle/service/hid/hid_system_server.cpp b/src/core/hle/service/hid/hid_system_server.cpp
index 83cfadada..6f1902ee5 100644
--- a/src/core/hle/service/hid/hid_system_server.cpp
+++ b/src/core/hle/service/hid/hid_system_server.cpp
@@ -36,24 +36,24 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr<Resour
36 {233, nullptr, "GetXcdHandleForNpadWithIrSensor"}, 36 {233, nullptr, "GetXcdHandleForNpadWithIrSensor"},
37 {301, nullptr, "ActivateNpadSystem"}, 37 {301, nullptr, "ActivateNpadSystem"},
38 {303, &IHidSystemServer::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"}, 38 {303, &IHidSystemServer::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"},
39 {304, nullptr, "EnableAssigningSingleOnSlSrPress"}, 39 {304, &IHidSystemServer::EnableAssigningSingleOnSlSrPress, "EnableAssigningSingleOnSlSrPress"},
40 {305, nullptr, "DisableAssigningSingleOnSlSrPress"}, 40 {305, &IHidSystemServer::DisableAssigningSingleOnSlSrPress, "DisableAssigningSingleOnSlSrPress"},
41 {306, &IHidSystemServer::GetLastActiveNpad, "GetLastActiveNpad"}, 41 {306, &IHidSystemServer::GetLastActiveNpad, "GetLastActiveNpad"},
42 {307, nullptr, "GetNpadSystemExtStyle"}, 42 {307, nullptr, "GetNpadSystemExtStyle"},
43 {308, nullptr, "ApplyNpadSystemCommonPolicyFull"}, 43 {308, &IHidSystemServer::ApplyNpadSystemCommonPolicyFull, "ApplyNpadSystemCommonPolicyFull"},
44 {309, nullptr, "GetNpadFullKeyGripColor"}, 44 {309, &IHidSystemServer::GetNpadFullKeyGripColor, "GetNpadFullKeyGripColor"},
45 {310, nullptr, "GetMaskedSupportedNpadStyleSet"}, 45 {310, &IHidSystemServer::GetMaskedSupportedNpadStyleSet, "GetMaskedSupportedNpadStyleSet"},
46 {311, nullptr, "SetNpadPlayerLedBlinkingDevice"}, 46 {311, nullptr, "SetNpadPlayerLedBlinkingDevice"},
47 {312, nullptr, "SetSupportedNpadStyleSetAll"}, 47 {312, &IHidSystemServer::SetSupportedNpadStyleSetAll, "SetSupportedNpadStyleSetAll"},
48 {313, nullptr, "GetNpadCaptureButtonAssignment"}, 48 {313, nullptr, "GetNpadCaptureButtonAssignment"},
49 {314, nullptr, "GetAppletFooterUiType"}, 49 {314, nullptr, "GetAppletFooterUiType"},
50 {315, nullptr, "GetAppletDetailedUiType"}, 50 {315, &IHidSystemServer::GetAppletDetailedUiType, "GetAppletDetailedUiType"},
51 {316, nullptr, "GetNpadInterfaceType"}, 51 {316, &IHidSystemServer::GetNpadInterfaceType, "GetNpadInterfaceType"},
52 {317, nullptr, "GetNpadLeftRightInterfaceType"}, 52 {317, &IHidSystemServer::GetNpadLeftRightInterfaceType, "GetNpadLeftRightInterfaceType"},
53 {318, nullptr, "HasBattery"}, 53 {318, &IHidSystemServer::HasBattery, "HasBattery"},
54 {319, nullptr, "HasLeftRightBattery"}, 54 {319, &IHidSystemServer::HasLeftRightBattery, "HasLeftRightBattery"},
55 {321, &IHidSystemServer::GetUniquePadsFromNpad, "GetUniquePadsFromNpad"}, 55 {321, &IHidSystemServer::GetUniquePadsFromNpad, "GetUniquePadsFromNpad"},
56 {322, nullptr, "GetIrSensorState"}, 56 {322, &IHidSystemServer::GetIrSensorState, "GetIrSensorState"},
57 {323, nullptr, "GetXcdHandleForNpadWithIrSensor"}, 57 {323, nullptr, "GetXcdHandleForNpadWithIrSensor"},
58 {324, nullptr, "GetUniquePadButtonSet"}, 58 {324, nullptr, "GetUniquePadButtonSet"},
59 {325, nullptr, "GetUniquePadColor"}, 59 {325, nullptr, "GetUniquePadColor"},
@@ -85,15 +85,15 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr<Resour
85 {541, nullptr, "GetPlayReportControllerUsages"}, 85 {541, nullptr, "GetPlayReportControllerUsages"},
86 {542, nullptr, "AcquirePlayReportRegisteredDeviceUpdateEvent"}, 86 {542, nullptr, "AcquirePlayReportRegisteredDeviceUpdateEvent"},
87 {543, nullptr, "GetRegisteredDevicesOld"}, 87 {543, nullptr, "GetRegisteredDevicesOld"},
88 {544, nullptr, "AcquireConnectionTriggerTimeoutEvent"}, 88 {544, &IHidSystemServer::AcquireConnectionTriggerTimeoutEvent, "AcquireConnectionTriggerTimeoutEvent"},
89 {545, nullptr, "SendConnectionTrigger"}, 89 {545, nullptr, "SendConnectionTrigger"},
90 {546, nullptr, "AcquireDeviceRegisteredEventForControllerSupport"}, 90 {546, &IHidSystemServer::AcquireDeviceRegisteredEventForControllerSupport, "AcquireDeviceRegisteredEventForControllerSupport"},
91 {547, nullptr, "GetAllowedBluetoothLinksCount"}, 91 {547, nullptr, "GetAllowedBluetoothLinksCount"},
92 {548, nullptr, "GetRegisteredDevices"}, 92 {548, &IHidSystemServer::GetRegisteredDevices, "GetRegisteredDevices"},
93 {549, nullptr, "GetConnectableRegisteredDevices"}, 93 {549, nullptr, "GetConnectableRegisteredDevices"},
94 {700, nullptr, "ActivateUniquePad"}, 94 {700, nullptr, "ActivateUniquePad"},
95 {702, nullptr, "AcquireUniquePadConnectionEventHandle"}, 95 {702, &IHidSystemServer::AcquireUniquePadConnectionEventHandle, "AcquireUniquePadConnectionEventHandle"},
96 {703, nullptr, "GetUniquePadIds"}, 96 {703, &IHidSystemServer::GetUniquePadIds, "GetUniquePadIds"},
97 {751, &IHidSystemServer::AcquireJoyDetachOnBluetoothOffEventHandle, "AcquireJoyDetachOnBluetoothOffEventHandle"}, 97 {751, &IHidSystemServer::AcquireJoyDetachOnBluetoothOffEventHandle, "AcquireJoyDetachOnBluetoothOffEventHandle"},
98 {800, nullptr, "ListSixAxisSensorHandles"}, 98 {800, nullptr, "ListSixAxisSensorHandles"},
99 {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"}, 99 {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"},
@@ -123,10 +123,10 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr<Resour
123 {850, &IHidSystemServer::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"}, 123 {850, &IHidSystemServer::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"},
124 {851, nullptr, "EnableUsbFullKeyController"}, 124 {851, nullptr, "EnableUsbFullKeyController"},
125 {852, nullptr, "IsUsbConnected"}, 125 {852, nullptr, "IsUsbConnected"},
126 {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"}, 126 {870, &IHidSystemServer::IsHandheldButtonPressedOnConsoleMode, "IsHandheldButtonPressedOnConsoleMode"},
127 {900, nullptr, "ActivateInputDetector"}, 127 {900, nullptr, "ActivateInputDetector"},
128 {901, nullptr, "NotifyInputDetector"}, 128 {901, nullptr, "NotifyInputDetector"},
129 {1000, nullptr, "InitializeFirmwareUpdate"}, 129 {1000, &IHidSystemServer::InitializeFirmwareUpdate, "InitializeFirmwareUpdate"},
130 {1001, nullptr, "GetFirmwareVersion"}, 130 {1001, nullptr, "GetFirmwareVersion"},
131 {1002, nullptr, "GetAvailableFirmwareVersion"}, 131 {1002, nullptr, "GetAvailableFirmwareVersion"},
132 {1003, nullptr, "IsFirmwareUpdateAvailable"}, 132 {1003, nullptr, "IsFirmwareUpdateAvailable"},
@@ -149,6 +149,7 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr<Resour
149 {1132, nullptr, "CheckUsbFirmwareUpdateRequired"}, 149 {1132, nullptr, "CheckUsbFirmwareUpdateRequired"},
150 {1133, nullptr, "StartUsbFirmwareUpdate"}, 150 {1133, nullptr, "StartUsbFirmwareUpdate"},
151 {1134, nullptr, "GetUsbFirmwareUpdateState"}, 151 {1134, nullptr, "GetUsbFirmwareUpdateState"},
152 {1135, &IHidSystemServer::InitializeUsbFirmwareUpdateWithoutMemory, "InitializeUsbFirmwareUpdateWithoutMemory"},
152 {1150, nullptr, "SetTouchScreenMagnification"}, 153 {1150, nullptr, "SetTouchScreenMagnification"},
153 {1151, nullptr, "GetTouchScreenFirmwareVersion"}, 154 {1151, nullptr, "GetTouchScreenFirmwareVersion"},
154 {1152, nullptr, "SetTouchScreenDefaultConfiguration"}, 155 {1152, nullptr, "SetTouchScreenDefaultConfiguration"},
@@ -220,11 +221,20 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr<Resour
220 221
221 RegisterHandlers(functions); 222 RegisterHandlers(functions);
222 223
223 joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent"); 224 joy_detach_event = service_context.CreateEvent("IHidSystemServer::JoyDetachEvent");
225 acquire_device_registered_event =
226 service_context.CreateEvent("IHidSystemServer::AcquireDeviceRegisteredEvent");
227 acquire_connection_trigger_timeout_event =
228 service_context.CreateEvent("IHidSystemServer::AcquireConnectionTriggerTimeoutEvent");
229 unique_pad_connection_event =
230 service_context.CreateEvent("IHidSystemServer::AcquireUniquePadConnectionEventHandle");
224} 231}
225 232
226IHidSystemServer::~IHidSystemServer() { 233IHidSystemServer::~IHidSystemServer() {
227 service_context.CloseEvent(joy_detach_event); 234 service_context.CloseEvent(joy_detach_event);
235 service_context.CloseEvent(acquire_device_registered_event);
236 service_context.CloseEvent(acquire_connection_trigger_timeout_event);
237 service_context.CloseEvent(unique_pad_connection_event);
228}; 238};
229 239
230void IHidSystemServer::ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { 240void IHidSystemServer::ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) {
@@ -238,29 +248,241 @@ void IHidSystemServer::ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) {
238 rb.Push(ResultSuccess); 248 rb.Push(ResultSuccess);
239} 249}
240 250
251void IHidSystemServer::EnableAssigningSingleOnSlSrPress(HLERequestContext& ctx) {
252 LOG_WARNING(Service_HID, "(STUBBED) called");
253
254 IPC::ResponseBuilder rb{ctx, 2};
255 rb.Push(ResultSuccess);
256}
257
258void IHidSystemServer::DisableAssigningSingleOnSlSrPress(HLERequestContext& ctx) {
259 LOG_WARNING(Service_HID, "(STUBBED) called");
260
261 IPC::ResponseBuilder rb{ctx, 2};
262 rb.Push(ResultSuccess);
263}
264
241void IHidSystemServer::GetLastActiveNpad(HLERequestContext& ctx) { 265void IHidSystemServer::GetLastActiveNpad(HLERequestContext& ctx) {
242 LOG_DEBUG(Service_HID, "(STUBBED) called"); 266 LOG_DEBUG(Service_HID, "(STUBBED) called"); // Spams a lot when controller applet is running
243 267
244 IPC::ResponseBuilder rb{ctx, 3}; 268 IPC::ResponseBuilder rb{ctx, 3};
245 rb.Push(ResultSuccess); 269 rb.Push(ResultSuccess);
246 rb.PushEnum(system.HIDCore().GetLastActiveController()); 270 rb.PushEnum(system.HIDCore().GetLastActiveController());
247} 271}
248 272
273void IHidSystemServer::ApplyNpadSystemCommonPolicyFull(HLERequestContext& ctx) {
274 LOG_WARNING(Service_HID, "called");
275
276 GetResourceManager()
277 ->GetController<Controller_NPad>(HidController::NPad)
278 .ApplyNpadSystemCommonPolicy();
279
280 IPC::ResponseBuilder rb{ctx, 2};
281 rb.Push(ResultSuccess);
282}
283
284void IHidSystemServer::GetNpadFullKeyGripColor(HLERequestContext& ctx) {
285 IPC::RequestParser rp{ctx};
286 const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
287
288 LOG_DEBUG(Service_HID, "(STUBBED) called, npad_id_type={}",
289 npad_id_type); // Spams a lot when controller applet is running
290
291 Core::HID::NpadColor left_color{};
292 Core::HID::NpadColor right_color{};
293 // TODO: Get colors from Npad
294
295 IPC::ResponseBuilder rb{ctx, 4};
296 rb.Push(ResultSuccess);
297 rb.PushRaw(left_color);
298 rb.PushRaw(right_color);
299}
300
301void IHidSystemServer::GetMaskedSupportedNpadStyleSet(HLERequestContext& ctx) {
302 IPC::RequestParser rp{ctx};
303
304 LOG_INFO(Service_HID, "(STUBBED) called");
305
306 Core::HID::NpadStyleSet supported_styleset =
307 GetResourceManager()
308 ->GetController<Controller_NPad>(HidController::NPad)
309 .GetSupportedStyleSet()
310 .raw;
311
312 IPC::ResponseBuilder rb{ctx, 3};
313 rb.Push(ResultSuccess);
314 rb.PushEnum(supported_styleset);
315}
316
317void IHidSystemServer::SetSupportedNpadStyleSetAll(HLERequestContext& ctx) {
318 IPC::RequestParser rp{ctx};
319
320 LOG_INFO(Service_HID, "(STUBBED) called");
321
322 Core::HID::NpadStyleSet supported_styleset =
323 GetResourceManager()
324 ->GetController<Controller_NPad>(HidController::NPad)
325 .GetSupportedStyleSet()
326 .raw;
327
328 IPC::ResponseBuilder rb{ctx, 3};
329 rb.Push(ResultSuccess);
330 rb.PushEnum(supported_styleset);
331}
332
333void IHidSystemServer::GetAppletDetailedUiType(HLERequestContext& ctx) {
334 IPC::RequestParser rp{ctx};
335 const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
336
337 LOG_DEBUG(Service_HID, "called, npad_id_type={}",
338 npad_id_type); // Spams a lot when controller applet is running
339
340 const Service::HID::Controller_NPad::AppletDetailedUiType detailed_ui_type =
341 GetResourceManager()
342 ->GetController<Controller_NPad>(HidController::NPad)
343 .GetAppletDetailedUiType(npad_id_type);
344
345 IPC::ResponseBuilder rb{ctx, 3};
346 rb.Push(ResultSuccess);
347 rb.PushRaw(detailed_ui_type);
348}
349
350void IHidSystemServer::GetNpadInterfaceType(HLERequestContext& ctx) {
351 IPC::RequestParser rp{ctx};
352 const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
353
354 LOG_DEBUG(Service_HID, "(STUBBED) called, npad_id_type={}",
355 npad_id_type); // Spams a lot when controller applet is running
356
357 IPC::ResponseBuilder rb{ctx, 3};
358 rb.Push(ResultSuccess);
359 rb.PushEnum(Core::HID::NpadInterfaceType::Bluetooth);
360}
361
362void IHidSystemServer::GetNpadLeftRightInterfaceType(HLERequestContext& ctx) {
363 IPC::RequestParser rp{ctx};
364 const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
365
366 LOG_DEBUG(Service_HID, "(STUBBED) called, npad_id_type={}",
367 npad_id_type); // Spams a lot when controller applet is running
368
369 IPC::ResponseBuilder rb{ctx, 4};
370 rb.Push(ResultSuccess);
371 rb.PushEnum(Core::HID::NpadInterfaceType::Bluetooth);
372 rb.PushEnum(Core::HID::NpadInterfaceType::Bluetooth);
373}
374
375void IHidSystemServer::HasBattery(HLERequestContext& ctx) {
376 IPC::RequestParser rp{ctx};
377 const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
378
379 LOG_DEBUG(Service_HID, "(STUBBED) called, npad_id_type={}",
380 npad_id_type); // Spams a lot when controller applet is running
381
382 IPC::ResponseBuilder rb{ctx, 3};
383 rb.Push(ResultSuccess);
384 rb.Push(false);
385}
386
387void IHidSystemServer::HasLeftRightBattery(HLERequestContext& ctx) {
388 IPC::RequestParser rp{ctx};
389 const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
390
391 LOG_DEBUG(Service_HID, "(STUBBED) called, npad_id_type={}",
392 npad_id_type); // Spams a lot when controller applet is running
393
394 struct LeftRightBattery {
395 bool left;
396 bool right;
397 };
398
399 LeftRightBattery left_right_battery{
400 .left = false,
401 .right = false,
402 };
403
404 IPC::ResponseBuilder rb{ctx, 3};
405 rb.Push(ResultSuccess);
406 rb.PushRaw(left_right_battery);
407}
408
249void IHidSystemServer::GetUniquePadsFromNpad(HLERequestContext& ctx) { 409void IHidSystemServer::GetUniquePadsFromNpad(HLERequestContext& ctx) {
250 IPC::RequestParser rp{ctx}; 410 IPC::RequestParser rp{ctx};
251 const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()}; 411 const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()};
252 412
253 LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type); 413 LOG_DEBUG(Service_HID, "(STUBBED) called, npad_id_type={}",
414 npad_id_type); // Spams a lot when controller applet is running
254 415
255 const std::vector<Core::HID::UniquePadId> unique_pads{}; 416 const std::vector<Core::HID::UniquePadId> unique_pads{};
256 417
257 ctx.WriteBuffer(unique_pads); 418 if (!unique_pads.empty()) {
419 ctx.WriteBuffer(unique_pads);
420 }
258 421
259 IPC::ResponseBuilder rb{ctx, 3}; 422 IPC::ResponseBuilder rb{ctx, 3};
260 rb.Push(ResultSuccess); 423 rb.Push(ResultSuccess);
261 rb.Push(static_cast<u32>(unique_pads.size())); 424 rb.Push(static_cast<u32>(unique_pads.size()));
262} 425}
263 426
427void IHidSystemServer::GetIrSensorState(HLERequestContext& ctx) {
428 IPC::RequestParser rp{ctx};
429
430 LOG_WARNING(Service_HID, "(STUBBED) called");
431
432 IPC::ResponseBuilder rb{ctx, 2};
433 rb.Push(ResultSuccess);
434}
435
436void IHidSystemServer::AcquireConnectionTriggerTimeoutEvent(HLERequestContext& ctx) {
437 LOG_INFO(Service_AM, "(STUBBED) called");
438
439 IPC::ResponseBuilder rb{ctx, 2, 1};
440 rb.Push(ResultSuccess);
441 rb.PushCopyObjects(acquire_device_registered_event->GetReadableEvent());
442}
443
444void IHidSystemServer::AcquireDeviceRegisteredEventForControllerSupport(HLERequestContext& ctx) {
445 LOG_INFO(Service_HID, "(STUBBED) called");
446
447 IPC::ResponseBuilder rb{ctx, 2, 1};
448 rb.Push(ResultSuccess);
449 rb.PushCopyObjects(acquire_device_registered_event->GetReadableEvent());
450}
451
452void IHidSystemServer::GetRegisteredDevices(HLERequestContext& ctx) {
453 LOG_WARNING(Service_HID, "(STUBBED) called");
454
455 struct RegisterData {
456 std::array<u8, 0x68> data;
457 };
458 static_assert(sizeof(RegisterData) == 0x68, "RegisterData is an invalid size");
459 std::vector<RegisterData> registered_devices{};
460
461 if (!registered_devices.empty()) {
462 ctx.WriteBuffer(registered_devices);
463 }
464
465 IPC::ResponseBuilder rb{ctx, 4};
466 rb.Push(ResultSuccess);
467 rb.Push<u64>(registered_devices.size());
468}
469
470void IHidSystemServer::AcquireUniquePadConnectionEventHandle(HLERequestContext& ctx) {
471 LOG_WARNING(Service_HID, "(STUBBED) called");
472
473 IPC::ResponseBuilder rb{ctx, 2, 1};
474 rb.PushCopyObjects(unique_pad_connection_event->GetReadableEvent());
475 rb.Push(ResultSuccess);
476}
477
478void IHidSystemServer::GetUniquePadIds(HLERequestContext& ctx) {
479 LOG_WARNING(Service_HID, "(STUBBED) called");
480
481 IPC::ResponseBuilder rb{ctx, 4};
482 rb.Push(ResultSuccess);
483 rb.Push<u64>(0);
484}
485
264void IHidSystemServer::AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx) { 486void IHidSystemServer::AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx) {
265 LOG_INFO(Service_AM, "called"); 487 LOG_INFO(Service_AM, "called");
266 488
@@ -279,6 +501,31 @@ void IHidSystemServer::IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) {
279 rb.Push(is_enabled); 501 rb.Push(is_enabled);
280} 502}
281 503
504void IHidSystemServer::IsHandheldButtonPressedOnConsoleMode(HLERequestContext& ctx) {
505 const bool button_pressed = false;
506
507 LOG_DEBUG(Service_HID, "(STUBBED) called, is_enabled={}",
508 button_pressed); // Spams a lot when controller applet is open
509
510 IPC::ResponseBuilder rb{ctx, 3};
511 rb.Push(ResultSuccess);
512 rb.Push(button_pressed);
513}
514
515void IHidSystemServer::InitializeFirmwareUpdate(HLERequestContext& ctx) {
516 LOG_WARNING(Service_HID, "(STUBBED) called");
517
518 IPC::ResponseBuilder rb{ctx, 2};
519 rb.Push(ResultSuccess);
520}
521
522void IHidSystemServer::InitializeUsbFirmwareUpdateWithoutMemory(HLERequestContext& ctx) {
523 LOG_WARNING(Service_HID, "(STUBBED) called");
524
525 IPC::ResponseBuilder rb{ctx, 2};
526 rb.Push(ResultSuccess);
527}
528
282void IHidSystemServer::GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) { 529void IHidSystemServer::GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) {
283 LOG_WARNING(Service_HID, "(STUBBED) called"); 530 LOG_WARNING(Service_HID, "(STUBBED) called");
284 531
diff --git a/src/core/hle/service/hid/hid_system_server.h b/src/core/hle/service/hid/hid_system_server.h
index d4b3910fa..822d5e5b9 100644
--- a/src/core/hle/service/hid/hid_system_server.h
+++ b/src/core/hle/service/hid/hid_system_server.h
@@ -24,15 +24,38 @@ public:
24 24
25private: 25private:
26 void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx); 26 void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx);
27 void EnableAssigningSingleOnSlSrPress(HLERequestContext& ctx);
28 void DisableAssigningSingleOnSlSrPress(HLERequestContext& ctx);
27 void GetLastActiveNpad(HLERequestContext& ctx); 29 void GetLastActiveNpad(HLERequestContext& ctx);
30 void ApplyNpadSystemCommonPolicyFull(HLERequestContext& ctx);
31 void GetNpadFullKeyGripColor(HLERequestContext& ctx);
32 void GetMaskedSupportedNpadStyleSet(HLERequestContext& ctx);
33 void SetSupportedNpadStyleSetAll(HLERequestContext& ctx);
34 void GetAppletDetailedUiType(HLERequestContext& ctx);
35 void GetNpadInterfaceType(HLERequestContext& ctx);
36 void GetNpadLeftRightInterfaceType(HLERequestContext& ctx);
37 void HasBattery(HLERequestContext& ctx);
38 void HasLeftRightBattery(HLERequestContext& ctx);
28 void GetUniquePadsFromNpad(HLERequestContext& ctx); 39 void GetUniquePadsFromNpad(HLERequestContext& ctx);
40 void GetIrSensorState(HLERequestContext& ctx);
41 void AcquireConnectionTriggerTimeoutEvent(HLERequestContext& ctx);
42 void AcquireDeviceRegisteredEventForControllerSupport(HLERequestContext& ctx);
43 void GetRegisteredDevices(HLERequestContext& ctx);
44 void AcquireUniquePadConnectionEventHandle(HLERequestContext& ctx);
45 void GetUniquePadIds(HLERequestContext& ctx);
29 void AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx); 46 void AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx);
30 void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx); 47 void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx);
48 void IsHandheldButtonPressedOnConsoleMode(HLERequestContext& ctx);
49 void InitializeFirmwareUpdate(HLERequestContext& ctx);
50 void InitializeUsbFirmwareUpdateWithoutMemory(HLERequestContext& ctx);
31 void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx); 51 void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx);
32 52
33 std::shared_ptr<ResourceManager> GetResourceManager(); 53 std::shared_ptr<ResourceManager> GetResourceManager();
34 54
55 Kernel::KEvent* acquire_connection_trigger_timeout_event;
56 Kernel::KEvent* acquire_device_registered_event;
35 Kernel::KEvent* joy_detach_event; 57 Kernel::KEvent* joy_detach_event;
58 Kernel::KEvent* unique_pad_connection_event;
36 KernelHelpers::ServiceContext service_context; 59 KernelHelpers::ServiceContext service_context;
37 std::shared_ptr<ResourceManager> resource_manager; 60 std::shared_ptr<ResourceManager> resource_manager;
38}; 61};
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index 7927f8264..961f89a14 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -115,12 +115,20 @@ public:
115 {400, nullptr, "InitializeSystem"}, 115 {400, nullptr, "InitializeSystem"},
116 {401, nullptr, "FinalizeSystem"}, 116 {401, nullptr, "FinalizeSystem"},
117 {402, nullptr, "SetOperationMode"}, 117 {402, nullptr, "SetOperationMode"},
118 {403, nullptr, "InitializeSystem2"}, 118 {403, &ISystemLocalCommunicationService::InitializeSystem2, "InitializeSystem2"},
119 }; 119 };
120 // clang-format on 120 // clang-format on
121 121
122 RegisterHandlers(functions); 122 RegisterHandlers(functions);
123 } 123 }
124
125private:
126 void InitializeSystem2(HLERequestContext& ctx) {
127 LOG_WARNING(Service_LDN, "(STUBBED) called");
128
129 IPC::ResponseBuilder rb{ctx, 2};
130 rb.Push(ResultSuccess);
131 }
124}; 132};
125 133
126class IUserLocalCommunicationService final 134class IUserLocalCommunicationService final
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index ec3af80af..19c667b42 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -431,8 +431,7 @@ void SET_SYS::GetAutoUpdateEnableFlag(HLERequestContext& ctx) {
431void SET_SYS::GetBatteryPercentageFlag(HLERequestContext& ctx) { 431void SET_SYS::GetBatteryPercentageFlag(HLERequestContext& ctx) {
432 u8 battery_percentage_flag{1}; 432 u8 battery_percentage_flag{1};
433 433
434 LOG_WARNING(Service_SET, "(STUBBED) called, battery_percentage_flag={}", 434 LOG_DEBUG(Service_SET, "(STUBBED) called, battery_percentage_flag={}", battery_percentage_flag);
435 battery_percentage_flag);
436 435
437 IPC::ResponseBuilder rb{ctx, 3}; 436 IPC::ResponseBuilder rb{ctx, 3};
438 rb.Push(ResultSuccess); 437 rb.Push(ResultSuccess);
@@ -492,6 +491,29 @@ void SET_SYS::GetChineseTraditionalInputMethod(HLERequestContext& ctx) {
492 rb.PushEnum(ChineseTraditionalInputMethod::Unknown0); 491 rb.PushEnum(ChineseTraditionalInputMethod::Unknown0);
493} 492}
494 493
494void SET_SYS::GetHomeMenuScheme(HLERequestContext& ctx) {
495 LOG_DEBUG(Service_SET, "(STUBBED) called");
496
497 const HomeMenuScheme default_color = {
498 .main = 0xFF323232,
499 .back = 0xFF323232,
500 .sub = 0xFFFFFFFF,
501 .bezel = 0xFFFFFFFF,
502 .extra = 0xFF000000,
503 };
504
505 IPC::ResponseBuilder rb{ctx, 7};
506 rb.Push(ResultSuccess);
507 rb.PushRaw(default_color);
508}
509
510void SET_SYS::GetHomeMenuSchemeModel(HLERequestContext& ctx) {
511 LOG_WARNING(Service_SET, "(STUBBED) called");
512
513 IPC::ResponseBuilder rb{ctx, 3};
514 rb.Push(ResultSuccess);
515 rb.Push(0);
516}
495void SET_SYS::GetFieldTestingFlag(HLERequestContext& ctx) { 517void SET_SYS::GetFieldTestingFlag(HLERequestContext& ctx) {
496 LOG_WARNING(Service_SET, "(STUBBED) called"); 518 LOG_WARNING(Service_SET, "(STUBBED) called");
497 519
@@ -674,7 +696,7 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
674 {171, nullptr, "SetChineseTraditionalInputMethod"}, 696 {171, nullptr, "SetChineseTraditionalInputMethod"},
675 {172, nullptr, "GetPtmCycleCountReliability"}, 697 {172, nullptr, "GetPtmCycleCountReliability"},
676 {173, nullptr, "SetPtmCycleCountReliability"}, 698 {173, nullptr, "SetPtmCycleCountReliability"},
677 {174, nullptr, "GetHomeMenuScheme"}, 699 {174, &SET_SYS::GetHomeMenuScheme, "GetHomeMenuScheme"},
678 {175, nullptr, "GetThemeSettings"}, 700 {175, nullptr, "GetThemeSettings"},
679 {176, nullptr, "SetThemeSettings"}, 701 {176, nullptr, "SetThemeSettings"},
680 {177, nullptr, "GetThemeKey"}, 702 {177, nullptr, "GetThemeKey"},
@@ -685,7 +707,7 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
685 {182, nullptr, "SetT"}, 707 {182, nullptr, "SetT"},
686 {183, nullptr, "GetPlatformRegion"}, 708 {183, nullptr, "GetPlatformRegion"},
687 {184, nullptr, "SetPlatformRegion"}, 709 {184, nullptr, "SetPlatformRegion"},
688 {185, nullptr, "GetHomeMenuSchemeModel"}, 710 {185, &SET_SYS::GetHomeMenuSchemeModel, "GetHomeMenuSchemeModel"},
689 {186, nullptr, "GetMemoryUsageRateFlag"}, 711 {186, nullptr, "GetMemoryUsageRateFlag"},
690 {187, nullptr, "GetTouchScreenMode"}, 712 {187, nullptr, "GetTouchScreenMode"},
691 {188, nullptr, "SetTouchScreenMode"}, 713 {188, nullptr, "SetTouchScreenMode"},
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index c7dba2a9e..93023c6dd 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -269,6 +269,16 @@ private:
269 }; 269 };
270 static_assert(sizeof(EulaVersion) == 0x30, "EulaVersion is incorrect size"); 270 static_assert(sizeof(EulaVersion) == 0x30, "EulaVersion is incorrect size");
271 271
272 /// This is nn::settings::system::HomeMenuScheme
273 struct HomeMenuScheme {
274 u32 main;
275 u32 back;
276 u32 sub;
277 u32 bezel;
278 u32 extra;
279 };
280 static_assert(sizeof(HomeMenuScheme) == 0x14, "HomeMenuScheme is incorrect size");
281
272 void SetLanguageCode(HLERequestContext& ctx); 282 void SetLanguageCode(HLERequestContext& ctx);
273 void GetFirmwareVersion(HLERequestContext& ctx); 283 void GetFirmwareVersion(HLERequestContext& ctx);
274 void GetFirmwareVersion2(HLERequestContext& ctx); 284 void GetFirmwareVersion2(HLERequestContext& ctx);
@@ -305,6 +315,8 @@ private:
305 void GetKeyboardLayout(HLERequestContext& ctx); 315 void GetKeyboardLayout(HLERequestContext& ctx);
306 void GetChineseTraditionalInputMethod(HLERequestContext& ctx); 316 void GetChineseTraditionalInputMethod(HLERequestContext& ctx);
307 void GetFieldTestingFlag(HLERequestContext& ctx); 317 void GetFieldTestingFlag(HLERequestContext& ctx);
318 void GetHomeMenuScheme(HLERequestContext& ctx);
319 void GetHomeMenuSchemeModel(HLERequestContext& ctx);
308 320
309 AccountSettings account_settings{ 321 AccountSettings account_settings{
310 .flags = {}, 322 .flags = {},
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index cf9266d54..b65b9f2a2 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -4,7 +4,7 @@
4add_subdirectory(host_shaders) 4add_subdirectory(host_shaders)
5 5
6if(LIBVA_FOUND) 6if(LIBVA_FOUND)
7 set_source_files_properties(host1x/codecs/codec.cpp 7 set_source_files_properties(host1x/ffmpeg/ffmpeg.cpp
8 PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1) 8 PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
9 list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES}) 9 list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES})
10endif() 10endif()
@@ -66,6 +66,8 @@ add_library(video_core STATIC
66 host1x/codecs/vp9.cpp 66 host1x/codecs/vp9.cpp
67 host1x/codecs/vp9.h 67 host1x/codecs/vp9.h
68 host1x/codecs/vp9_types.h 68 host1x/codecs/vp9_types.h
69 host1x/ffmpeg/ffmpeg.cpp
70 host1x/ffmpeg/ffmpeg.h
69 host1x/control.cpp 71 host1x/control.cpp
70 host1x/control.h 72 host1x/control.h
71 host1x/host1x.cpp 73 host1x/host1x.cpp
diff --git a/src/video_core/host1x/codecs/codec.cpp b/src/video_core/host1x/codecs/codec.cpp
index dbcf508e5..1030db681 100644
--- a/src/video_core/host1x/codecs/codec.cpp
+++ b/src/video_core/host1x/codecs/codec.cpp
@@ -1,11 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <algorithm>
5#include <fstream>
6#include <vector>
7#include "common/assert.h" 4#include "common/assert.h"
8#include "common/scope_exit.h"
9#include "common/settings.h" 5#include "common/settings.h"
10#include "video_core/host1x/codecs/codec.h" 6#include "video_core/host1x/codecs/codec.h"
11#include "video_core/host1x/codecs/h264.h" 7#include "video_core/host1x/codecs/h264.h"
@@ -14,242 +10,17 @@
14#include "video_core/host1x/host1x.h" 10#include "video_core/host1x/host1x.h"
15#include "video_core/memory_manager.h" 11#include "video_core/memory_manager.h"
16 12
17extern "C" {
18#include <libavfilter/buffersink.h>
19#include <libavfilter/buffersrc.h>
20#include <libavutil/opt.h>
21#ifdef LIBVA_FOUND
22// for querying VAAPI driver information
23#include <libavutil/hwcontext_vaapi.h>
24#endif
25}
26
27namespace Tegra { 13namespace Tegra {
28namespace {
29constexpr AVPixelFormat PREFERRED_GPU_FMT = AV_PIX_FMT_NV12;
30constexpr AVPixelFormat PREFERRED_CPU_FMT = AV_PIX_FMT_YUV420P;
31constexpr std::array PREFERRED_GPU_DECODERS = {
32 AV_HWDEVICE_TYPE_CUDA,
33#ifdef _WIN32
34 AV_HWDEVICE_TYPE_D3D11VA,
35 AV_HWDEVICE_TYPE_DXVA2,
36#elif defined(__unix__)
37 AV_HWDEVICE_TYPE_VAAPI,
38 AV_HWDEVICE_TYPE_VDPAU,
39#endif
40 // last resort for Linux Flatpak (w/ NVIDIA)
41 AV_HWDEVICE_TYPE_VULKAN,
42};
43
44void AVPacketDeleter(AVPacket* ptr) {
45 av_packet_free(&ptr);
46}
47
48using AVPacketPtr = std::unique_ptr<AVPacket, decltype(&AVPacketDeleter)>;
49
50AVPixelFormat GetGpuFormat(AVCodecContext* av_codec_ctx, const AVPixelFormat* pix_fmts) {
51 for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
52 if (*p == av_codec_ctx->pix_fmt) {
53 return av_codec_ctx->pix_fmt;
54 }
55 }
56 LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU");
57 av_buffer_unref(&av_codec_ctx->hw_device_ctx);
58 av_codec_ctx->pix_fmt = PREFERRED_CPU_FMT;
59 return PREFERRED_CPU_FMT;
60}
61
62// List all the currently available hwcontext in ffmpeg
63std::vector<AVHWDeviceType> ListSupportedContexts() {
64 std::vector<AVHWDeviceType> contexts{};
65 AVHWDeviceType current_device_type = AV_HWDEVICE_TYPE_NONE;
66 do {
67 current_device_type = av_hwdevice_iterate_types(current_device_type);
68 contexts.push_back(current_device_type);
69 } while (current_device_type != AV_HWDEVICE_TYPE_NONE);
70 return contexts;
71}
72
73} // namespace
74
75void AVFrameDeleter(AVFrame* ptr) {
76 av_frame_free(&ptr);
77}
78 14
79Codec::Codec(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs) 15Codec::Codec(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs)
80 : host1x(host1x_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(host1x)), 16 : host1x(host1x_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(host1x)),
81 vp8_decoder(std::make_unique<Decoder::VP8>(host1x)), 17 vp8_decoder(std::make_unique<Decoder::VP8>(host1x)),
82 vp9_decoder(std::make_unique<Decoder::VP9>(host1x)) {} 18 vp9_decoder(std::make_unique<Decoder::VP9>(host1x)) {}
83 19
84Codec::~Codec() { 20Codec::~Codec() = default;
85 if (!initialized) {
86 return;
87 }
88 // Free libav memory
89 avcodec_free_context(&av_codec_ctx);
90 av_buffer_unref(&av_gpu_decoder);
91
92 if (filters_initialized) {
93 avfilter_graph_free(&av_filter_graph);
94 }
95}
96
97bool Codec::CreateGpuAvDevice() {
98 static constexpr auto HW_CONFIG_METHOD = AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX;
99 static const auto supported_contexts = ListSupportedContexts();
100 for (const auto& type : PREFERRED_GPU_DECODERS) {
101 if (std::none_of(supported_contexts.begin(), supported_contexts.end(),
102 [&type](const auto& context) { return context == type; })) {
103 LOG_DEBUG(Service_NVDRV, "{} explicitly unsupported", av_hwdevice_get_type_name(type));
104 continue;
105 }
106 // Avoid memory leak from not cleaning up after av_hwdevice_ctx_create
107 av_buffer_unref(&av_gpu_decoder);
108 const int hwdevice_res = av_hwdevice_ctx_create(&av_gpu_decoder, type, nullptr, nullptr, 0);
109 if (hwdevice_res < 0) {
110 LOG_DEBUG(Service_NVDRV, "{} av_hwdevice_ctx_create failed {}",
111 av_hwdevice_get_type_name(type), hwdevice_res);
112 continue;
113 }
114#ifdef LIBVA_FOUND
115 if (type == AV_HWDEVICE_TYPE_VAAPI) {
116 // we need to determine if this is an impersonated VAAPI driver
117 AVHWDeviceContext* hwctx =
118 static_cast<AVHWDeviceContext*>(static_cast<void*>(av_gpu_decoder->data));
119 AVVAAPIDeviceContext* vactx = static_cast<AVVAAPIDeviceContext*>(hwctx->hwctx);
120 const char* vendor_name = vaQueryVendorString(vactx->display);
121 if (strstr(vendor_name, "VDPAU backend")) {
122 // VDPAU impersonated VAAPI impl's are super buggy, we need to skip them
123 LOG_DEBUG(Service_NVDRV, "Skipping vdapu impersonated VAAPI driver");
124 continue;
125 } else {
126 // according to some user testing, certain vaapi driver (Intel?) could be buggy
127 // so let's log the driver name which may help the developers/supporters
128 LOG_DEBUG(Service_NVDRV, "Using VAAPI driver: {}", vendor_name);
129 }
130 }
131#endif
132 for (int i = 0;; i++) {
133 const AVCodecHWConfig* config = avcodec_get_hw_config(av_codec, i);
134 if (!config) {
135 LOG_DEBUG(Service_NVDRV, "{} decoder does not support device type {}.",
136 av_codec->name, av_hwdevice_get_type_name(type));
137 break;
138 }
139 if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) {
140 LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
141 av_codec_ctx->pix_fmt = config->pix_fmt;
142 return true;
143 }
144 }
145 }
146 return false;
147}
148
149void Codec::InitializeAvCodecContext() {
150 av_codec_ctx = avcodec_alloc_context3(av_codec);
151 av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
152 av_codec_ctx->thread_count = 0;
153 av_codec_ctx->thread_type &= ~FF_THREAD_FRAME;
154}
155
156void Codec::InitializeGpuDecoder() {
157 if (!CreateGpuAvDevice()) {
158 av_buffer_unref(&av_gpu_decoder);
159 return;
160 }
161 auto* hw_device_ctx = av_buffer_ref(av_gpu_decoder);
162 ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed");
163 av_codec_ctx->hw_device_ctx = hw_device_ctx;
164 av_codec_ctx->get_format = GetGpuFormat;
165}
166
167void Codec::InitializeAvFilters(AVFrame* frame) {
168 const AVFilter* buffer_src = avfilter_get_by_name("buffer");
169 const AVFilter* buffer_sink = avfilter_get_by_name("buffersink");
170 AVFilterInOut* inputs = avfilter_inout_alloc();
171 AVFilterInOut* outputs = avfilter_inout_alloc();
172 SCOPE_EXIT({
173 avfilter_inout_free(&inputs);
174 avfilter_inout_free(&outputs);
175 });
176
177 // Don't know how to get the accurate time_base but it doesn't matter for yadif filter
178 // so just use 1/1 to make buffer filter happy
179 std::string args = fmt::format("video_size={}x{}:pix_fmt={}:time_base=1/1", frame->width,
180 frame->height, frame->format);
181
182 av_filter_graph = avfilter_graph_alloc();
183 int ret = avfilter_graph_create_filter(&av_filter_src_ctx, buffer_src, "in", args.c_str(),
184 nullptr, av_filter_graph);
185 if (ret < 0) {
186 LOG_ERROR(Service_NVDRV, "avfilter_graph_create_filter source error: {}", ret);
187 return;
188 }
189
190 ret = avfilter_graph_create_filter(&av_filter_sink_ctx, buffer_sink, "out", nullptr, nullptr,
191 av_filter_graph);
192 if (ret < 0) {
193 LOG_ERROR(Service_NVDRV, "avfilter_graph_create_filter sink error: {}", ret);
194 return;
195 }
196
197 inputs->name = av_strdup("out");
198 inputs->filter_ctx = av_filter_sink_ctx;
199 inputs->pad_idx = 0;
200 inputs->next = nullptr;
201
202 outputs->name = av_strdup("in");
203 outputs->filter_ctx = av_filter_src_ctx;
204 outputs->pad_idx = 0;
205 outputs->next = nullptr;
206
207 const char* description = "yadif=1:-1:0";
208 ret = avfilter_graph_parse_ptr(av_filter_graph, description, &inputs, &outputs, nullptr);
209 if (ret < 0) {
210 LOG_ERROR(Service_NVDRV, "avfilter_graph_parse_ptr error: {}", ret);
211 return;
212 }
213
214 ret = avfilter_graph_config(av_filter_graph, nullptr);
215 if (ret < 0) {
216 LOG_ERROR(Service_NVDRV, "avfilter_graph_config error: {}", ret);
217 return;
218 }
219
220 filters_initialized = true;
221}
222 21
223void Codec::Initialize() { 22void Codec::Initialize() {
224 const AVCodecID codec = [&] { 23 initialized = decode_api.Initialize(current_codec);
225 switch (current_codec) {
226 case Host1x::NvdecCommon::VideoCodec::H264:
227 return AV_CODEC_ID_H264;
228 case Host1x::NvdecCommon::VideoCodec::VP8:
229 return AV_CODEC_ID_VP8;
230 case Host1x::NvdecCommon::VideoCodec::VP9:
231 return AV_CODEC_ID_VP9;
232 default:
233 UNIMPLEMENTED_MSG("Unknown codec {}", current_codec);
234 return AV_CODEC_ID_NONE;
235 }
236 }();
237 av_codec = avcodec_find_decoder(codec);
238
239 InitializeAvCodecContext();
240 if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) {
241 InitializeGpuDecoder();
242 }
243 if (const int res = avcodec_open2(av_codec_ctx, av_codec, nullptr); res < 0) {
244 LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed with result {}", res);
245 avcodec_free_context(&av_codec_ctx);
246 av_buffer_unref(&av_gpu_decoder);
247 return;
248 }
249 if (!av_codec_ctx->hw_device_ctx) {
250 LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding");
251 }
252 initialized = true;
253} 24}
254 25
255void Codec::SetTargetCodec(Host1x::NvdecCommon::VideoCodec codec) { 26void Codec::SetTargetCodec(Host1x::NvdecCommon::VideoCodec codec) {
@@ -264,14 +35,18 @@ void Codec::Decode() {
264 if (is_first_frame) { 35 if (is_first_frame) {
265 Initialize(); 36 Initialize();
266 } 37 }
38
267 if (!initialized) { 39 if (!initialized) {
268 return; 40 return;
269 } 41 }
42
43 // Assemble bitstream.
270 bool vp9_hidden_frame = false; 44 bool vp9_hidden_frame = false;
271 const auto& frame_data = [&]() { 45 size_t configuration_size = 0;
46 const auto packet_data = [&]() {
272 switch (current_codec) { 47 switch (current_codec) {
273 case Tegra::Host1x::NvdecCommon::VideoCodec::H264: 48 case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
274 return h264_decoder->ComposeFrame(state, is_first_frame); 49 return h264_decoder->ComposeFrame(state, &configuration_size, is_first_frame);
275 case Tegra::Host1x::NvdecCommon::VideoCodec::VP8: 50 case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
276 return vp8_decoder->ComposeFrame(state); 51 return vp8_decoder->ComposeFrame(state);
277 case Tegra::Host1x::NvdecCommon::VideoCodec::VP9: 52 case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
@@ -283,89 +58,35 @@ void Codec::Decode() {
283 return std::span<const u8>{}; 58 return std::span<const u8>{};
284 } 59 }
285 }(); 60 }();
286 AVPacketPtr packet{av_packet_alloc(), AVPacketDeleter}; 61
287 if (!packet) { 62 // Send assembled bitstream to decoder.
288 LOG_ERROR(Service_NVDRV, "av_packet_alloc failed"); 63 if (!decode_api.SendPacket(packet_data, configuration_size)) {
289 return;
290 }
291 packet->data = const_cast<u8*>(frame_data.data());
292 packet->size = static_cast<s32>(frame_data.size());
293 if (const int res = avcodec_send_packet(av_codec_ctx, packet.get()); res != 0) {
294 LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", res);
295 return; 64 return;
296 } 65 }
297 // Only receive/store visible frames 66
67 // Only receive/store visible frames.
298 if (vp9_hidden_frame) { 68 if (vp9_hidden_frame) {
299 return; 69 return;
300 } 70 }
301 AVFramePtr initial_frame{av_frame_alloc(), AVFrameDeleter};
302 AVFramePtr final_frame{nullptr, AVFrameDeleter};
303 ASSERT_MSG(initial_frame, "av_frame_alloc initial_frame failed");
304 if (const int ret = avcodec_receive_frame(av_codec_ctx, initial_frame.get()); ret) {
305 LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret);
306 return;
307 }
308 if (initial_frame->width == 0 || initial_frame->height == 0) {
309 LOG_WARNING(Service_NVDRV, "Zero width or height in frame");
310 return;
311 }
312 bool is_interlaced = initial_frame->interlaced_frame != 0;
313 if (av_codec_ctx->hw_device_ctx) {
314 final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
315 ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed");
316 // Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp
317 // because Intel drivers crash unless using AV_PIX_FMT_NV12
318 final_frame->format = PREFERRED_GPU_FMT;
319 const int ret = av_hwframe_transfer_data(final_frame.get(), initial_frame.get(), 0);
320 ASSERT_MSG(!ret, "av_hwframe_transfer_data error {}", ret);
321 } else {
322 final_frame = std::move(initial_frame);
323 }
324 if (final_frame->format != PREFERRED_CPU_FMT && final_frame->format != PREFERRED_GPU_FMT) {
325 UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format);
326 return;
327 }
328 if (!is_interlaced) {
329 av_frames.push(std::move(final_frame));
330 } else {
331 if (!filters_initialized) {
332 InitializeAvFilters(final_frame.get());
333 }
334 if (const int ret = av_buffersrc_add_frame_flags(av_filter_src_ctx, final_frame.get(),
335 AV_BUFFERSRC_FLAG_KEEP_REF);
336 ret) {
337 LOG_DEBUG(Service_NVDRV, "av_buffersrc_add_frame_flags error {}", ret);
338 return;
339 }
340 while (true) {
341 auto filter_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
342 71
343 int ret = av_buffersink_get_frame(av_filter_sink_ctx, filter_frame.get()); 72 // Receive output frames from decoder.
73 decode_api.ReceiveFrames(frames);
344 74
345 if (ret == AVERROR(EAGAIN) || ret == AVERROR(AVERROR_EOF)) 75 while (frames.size() > 10) {
346 break; 76 LOG_DEBUG(HW_GPU, "ReceiveFrames overflow, dropped frame");
347 if (ret < 0) { 77 frames.pop();
348 LOG_DEBUG(Service_NVDRV, "av_buffersink_get_frame error {}", ret);
349 return;
350 }
351
352 av_frames.push(std::move(filter_frame));
353 }
354 }
355 while (av_frames.size() > 10) {
356 LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame");
357 av_frames.pop();
358 } 78 }
359} 79}
360 80
361AVFramePtr Codec::GetCurrentFrame() { 81std::unique_ptr<FFmpeg::Frame> Codec::GetCurrentFrame() {
362 // Sometimes VIC will request more frames than have been decoded. 82 // Sometimes VIC will request more frames than have been decoded.
363 // in this case, return a nullptr and don't overwrite previous frame data 83 // in this case, return a blank frame and don't overwrite previous data.
364 if (av_frames.empty()) { 84 if (frames.empty()) {
365 return AVFramePtr{nullptr, AVFrameDeleter}; 85 return {};
366 } 86 }
367 AVFramePtr frame = std::move(av_frames.front()); 87
368 av_frames.pop(); 88 auto frame = std::move(frames.front());
89 frames.pop();
369 return frame; 90 return frame;
370} 91}
371 92
diff --git a/src/video_core/host1x/codecs/codec.h b/src/video_core/host1x/codecs/codec.h
index 06fe00a4b..f700ae129 100644
--- a/src/video_core/host1x/codecs/codec.h
+++ b/src/video_core/host1x/codecs/codec.h
@@ -4,28 +4,15 @@
4#pragma once 4#pragma once
5 5
6#include <memory> 6#include <memory>
7#include <optional>
7#include <string_view> 8#include <string_view>
8#include <queue> 9#include <queue>
9#include "common/common_types.h" 10#include "common/common_types.h"
11#include "video_core/host1x/ffmpeg/ffmpeg.h"
10#include "video_core/host1x/nvdec_common.h" 12#include "video_core/host1x/nvdec_common.h"
11 13
12extern "C" {
13#if defined(__GNUC__) || defined(__clang__)
14#pragma GCC diagnostic push
15#pragma GCC diagnostic ignored "-Wconversion"
16#endif
17#include <libavcodec/avcodec.h>
18#include <libavfilter/avfilter.h>
19#if defined(__GNUC__) || defined(__clang__)
20#pragma GCC diagnostic pop
21#endif
22}
23
24namespace Tegra { 14namespace Tegra {
25 15
26void AVFrameDeleter(AVFrame* ptr);
27using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>;
28
29namespace Decoder { 16namespace Decoder {
30class H264; 17class H264;
31class VP8; 18class VP8;
@@ -51,7 +38,7 @@ public:
51 void Decode(); 38 void Decode();
52 39
53 /// Returns next decoded frame 40 /// Returns next decoded frame
54 [[nodiscard]] AVFramePtr GetCurrentFrame(); 41 [[nodiscard]] std::unique_ptr<FFmpeg::Frame> GetCurrentFrame();
55 42
56 /// Returns the value of current_codec 43 /// Returns the value of current_codec
57 [[nodiscard]] Host1x::NvdecCommon::VideoCodec GetCurrentCodec() const; 44 [[nodiscard]] Host1x::NvdecCommon::VideoCodec GetCurrentCodec() const;
@@ -60,25 +47,9 @@ public:
60 [[nodiscard]] std::string_view GetCurrentCodecName() const; 47 [[nodiscard]] std::string_view GetCurrentCodecName() const;
61 48
62private: 49private:
63 void InitializeAvCodecContext();
64
65 void InitializeAvFilters(AVFrame* frame);
66
67 void InitializeGpuDecoder();
68
69 bool CreateGpuAvDevice();
70
71 bool initialized{}; 50 bool initialized{};
72 bool filters_initialized{};
73 Host1x::NvdecCommon::VideoCodec current_codec{Host1x::NvdecCommon::VideoCodec::None}; 51 Host1x::NvdecCommon::VideoCodec current_codec{Host1x::NvdecCommon::VideoCodec::None};
74 52 FFmpeg::DecodeApi decode_api;
75 const AVCodec* av_codec{nullptr};
76 AVCodecContext* av_codec_ctx{nullptr};
77 AVBufferRef* av_gpu_decoder{nullptr};
78
79 AVFilterContext* av_filter_src_ctx{nullptr};
80 AVFilterContext* av_filter_sink_ctx{nullptr};
81 AVFilterGraph* av_filter_graph{nullptr};
82 53
83 Host1x::Host1x& host1x; 54 Host1x::Host1x& host1x;
84 const Host1x::NvdecCommon::NvdecRegisters& state; 55 const Host1x::NvdecCommon::NvdecRegisters& state;
@@ -86,7 +57,7 @@ private:
86 std::unique_ptr<Decoder::VP8> vp8_decoder; 57 std::unique_ptr<Decoder::VP8> vp8_decoder;
87 std::unique_ptr<Decoder::VP9> vp9_decoder; 58 std::unique_ptr<Decoder::VP9> vp9_decoder;
88 59
89 std::queue<AVFramePtr> av_frames{}; 60 std::queue<std::unique_ptr<FFmpeg::Frame>> frames{};
90}; 61};
91 62
92} // namespace Tegra 63} // namespace Tegra
diff --git a/src/video_core/host1x/codecs/h264.cpp b/src/video_core/host1x/codecs/h264.cpp
index ece79b1e2..309a7f1d5 100644
--- a/src/video_core/host1x/codecs/h264.cpp
+++ b/src/video_core/host1x/codecs/h264.cpp
@@ -30,7 +30,7 @@ H264::H264(Host1x::Host1x& host1x_) : host1x{host1x_} {}
30H264::~H264() = default; 30H264::~H264() = default;
31 31
32std::span<const u8> H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state, 32std::span<const u8> H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state,
33 bool is_first_frame) { 33 size_t* out_configuration_size, bool is_first_frame) {
34 H264DecoderContext context; 34 H264DecoderContext context;
35 host1x.MemoryManager().ReadBlock(state.picture_info_offset, &context, 35 host1x.MemoryManager().ReadBlock(state.picture_info_offset, &context,
36 sizeof(H264DecoderContext)); 36 sizeof(H264DecoderContext));
@@ -39,6 +39,7 @@ std::span<const u8> H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters
39 if (!is_first_frame && frame_number != 0) { 39 if (!is_first_frame && frame_number != 0) {
40 frame.resize_destructive(context.stream_len); 40 frame.resize_destructive(context.stream_len);
41 host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size()); 41 host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size());
42 *out_configuration_size = 0;
42 return frame; 43 return frame;
43 } 44 }
44 45
@@ -157,6 +158,7 @@ std::span<const u8> H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters
157 frame.resize(encoded_header.size() + context.stream_len); 158 frame.resize(encoded_header.size() + context.stream_len);
158 std::memcpy(frame.data(), encoded_header.data(), encoded_header.size()); 159 std::memcpy(frame.data(), encoded_header.data(), encoded_header.size());
159 160
161 *out_configuration_size = encoded_header.size();
160 host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset, 162 host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset,
161 frame.data() + encoded_header.size(), context.stream_len); 163 frame.data() + encoded_header.size(), context.stream_len);
162 164
diff --git a/src/video_core/host1x/codecs/h264.h b/src/video_core/host1x/codecs/h264.h
index d6b556322..1deaf4632 100644
--- a/src/video_core/host1x/codecs/h264.h
+++ b/src/video_core/host1x/codecs/h264.h
@@ -67,6 +67,7 @@ public:
67 67
68 /// Compose the H264 frame for FFmpeg decoding 68 /// Compose the H264 frame for FFmpeg decoding
69 [[nodiscard]] std::span<const u8> ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state, 69 [[nodiscard]] std::span<const u8> ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state,
70 size_t* out_configuration_size,
70 bool is_first_frame = false); 71 bool is_first_frame = false);
71 72
72private: 73private:
diff --git a/src/video_core/host1x/ffmpeg/ffmpeg.cpp b/src/video_core/host1x/ffmpeg/ffmpeg.cpp
new file mode 100644
index 000000000..dcd07e6d2
--- /dev/null
+++ b/src/video_core/host1x/ffmpeg/ffmpeg.cpp
@@ -0,0 +1,419 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/assert.h"
5#include "common/logging/log.h"
6#include "common/scope_exit.h"
7#include "common/settings.h"
8#include "video_core/host1x/ffmpeg/ffmpeg.h"
9
10extern "C" {
11#ifdef LIBVA_FOUND
12// for querying VAAPI driver information
13#include <libavutil/hwcontext_vaapi.h>
14#endif
15}
16
17namespace FFmpeg {
18
19namespace {
20
21constexpr AVPixelFormat PreferredGpuFormat = AV_PIX_FMT_NV12;
22constexpr AVPixelFormat PreferredCpuFormat = AV_PIX_FMT_YUV420P;
23constexpr std::array PreferredGpuDecoders = {
24 AV_HWDEVICE_TYPE_CUDA,
25#ifdef _WIN32
26 AV_HWDEVICE_TYPE_D3D11VA,
27 AV_HWDEVICE_TYPE_DXVA2,
28#elif defined(__unix__)
29 AV_HWDEVICE_TYPE_VAAPI,
30 AV_HWDEVICE_TYPE_VDPAU,
31#endif
32 // last resort for Linux Flatpak (w/ NVIDIA)
33 AV_HWDEVICE_TYPE_VULKAN,
34};
35
36AVPixelFormat GetGpuFormat(AVCodecContext* codec_context, const AVPixelFormat* pix_fmts) {
37 for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
38 if (*p == codec_context->pix_fmt) {
39 return codec_context->pix_fmt;
40 }
41 }
42
43 LOG_INFO(HW_GPU, "Could not find compatible GPU AV format, falling back to CPU");
44 av_buffer_unref(&codec_context->hw_device_ctx);
45
46 codec_context->pix_fmt = PreferredCpuFormat;
47 return codec_context->pix_fmt;
48}
49
50std::string AVError(int errnum) {
51 char errbuf[AV_ERROR_MAX_STRING_SIZE] = {};
52 av_make_error_string(errbuf, sizeof(errbuf) - 1, errnum);
53 return errbuf;
54}
55
56} // namespace
57
58Packet::Packet(std::span<const u8> data) {
59 m_packet = av_packet_alloc();
60 m_packet->data = const_cast<u8*>(data.data());
61 m_packet->size = static_cast<s32>(data.size());
62}
63
64Packet::~Packet() {
65 av_packet_free(&m_packet);
66}
67
68Frame::Frame() {
69 m_frame = av_frame_alloc();
70}
71
72Frame::~Frame() {
73 av_frame_free(&m_frame);
74}
75
76Decoder::Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec) {
77 const AVCodecID av_codec = [&] {
78 switch (codec) {
79 case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
80 return AV_CODEC_ID_H264;
81 case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
82 return AV_CODEC_ID_VP8;
83 case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
84 return AV_CODEC_ID_VP9;
85 default:
86 UNIMPLEMENTED_MSG("Unknown codec {}", codec);
87 return AV_CODEC_ID_NONE;
88 }
89 }();
90
91 m_codec = avcodec_find_decoder(av_codec);
92}
93
94bool Decoder::SupportsDecodingOnDevice(AVPixelFormat* out_pix_fmt, AVHWDeviceType type) const {
95 for (int i = 0;; i++) {
96 const AVCodecHWConfig* config = avcodec_get_hw_config(m_codec, i);
97 if (!config) {
98 LOG_DEBUG(HW_GPU, "{} decoder does not support device type {}", m_codec->name,
99 av_hwdevice_get_type_name(type));
100 break;
101 }
102 if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) != 0 &&
103 config->device_type == type) {
104 LOG_INFO(HW_GPU, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
105 *out_pix_fmt = config->pix_fmt;
106 return true;
107 }
108 }
109
110 return false;
111}
112
113std::vector<AVHWDeviceType> HardwareContext::GetSupportedDeviceTypes() {
114 std::vector<AVHWDeviceType> types;
115 AVHWDeviceType current_device_type = AV_HWDEVICE_TYPE_NONE;
116
117 while (true) {
118 current_device_type = av_hwdevice_iterate_types(current_device_type);
119 if (current_device_type == AV_HWDEVICE_TYPE_NONE) {
120 return types;
121 }
122
123 types.push_back(current_device_type);
124 }
125}
126
127HardwareContext::~HardwareContext() {
128 av_buffer_unref(&m_gpu_decoder);
129}
130
131bool HardwareContext::InitializeForDecoder(DecoderContext& decoder_context,
132 const Decoder& decoder) {
133 const auto supported_types = GetSupportedDeviceTypes();
134 for (const auto type : PreferredGpuDecoders) {
135 AVPixelFormat hw_pix_fmt;
136
137 if (std::ranges::find(supported_types, type) == supported_types.end()) {
138 LOG_DEBUG(HW_GPU, "{} explicitly unsupported", av_hwdevice_get_type_name(type));
139 continue;
140 }
141
142 if (!this->InitializeWithType(type)) {
143 continue;
144 }
145
146 if (decoder.SupportsDecodingOnDevice(&hw_pix_fmt, type)) {
147 decoder_context.InitializeHardwareDecoder(*this, hw_pix_fmt);
148 return true;
149 }
150 }
151
152 return false;
153}
154
155bool HardwareContext::InitializeWithType(AVHWDeviceType type) {
156 av_buffer_unref(&m_gpu_decoder);
157
158 if (const int ret = av_hwdevice_ctx_create(&m_gpu_decoder, type, nullptr, nullptr, 0);
159 ret < 0) {
160 LOG_DEBUG(HW_GPU, "av_hwdevice_ctx_create({}) failed: {}", av_hwdevice_get_type_name(type),
161 AVError(ret));
162 return false;
163 }
164
165#ifdef LIBVA_FOUND
166 if (type == AV_HWDEVICE_TYPE_VAAPI) {
167 // We need to determine if this is an impersonated VAAPI driver.
168 auto* hwctx = reinterpret_cast<AVHWDeviceContext*>(m_gpu_decoder->data);
169 auto* vactx = static_cast<AVVAAPIDeviceContext*>(hwctx->hwctx);
170 const char* vendor_name = vaQueryVendorString(vactx->display);
171 if (strstr(vendor_name, "VDPAU backend")) {
172 // VDPAU impersonated VAAPI impls are super buggy, we need to skip them.
173 LOG_DEBUG(HW_GPU, "Skipping VDPAU impersonated VAAPI driver");
174 return false;
175 } else {
176 // According to some user testing, certain VAAPI drivers (Intel?) could be buggy.
177 // Log the driver name just in case.
178 LOG_DEBUG(HW_GPU, "Using VAAPI driver: {}", vendor_name);
179 }
180 }
181#endif
182
183 return true;
184}
185
186DecoderContext::DecoderContext(const Decoder& decoder) {
187 m_codec_context = avcodec_alloc_context3(decoder.GetCodec());
188 av_opt_set(m_codec_context->priv_data, "tune", "zerolatency", 0);
189 m_codec_context->thread_count = 0;
190 m_codec_context->thread_type &= ~FF_THREAD_FRAME;
191}
192
193DecoderContext::~DecoderContext() {
194 av_buffer_unref(&m_codec_context->hw_device_ctx);
195 avcodec_free_context(&m_codec_context);
196}
197
198void DecoderContext::InitializeHardwareDecoder(const HardwareContext& context,
199 AVPixelFormat hw_pix_fmt) {
200 m_codec_context->hw_device_ctx = av_buffer_ref(context.GetBufferRef());
201 m_codec_context->get_format = GetGpuFormat;
202 m_codec_context->pix_fmt = hw_pix_fmt;
203}
204
205bool DecoderContext::OpenContext(const Decoder& decoder) {
206 if (const int ret = avcodec_open2(m_codec_context, decoder.GetCodec(), nullptr); ret < 0) {
207 LOG_ERROR(HW_GPU, "avcodec_open2 error: {}", AVError(ret));
208 return false;
209 }
210
211 if (!m_codec_context->hw_device_ctx) {
212 LOG_INFO(HW_GPU, "Using FFmpeg software decoding");
213 }
214
215 return true;
216}
217
218bool DecoderContext::SendPacket(const Packet& packet) {
219 if (const int ret = avcodec_send_packet(m_codec_context, packet.GetPacket()); ret < 0) {
220 LOG_ERROR(HW_GPU, "avcodec_send_packet error: {}", AVError(ret));
221 return false;
222 }
223
224 return true;
225}
226
227std::unique_ptr<Frame> DecoderContext::ReceiveFrame(bool* out_is_interlaced) {
228 auto dst_frame = std::make_unique<Frame>();
229
230 const auto ReceiveImpl = [&](AVFrame* frame) {
231 if (const int ret = avcodec_receive_frame(m_codec_context, frame); ret < 0) {
232 LOG_ERROR(HW_GPU, "avcodec_receive_frame error: {}", AVError(ret));
233 return false;
234 }
235
236 *out_is_interlaced = frame->interlaced_frame != 0;
237 return true;
238 };
239
240 if (m_codec_context->hw_device_ctx) {
241 // If we have a hardware context, make a separate frame here to receive the
242 // hardware result before sending it to the output.
243 Frame intermediate_frame;
244
245 if (!ReceiveImpl(intermediate_frame.GetFrame())) {
246 return {};
247 }
248
249 dst_frame->SetFormat(PreferredGpuFormat);
250 if (const int ret =
251 av_hwframe_transfer_data(dst_frame->GetFrame(), intermediate_frame.GetFrame(), 0);
252 ret < 0) {
253 LOG_ERROR(HW_GPU, "av_hwframe_transfer_data error: {}", AVError(ret));
254 return {};
255 }
256 } else {
257 // Otherwise, decode the frame as normal.
258 if (!ReceiveImpl(dst_frame->GetFrame())) {
259 return {};
260 }
261 }
262
263 return dst_frame;
264}
265
266DeinterlaceFilter::DeinterlaceFilter(const Frame& frame) {
267 const AVFilter* buffer_src = avfilter_get_by_name("buffer");
268 const AVFilter* buffer_sink = avfilter_get_by_name("buffersink");
269 AVFilterInOut* inputs = avfilter_inout_alloc();
270 AVFilterInOut* outputs = avfilter_inout_alloc();
271 SCOPE_EXIT({
272 avfilter_inout_free(&inputs);
273 avfilter_inout_free(&outputs);
274 });
275
276 // Don't know how to get the accurate time_base but it doesn't matter for yadif filter
277 // so just use 1/1 to make buffer filter happy
278 std::string args = fmt::format("video_size={}x{}:pix_fmt={}:time_base=1/1", frame.GetWidth(),
279 frame.GetHeight(), static_cast<int>(frame.GetPixelFormat()));
280
281 m_filter_graph = avfilter_graph_alloc();
282 int ret = avfilter_graph_create_filter(&m_source_context, buffer_src, "in", args.c_str(),
283 nullptr, m_filter_graph);
284 if (ret < 0) {
285 LOG_ERROR(HW_GPU, "avfilter_graph_create_filter source error: {}", AVError(ret));
286 return;
287 }
288
289 ret = avfilter_graph_create_filter(&m_sink_context, buffer_sink, "out", nullptr, nullptr,
290 m_filter_graph);
291 if (ret < 0) {
292 LOG_ERROR(HW_GPU, "avfilter_graph_create_filter sink error: {}", AVError(ret));
293 return;
294 }
295
296 inputs->name = av_strdup("out");
297 inputs->filter_ctx = m_sink_context;
298 inputs->pad_idx = 0;
299 inputs->next = nullptr;
300
301 outputs->name = av_strdup("in");
302 outputs->filter_ctx = m_source_context;
303 outputs->pad_idx = 0;
304 outputs->next = nullptr;
305
306 const char* description = "yadif=1:-1:0";
307 ret = avfilter_graph_parse_ptr(m_filter_graph, description, &inputs, &outputs, nullptr);
308 if (ret < 0) {
309 LOG_ERROR(HW_GPU, "avfilter_graph_parse_ptr error: {}", AVError(ret));
310 return;
311 }
312
313 ret = avfilter_graph_config(m_filter_graph, nullptr);
314 if (ret < 0) {
315 LOG_ERROR(HW_GPU, "avfilter_graph_config error: {}", AVError(ret));
316 return;
317 }
318
319 m_initialized = true;
320}
321
322bool DeinterlaceFilter::AddSourceFrame(const Frame& frame) {
323 if (const int ret = av_buffersrc_add_frame_flags(m_source_context, frame.GetFrame(),
324 AV_BUFFERSRC_FLAG_KEEP_REF);
325 ret < 0) {
326 LOG_ERROR(HW_GPU, "av_buffersrc_add_frame_flags error: {}", AVError(ret));
327 return false;
328 }
329
330 return true;
331}
332
333std::unique_ptr<Frame> DeinterlaceFilter::DrainSinkFrame() {
334 auto dst_frame = std::make_unique<Frame>();
335 const int ret = av_buffersink_get_frame(m_sink_context, dst_frame->GetFrame());
336
337 if (ret == AVERROR(EAGAIN) || ret == AVERROR(AVERROR_EOF)) {
338 return {};
339 }
340
341 if (ret < 0) {
342 LOG_ERROR(HW_GPU, "av_buffersink_get_frame error: {}", AVError(ret));
343 return {};
344 }
345
346 return dst_frame;
347}
348
349DeinterlaceFilter::~DeinterlaceFilter() {
350 avfilter_graph_free(&m_filter_graph);
351}
352
353void DecodeApi::Reset() {
354 m_deinterlace_filter.reset();
355 m_hardware_context.reset();
356 m_decoder_context.reset();
357 m_decoder.reset();
358}
359
360bool DecodeApi::Initialize(Tegra::Host1x::NvdecCommon::VideoCodec codec) {
361 this->Reset();
362 m_decoder.emplace(codec);
363 m_decoder_context.emplace(*m_decoder);
364
365 // Enable GPU decoding if requested.
366 if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) {
367 m_hardware_context.emplace();
368 m_hardware_context->InitializeForDecoder(*m_decoder_context, *m_decoder);
369 }
370
371 // Open the decoder context.
372 if (!m_decoder_context->OpenContext(*m_decoder)) {
373 this->Reset();
374 return false;
375 }
376
377 return true;
378}
379
380bool DecodeApi::SendPacket(std::span<const u8> packet_data, size_t configuration_size) {
381 FFmpeg::Packet packet(packet_data);
382 return m_decoder_context->SendPacket(packet);
383}
384
385void DecodeApi::ReceiveFrames(std::queue<std::unique_ptr<Frame>>& frame_queue) {
386 // Receive raw frame from decoder.
387 bool is_interlaced;
388 auto frame = m_decoder_context->ReceiveFrame(&is_interlaced);
389 if (!frame) {
390 return;
391 }
392
393 if (!is_interlaced) {
394 // If the frame is not interlaced, we can pend it now.
395 frame_queue.push(std::move(frame));
396 } else {
397 // Create the deinterlacer if needed.
398 if (!m_deinterlace_filter) {
399 m_deinterlace_filter.emplace(*frame);
400 }
401
402 // Add the frame we just received.
403 if (!m_deinterlace_filter->AddSourceFrame(*frame)) {
404 return;
405 }
406
407 // Pend output fields.
408 while (true) {
409 auto filter_frame = m_deinterlace_filter->DrainSinkFrame();
410 if (!filter_frame) {
411 break;
412 }
413
414 frame_queue.push(std::move(filter_frame));
415 }
416 }
417}
418
419} // namespace FFmpeg
diff --git a/src/video_core/host1x/ffmpeg/ffmpeg.h b/src/video_core/host1x/ffmpeg/ffmpeg.h
new file mode 100644
index 000000000..1de0bbd83
--- /dev/null
+++ b/src/video_core/host1x/ffmpeg/ffmpeg.h
@@ -0,0 +1,213 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <optional>
8#include <span>
9#include <vector>
10#include <queue>
11
12#include "common/common_funcs.h"
13#include "common/common_types.h"
14#include "video_core/host1x/nvdec_common.h"
15
16extern "C" {
17#if defined(__GNUC__) || defined(__clang__)
18#pragma GCC diagnostic push
19#pragma GCC diagnostic ignored "-Wconversion"
20#endif
21
22#include <libavcodec/avcodec.h>
23#include <libavfilter/avfilter.h>
24#include <libavfilter/buffersink.h>
25#include <libavfilter/buffersrc.h>
26#include <libavutil/avutil.h>
27#include <libavutil/opt.h>
28
29#if defined(__GNUC__) || defined(__clang__)
30#pragma GCC diagnostic pop
31#endif
32}
33
34namespace FFmpeg {
35
36class Packet;
37class Frame;
38class Decoder;
39class HardwareContext;
40class DecoderContext;
41class DeinterlaceFilter;
42
43// Wraps an AVPacket, a container for compressed bitstream data.
44class Packet {
45public:
46 YUZU_NON_COPYABLE(Packet);
47 YUZU_NON_MOVEABLE(Packet);
48
49 explicit Packet(std::span<const u8> data);
50 ~Packet();
51
52 AVPacket* GetPacket() const {
53 return m_packet;
54 }
55
56private:
57 AVPacket* m_packet{};
58};
59
60// Wraps an AVFrame, a container for audio and video stream data.
61class Frame {
62public:
63 YUZU_NON_COPYABLE(Frame);
64 YUZU_NON_MOVEABLE(Frame);
65
66 explicit Frame();
67 ~Frame();
68
69 int GetWidth() const {
70 return m_frame->width;
71 }
72
73 int GetHeight() const {
74 return m_frame->height;
75 }
76
77 AVPixelFormat GetPixelFormat() const {
78 return static_cast<AVPixelFormat>(m_frame->format);
79 }
80
81 int GetStride(int plane) const {
82 return m_frame->linesize[plane];
83 }
84
85 int* GetStrides() const {
86 return m_frame->linesize;
87 }
88
89 u8* GetData(int plane) const {
90 return m_frame->data[plane];
91 }
92
93 u8** GetPlanes() const {
94 return m_frame->data;
95 }
96
97 void SetFormat(int format) {
98 m_frame->format = format;
99 }
100
101 AVFrame* GetFrame() const {
102 return m_frame;
103 }
104
105private:
106 AVFrame* m_frame{};
107};
108
109// Wraps an AVCodec, a type containing information about a codec.
110class Decoder {
111public:
112 YUZU_NON_COPYABLE(Decoder);
113 YUZU_NON_MOVEABLE(Decoder);
114
115 explicit Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec);
116 ~Decoder() = default;
117
118 bool SupportsDecodingOnDevice(AVPixelFormat* out_pix_fmt, AVHWDeviceType type) const;
119
120 const AVCodec* GetCodec() const {
121 return m_codec;
122 }
123
124private:
125 const AVCodec* m_codec{};
126};
127
128// Wraps AVBufferRef for an accelerated decoder.
129class HardwareContext {
130public:
131 YUZU_NON_COPYABLE(HardwareContext);
132 YUZU_NON_MOVEABLE(HardwareContext);
133
134 static std::vector<AVHWDeviceType> GetSupportedDeviceTypes();
135
136 explicit HardwareContext() = default;
137 ~HardwareContext();
138
139 bool InitializeForDecoder(DecoderContext& decoder_context, const Decoder& decoder);
140
141 AVBufferRef* GetBufferRef() const {
142 return m_gpu_decoder;
143 }
144
145private:
146 bool InitializeWithType(AVHWDeviceType type);
147
148 AVBufferRef* m_gpu_decoder{};
149};
150
151// Wraps an AVCodecContext.
152class DecoderContext {
153public:
154 YUZU_NON_COPYABLE(DecoderContext);
155 YUZU_NON_MOVEABLE(DecoderContext);
156
157 explicit DecoderContext(const Decoder& decoder);
158 ~DecoderContext();
159
160 void InitializeHardwareDecoder(const HardwareContext& context, AVPixelFormat hw_pix_fmt);
161 bool OpenContext(const Decoder& decoder);
162 bool SendPacket(const Packet& packet);
163 std::unique_ptr<Frame> ReceiveFrame(bool* out_is_interlaced);
164
165 AVCodecContext* GetCodecContext() const {
166 return m_codec_context;
167 }
168
169private:
170 AVCodecContext* m_codec_context{};
171};
172
173// Wraps an AVFilterGraph.
174class DeinterlaceFilter {
175public:
176 YUZU_NON_COPYABLE(DeinterlaceFilter);
177 YUZU_NON_MOVEABLE(DeinterlaceFilter);
178
179 explicit DeinterlaceFilter(const Frame& frame);
180 ~DeinterlaceFilter();
181
182 bool AddSourceFrame(const Frame& frame);
183 std::unique_ptr<Frame> DrainSinkFrame();
184
185private:
186 AVFilterGraph* m_filter_graph{};
187 AVFilterContext* m_source_context{};
188 AVFilterContext* m_sink_context{};
189 bool m_initialized{};
190};
191
192class DecodeApi {
193public:
194 YUZU_NON_COPYABLE(DecodeApi);
195 YUZU_NON_MOVEABLE(DecodeApi);
196
197 DecodeApi() = default;
198 ~DecodeApi() = default;
199
200 bool Initialize(Tegra::Host1x::NvdecCommon::VideoCodec codec);
201 void Reset();
202
203 bool SendPacket(std::span<const u8> packet_data, size_t configuration_size);
204 void ReceiveFrames(std::queue<std::unique_ptr<Frame>>& frame_queue);
205
206private:
207 std::optional<FFmpeg::Decoder> m_decoder;
208 std::optional<FFmpeg::DecoderContext> m_decoder_context;
209 std::optional<FFmpeg::HardwareContext> m_hardware_context;
210 std::optional<FFmpeg::DeinterlaceFilter> m_deinterlace_filter;
211};
212
213} // namespace FFmpeg
diff --git a/src/video_core/host1x/nvdec.cpp b/src/video_core/host1x/nvdec.cpp
index a4bd5b79f..b8f5866d3 100644
--- a/src/video_core/host1x/nvdec.cpp
+++ b/src/video_core/host1x/nvdec.cpp
@@ -28,7 +28,7 @@ void Nvdec::ProcessMethod(u32 method, u32 argument) {
28 } 28 }
29} 29}
30 30
31AVFramePtr Nvdec::GetFrame() { 31std::unique_ptr<FFmpeg::Frame> Nvdec::GetFrame() {
32 return codec->GetCurrentFrame(); 32 return codec->GetCurrentFrame();
33} 33}
34 34
diff --git a/src/video_core/host1x/nvdec.h b/src/video_core/host1x/nvdec.h
index 3949d5181..ddddb8d28 100644
--- a/src/video_core/host1x/nvdec.h
+++ b/src/video_core/host1x/nvdec.h
@@ -23,7 +23,7 @@ public:
23 void ProcessMethod(u32 method, u32 argument); 23 void ProcessMethod(u32 method, u32 argument);
24 24
25 /// Return most recently decoded frame 25 /// Return most recently decoded frame
26 [[nodiscard]] AVFramePtr GetFrame(); 26 [[nodiscard]] std::unique_ptr<FFmpeg::Frame> GetFrame();
27 27
28private: 28private:
29 /// Invoke codec to decode a frame 29 /// Invoke codec to decode a frame
diff --git a/src/video_core/host1x/vic.cpp b/src/video_core/host1x/vic.cpp
index 10d7ef884..2a5eba415 100644
--- a/src/video_core/host1x/vic.cpp
+++ b/src/video_core/host1x/vic.cpp
@@ -82,27 +82,26 @@ void Vic::Execute() {
82 return; 82 return;
83 } 83 }
84 const VicConfig config{host1x.MemoryManager().Read<u64>(config_struct_address + 0x20)}; 84 const VicConfig config{host1x.MemoryManager().Read<u64>(config_struct_address + 0x20)};
85 const AVFramePtr frame_ptr = nvdec_processor->GetFrame(); 85 auto frame = nvdec_processor->GetFrame();
86 const auto* frame = frame_ptr.get();
87 if (!frame) { 86 if (!frame) {
88 return; 87 return;
89 } 88 }
90 const u64 surface_width = config.surface_width_minus1 + 1; 89 const u64 surface_width = config.surface_width_minus1 + 1;
91 const u64 surface_height = config.surface_height_minus1 + 1; 90 const u64 surface_height = config.surface_height_minus1 + 1;
92 if (static_cast<u64>(frame->width) != surface_width || 91 if (static_cast<u64>(frame->GetWidth()) != surface_width ||
93 static_cast<u64>(frame->height) != surface_height) { 92 static_cast<u64>(frame->GetHeight()) != surface_height) {
94 // TODO: Properly support multiple video streams with differing frame dimensions 93 // TODO: Properly support multiple video streams with differing frame dimensions
95 LOG_WARNING(Service_NVDRV, "Frame dimensions {}x{} don't match surface dimensions {}x{}", 94 LOG_WARNING(Service_NVDRV, "Frame dimensions {}x{} don't match surface dimensions {}x{}",
96 frame->width, frame->height, surface_width, surface_height); 95 frame->GetWidth(), frame->GetHeight(), surface_width, surface_height);
97 } 96 }
98 switch (config.pixel_format) { 97 switch (config.pixel_format) {
99 case VideoPixelFormat::RGBA8: 98 case VideoPixelFormat::RGBA8:
100 case VideoPixelFormat::BGRA8: 99 case VideoPixelFormat::BGRA8:
101 case VideoPixelFormat::RGBX8: 100 case VideoPixelFormat::RGBX8:
102 WriteRGBFrame(frame, config); 101 WriteRGBFrame(std::move(frame), config);
103 break; 102 break;
104 case VideoPixelFormat::YUV420: 103 case VideoPixelFormat::YUV420:
105 WriteYUVFrame(frame, config); 104 WriteYUVFrame(std::move(frame), config);
106 break; 105 break;
107 default: 106 default:
108 UNIMPLEMENTED_MSG("Unknown video pixel format {:X}", config.pixel_format.Value()); 107 UNIMPLEMENTED_MSG("Unknown video pixel format {:X}", config.pixel_format.Value());
@@ -110,10 +109,14 @@ void Vic::Execute() {
110 } 109 }
111} 110}
112 111
113void Vic::WriteRGBFrame(const AVFrame* frame, const VicConfig& config) { 112void Vic::WriteRGBFrame(std::unique_ptr<FFmpeg::Frame> frame, const VicConfig& config) {
114 LOG_TRACE(Service_NVDRV, "Writing RGB Frame"); 113 LOG_TRACE(Service_NVDRV, "Writing RGB Frame");
115 114
116 if (!scaler_ctx || frame->width != scaler_width || frame->height != scaler_height) { 115 const auto frame_width = frame->GetWidth();
116 const auto frame_height = frame->GetHeight();
117 const auto frame_format = frame->GetPixelFormat();
118
119 if (!scaler_ctx || frame_width != scaler_width || frame_height != scaler_height) {
117 const AVPixelFormat target_format = [pixel_format = config.pixel_format]() { 120 const AVPixelFormat target_format = [pixel_format = config.pixel_format]() {
118 switch (pixel_format) { 121 switch (pixel_format) {
119 case VideoPixelFormat::RGBA8: 122 case VideoPixelFormat::RGBA8:
@@ -129,27 +132,26 @@ void Vic::WriteRGBFrame(const AVFrame* frame, const VicConfig& config) {
129 132
130 sws_freeContext(scaler_ctx); 133 sws_freeContext(scaler_ctx);
131 // Frames are decoded into either YUV420 or NV12 formats. Convert to desired RGB format 134 // Frames are decoded into either YUV420 or NV12 formats. Convert to desired RGB format
132 scaler_ctx = sws_getContext(frame->width, frame->height, 135 scaler_ctx = sws_getContext(frame_width, frame_height, frame_format, frame_width,
133 static_cast<AVPixelFormat>(frame->format), frame->width, 136 frame_height, target_format, 0, nullptr, nullptr, nullptr);
134 frame->height, target_format, 0, nullptr, nullptr, nullptr); 137 scaler_width = frame_width;
135 scaler_width = frame->width; 138 scaler_height = frame_height;
136 scaler_height = frame->height;
137 converted_frame_buffer.reset(); 139 converted_frame_buffer.reset();
138 } 140 }
139 if (!converted_frame_buffer) { 141 if (!converted_frame_buffer) {
140 const size_t frame_size = frame->width * frame->height * 4; 142 const size_t frame_size = frame_width * frame_height * 4;
141 converted_frame_buffer = AVMallocPtr{static_cast<u8*>(av_malloc(frame_size)), av_free}; 143 converted_frame_buffer = AVMallocPtr{static_cast<u8*>(av_malloc(frame_size)), av_free};
142 } 144 }
143 const std::array<int, 4> converted_stride{frame->width * 4, frame->height * 4, 0, 0}; 145 const std::array<int, 4> converted_stride{frame_width * 4, frame_height * 4, 0, 0};
144 u8* const converted_frame_buf_addr{converted_frame_buffer.get()}; 146 u8* const converted_frame_buf_addr{converted_frame_buffer.get()};
145 sws_scale(scaler_ctx, frame->data, frame->linesize, 0, frame->height, &converted_frame_buf_addr, 147 sws_scale(scaler_ctx, frame->GetPlanes(), frame->GetStrides(), 0, frame_height,
146 converted_stride.data()); 148 &converted_frame_buf_addr, converted_stride.data());
147 149
148 // Use the minimum of surface/frame dimensions to avoid buffer overflow. 150 // Use the minimum of surface/frame dimensions to avoid buffer overflow.
149 const u32 surface_width = static_cast<u32>(config.surface_width_minus1) + 1; 151 const u32 surface_width = static_cast<u32>(config.surface_width_minus1) + 1;
150 const u32 surface_height = static_cast<u32>(config.surface_height_minus1) + 1; 152 const u32 surface_height = static_cast<u32>(config.surface_height_minus1) + 1;
151 const u32 width = std::min(surface_width, static_cast<u32>(frame->width)); 153 const u32 width = std::min(surface_width, static_cast<u32>(frame_width));
152 const u32 height = std::min(surface_height, static_cast<u32>(frame->height)); 154 const u32 height = std::min(surface_height, static_cast<u32>(frame_height));
153 const u32 blk_kind = static_cast<u32>(config.block_linear_kind); 155 const u32 blk_kind = static_cast<u32>(config.block_linear_kind);
154 if (blk_kind != 0) { 156 if (blk_kind != 0) {
155 // swizzle pitch linear to block linear 157 // swizzle pitch linear to block linear
@@ -169,23 +171,23 @@ void Vic::WriteRGBFrame(const AVFrame* frame, const VicConfig& config) {
169 } 171 }
170} 172}
171 173
172void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) { 174void Vic::WriteYUVFrame(std::unique_ptr<FFmpeg::Frame> frame, const VicConfig& config) {
173 LOG_TRACE(Service_NVDRV, "Writing YUV420 Frame"); 175 LOG_TRACE(Service_NVDRV, "Writing YUV420 Frame");
174 176
175 const std::size_t surface_width = config.surface_width_minus1 + 1; 177 const std::size_t surface_width = config.surface_width_minus1 + 1;
176 const std::size_t surface_height = config.surface_height_minus1 + 1; 178 const std::size_t surface_height = config.surface_height_minus1 + 1;
177 const std::size_t aligned_width = (surface_width + 0xff) & ~0xffUL; 179 const std::size_t aligned_width = (surface_width + 0xff) & ~0xffUL;
178 // Use the minimum of surface/frame dimensions to avoid buffer overflow. 180 // Use the minimum of surface/frame dimensions to avoid buffer overflow.
179 const auto frame_width = std::min(surface_width, static_cast<size_t>(frame->width)); 181 const auto frame_width = std::min(surface_width, static_cast<size_t>(frame->GetWidth()));
180 const auto frame_height = std::min(surface_height, static_cast<size_t>(frame->height)); 182 const auto frame_height = std::min(surface_height, static_cast<size_t>(frame->GetHeight()));
181 183
182 const auto stride = static_cast<size_t>(frame->linesize[0]); 184 const auto stride = static_cast<size_t>(frame->GetStride(0));
183 185
184 luma_buffer.resize_destructive(aligned_width * surface_height); 186 luma_buffer.resize_destructive(aligned_width * surface_height);
185 chroma_buffer.resize_destructive(aligned_width * surface_height / 2); 187 chroma_buffer.resize_destructive(aligned_width * surface_height / 2);
186 188
187 // Populate luma buffer 189 // Populate luma buffer
188 const u8* luma_src = frame->data[0]; 190 const u8* luma_src = frame->GetData(0);
189 for (std::size_t y = 0; y < frame_height; ++y) { 191 for (std::size_t y = 0; y < frame_height; ++y) {
190 const std::size_t src = y * stride; 192 const std::size_t src = y * stride;
191 const std::size_t dst = y * aligned_width; 193 const std::size_t dst = y * aligned_width;
@@ -196,16 +198,16 @@ void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) {
196 198
197 // Chroma 199 // Chroma
198 const std::size_t half_height = frame_height / 2; 200 const std::size_t half_height = frame_height / 2;
199 const auto half_stride = static_cast<size_t>(frame->linesize[1]); 201 const auto half_stride = static_cast<size_t>(frame->GetStride(1));
200 202
201 switch (frame->format) { 203 switch (frame->GetPixelFormat()) {
202 case AV_PIX_FMT_YUV420P: { 204 case AV_PIX_FMT_YUV420P: {
203 // Frame from FFmpeg software 205 // Frame from FFmpeg software
204 // Populate chroma buffer from both channels with interleaving. 206 // Populate chroma buffer from both channels with interleaving.
205 const std::size_t half_width = frame_width / 2; 207 const std::size_t half_width = frame_width / 2;
206 u8* chroma_buffer_data = chroma_buffer.data(); 208 u8* chroma_buffer_data = chroma_buffer.data();
207 const u8* chroma_b_src = frame->data[1]; 209 const u8* chroma_b_src = frame->GetData(1);
208 const u8* chroma_r_src = frame->data[2]; 210 const u8* chroma_r_src = frame->GetData(2);
209 for (std::size_t y = 0; y < half_height; ++y) { 211 for (std::size_t y = 0; y < half_height; ++y) {
210 const std::size_t src = y * half_stride; 212 const std::size_t src = y * half_stride;
211 const std::size_t dst = y * aligned_width; 213 const std::size_t dst = y * aligned_width;
@@ -219,7 +221,7 @@ void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) {
219 case AV_PIX_FMT_NV12: { 221 case AV_PIX_FMT_NV12: {
220 // Frame from VA-API hardware 222 // Frame from VA-API hardware
221 // This is already interleaved so just copy 223 // This is already interleaved so just copy
222 const u8* chroma_src = frame->data[1]; 224 const u8* chroma_src = frame->GetData(1);
223 for (std::size_t y = 0; y < half_height; ++y) { 225 for (std::size_t y = 0; y < half_height; ++y) {
224 const std::size_t src = y * stride; 226 const std::size_t src = y * stride;
225 const std::size_t dst = y * aligned_width; 227 const std::size_t dst = y * aligned_width;
diff --git a/src/video_core/host1x/vic.h b/src/video_core/host1x/vic.h
index 3d9753047..6c868f062 100644
--- a/src/video_core/host1x/vic.h
+++ b/src/video_core/host1x/vic.h
@@ -39,9 +39,9 @@ public:
39private: 39private:
40 void Execute(); 40 void Execute();
41 41
42 void WriteRGBFrame(const AVFrame* frame, const VicConfig& config); 42 void WriteRGBFrame(std::unique_ptr<FFmpeg::Frame> frame, const VicConfig& config);
43 43
44 void WriteYUVFrame(const AVFrame* frame, const VicConfig& config); 44 void WriteYUVFrame(std::unique_ptr<FFmpeg::Frame> frame, const VicConfig& config);
45 45
46 Host1x& host1x; 46 Host1x& host1x;
47 std::shared_ptr<Tegra::Host1x::Nvdec> nvdec_processor; 47 std::shared_ptr<Tegra::Host1x::Nvdec> nvdec_processor;
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 33e1fb663..181b2817c 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -252,6 +252,7 @@ file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*)
252if (ENABLE_QT_TRANSLATION) 252if (ENABLE_QT_TRANSLATION)
253 set(YUZU_QT_LANGUAGES "${PROJECT_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend") 253 set(YUZU_QT_LANGUAGES "${PROJECT_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend")
254 option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF) 254 option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF)
255 option(WORKAROUND_BROKEN_LUPDATE "Run lupdate directly through CMake if Qt's convenience wrappers don't work" OFF)
255 256
256 # Update source TS file if enabled 257 # Update source TS file if enabled
257 if (GENERATE_QT_TRANSLATION) 258 if (GENERATE_QT_TRANSLATION)
@@ -259,19 +260,51 @@ if (ENABLE_QT_TRANSLATION)
259 # these calls to qt_create_translation also creates a rule to generate en.qm which conflicts with providing english plurals 260 # these calls to qt_create_translation also creates a rule to generate en.qm which conflicts with providing english plurals
260 # so we have to set a OUTPUT_LOCATION so that we don't have multiple rules to generate en.qm 261 # so we have to set a OUTPUT_LOCATION so that we don't have multiple rules to generate en.qm
261 set_source_files_properties(${YUZU_QT_LANGUAGES}/en.ts PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/translations") 262 set_source_files_properties(${YUZU_QT_LANGUAGES}/en.ts PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/translations")
262 qt_create_translation(QM_FILES 263 if (WORKAROUND_BROKEN_LUPDATE)
263 ${SRCS} 264 add_custom_command(OUTPUT ${YUZU_QT_LANGUAGES}/en.ts
264 ${UIS} 265 COMMAND lupdate
265 ${YUZU_QT_LANGUAGES}/en.ts 266 -source-language en_US
266 OPTIONS 267 -target-language en_US
267 -source-language en_US 268 ${SRCS}
268 -target-language en_US 269 ${UIS}
269 ) 270 -ts ${YUZU_QT_LANGUAGES}/en.ts
271 DEPENDS
272 ${SRCS}
273 ${UIS}
274 WORKING_DIRECTORY
275 ${CMAKE_CURRENT_SOURCE_DIR}
276 )
277 else()
278 qt_create_translation(QM_FILES
279 ${SRCS}
280 ${UIS}
281 ${YUZU_QT_LANGUAGES}/en.ts
282 OPTIONS
283 -source-language en_US
284 -target-language en_US
285 )
286 endif()
270 287
271 # Generate plurals into dist/english_plurals/generated_en.ts so it can be used to revise dist/english_plurals/en.ts 288 # Generate plurals into dist/english_plurals/generated_en.ts so it can be used to revise dist/english_plurals/en.ts
272 set(GENERATED_PLURALS_FILE ${PROJECT_SOURCE_DIR}/dist/english_plurals/generated_en.ts) 289 set(GENERATED_PLURALS_FILE ${PROJECT_SOURCE_DIR}/dist/english_plurals/generated_en.ts)
273 set_source_files_properties(${GENERATED_PLURALS_FILE} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/plurals") 290 set_source_files_properties(${GENERATED_PLURALS_FILE} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/plurals")
274 qt_create_translation(QM_FILES ${SRCS} ${UIS} ${GENERATED_PLURALS_FILE} OPTIONS -pluralonly -source-language en_US -target-language en_US) 291 if (WORKAROUND_BROKEN_LUPDATE)
292 add_custom_command(OUTPUT ${GENERATED_PLURALS_FILE}
293 COMMAND lupdate
294 -source-language en_US
295 -target-language en_US
296 ${SRCS}
297 ${UIS}
298 -ts ${GENERATED_PLURALS_FILE}
299 DEPENDS
300 ${SRCS}
301 ${UIS}
302 WORKING_DIRECTORY
303 ${CMAKE_CURRENT_SOURCE_DIR}
304 )
305 else()
306 qt_create_translation(QM_FILES ${SRCS} ${UIS} ${GENERATED_PLURALS_FILE} OPTIONS -pluralonly -source-language en_US -target-language en_US)
307 endif()
275 308
276 add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts ${GENERATED_PLURALS_FILE}) 309 add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts ${GENERATED_PLURALS_FILE})
277 endif() 310 endif()
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index 1434b1a56..a7b5def32 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -1,17 +1,18 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/time_zone.h"
5#include "yuzu/configuration/shared_translation.h" 4#include "yuzu/configuration/shared_translation.h"
6 5
7#include <map> 6#include <map>
8#include <memory> 7#include <memory>
9#include <tuple> 8#include <tuple>
10#include <utility> 9#include <utility>
10#include <QCoreApplication>
11#include <QWidget> 11#include <QWidget>
12#include "common/settings.h" 12#include "common/settings.h"
13#include "common/settings_enums.h" 13#include "common/settings_enums.h"
14#include "common/settings_setting.h" 14#include "common/settings_setting.h"
15#include "common/time_zone.h"
15#include "yuzu/uisettings.h" 16#include "yuzu/uisettings.h"
16 17
17namespace ConfigurationShared { 18namespace ConfigurationShared {
@@ -21,123 +22,135 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
21 const auto& tr = [parent](const char* text) -> QString { return parent->tr(text); }; 22 const auto& tr = [parent](const char* text) -> QString { return parent->tr(text); };
22 23
23#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \ 24#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \
24 translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{tr((NAME)), tr((TOOLTIP))}}) 25 translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{(NAME), (TOOLTIP)}})
25 26
26 // A setting can be ignored by giving it a blank name 27 // A setting can be ignored by giving it a blank name
27 28
28 // Audio 29 // Audio
29 INSERT(Settings, sink_id, "Output Engine:", ""); 30 INSERT(Settings, sink_id, tr("Output Engine:"), QStringLiteral());
30 INSERT(Settings, audio_output_device_id, "Output Device:", ""); 31 INSERT(Settings, audio_output_device_id, tr("Output Device:"), QStringLiteral());
31 INSERT(Settings, audio_input_device_id, "Input Device:", ""); 32 INSERT(Settings, audio_input_device_id, tr("Input Device:"), QStringLiteral());
32 INSERT(Settings, audio_muted, "Mute audio", ""); 33 INSERT(Settings, audio_muted, tr("Mute audio"), QStringLiteral());
33 INSERT(Settings, volume, "Volume:", ""); 34 INSERT(Settings, volume, tr("Volume:"), QStringLiteral());
34 INSERT(Settings, dump_audio_commands, "", ""); 35 INSERT(Settings, dump_audio_commands, QStringLiteral(), QStringLiteral());
35 INSERT(UISettings, mute_when_in_background, "Mute audio when in background", ""); 36 INSERT(UISettings, mute_when_in_background, tr("Mute audio when in background"),
37 QStringLiteral());
36 38
37 // Core 39 // Core
38 INSERT(Settings, use_multi_core, "Multicore CPU Emulation", ""); 40 INSERT(Settings, use_multi_core, tr("Multicore CPU Emulation"), QStringLiteral());
39 INSERT(Settings, memory_layout_mode, "Memory Layout", ""); 41 INSERT(Settings, memory_layout_mode, tr("Memory Layout"), QStringLiteral());
40 INSERT(Settings, use_speed_limit, "", ""); 42 INSERT(Settings, use_speed_limit, QStringLiteral(), QStringLiteral());
41 INSERT(Settings, speed_limit, "Limit Speed Percent", ""); 43 INSERT(Settings, speed_limit, tr("Limit Speed Percent"), QStringLiteral());
42 44
43 // Cpu 45 // Cpu
44 INSERT(Settings, cpu_accuracy, "Accuracy:", ""); 46 INSERT(Settings, cpu_accuracy, tr("Accuracy:"), QStringLiteral());
45 47
46 // Cpu Debug 48 // Cpu Debug
47 49
48 // Cpu Unsafe 50 // Cpu Unsafe
49 INSERT(Settings, cpuopt_unsafe_unfuse_fma,
50 "Unfuse FMA (improve performance on CPUs without FMA)",
51 "This option improves speed by reducing accuracy of fused-multiply-add instructions on "
52 "CPUs without native FMA support.");
53 INSERT(Settings, cpuopt_unsafe_reduce_fp_error, "Faster FRSQRTE and FRECPE",
54 "This option improves the speed of some approximate floating-point functions by using "
55 "less accurate native approximations.");
56 INSERT(Settings, cpuopt_unsafe_ignore_standard_fpcr, "Faster ASIMD instructions (32 bits only)",
57 "This option improves the speed of 32 bits ASIMD floating-point functions by running "
58 "with incorrect rounding modes.");
59 INSERT(Settings, cpuopt_unsafe_inaccurate_nan, "Inaccurate NaN handling",
60 "This option improves speed by removing NaN checking. Please note this also reduces "
61 "accuracy of certain floating-point instructions.");
62 INSERT( 51 INSERT(
63 Settings, cpuopt_unsafe_fastmem_check, "Disable address space checks", 52 Settings, cpuopt_unsafe_unfuse_fma,
64 "This option improves speed by eliminating a safety check before every memory read/write " 53 tr("Unfuse FMA (improve performance on CPUs without FMA)"),
65 "in guest. Disabling it may allow a game to read/write the emulator's memory."); 54 tr("This option improves speed by reducing accuracy of fused-multiply-add instructions on "
66 INSERT(Settings, cpuopt_unsafe_ignore_global_monitor, "Ignore global monitor", 55 "CPUs without native FMA support."));
67 "This option improves speed by relying only on the semantics of cmpxchg to ensure " 56 INSERT(
57 Settings, cpuopt_unsafe_reduce_fp_error, tr("Faster FRSQRTE and FRECPE"),
58 tr("This option improves the speed of some approximate floating-point functions by using "
59 "less accurate native approximations."));
60 INSERT(Settings, cpuopt_unsafe_ignore_standard_fpcr,
61 tr("Faster ASIMD instructions (32 bits only)"),
62 tr("This option improves the speed of 32 bits ASIMD floating-point functions by running "
63 "with incorrect rounding modes."));
64 INSERT(Settings, cpuopt_unsafe_inaccurate_nan, tr("Inaccurate NaN handling"),
65 tr("This option improves speed by removing NaN checking. Please note this also reduces "
66 "accuracy of certain floating-point instructions."));
67 INSERT(Settings, cpuopt_unsafe_fastmem_check, tr("Disable address space checks"),
68 tr("This option improves speed by eliminating a safety check before every memory "
69 "read/write "
70 "in guest. Disabling it may allow a game to read/write the emulator's memory."));
71 INSERT(
72 Settings, cpuopt_unsafe_ignore_global_monitor, tr("Ignore global monitor"),
73 tr("This option improves speed by relying only on the semantics of cmpxchg to ensure "
68 "safety of exclusive access instructions. Please note this may result in deadlocks and " 74 "safety of exclusive access instructions. Please note this may result in deadlocks and "
69 "other race conditions."); 75 "other race conditions."));
70 76
71 // Renderer 77 // Renderer
72 INSERT(Settings, renderer_backend, "API:", ""); 78 INSERT(Settings, renderer_backend, tr("API:"), QStringLiteral());
73 INSERT(Settings, vulkan_device, "Device:", ""); 79 INSERT(Settings, vulkan_device, tr("Device:"), QStringLiteral());
74 INSERT(Settings, shader_backend, "Shader Backend:", ""); 80 INSERT(Settings, shader_backend, tr("Shader Backend:"), QStringLiteral());
75 INSERT(Settings, resolution_setup, "Resolution:", ""); 81 INSERT(Settings, resolution_setup, tr("Resolution:"), QStringLiteral());
76 INSERT(Settings, scaling_filter, "Window Adapting Filter:", ""); 82 INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QStringLiteral());
77 INSERT(Settings, fsr_sharpening_slider, "FSR Sharpness:", ""); 83 INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"), QStringLiteral());
78 INSERT(Settings, anti_aliasing, "Anti-Aliasing Method:", ""); 84 INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"), QStringLiteral());
79 INSERT(Settings, fullscreen_mode, "Fullscreen Mode:", ""); 85 INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"), QStringLiteral());
80 INSERT(Settings, aspect_ratio, "Aspect Ratio:", ""); 86 INSERT(Settings, aspect_ratio, tr("Aspect Ratio:"), QStringLiteral());
81 INSERT(Settings, use_disk_shader_cache, "Use disk pipeline cache", ""); 87 INSERT(Settings, use_disk_shader_cache, tr("Use disk pipeline cache"), QStringLiteral());
82 INSERT(Settings, use_asynchronous_gpu_emulation, "Use asynchronous GPU emulation", ""); 88 INSERT(Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"),
83 INSERT(Settings, nvdec_emulation, "NVDEC emulation:", ""); 89 QStringLiteral());
84 INSERT(Settings, accelerate_astc, "ASTC Decoding Method:", ""); 90 INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"), QStringLiteral());
85 INSERT(Settings, astc_recompression, "ASTC Recompression Method:", ""); 91 INSERT(Settings, accelerate_astc, tr("ASTC Decoding Method:"), QStringLiteral());
86 INSERT(Settings, vsync_mode, "VSync Mode:", 92 INSERT(Settings, astc_recompression, tr("ASTC Recompression Method:"), QStringLiteral());
87 "FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen " 93 INSERT(
94 Settings, vsync_mode, tr("VSync Mode:"),
95 tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
88 "refresh rate.\nFIFO Relaxed is similar to FIFO but allows tearing as it recovers from " 96 "refresh rate.\nFIFO Relaxed is similar to FIFO but allows tearing as it recovers from "
89 "a slow down.\nMailbox can have lower latency than FIFO and does not tear but may drop " 97 "a slow down.\nMailbox can have lower latency than FIFO and does not tear but may drop "
90 "frames.\nImmediate (no synchronization) just presents whatever is available and can " 98 "frames.\nImmediate (no synchronization) just presents whatever is available and can "
91 "exhibit tearing."); 99 "exhibit tearing."));
92 INSERT(Settings, bg_red, "", ""); 100 INSERT(Settings, bg_red, QStringLiteral(), QStringLiteral());
93 INSERT(Settings, bg_green, "", ""); 101 INSERT(Settings, bg_green, QStringLiteral(), QStringLiteral());
94 INSERT(Settings, bg_blue, "", ""); 102 INSERT(Settings, bg_blue, QStringLiteral(), QStringLiteral());
95 103
96 // Renderer (Advanced Graphics) 104 // Renderer (Advanced Graphics)
97 INSERT(Settings, async_presentation, "Enable asynchronous presentation (Vulkan only)", ""); 105 INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"),
98 INSERT(Settings, renderer_force_max_clock, "Force maximum clocks (Vulkan only)", 106 QStringLiteral());
99 "Runs work in the background while waiting for graphics commands to keep the GPU from " 107 INSERT(
100 "lowering its clock speed."); 108 Settings, renderer_force_max_clock, tr("Force maximum clocks (Vulkan only)"),
101 INSERT(Settings, max_anisotropy, "Anisotropic Filtering:", ""); 109 tr("Runs work in the background while waiting for graphics commands to keep the GPU from "
102 INSERT(Settings, gpu_accuracy, "Accuracy Level:", ""); 110 "lowering its clock speed."));
103 INSERT(Settings, use_asynchronous_shaders, "Use asynchronous shader building (Hack)", 111 INSERT(Settings, max_anisotropy, tr("Anisotropic Filtering:"), QStringLiteral());
104 "Enables asynchronous shader compilation, which may reduce shader stutter. This feature " 112 INSERT(Settings, gpu_accuracy, tr("Accuracy Level:"), QStringLiteral());
105 "is experimental."); 113 INSERT(
106 INSERT(Settings, use_fast_gpu_time, "Use Fast GPU Time (Hack)", 114 Settings, use_asynchronous_shaders, tr("Use asynchronous shader building (Hack)"),
107 "Enables Fast GPU Time. This option will force most games to run at their highest " 115 tr("Enables asynchronous shader compilation, which may reduce shader stutter. This feature "
108 "native resolution."); 116 "is experimental."));
109 INSERT(Settings, use_vulkan_driver_pipeline_cache, "Use Vulkan pipeline cache", 117 INSERT(Settings, use_fast_gpu_time, tr("Use Fast GPU Time (Hack)"),
110 "Enables GPU vendor-specific pipeline cache. This option can improve shader loading " 118 tr("Enables Fast GPU Time. This option will force most games to run at their highest "
111 "time significantly in cases where the Vulkan driver does not store pipeline cache " 119 "native resolution."));
112 "files internally."); 120 INSERT(Settings, use_vulkan_driver_pipeline_cache, tr("Use Vulkan pipeline cache"),
113 INSERT(Settings, enable_compute_pipelines, "Enable Compute Pipelines (Intel Vulkan Only)", 121 tr("Enables GPU vendor-specific pipeline cache. This option can improve shader loading "
114 "Enable compute pipelines, required by some games.\nThis setting only exists for Intel " 122 "time significantly in cases where the Vulkan driver does not store pipeline cache "
123 "files internally."));
124 INSERT(
125 Settings, enable_compute_pipelines, tr("Enable Compute Pipelines (Intel Vulkan Only)"),
126 tr("Enable compute pipelines, required by some games.\nThis setting only exists for Intel "
115 "proprietary drivers, and may crash if enabled.\nCompute pipelines are always enabled " 127 "proprietary drivers, and may crash if enabled.\nCompute pipelines are always enabled "
116 "on all other drivers."); 128 "on all other drivers."));
117 INSERT(Settings, use_reactive_flushing, "Enable Reactive Flushing", 129 INSERT(
118 "Uses reactive flushing instead of predictive flushing, allowing more accurate memory " 130 Settings, use_reactive_flushing, tr("Enable Reactive Flushing"),
119 "syncing."); 131 tr("Uses reactive flushing instead of predictive flushing, allowing more accurate memory "
120 INSERT(Settings, use_video_framerate, "Sync to framerate of video playback", 132 "syncing."));
121 "Run the game at normal speed during video playback, even when the framerate is " 133 INSERT(Settings, use_video_framerate, tr("Sync to framerate of video playback"),
122 "unlocked."); 134 tr("Run the game at normal speed during video playback, even when the framerate is "
123 INSERT(Settings, barrier_feedback_loops, "Barrier feedback loops", 135 "unlocked."));
124 "Improves rendering of transparency effects in specific games."); 136 INSERT(Settings, barrier_feedback_loops, tr("Barrier feedback loops"),
137 tr("Improves rendering of transparency effects in specific games."));
125 138
126 // Renderer (Debug) 139 // Renderer (Debug)
127 140
128 // System 141 // System
129 INSERT(Settings, rng_seed, "RNG Seed", ""); 142 INSERT(Settings, rng_seed, tr("RNG Seed"), QStringLiteral());
130 INSERT(Settings, rng_seed_enabled, "", ""); 143 INSERT(Settings, rng_seed_enabled, QStringLiteral(), QStringLiteral());
131 INSERT(Settings, device_name, "Device Name", ""); 144 INSERT(Settings, device_name, tr("Device Name"), QStringLiteral());
132 INSERT(Settings, custom_rtc, "Custom RTC", ""); 145 INSERT(Settings, custom_rtc, tr("Custom RTC"), QStringLiteral());
133 INSERT(Settings, custom_rtc_enabled, "", ""); 146 INSERT(Settings, custom_rtc_enabled, QStringLiteral(), QStringLiteral());
134 INSERT(Settings, language_index, 147 INSERT(Settings, language_index, tr("Language:"),
135 "Language:", "Note: this can be overridden when region setting is auto-select"); 148 tr("Note: this can be overridden when region setting is auto-select"));
136 INSERT(Settings, region_index, "Region:", ""); 149 INSERT(Settings, region_index, tr("Region:"), QStringLiteral());
137 INSERT(Settings, time_zone_index, "Time Zone:", ""); 150 INSERT(Settings, time_zone_index, tr("Time Zone:"), QStringLiteral());
138 INSERT(Settings, sound_index, "Sound Output Mode:", ""); 151 INSERT(Settings, sound_index, tr("Sound Output Mode:"), QStringLiteral());
139 INSERT(Settings, use_docked_mode, "Console Mode:", ""); 152 INSERT(Settings, use_docked_mode, tr("Console Mode:"), QStringLiteral());
140 INSERT(Settings, current_user, "", ""); 153 INSERT(Settings, current_user, QStringLiteral(), QStringLiteral());
141 154
142 // Controls 155 // Controls
143 156
@@ -154,11 +167,14 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
154 // Ui 167 // Ui
155 168
156 // Ui General 169 // Ui General
157 INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", ""); 170 INSERT(UISettings, select_user_on_boot, tr("Prompt for user on game boot"), QStringLiteral());
158 INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", ""); 171 INSERT(UISettings, pause_when_in_background, tr("Pause emulation when in background"),
159 INSERT(UISettings, confirm_before_stopping, "Confirm before stopping emulation", ""); 172 QStringLiteral());
160 INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", ""); 173 INSERT(UISettings, confirm_before_stopping, tr("Confirm before stopping emulation"),
161 INSERT(UISettings, controller_applet_disabled, "Disable controller applet", ""); 174 QStringLiteral());
175 INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"), QStringLiteral());
176 INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"),
177 QStringLiteral());
162 178
163 // Ui Debugging 179 // Ui Debugging
164 180
@@ -178,140 +194,141 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
178 return parent->tr(text, context); 194 return parent->tr(text, context);
179 }; 195 };
180 196
181#define PAIR(ENUM, VALUE, TRANSLATION) \ 197#define PAIR(ENUM, VALUE, TRANSLATION) {static_cast<u32>(Settings::ENUM::VALUE), (TRANSLATION)}
182 { static_cast<u32>(Settings::ENUM::VALUE), tr(TRANSLATION) }
183#define CTX_PAIR(ENUM, VALUE, TRANSLATION, CONTEXT) \
184 { static_cast<u32>(Settings::ENUM::VALUE), tr(TRANSLATION, CONTEXT) }
185 198
186 // Intentionally skipping VSyncMode to let the UI fill that one out 199 // Intentionally skipping VSyncMode to let the UI fill that one out
187 200
188 translations->insert({Settings::EnumMetadata<Settings::AstcDecodeMode>::Index(), 201 translations->insert({Settings::EnumMetadata<Settings::AstcDecodeMode>::Index(),
189 { 202 {
190 PAIR(AstcDecodeMode, Cpu, "CPU"), 203 PAIR(AstcDecodeMode, Cpu, tr("CPU")),
191 PAIR(AstcDecodeMode, Gpu, "GPU"), 204 PAIR(AstcDecodeMode, Gpu, tr("GPU")),
192 PAIR(AstcDecodeMode, CpuAsynchronous, "CPU Asynchronous"), 205 PAIR(AstcDecodeMode, CpuAsynchronous, tr("CPU Asynchronous")),
193 }});
194 translations->insert({Settings::EnumMetadata<Settings::AstcRecompression>::Index(),
195 {
196 PAIR(AstcRecompression, Uncompressed, "Uncompressed (Best quality)"),
197 PAIR(AstcRecompression, Bc1, "BC1 (Low quality)"),
198 PAIR(AstcRecompression, Bc3, "BC3 (Medium quality)"),
199 }}); 206 }});
207 translations->insert(
208 {Settings::EnumMetadata<Settings::AstcRecompression>::Index(),
209 {
210 PAIR(AstcRecompression, Uncompressed, tr("Uncompressed (Best quality)")),
211 PAIR(AstcRecompression, Bc1, tr("BC1 (Low quality)")),
212 PAIR(AstcRecompression, Bc3, tr("BC3 (Medium quality)")),
213 }});
200 translations->insert({Settings::EnumMetadata<Settings::RendererBackend>::Index(), 214 translations->insert({Settings::EnumMetadata<Settings::RendererBackend>::Index(),
201 { 215 {
202#ifdef HAS_OPENGL 216#ifdef HAS_OPENGL
203 PAIR(RendererBackend, OpenGL, "OpenGL"), 217 PAIR(RendererBackend, OpenGL, tr("OpenGL")),
204#endif 218#endif
205 PAIR(RendererBackend, Vulkan, "Vulkan"), 219 PAIR(RendererBackend, Vulkan, tr("Vulkan")),
206 PAIR(RendererBackend, Null, "Null"), 220 PAIR(RendererBackend, Null, tr("Null")),
207 }});
208 translations->insert({Settings::EnumMetadata<Settings::ShaderBackend>::Index(),
209 {
210 PAIR(ShaderBackend, Glsl, "GLSL"),
211 PAIR(ShaderBackend, Glasm, "GLASM (Assembly Shaders, NVIDIA Only)"),
212 PAIR(ShaderBackend, SpirV, "SPIR-V (Experimental, Mesa Only)"),
213 }}); 221 }});
222 translations->insert(
223 {Settings::EnumMetadata<Settings::ShaderBackend>::Index(),
224 {
225 PAIR(ShaderBackend, Glsl, tr("GLSL")),
226 PAIR(ShaderBackend, Glasm, tr("GLASM (Assembly Shaders, NVIDIA Only)")),
227 PAIR(ShaderBackend, SpirV, tr("SPIR-V (Experimental, Mesa Only)")),
228 }});
214 translations->insert({Settings::EnumMetadata<Settings::GpuAccuracy>::Index(), 229 translations->insert({Settings::EnumMetadata<Settings::GpuAccuracy>::Index(),
215 { 230 {
216 PAIR(GpuAccuracy, Normal, "Normal"), 231 PAIR(GpuAccuracy, Normal, tr("Normal")),
217 PAIR(GpuAccuracy, High, "High"), 232 PAIR(GpuAccuracy, High, tr("High")),
218 PAIR(GpuAccuracy, Extreme, "Extreme"), 233 PAIR(GpuAccuracy, Extreme, tr("Extreme")),
219 }});
220 translations->insert({Settings::EnumMetadata<Settings::CpuAccuracy>::Index(),
221 {
222 PAIR(CpuAccuracy, Auto, "Auto"),
223 PAIR(CpuAccuracy, Accurate, "Accurate"),
224 PAIR(CpuAccuracy, Unsafe, "Unsafe"),
225 PAIR(CpuAccuracy, Paranoid, "Paranoid (disables most optimizations)"),
226 }}); 234 }});
235 translations->insert(
236 {Settings::EnumMetadata<Settings::CpuAccuracy>::Index(),
237 {
238 PAIR(CpuAccuracy, Auto, tr("Auto")),
239 PAIR(CpuAccuracy, Accurate, tr("Accurate")),
240 PAIR(CpuAccuracy, Unsafe, tr("Unsafe")),
241 PAIR(CpuAccuracy, Paranoid, tr("Paranoid (disables most optimizations)")),
242 }});
227 translations->insert({Settings::EnumMetadata<Settings::FullscreenMode>::Index(), 243 translations->insert({Settings::EnumMetadata<Settings::FullscreenMode>::Index(),
228 { 244 {
229 PAIR(FullscreenMode, Borderless, "Borderless Windowed"), 245 PAIR(FullscreenMode, Borderless, tr("Borderless Windowed")),
230 PAIR(FullscreenMode, Exclusive, "Exclusive Fullscreen"), 246 PAIR(FullscreenMode, Exclusive, tr("Exclusive Fullscreen")),
231 }}); 247 }});
232 translations->insert({Settings::EnumMetadata<Settings::NvdecEmulation>::Index(), 248 translations->insert({Settings::EnumMetadata<Settings::NvdecEmulation>::Index(),
233 { 249 {
234 PAIR(NvdecEmulation, Off, "No Video Output"), 250 PAIR(NvdecEmulation, Off, tr("No Video Output")),
235 PAIR(NvdecEmulation, Cpu, "CPU Video Decoding"), 251 PAIR(NvdecEmulation, Cpu, tr("CPU Video Decoding")),
236 PAIR(NvdecEmulation, Gpu, "GPU Video Decoding (Default)"), 252 PAIR(NvdecEmulation, Gpu, tr("GPU Video Decoding (Default)")),
237 }});
238 translations->insert({Settings::EnumMetadata<Settings::ResolutionSetup>::Index(),
239 {
240 PAIR(ResolutionSetup, Res1_2X, "0.5X (360p/540p) [EXPERIMENTAL]"),
241 PAIR(ResolutionSetup, Res3_4X, "0.75X (540p/810p) [EXPERIMENTAL]"),
242 PAIR(ResolutionSetup, Res1X, "1X (720p/1080p)"),
243 PAIR(ResolutionSetup, Res3_2X, "1.5X (1080p/1620p) [EXPERIMENTAL]"),
244 PAIR(ResolutionSetup, Res2X, "2X (1440p/2160p)"),
245 PAIR(ResolutionSetup, Res3X, "3X (2160p/3240p)"),
246 PAIR(ResolutionSetup, Res4X, "4X (2880p/4320p)"),
247 PAIR(ResolutionSetup, Res5X, "5X (3600p/5400p)"),
248 PAIR(ResolutionSetup, Res6X, "6X (4320p/6480p)"),
249 PAIR(ResolutionSetup, Res7X, "7X (5040p/7560p)"),
250 PAIR(ResolutionSetup, Res8X, "8X (5760p/8640p)"),
251 }}); 253 }});
254 translations->insert(
255 {Settings::EnumMetadata<Settings::ResolutionSetup>::Index(),
256 {
257 PAIR(ResolutionSetup, Res1_2X, tr("0.5X (360p/540p) [EXPERIMENTAL]")),
258 PAIR(ResolutionSetup, Res3_4X, tr("0.75X (540p/810p) [EXPERIMENTAL]")),
259 PAIR(ResolutionSetup, Res1X, tr("1X (720p/1080p)")),
260 PAIR(ResolutionSetup, Res3_2X, tr("1.5X (1080p/1620p) [EXPERIMENTAL]")),
261 PAIR(ResolutionSetup, Res2X, tr("2X (1440p/2160p)")),
262 PAIR(ResolutionSetup, Res3X, tr("3X (2160p/3240p)")),
263 PAIR(ResolutionSetup, Res4X, tr("4X (2880p/4320p)")),
264 PAIR(ResolutionSetup, Res5X, tr("5X (3600p/5400p)")),
265 PAIR(ResolutionSetup, Res6X, tr("6X (4320p/6480p)")),
266 PAIR(ResolutionSetup, Res7X, tr("7X (5040p/7560p)")),
267 PAIR(ResolutionSetup, Res8X, tr("8X (5760p/8640p)")),
268 }});
252 translations->insert({Settings::EnumMetadata<Settings::ScalingFilter>::Index(), 269 translations->insert({Settings::EnumMetadata<Settings::ScalingFilter>::Index(),
253 { 270 {
254 PAIR(ScalingFilter, NearestNeighbor, "Nearest Neighbor"), 271 PAIR(ScalingFilter, NearestNeighbor, tr("Nearest Neighbor")),
255 PAIR(ScalingFilter, Bilinear, "Bilinear"), 272 PAIR(ScalingFilter, Bilinear, tr("Bilinear")),
256 PAIR(ScalingFilter, Bicubic, "Bicubic"), 273 PAIR(ScalingFilter, Bicubic, tr("Bicubic")),
257 PAIR(ScalingFilter, Gaussian, "Gaussian"), 274 PAIR(ScalingFilter, Gaussian, tr("Gaussian")),
258 PAIR(ScalingFilter, ScaleForce, "ScaleForce"), 275 PAIR(ScalingFilter, ScaleForce, tr("ScaleForce")),
259 PAIR(ScalingFilter, Fsr, "AMD FidelityFX™️ Super Resolution"), 276 PAIR(ScalingFilter, Fsr, tr("AMD FidelityFX™️ Super Resolution")),
260 }}); 277 }});
261 translations->insert({Settings::EnumMetadata<Settings::AntiAliasing>::Index(), 278 translations->insert({Settings::EnumMetadata<Settings::AntiAliasing>::Index(),
262 { 279 {
263 PAIR(AntiAliasing, None, "None"), 280 PAIR(AntiAliasing, None, tr("None")),
264 PAIR(AntiAliasing, Fxaa, "FXAA"), 281 PAIR(AntiAliasing, Fxaa, tr("FXAA")),
265 PAIR(AntiAliasing, Smaa, "SMAA"), 282 PAIR(AntiAliasing, Smaa, tr("SMAA")),
266 }}); 283 }});
267 translations->insert({Settings::EnumMetadata<Settings::AspectRatio>::Index(), 284 translations->insert({Settings::EnumMetadata<Settings::AspectRatio>::Index(),
268 { 285 {
269 PAIR(AspectRatio, R16_9, "Default (16:9)"), 286 PAIR(AspectRatio, R16_9, tr("Default (16:9)")),
270 PAIR(AspectRatio, R4_3, "Force 4:3"), 287 PAIR(AspectRatio, R4_3, tr("Force 4:3")),
271 PAIR(AspectRatio, R21_9, "Force 21:9"), 288 PAIR(AspectRatio, R21_9, tr("Force 21:9")),
272 PAIR(AspectRatio, R16_10, "Force 16:10"), 289 PAIR(AspectRatio, R16_10, tr("Force 16:10")),
273 PAIR(AspectRatio, Stretch, "Stretch to Window"), 290 PAIR(AspectRatio, Stretch, tr("Stretch to Window")),
274 }}); 291 }});
275 translations->insert({Settings::EnumMetadata<Settings::AnisotropyMode>::Index(), 292 translations->insert({Settings::EnumMetadata<Settings::AnisotropyMode>::Index(),
276 { 293 {
277 PAIR(AnisotropyMode, Automatic, "Automatic"), 294 PAIR(AnisotropyMode, Automatic, tr("Automatic")),
278 PAIR(AnisotropyMode, Default, "Default"), 295 PAIR(AnisotropyMode, Default, tr("Default")),
279 PAIR(AnisotropyMode, X2, "2x"), 296 PAIR(AnisotropyMode, X2, tr("2x")),
280 PAIR(AnisotropyMode, X4, "4x"), 297 PAIR(AnisotropyMode, X4, tr("4x")),
281 PAIR(AnisotropyMode, X8, "8x"), 298 PAIR(AnisotropyMode, X8, tr("8x")),
282 PAIR(AnisotropyMode, X16, "16x"), 299 PAIR(AnisotropyMode, X16, tr("16x")),
283 }}); 300 }});
284 translations->insert( 301 translations->insert(
285 {Settings::EnumMetadata<Settings::Language>::Index(), 302 {Settings::EnumMetadata<Settings::Language>::Index(),
286 { 303 {
287 PAIR(Language, Japanese, "Japanese (日本語)"), 304 PAIR(Language, Japanese, tr("Japanese (日本語)")),
288 PAIR(Language, EnglishAmerican, "American English"), 305 PAIR(Language, EnglishAmerican, tr("American English")),
289 PAIR(Language, French, "French (français)"), 306 PAIR(Language, French, tr("French (français)")),
290 PAIR(Language, German, "German (Deutsch)"), 307 PAIR(Language, German, tr("German (Deutsch)")),
291 PAIR(Language, Italian, "Italian (italiano)"), 308 PAIR(Language, Italian, tr("Italian (italiano)")),
292 PAIR(Language, Spanish, "Spanish (español)"), 309 PAIR(Language, Spanish, tr("Spanish (español)")),
293 PAIR(Language, Chinese, "Chinese"), 310 PAIR(Language, Chinese, tr("Chinese")),
294 PAIR(Language, Korean, "Korean (한국어)"), 311 PAIR(Language, Korean, tr("Korean (한국어)")),
295 PAIR(Language, Dutch, "Dutch (Nederlands)"), 312 PAIR(Language, Dutch, tr("Dutch (Nederlands)")),
296 PAIR(Language, Portuguese, "Portuguese (português)"), 313 PAIR(Language, Portuguese, tr("Portuguese (português)")),
297 PAIR(Language, Russian, "Russian (Русский)"), 314 PAIR(Language, Russian, tr("Russian (Русский)")),
298 PAIR(Language, Taiwanese, "Taiwanese"), 315 PAIR(Language, Taiwanese, tr("Taiwanese")),
299 PAIR(Language, EnglishBritish, "British English"), 316 PAIR(Language, EnglishBritish, tr("British English")),
300 PAIR(Language, FrenchCanadian, "Canadian French"), 317 PAIR(Language, FrenchCanadian, tr("Canadian French")),
301 PAIR(Language, SpanishLatin, "Latin American Spanish"), 318 PAIR(Language, SpanishLatin, tr("Latin American Spanish")),
302 PAIR(Language, ChineseSimplified, "Simplified Chinese"), 319 PAIR(Language, ChineseSimplified, tr("Simplified Chinese")),
303 PAIR(Language, ChineseTraditional, "Traditional Chinese (正體中文)"), 320 PAIR(Language, ChineseTraditional, tr("Traditional Chinese (正體中文)")),
304 PAIR(Language, PortugueseBrazilian, "Brazilian Portuguese (português do Brasil)"), 321 PAIR(Language, PortugueseBrazilian, tr("Brazilian Portuguese (português do Brasil)")),
305 }}); 322 }});
306 translations->insert({Settings::EnumMetadata<Settings::Region>::Index(), 323 translations->insert({Settings::EnumMetadata<Settings::Region>::Index(),
307 { 324 {
308 PAIR(Region, Japan, "Japan"), 325 PAIR(Region, Japan, tr("Japan")),
309 PAIR(Region, Usa, "USA"), 326 PAIR(Region, Usa, tr("USA")),
310 PAIR(Region, Europe, "Europe"), 327 PAIR(Region, Europe, tr("Europe")),
311 PAIR(Region, Australia, "Australia"), 328 PAIR(Region, Australia, tr("Australia")),
312 PAIR(Region, China, "China"), 329 PAIR(Region, China, tr("China")),
313 PAIR(Region, Korea, "Korea"), 330 PAIR(Region, Korea, tr("Korea")),
314 PAIR(Region, Taiwan, "Taiwan"), 331 PAIR(Region, Taiwan, tr("Taiwan")),
315 }}); 332 }});
316 translations->insert( 333 translations->insert(
317 {Settings::EnumMetadata<Settings::TimeZone>::Index(), 334 {Settings::EnumMetadata<Settings::TimeZone>::Index(),
@@ -323,72 +340,74 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
323 {static_cast<u32>(Settings::TimeZone::Default), 340 {static_cast<u32>(Settings::TimeZone::Default),
324 tr("Default (%1)", "Default time zone") 341 tr("Default (%1)", "Default time zone")
325 .arg(QString::fromStdString(Common::TimeZone::GetDefaultTimeZone()))}, 342 .arg(QString::fromStdString(Common::TimeZone::GetDefaultTimeZone()))},
326 PAIR(TimeZone, Cet, "CET"), 343 PAIR(TimeZone, Cet, tr("CET")),
327 PAIR(TimeZone, Cst6Cdt, "CST6CDT"), 344 PAIR(TimeZone, Cst6Cdt, tr("CST6CDT")),
328 PAIR(TimeZone, Cuba, "Cuba"), 345 PAIR(TimeZone, Cuba, tr("Cuba")),
329 PAIR(TimeZone, Eet, "EET"), 346 PAIR(TimeZone, Eet, tr("EET")),
330 PAIR(TimeZone, Egypt, "Egypt"), 347 PAIR(TimeZone, Egypt, tr("Egypt")),
331 PAIR(TimeZone, Eire, "Eire"), 348 PAIR(TimeZone, Eire, tr("Eire")),
332 PAIR(TimeZone, Est, "EST"), 349 PAIR(TimeZone, Est, tr("EST")),
333 PAIR(TimeZone, Est5Edt, "EST5EDT"), 350 PAIR(TimeZone, Est5Edt, tr("EST5EDT")),
334 PAIR(TimeZone, Gb, "GB"), 351 PAIR(TimeZone, Gb, tr("GB")),
335 PAIR(TimeZone, GbEire, "GB-Eire"), 352 PAIR(TimeZone, GbEire, tr("GB-Eire")),
336 PAIR(TimeZone, Gmt, "GMT"), 353 PAIR(TimeZone, Gmt, tr("GMT")),
337 PAIR(TimeZone, GmtPlusZero, "GMT+0"), 354 PAIR(TimeZone, GmtPlusZero, tr("GMT+0")),
338 PAIR(TimeZone, GmtMinusZero, "GMT-0"), 355 PAIR(TimeZone, GmtMinusZero, tr("GMT-0")),
339 PAIR(TimeZone, GmtZero, "GMT0"), 356 PAIR(TimeZone, GmtZero, tr("GMT0")),
340 PAIR(TimeZone, Greenwich, "Greenwich"), 357 PAIR(TimeZone, Greenwich, tr("Greenwich")),
341 PAIR(TimeZone, Hongkong, "Hongkong"), 358 PAIR(TimeZone, Hongkong, tr("Hongkong")),
342 PAIR(TimeZone, Hst, "HST"), 359 PAIR(TimeZone, Hst, tr("HST")),
343 PAIR(TimeZone, Iceland, "Iceland"), 360 PAIR(TimeZone, Iceland, tr("Iceland")),
344 PAIR(TimeZone, Iran, "Iran"), 361 PAIR(TimeZone, Iran, tr("Iran")),
345 PAIR(TimeZone, Israel, "Israel"), 362 PAIR(TimeZone, Israel, tr("Israel")),
346 PAIR(TimeZone, Jamaica, "Jamaica"), 363 PAIR(TimeZone, Jamaica, tr("Jamaica")),
347 PAIR(TimeZone, Japan, "Japan"), 364 PAIR(TimeZone, Japan, tr("Japan")),
348 PAIR(TimeZone, Kwajalein, "Kwajalein"), 365 PAIR(TimeZone, Kwajalein, tr("Kwajalein")),
349 PAIR(TimeZone, Libya, "Libya"), 366 PAIR(TimeZone, Libya, tr("Libya")),
350 PAIR(TimeZone, Met, "MET"), 367 PAIR(TimeZone, Met, tr("MET")),
351 PAIR(TimeZone, Mst, "MST"), 368 PAIR(TimeZone, Mst, tr("MST")),
352 PAIR(TimeZone, Mst7Mdt, "MST7MDT"), 369 PAIR(TimeZone, Mst7Mdt, tr("MST7MDT")),
353 PAIR(TimeZone, Navajo, "Navajo"), 370 PAIR(TimeZone, Navajo, tr("Navajo")),
354 PAIR(TimeZone, Nz, "NZ"), 371 PAIR(TimeZone, Nz, tr("NZ")),
355 PAIR(TimeZone, NzChat, "NZ-CHAT"), 372 PAIR(TimeZone, NzChat, tr("NZ-CHAT")),
356 PAIR(TimeZone, Poland, "Poland"), 373 PAIR(TimeZone, Poland, tr("Poland")),
357 PAIR(TimeZone, Portugal, "Portugal"), 374 PAIR(TimeZone, Portugal, tr("Portugal")),
358 PAIR(TimeZone, Prc, "PRC"), 375 PAIR(TimeZone, Prc, tr("PRC")),
359 PAIR(TimeZone, Pst8Pdt, "PST8PDT"), 376 PAIR(TimeZone, Pst8Pdt, tr("PST8PDT")),
360 PAIR(TimeZone, Roc, "ROC"), 377 PAIR(TimeZone, Roc, tr("ROC")),
361 PAIR(TimeZone, Rok, "ROK"), 378 PAIR(TimeZone, Rok, tr("ROK")),
362 PAIR(TimeZone, Singapore, "Singapore"), 379 PAIR(TimeZone, Singapore, tr("Singapore")),
363 PAIR(TimeZone, Turkey, "Turkey"), 380 PAIR(TimeZone, Turkey, tr("Turkey")),
364 PAIR(TimeZone, Uct, "UCT"), 381 PAIR(TimeZone, Uct, tr("UCT")),
365 PAIR(TimeZone, Universal, "Universal"), 382 PAIR(TimeZone, Universal, tr("Universal")),
366 PAIR(TimeZone, Utc, "UTC"), 383 PAIR(TimeZone, Utc, tr("UTC")),
367 PAIR(TimeZone, WSu, "W-SU"), 384 PAIR(TimeZone, WSu, tr("W-SU")),
368 PAIR(TimeZone, Wet, "WET"), 385 PAIR(TimeZone, Wet, tr("WET")),
369 PAIR(TimeZone, Zulu, "Zulu"), 386 PAIR(TimeZone, Zulu, tr("Zulu")),
370 }}); 387 }});
371 translations->insert({Settings::EnumMetadata<Settings::AudioMode>::Index(), 388 translations->insert({Settings::EnumMetadata<Settings::AudioMode>::Index(),
372 { 389 {
373 PAIR(AudioMode, Mono, "Mono"), 390 PAIR(AudioMode, Mono, tr("Mono")),
374 PAIR(AudioMode, Stereo, "Stereo"), 391 PAIR(AudioMode, Stereo, tr("Stereo")),
375 PAIR(AudioMode, Surround, "Surround"), 392 PAIR(AudioMode, Surround, tr("Surround")),
376 }}); 393 }});
377 translations->insert({Settings::EnumMetadata<Settings::MemoryLayout>::Index(), 394 translations->insert({Settings::EnumMetadata<Settings::MemoryLayout>::Index(),
378 { 395 {
379 PAIR(MemoryLayout, Memory_4Gb, "4GB DRAM (Default)"), 396 PAIR(MemoryLayout, Memory_4Gb, tr("4GB DRAM (Default)")),
380 PAIR(MemoryLayout, Memory_6Gb, "6GB DRAM (Unsafe)"), 397 PAIR(MemoryLayout, Memory_6Gb, tr("6GB DRAM (Unsafe)")),
381 PAIR(MemoryLayout, Memory_8Gb, "8GB DRAM (Unsafe)"), 398 PAIR(MemoryLayout, Memory_8Gb, tr("8GB DRAM (Unsafe)")),
399 }});
400 translations->insert({Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
401 {
402 PAIR(ConsoleMode, Docked, tr("Docked")),
403 PAIR(ConsoleMode, Handheld, tr("Handheld")),
382 }}); 404 }});
383 translations->insert(
384 {Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
385 {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}});
386 translations->insert( 405 translations->insert(
387 {Settings::EnumMetadata<Settings::ConfirmStop>::Index(), 406 {Settings::EnumMetadata<Settings::ConfirmStop>::Index(),
388 { 407 {
389 PAIR(ConfirmStop, Ask_Always, "Always ask (Default)"), 408 PAIR(ConfirmStop, Ask_Always, tr("Always ask (Default)")),
390 PAIR(ConfirmStop, Ask_Based_On_Game, "Only if game specifies not to stop"), 409 PAIR(ConfirmStop, Ask_Based_On_Game, tr("Only if game specifies not to stop")),
391 PAIR(ConfirmStop, Ask_Never, "Never ask"), 410 PAIR(ConfirmStop, Ask_Never, tr("Never ask")),
392 }}); 411 }});
393 412
394#undef PAIR 413#undef PAIR
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index f6b548fd3..f22db233b 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1575,6 +1575,7 @@ void GMainWindow::ConnectMenuEvents() {
1575 connect_menu(ui->action_Load_Cabinet_Formatter, 1575 connect_menu(ui->action_Load_Cabinet_Formatter,
1576 [this]() { OnCabinet(Service::NFP::CabinetMode::StartFormatter); }); 1576 [this]() { OnCabinet(Service::NFP::CabinetMode::StartFormatter); });
1577 connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit); 1577 connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit);
1578 connect_menu(ui->action_Open_Controller_Menu, &GMainWindow::OnOpenControllerMenu);
1578 connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot); 1579 connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
1579 1580
1580 // TAS 1581 // TAS
@@ -1602,14 +1603,13 @@ void GMainWindow::UpdateMenuState() {
1602 ui->action_Pause, 1603 ui->action_Pause,
1603 }; 1604 };
1604 1605
1605 const std::array applet_actions{ 1606 const std::array applet_actions{ui->action_Load_Album,
1606 ui->action_Load_Album, 1607 ui->action_Load_Cabinet_Nickname_Owner,
1607 ui->action_Load_Cabinet_Nickname_Owner, 1608 ui->action_Load_Cabinet_Eraser,
1608 ui->action_Load_Cabinet_Eraser, 1609 ui->action_Load_Cabinet_Restorer,
1609 ui->action_Load_Cabinet_Restorer, 1610 ui->action_Load_Cabinet_Formatter,
1610 ui->action_Load_Cabinet_Formatter, 1611 ui->action_Load_Mii_Edit,
1611 ui->action_Load_Mii_Edit, 1612 ui->action_Open_Controller_Menu};
1612 };
1613 1613
1614 for (QAction* action : running_actions) { 1614 for (QAction* action : running_actions) {
1615 action->setEnabled(emulation_running); 1615 action->setEnabled(emulation_running);
@@ -4375,6 +4375,31 @@ void GMainWindow::OnMiiEdit() {
4375 BootGame(filename, MiiEditId); 4375 BootGame(filename, MiiEditId);
4376} 4376}
4377 4377
4378void GMainWindow::OnOpenControllerMenu() {
4379 constexpr u64 ControllerAppletId =
4380 static_cast<u64>(Service::AM::Applets::AppletProgramId::Controller);
4381 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4382 if (!bis_system) {
4383 QMessageBox::warning(this, tr("No firmware available"),
4384 tr("Please install the firmware to use the Controller Menu."));
4385 return;
4386 }
4387
4388 auto controller_applet_nca =
4389 bis_system->GetEntry(ControllerAppletId, FileSys::ContentRecordType::Program);
4390 if (!controller_applet_nca) {
4391 QMessageBox::warning(this, tr("Controller Applet"),
4392 tr("Controller Menu is not available. Please reinstall firmware."));
4393 return;
4394 }
4395
4396 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::Controller);
4397
4398 const auto filename = QString::fromStdString((controller_applet_nca->GetFullPath()));
4399 UISettings::values.roms_path = QFileInfo(filename).path();
4400 BootGame(filename, ControllerAppletId);
4401}
4402
4378void GMainWindow::OnCaptureScreenshot() { 4403void GMainWindow::OnCaptureScreenshot() {
4379 if (emu_thread == nullptr || !emu_thread->IsRunning()) { 4404 if (emu_thread == nullptr || !emu_thread->IsRunning()) {
4380 return; 4405 return;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index f67c4cfda..49ee1e1d2 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -410,6 +410,7 @@ private slots:
410 void OnAlbum(); 410 void OnAlbum();
411 void OnCabinet(Service::NFP::CabinetMode mode); 411 void OnCabinet(Service::NFP::CabinetMode mode);
412 void OnMiiEdit(); 412 void OnMiiEdit();
413 void OnOpenControllerMenu();
413 void OnCaptureScreenshot(); 414 void OnCaptureScreenshot();
414 void OnReinitializeKeys(ReinitializeKeyBehavior behavior); 415 void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
415 void OnLanguageChanged(const QString& locale); 416 void OnLanguageChanged(const QString& locale);
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 88684ffb5..e53f9951e 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -25,7 +25,7 @@
25 </property> 25 </property>
26 <widget class="QWidget" name="centralwidget"> 26 <widget class="QWidget" name="centralwidget">
27 <layout class="QHBoxLayout" name="horizontalLayout"> 27 <layout class="QHBoxLayout" name="horizontalLayout">
28 <property name="margin"> 28 <property name="margin" stdset="0">
29 <number>0</number> 29 <number>0</number>
30 </property> 30 </property>
31 </layout> 31 </layout>
@@ -36,7 +36,7 @@
36 <x>0</x> 36 <x>0</x>
37 <y>0</y> 37 <y>0</y>
38 <width>1280</width> 38 <width>1280</width>
39 <height>26</height> 39 <height>21</height>
40 </rect> 40 </rect>
41 </property> 41 </property>
42 <widget class="QMenu" name="menu_File"> 42 <widget class="QMenu" name="menu_File">
@@ -162,6 +162,7 @@
162 <addaction name="menu_cabinet_applet"/> 162 <addaction name="menu_cabinet_applet"/>
163 <addaction name="action_Load_Album"/> 163 <addaction name="action_Load_Album"/>
164 <addaction name="action_Load_Mii_Edit"/> 164 <addaction name="action_Load_Mii_Edit"/>
165 <addaction name="action_Open_Controller_Menu"/>
165 <addaction name="separator"/> 166 <addaction name="separator"/>
166 <addaction name="action_Capture_Screenshot"/> 167 <addaction name="action_Capture_Screenshot"/>
167 <addaction name="menuTAS"/> 168 <addaction name="menuTAS"/>
@@ -382,9 +383,9 @@
382 </property> 383 </property>
383 </action> 384 </action>
384 <action name="action_Load_Album"> 385 <action name="action_Load_Album">
385 <property name="text"> 386 <property name="text">
386 <string>Open &amp;Album</string> 387 <string>Open &amp;Album</string>
387 </property> 388 </property>
388 </action> 389 </action>
389 <action name="action_Load_Cabinet_Nickname_Owner"> 390 <action name="action_Load_Cabinet_Nickname_Owner">
390 <property name="text"> 391 <property name="text">
@@ -407,9 +408,9 @@
407 </property> 408 </property>
408 </action> 409 </action>
409 <action name="action_Load_Mii_Edit"> 410 <action name="action_Load_Mii_Edit">
410 <property name="text"> 411 <property name="text">
411 <string>Open &amp;Mii Editor</string> 412 <string>Open &amp;Mii Editor</string>
412 </property> 413 </property>
413 </action> 414 </action>
414 <action name="action_Configure_Tas"> 415 <action name="action_Configure_Tas">
415 <property name="text"> 416 <property name="text">
@@ -454,6 +455,11 @@
454 <string>R&amp;ecord</string> 455 <string>R&amp;ecord</string>
455 </property> 456 </property>
456 </action> 457 </action>
458 <action name="action_Open_Controller_Menu">
459 <property name="text">
460 <string>Open &amp;Controller Menu</string>
461 </property>
462 </action>
457 </widget> 463 </widget>
458 <resources> 464 <resources>
459 <include location="yuzu.qrc"/> 465 <include location="yuzu.qrc"/>