diff options
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 | |||
| 21 | import org.yuzu.yuzu_emu.model.HomeViewModel | 21 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 22 | import org.yuzu.yuzu_emu.model.Installable | 22 | import org.yuzu.yuzu_emu.model.Installable |
| 23 | import org.yuzu.yuzu_emu.ui.main.MainActivity | 23 | import org.yuzu.yuzu_emu.ui.main.MainActivity |
| 24 | import java.time.LocalDateTime | ||
| 25 | import java.time.format.DateTimeFormatter | ||
| 24 | 26 | ||
| 25 | class InstallableFragment : Fragment() { | 27 | class 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 | |||
| 6 | import android.content.Intent | 6 | import android.content.Intent |
| 7 | import android.net.Uri | 7 | import android.net.Uri |
| 8 | import android.os.Bundle | 8 | import android.os.Bundle |
| 9 | import android.provider.DocumentsContract | ||
| 10 | import android.view.View | 9 | import android.view.View |
| 11 | import android.view.ViewGroup.MarginLayoutParams | 10 | import android.view.ViewGroup.MarginLayoutParams |
| 12 | import android.view.WindowManager | 11 | import android.view.WindowManager |
| @@ -20,7 +19,6 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen | |||
| 20 | import androidx.core.view.ViewCompat | 19 | import androidx.core.view.ViewCompat |
| 21 | import androidx.core.view.WindowCompat | 20 | import androidx.core.view.WindowCompat |
| 22 | import androidx.core.view.WindowInsetsCompat | 21 | import androidx.core.view.WindowInsetsCompat |
| 23 | import androidx.documentfile.provider.DocumentFile | ||
| 24 | import androidx.lifecycle.Lifecycle | 22 | import androidx.lifecycle.Lifecycle |
| 25 | import androidx.lifecycle.lifecycleScope | 23 | import androidx.lifecycle.lifecycleScope |
| 26 | import androidx.lifecycle.repeatOnLifecycle | 24 | import androidx.lifecycle.repeatOnLifecycle |
| @@ -41,7 +39,6 @@ import org.yuzu.yuzu_emu.NativeLibrary | |||
| 41 | import org.yuzu.yuzu_emu.R | 39 | import org.yuzu.yuzu_emu.R |
| 42 | import org.yuzu.yuzu_emu.activities.EmulationActivity | 40 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
| 43 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding | 41 | import org.yuzu.yuzu_emu.databinding.ActivityMainBinding |
| 44 | import org.yuzu.yuzu_emu.features.DocumentProvider | ||
| 45 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 42 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 46 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment | 43 | import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment |
| 47 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | 44 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
| @@ -53,9 +50,6 @@ import org.yuzu.yuzu_emu.model.TaskViewModel | |||
| 53 | import org.yuzu.yuzu_emu.utils.* | 50 | import org.yuzu.yuzu_emu.utils.* |
| 54 | import java.io.BufferedInputStream | 51 | import java.io.BufferedInputStream |
| 55 | import java.io.BufferedOutputStream | 52 | import java.io.BufferedOutputStream |
| 56 | import java.io.FileOutputStream | ||
| 57 | import java.time.LocalDateTime | ||
| 58 | import java.time.format.DateTimeFormatter | ||
| 59 | import java.util.zip.ZipEntry | 53 | import java.util.zip.ZipEntry |
| 60 | import java.util.zip.ZipInputStream | 54 | import 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 | ||
| 221 | enum 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 |
| 222 | enum class NpadStyleIndex : u8 { | 229 | enum 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 | ||
| 103 | void 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 | |||
| 100 | void IWindowController::AcquireForegroundRights(HLERequestContext& ctx) { | 113 | void 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 | ||
| 1669 | void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext& ctx) { | 1685 | void 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 | ||
| 1705 | void 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 | ||
| 1776 | void 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 | |||
| 1740 | void ILibraryAppletSelfAccessor::PushInShowCabinetData() { | 1825 | void 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 | ||
| 88 | private: | 88 | private: |
| 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 { | |||
| 56 | struct ControllerSupportArgPrivate { | 56 | struct 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 | ||
| 128 | private: | 128 | private: |
| 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 | |||
| 296 | private: | ||
| 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 | ||
| 297 | class BTM_SYS final : public ServiceFramework<BTM_SYS> { | 337 | class BTM_SYS final : public ServiceFramework<BTM_SYS> { |
| @@ -308,7 +348,7 @@ public: | |||
| 308 | 348 | ||
| 309 | private: | 349 | private: |
| 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 | ||
| 1730 | Controller_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 | ||
| 207 | private: | 248 | private: |
| 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 | ||
| 226 | IHidSystemServer::~IHidSystemServer() { | 233 | IHidSystemServer::~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 | ||
| 230 | void IHidSystemServer::ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { | 240 | void 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 | ||
| 251 | void IHidSystemServer::EnableAssigningSingleOnSlSrPress(HLERequestContext& ctx) { | ||
| 252 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 253 | |||
| 254 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 255 | rb.Push(ResultSuccess); | ||
| 256 | } | ||
| 257 | |||
| 258 | void IHidSystemServer::DisableAssigningSingleOnSlSrPress(HLERequestContext& ctx) { | ||
| 259 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 260 | |||
| 261 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 262 | rb.Push(ResultSuccess); | ||
| 263 | } | ||
| 264 | |||
| 241 | void IHidSystemServer::GetLastActiveNpad(HLERequestContext& ctx) { | 265 | void 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 | ||
| 273 | void 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 | |||
| 284 | void 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 | |||
| 301 | void 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 | |||
| 317 | void 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 | |||
| 333 | void 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 | |||
| 350 | void 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 | |||
| 362 | void 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 | |||
| 375 | void 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 | |||
| 387 | void 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 | |||
| 249 | void IHidSystemServer::GetUniquePadsFromNpad(HLERequestContext& ctx) { | 409 | void 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 | ||
| 427 | void 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 | |||
| 436 | void 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 | |||
| 444 | void 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 | |||
| 452 | void 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 | |||
| 470 | void 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 | |||
| 478 | void 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 | |||
| 264 | void IHidSystemServer::AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx) { | 486 | void 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 | ||
| 504 | void 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 | |||
| 515 | void IHidSystemServer::InitializeFirmwareUpdate(HLERequestContext& ctx) { | ||
| 516 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 517 | |||
| 518 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 519 | rb.Push(ResultSuccess); | ||
| 520 | } | ||
| 521 | |||
| 522 | void IHidSystemServer::InitializeUsbFirmwareUpdateWithoutMemory(HLERequestContext& ctx) { | ||
| 523 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 524 | |||
| 525 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 526 | rb.Push(ResultSuccess); | ||
| 527 | } | ||
| 528 | |||
| 282 | void IHidSystemServer::GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) { | 529 | void 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 | ||
| 25 | private: | 25 | private: |
| 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 | |||
| 125 | private: | ||
| 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 | ||
| 126 | class IUserLocalCommunicationService final | 134 | class 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) { | |||
| 431 | void SET_SYS::GetBatteryPercentageFlag(HLERequestContext& ctx) { | 431 | void 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 | ||
| 494 | void 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 | |||
| 510 | void 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 | } | ||
| 495 | void SET_SYS::GetFieldTestingFlag(HLERequestContext& ctx) { | 517 | void 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 @@ | |||
| 4 | add_subdirectory(host_shaders) | 4 | add_subdirectory(host_shaders) |
| 5 | 5 | ||
| 6 | if(LIBVA_FOUND) | 6 | if(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}) |
| 10 | endif() | 10 | endif() |
| @@ -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 | ||
| 17 | extern "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 | |||
| 27 | namespace Tegra { | 13 | namespace Tegra { |
| 28 | namespace { | ||
| 29 | constexpr AVPixelFormat PREFERRED_GPU_FMT = AV_PIX_FMT_NV12; | ||
| 30 | constexpr AVPixelFormat PREFERRED_CPU_FMT = AV_PIX_FMT_YUV420P; | ||
| 31 | constexpr 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 | |||
| 44 | void AVPacketDeleter(AVPacket* ptr) { | ||
| 45 | av_packet_free(&ptr); | ||
| 46 | } | ||
| 47 | |||
| 48 | using AVPacketPtr = std::unique_ptr<AVPacket, decltype(&AVPacketDeleter)>; | ||
| 49 | |||
| 50 | AVPixelFormat 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 | ||
| 63 | std::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 | |||
| 75 | void AVFrameDeleter(AVFrame* ptr) { | ||
| 76 | av_frame_free(&ptr); | ||
| 77 | } | ||
| 78 | 14 | ||
| 79 | Codec::Codec(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs) | 15 | Codec::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 | ||
| 84 | Codec::~Codec() { | 20 | Codec::~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 | |||
| 97 | bool 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 | |||
| 149 | void 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 | |||
| 156 | void 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 | |||
| 167 | void 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 | ||
| 223 | void Codec::Initialize() { | 22 | void 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 | ||
| 255 | void Codec::SetTargetCodec(Host1x::NvdecCommon::VideoCodec codec) { | 26 | void 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 | ||
| 361 | AVFramePtr Codec::GetCurrentFrame() { | 81 | std::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 | ||
| 12 | extern "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 | |||
| 24 | namespace Tegra { | 14 | namespace Tegra { |
| 25 | 15 | ||
| 26 | void AVFrameDeleter(AVFrame* ptr); | ||
| 27 | using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>; | ||
| 28 | |||
| 29 | namespace Decoder { | 16 | namespace Decoder { |
| 30 | class H264; | 17 | class H264; |
| 31 | class VP8; | 18 | class 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 | ||
| 62 | private: | 49 | private: |
| 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_} {} | |||
| 30 | H264::~H264() = default; | 30 | H264::~H264() = default; |
| 31 | 31 | ||
| 32 | std::span<const u8> H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state, | 32 | std::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 | ||
| 72 | private: | 73 | private: |
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 | |||
| 10 | extern "C" { | ||
| 11 | #ifdef LIBVA_FOUND | ||
| 12 | // for querying VAAPI driver information | ||
| 13 | #include <libavutil/hwcontext_vaapi.h> | ||
| 14 | #endif | ||
| 15 | } | ||
| 16 | |||
| 17 | namespace FFmpeg { | ||
| 18 | |||
| 19 | namespace { | ||
| 20 | |||
| 21 | constexpr AVPixelFormat PreferredGpuFormat = AV_PIX_FMT_NV12; | ||
| 22 | constexpr AVPixelFormat PreferredCpuFormat = AV_PIX_FMT_YUV420P; | ||
| 23 | constexpr 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 | |||
| 36 | AVPixelFormat 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 | |||
| 50 | std::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 | |||
| 58 | Packet::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 | |||
| 64 | Packet::~Packet() { | ||
| 65 | av_packet_free(&m_packet); | ||
| 66 | } | ||
| 67 | |||
| 68 | Frame::Frame() { | ||
| 69 | m_frame = av_frame_alloc(); | ||
| 70 | } | ||
| 71 | |||
| 72 | Frame::~Frame() { | ||
| 73 | av_frame_free(&m_frame); | ||
| 74 | } | ||
| 75 | |||
| 76 | Decoder::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 | |||
| 94 | bool 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 | |||
| 113 | std::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 | |||
| 127 | HardwareContext::~HardwareContext() { | ||
| 128 | av_buffer_unref(&m_gpu_decoder); | ||
| 129 | } | ||
| 130 | |||
| 131 | bool 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 | |||
| 155 | bool 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 | |||
| 186 | DecoderContext::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 | |||
| 193 | DecoderContext::~DecoderContext() { | ||
| 194 | av_buffer_unref(&m_codec_context->hw_device_ctx); | ||
| 195 | avcodec_free_context(&m_codec_context); | ||
| 196 | } | ||
| 197 | |||
| 198 | void 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 | |||
| 205 | bool 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 | |||
| 218 | bool 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 | |||
| 227 | std::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 | |||
| 266 | DeinterlaceFilter::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 | |||
| 322 | bool 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 | |||
| 333 | std::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 | |||
| 349 | DeinterlaceFilter::~DeinterlaceFilter() { | ||
| 350 | avfilter_graph_free(&m_filter_graph); | ||
| 351 | } | ||
| 352 | |||
| 353 | void DecodeApi::Reset() { | ||
| 354 | m_deinterlace_filter.reset(); | ||
| 355 | m_hardware_context.reset(); | ||
| 356 | m_decoder_context.reset(); | ||
| 357 | m_decoder.reset(); | ||
| 358 | } | ||
| 359 | |||
| 360 | bool 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 | |||
| 380 | bool 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 | |||
| 385 | void 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 | |||
| 16 | extern "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 | |||
| 34 | namespace FFmpeg { | ||
| 35 | |||
| 36 | class Packet; | ||
| 37 | class Frame; | ||
| 38 | class Decoder; | ||
| 39 | class HardwareContext; | ||
| 40 | class DecoderContext; | ||
| 41 | class DeinterlaceFilter; | ||
| 42 | |||
| 43 | // Wraps an AVPacket, a container for compressed bitstream data. | ||
| 44 | class Packet { | ||
| 45 | public: | ||
| 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 | |||
| 56 | private: | ||
| 57 | AVPacket* m_packet{}; | ||
| 58 | }; | ||
| 59 | |||
| 60 | // Wraps an AVFrame, a container for audio and video stream data. | ||
| 61 | class Frame { | ||
| 62 | public: | ||
| 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 | |||
| 105 | private: | ||
| 106 | AVFrame* m_frame{}; | ||
| 107 | }; | ||
| 108 | |||
| 109 | // Wraps an AVCodec, a type containing information about a codec. | ||
| 110 | class Decoder { | ||
| 111 | public: | ||
| 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 | |||
| 124 | private: | ||
| 125 | const AVCodec* m_codec{}; | ||
| 126 | }; | ||
| 127 | |||
| 128 | // Wraps AVBufferRef for an accelerated decoder. | ||
| 129 | class HardwareContext { | ||
| 130 | public: | ||
| 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 | |||
| 145 | private: | ||
| 146 | bool InitializeWithType(AVHWDeviceType type); | ||
| 147 | |||
| 148 | AVBufferRef* m_gpu_decoder{}; | ||
| 149 | }; | ||
| 150 | |||
| 151 | // Wraps an AVCodecContext. | ||
| 152 | class DecoderContext { | ||
| 153 | public: | ||
| 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 | |||
| 169 | private: | ||
| 170 | AVCodecContext* m_codec_context{}; | ||
| 171 | }; | ||
| 172 | |||
| 173 | // Wraps an AVFilterGraph. | ||
| 174 | class DeinterlaceFilter { | ||
| 175 | public: | ||
| 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 | |||
| 185 | private: | ||
| 186 | AVFilterGraph* m_filter_graph{}; | ||
| 187 | AVFilterContext* m_source_context{}; | ||
| 188 | AVFilterContext* m_sink_context{}; | ||
| 189 | bool m_initialized{}; | ||
| 190 | }; | ||
| 191 | |||
| 192 | class DecodeApi { | ||
| 193 | public: | ||
| 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 | |||
| 206 | private: | ||
| 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 | ||
| 31 | AVFramePtr Nvdec::GetFrame() { | 31 | std::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 | ||
| 28 | private: | 28 | private: |
| 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 | ||
| 113 | void Vic::WriteRGBFrame(const AVFrame* frame, const VicConfig& config) { | 112 | void 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 | ||
| 172 | void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) { | 174 | void 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: | |||
| 39 | private: | 39 | private: |
| 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/*) | |||
| 252 | if (ENABLE_QT_TRANSLATION) | 252 | if (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 | ||
| 17 | namespace ConfigurationShared { | 18 | namespace 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 | ||
| 4378 | void 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 | |||
| 4378 | void GMainWindow::OnCaptureScreenshot() { | 4403 | void 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 &Album</string> | 387 | <string>Open &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 &Mii Editor</string> | 412 | <string>Open &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&ecord</string> | 455 | <string>R&ecord</string> |
| 455 | </property> | 456 | </property> |
| 456 | </action> | 457 | </action> |
| 458 | <action name="action_Open_Controller_Menu"> | ||
| 459 | <property name="text"> | ||
| 460 | <string>Open &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"/> |