diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 397b44c9f9..1f0acf2835 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -607,12 +607,6 @@ object NativeLibrary { */ external fun addFileToFilesystemProvider(path: String) - /** - * Adds a game-folder file to the manual filesystem provider, respecting the internal gate for - * game-folder external-content mounting. - */ - external fun addGameFolderFileToFilesystemProvider(path: String) - /** * Clears all files added to the manual filesystem provider in our EmulationSession instance */ diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/fetcher/SpacingItemDecoration.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/fetcher/SpacingItemDecoration.kt index b3ffcc2a35..f3d000a739 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/fetcher/SpacingItemDecoration.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/fetcher/SpacingItemDecoration.kt @@ -1,11 +1,10 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.features.fetcher import android.graphics.Rect import android.view.View -import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() { @@ -16,20 +15,8 @@ class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecorat state: RecyclerView.State ) { outRect.bottom = spacing - - val position = parent.getChildAdapterPosition(view) - if (position == RecyclerView.NO_POSITION) return - - if (position == 0) { + if (parent.getChildAdapterPosition(view) == 0) { outRect.top = spacing - return - } - - // If the item is in the first row, but NOT in first column add top spacing as well - val layoutManager = parent.layoutManager - if (layoutManager is GridLayoutManager && layoutManager.spanSizeLookup.getSpanGroupIndex(position, layoutManager.spanCount) == 0) { - outRect.top = spacing - return } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt index 4dcb35c010..f66f4bac7f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -68,9 +68,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener MaterialAlertDialogBuilder(requireContext()) .setMessage(R.string.reset_setting_confirmation) .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> - val item = settingsViewModel.clickedItem ?: return@setPositiveButton - clearDialogState() - when (item) { + when (val item = settingsViewModel.clickedItem) { is AnalogInputSetting -> { val stickParam = NativeInput.getStickParam( item.playerIndex, @@ -109,17 +107,12 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener } else -> { - item.setting.reset() + settingsViewModel.clickedItem!!.setting.reset() settingsViewModel.setAdapterItemChanged(position) } } } - .setNegativeButton(android.R.string.cancel) { _: DialogInterface, _: Int -> - clearDialogState() - } - .setOnCancelListener { - clearDialogState() - } + .setNegativeButton(android.R.string.cancel, null) .create() } @@ -193,6 +186,27 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener updateButtonState(isValid) } + /* + * xbzk: these two events, along with attachRepeat feature, + * were causing spinbox buttons to respond twice per press + * cutting these out to retain accelerated press functionality + * TODO: clean this out later if no issues arise + * + spinboxBinding.buttonDecrement.setOnClickListener { + val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue + val newValue = current - 1 + spinboxBinding.editValue.setText(newValue.toString()) + updateValidity(newValue) + } + + spinboxBinding.buttonIncrement.setOnClickListener { + val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue + val newValue = current + 1 + spinboxBinding.editValue.setText(newValue.toString()) + updateValidity(newValue) + } + */ + fun attachRepeat(button: View, delta: Int) { val handler = Handler(Looper.getMainLooper()) var runnable: Runnable? = null @@ -425,13 +439,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener private fun closeDialog() { settingsViewModel.setAdapterItemChanged(position) - clearDialogState() - dismiss() - } - - private fun clearDialogState() { settingsViewModel.clickedItem = null settingsViewModel.setSliderProgress(-1f) + dismiss() } private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 77104e0614..0fc4fb0b7f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1066,10 +1066,7 @@ class SettingsFragmentPresenter( IntSetting.THEME.getValueAsString() override val defaultValue: Int = IntSetting.THEME.defaultValue - override fun reset() { - IntSetting.THEME.setInt(defaultValue) - settingsViewModel.setShouldRecreate(true) - } + override fun reset() = IntSetting.THEME.setInt(defaultValue) } add(HeaderSetting(R.string.app_settings)) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt index 2a0e72be26..c682a13cfc 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt @@ -127,6 +127,10 @@ class AddonViewModel : ViewModel() { return } + // Check if there are multiple update versions + val updates = _patchList.value.filter { PatchType.from(it.type) == PatchType.Update } + val hasMultipleUpdates = updates.size > 1 + NativeConfig.setDisabledAddons( game!!.programId, _patchList.value.mapNotNull { @@ -136,7 +140,7 @@ class AddonViewModel : ViewModel() { if (PatchType.from(it.type) == PatchType.Update) { if (it.name.contains("(NAND)") || it.name.contains("(SDMC)")) { it.name - } else if (it.numericVersion != 0L) { + } else if (hasMultipleUpdates) { "Update@${it.numericVersion}" } else { it.name 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 db4cc0f60e..8a4262ebe7 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 @@ -424,9 +424,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { ) val uriString = result.toString() - val folder = gamesViewModel.folders.value.firstOrNull { - it.uriString == uriString && it.type == org.yuzu.yuzu_emu.model.DirectoryType.EXTERNAL_CONTENT - } + val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString } if (folder != null) { Toast.makeText( applicationContext, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt index 4a3cf61daa..fff5fdfb9b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt @@ -51,24 +51,11 @@ object GameHelper { // Scan External Content directories and register all NSP/XCI files val externalContentDirs = NativeConfig.getExternalContentDirs() - val uniqueExternalContentDirs = linkedSetOf() - externalContentDirs.forEach { externalDir -> - if (externalDir.isNotEmpty()) { - uniqueExternalContentDirs.add(externalDir) - } - } - - val mountedContainerUris = mutableSetOf() - for (externalDir in uniqueExternalContentDirs) { + for (externalDir in externalContentDirs) { if (externalDir.isNotEmpty()) { val externalDirUri = externalDir.toUri() if (FileUtil.isTreeUriValid(externalDirUri)) { - scanContentContainersRecursive(FileUtil.listFiles(externalDirUri), 3) { - val containerUri = it.uri.toString() - if (mountedContainerUris.add(containerUri)) { - NativeLibrary.addFileToFilesystemProvider(containerUri) - } - } + scanExternalContentRecursive(FileUtil.listFiles(externalDirUri), 3) } } } @@ -78,13 +65,10 @@ object GameHelper { val gameDirUri = gameDir.uriString.toUri() val isValid = FileUtil.isTreeUriValid(gameDirUri) if (isValid) { - val scanDepth = if (gameDir.deepScan) 3 else 1 - addGamesRecursive( games, FileUtil.listFiles(gameDirUri), - scanDepth, - mountedContainerUris + if (gameDir.deepScan) 3 else 1 ) } else { badDirs.add(index) @@ -119,10 +103,9 @@ object GameHelper { // be done better imo. private val externalContentExtensions = setOf("nsp", "xci") - private fun scanContentContainersRecursive( + private fun scanExternalContentRecursive( files: Array, - depth: Int, - onContainerFound: (MinimalDocumentFile) -> Unit + depth: Int ) { if (depth <= 0) { return @@ -130,15 +113,14 @@ object GameHelper { files.forEach { if (it.isDirectory) { - scanContentContainersRecursive( + scanExternalContentRecursive( FileUtil.listFiles(it.uri), - depth - 1, - onContainerFound + depth - 1 ) } else { val extension = FileUtil.getExtension(it.uri).lowercase() if (externalContentExtensions.contains(extension)) { - onContainerFound(it) + NativeLibrary.addFileToFilesystemProvider(it.uri.toString()) } } } @@ -147,8 +129,7 @@ object GameHelper { private fun addGamesRecursive( games: MutableList, files: Array, - depth: Int, - mountedContainerUris: MutableSet + depth: Int ) { if (depth <= 0) { return @@ -159,20 +140,11 @@ object GameHelper { addGamesRecursive( games, FileUtil.listFiles(it.uri), - depth - 1, - mountedContainerUris + depth - 1 ) } else { - val extension = FileUtil.getExtension(it.uri).lowercase() - val filePath = it.uri.toString() - - if (externalContentExtensions.contains(extension) && - mountedContainerUris.add(filePath)) { - NativeLibrary.addGameFolderFileToFilesystemProvider(filePath) - } - - if (Game.extensions.contains(extension)) { - val game = getGame(it.uri, true, false) + if (Game.extensions.contains(FileUtil.getExtension(it.uri))) { + val game = getGame(it.uri, true) if (game != null) { games.add(game) } @@ -181,20 +153,14 @@ object GameHelper { } } - fun getGame( - uri: Uri, - addedToLibrary: Boolean, - registerFilesystemProvider: Boolean = true - ): Game? { + fun getGame(uri: Uri, addedToLibrary: Boolean): Game? { val filePath = uri.toString() if (!GameMetadata.getIsValid(filePath)) { return null } - if (registerFilesystemProvider) { - // Needed to update installed content information - NativeLibrary.addFileToFilesystemProvider(filePath) - } + // Needed to update installed content information + NativeLibrary.addFileToFilesystemProvider(filePath) var name = GameMetadata.getTitle(filePath) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PathUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PathUtil.kt index 744e1d149c..a840b3b846 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PathUtil.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PathUtil.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.utils @@ -80,14 +80,16 @@ object PathUtil { } } + // This really shouldn't be necessary, but the Android API seemingly + // doesn't have a way of doing this? + // Apparently, on certain devices the mount location can vary, so add + // extra cases here if we discover any new ones. fun getRemovableStoragePath(idString: String): String? { - val possibleMountPaths = listOf("/mnt/media_rw/$idString", "/storage/$idString") + var pathFile: File - for (mountPath in possibleMountPaths) { - val pathFile = File(mountPath); - if (pathFile.exists()) { - return pathFile.absolutePath - } + pathFile = File("/mnt/media_rw/$idString"); + if (pathFile.exists()) { + return pathFile.absolutePath } return null diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp index 0c5696ef3f..0171e2a7b3 100644 --- a/src/android/app/src/main/jni/android_config.cpp +++ b/src/android/app/src/main/jni/android_config.cpp @@ -33,12 +33,6 @@ void AndroidConfig::ReadAndroidValues() { if (global) { ReadAndroidUIValues(); ReadUIValues(); - BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage)); - Settings::values.ext_content_from_game_dirs = ReadBooleanSetting( - std::string("ext_content_from_game_dirs"), - std::make_optional( - Settings::values.ext_content_from_game_dirs.GetDefault())); - EndGroup(); ReadOverlayValues(); } ReadDriverValues(); diff --git a/src/android/app/src/main/jni/game_metadata.cpp b/src/android/app/src/main/jni/game_metadata.cpp index 9acba456f1..e9c03b6440 100644 --- a/src/android/app/src/main/jni/game_metadata.cpp +++ b/src/android/app/src/main/jni/game_metadata.cpp @@ -96,11 +96,6 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobj return false; } - if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) && - !Loader::IsBootableGameContainer(file, file_type)) { - return false; - } - u64 program_id = 0; Loader::ResultStatus res = loader->ReadProgramId(program_id); if (res != Loader::ResultStatus::Success) { diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 3f0029c78a..c429f4a1e4 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -217,8 +217,107 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) return; } - if (m_manual_provider->AddEntriesFromContainer(file)) { - return; + const auto extension = Common::ToLower(filepath.substr(filepath.find_last_of('.') + 1)); + + if (extension == "nsp") { + auto nsp = std::make_shared(file); + if (nsp->GetStatus() == Loader::ResultStatus::Success) { + std::map nsp_versions; + std::map nsp_version_strings; + + for (const auto& [title_id, nca_map] : nsp->GetNCAs()) { + for (const auto& [type_pair, nca] : nca_map) { + const auto& [title_type, content_type] = type_pair; + + if (content_type == FileSys::ContentRecordType::Meta) { + const auto meta_nca = std::make_shared(nca->GetBaseFile()); + if (meta_nca->GetStatus() == Loader::ResultStatus::Success) { + const auto section0 = meta_nca->GetSubdirectories(); + if (!section0.empty()) { + for (const auto& meta_file : section0[0]->GetFiles()) { + if (meta_file->GetExtension() == "cnmt") { + FileSys::CNMT cnmt(meta_file); + nsp_versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion(); + } + } + } + } + } + + if (content_type == FileSys::ContentRecordType::Control && + title_type == FileSys::TitleType::Update) { + auto romfs = nca->GetRomFS(); + if (romfs) { + auto extracted = FileSys::ExtractRomFS(romfs); + if (extracted) { + auto nacp_file = extracted->GetFile("control.nacp"); + if (!nacp_file) { + nacp_file = extracted->GetFile("Control.nacp"); + } + if (nacp_file) { + FileSys::NACP nacp(nacp_file); + auto ver_str = nacp.GetVersionString(); + if (!ver_str.empty()) { + nsp_version_strings[title_id] = ver_str; + } + } + } + } + } + } + } + + for (const auto& [title_id, nca_map] : nsp->GetNCAs()) { + for (const auto& [type_pair, nca] : nca_map) { + const auto& [title_type, content_type] = type_pair; + + if (title_type == FileSys::TitleType::Update) { + u32 version = 0; + auto ver_it = nsp_versions.find(title_id); + if (ver_it != nsp_versions.end()) { + version = ver_it->second; + } + + std::string version_string; + auto str_it = nsp_version_strings.find(title_id); + if (str_it != nsp_version_strings.end()) { + version_string = str_it->second; + } + + m_manual_provider->AddEntryWithVersion( + title_type, content_type, title_id, version, version_string, + nca->GetBaseFile()); + + LOG_DEBUG(Frontend, "Added NSP update entry - TitleID: {:016X}, Version: {}, VersionStr: {}", + title_id, version, version_string); + } else { + // Use regular AddEntry for non-updates + m_manual_provider->AddEntry(title_type, content_type, title_id, + nca->GetBaseFile()); + LOG_DEBUG(Frontend, "Added NSP entry - TitleID: {:016X}, TitleType: {}, ContentType: {}", + title_id, static_cast(title_type), static_cast(content_type)); + } + } + } + return; + } + } + + // Handle XCI files + if (extension == "xci") { + FileSys::XCI xci{file}; + if (xci.GetStatus() == Loader::ResultStatus::Success) { + const auto nsp = xci.GetSecurePartitionNSP(); + if (nsp) { + for (const auto& title : nsp->GetNCAs()) { + for (const auto& entry : title.second) { + m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first, + entry.second->GetBaseFile()); + } + } + } + return; + } } auto loader = Loader::GetLoader(m_system, file); @@ -240,13 +339,6 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) } } -void EmulationSession::ConfigureFilesystemProviderFromGameFolder(const std::string& filepath) { - if (!Settings::values.ext_content_from_game_dirs.GetValue()) { - return; - } - ConfigureFilesystemProvider(filepath); -} - void EmulationSession::InitializeSystem(bool reload) { if (!reload) { // Initialize logging system @@ -1517,12 +1609,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* e Common::Android::GetJString(env, jpath)); } -void Java_org_yuzu_yuzu_1emu_NativeLibrary_addGameFolderFileToFilesystemProvider( - JNIEnv* env, jobject jobj, jstring jpath) { - EmulationSession::GetInstance().ConfigureFilesystemProviderFromGameFolder( - Common::Android::GetJString(env, jpath)); -} - void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, jobject jobj) { EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries(); } diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index f2e5c2cfd6..dfbc8b2943 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -49,7 +46,6 @@ public: const Core::PerfStatsResults& PerfStats(); int ShadersBuilding(); void ConfigureFilesystemProvider(const std::string& filepath); - void ConfigureFilesystemProviderFromGameFolder(const std::string& filepath); void InitializeSystem(bool reload); void SetAppletId(int applet_id); Core::SystemResultStatus InitializeEmulation(const std::string& filepath, diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index 234c831ac0..28d38c8881 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index ababd780b1..e4771d7656 100644 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp index 6b528e9db0..c11e88b317 100644 --- a/src/audio_core/audio_in_manager.cpp +++ b/src/audio_core/audio_in_manager.cpp @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp index 569df8d1e0..dee3d493e5 100644 --- a/src/audio_core/audio_out_manager.cpp +++ b/src/audio_core/audio_out_manager.cpp @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/common/settings.h b/src/common/settings.h index 7c6c0d062f..7ea4136576 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -756,8 +756,6 @@ struct Values { Category::DataStorage}; Setting gamecard_path{linkage, std::string(), "gamecard_path", Category::DataStorage}; - Setting ext_content_from_game_dirs{linkage, true, "ext_content_from_game_dirs", - Category::DataStorage}; std::vector external_content_dirs; // Debugging diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index e9c3bb75c2..82944ceceb 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -117,12 +117,6 @@ void AppendCommaIfNotEmpty(std::string& to, std::string_view with) { bool IsDirValidAndNonEmpty(const VirtualDir& dir) { return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty()); } - -bool IsVersionedExternalUpdateDisabled(const std::vector& disabled, u32 version) { - const std::string disabled_key = fmt::format("Update@{}", version); - return std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend() || - std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); -} } // Anonymous namespace PatchManager::PatchManager(u64 title_id_, @@ -161,7 +155,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { if (!update_versions.empty()) { checked_external = true; for (const auto& update_entry : update_versions) { - if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) { + std::string disabled_key = fmt::format("Update@{}", update_entry.version); + if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) { update_disabled = false; enabled_version = update_entry.version; break; @@ -180,7 +175,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { if (!manual_update_versions.empty()) { checked_manual = true; for (const auto& update_entry : manual_update_versions) { - if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) { + std::string disabled_key = fmt::format("Update@{}", update_entry.version); + if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) { update_disabled = false; enabled_version = update_entry.version; break; @@ -584,7 +580,8 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs if (!update_versions.empty()) { checked_external = true; for (const auto& update_entry : update_versions) { - if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) { + std::string disabled_key = fmt::format("Update@{}", update_entry.version); + if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) { update_disabled = false; enabled_version = update_entry.version; update_raw = external_provider->GetEntryForVersion(update_tid, type, update_entry.version); @@ -603,7 +600,8 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs if (!manual_update_versions.empty()) { checked_manual = true; for (const auto& update_entry : manual_update_versions) { - if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) { + std::string disabled_key = fmt::format("Update@{}", update_entry.version); + if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) { update_disabled = false; enabled_version = update_entry.version; update_raw = manual_provider->GetEntryForVersion(update_tid, type, update_entry.version); @@ -706,8 +704,9 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { version_str = FormatTitleVersion(update_entry.version); } + std::string disabled_key = fmt::format("Update@{}", update_entry.version); const auto update_disabled = - IsVersionedExternalUpdateDisabled(disabled, update_entry.version); + std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend(); Patch update_patch = {.enabled = !update_disabled, .name = "Update", @@ -733,8 +732,9 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { version_str = FormatTitleVersion(update_entry.version); } + std::string disabled_key = fmt::format("Update@{}", update_entry.version); const auto update_disabled = - IsVersionedExternalUpdateDisabled(disabled, update_entry.version); + std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend(); Patch update_patch = {.enabled = !update_disabled, @@ -771,8 +771,7 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { std::nullopt, std::nullopt, ContentRecordType::Program, update_tid); for (const auto& [slot, entry] : all_updates) { - if (slot == ContentProviderUnionSlot::External || - slot == ContentProviderUnionSlot::FrontendManual) { + if (slot == ContentProviderUnionSlot::External) { continue; } diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 020d403c95..7bf2ad8fcd 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -104,206 +104,6 @@ static std::string GetCNMTName(TitleType type, u64 title_id) { return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); } -static std::shared_ptr OpenContainerAsNsp(const VirtualFile& file, Loader::FileType type) { - if (!file) { - return nullptr; - } - - if (type == Loader::FileType::Unknown || type == Loader::FileType::Error) { - type = Loader::IdentifyFile(file); - if (type == Loader::FileType::Unknown) { - type = Loader::GuessFromFilename(file->GetName()); - } - } - - if (type == Loader::FileType::NSP) { - auto nsp = std::make_shared(file); - return nsp->GetStatus() == Loader::ResultStatus::Success ? nsp : nullptr; - } - - if (type == Loader::FileType::XCI) { - XCI xci(file); - if (xci.GetStatus() != Loader::ResultStatus::Success) { - return nullptr; - } - - auto secure_partition = xci.GetSecurePartitionNSP(); - if (secure_partition == nullptr) { - return nullptr; - } - - return secure_partition; - } - - // SAF-backed files can occasionally fail type-guessing despite being valid NSP/XCI. - // As a last resort, probe both container parsers directly. - { - auto nsp = std::make_shared(file); - if (nsp->GetStatus() == Loader::ResultStatus::Success) { - return nsp; - } - } - { - XCI xci(file); - if (xci.GetStatus() == Loader::ResultStatus::Success) { - auto secure_partition = xci.GetSecurePartitionNSP(); - if (secure_partition != nullptr) { - return secure_partition; - } - } - } - - return nullptr; -} - -template -bool ForEachContainerEntry(const std::shared_ptr& nsp, bool only_content, - std::optional base_program_id, Callback&& on_entry) { - if (!nsp) { - return false; - } - - const auto& ncas = nsp->GetNCAs(); - if (ncas.empty()) { - return false; - } - - std::map versions; - std::map version_strings; - - for (const auto& [title_id, nca_map] : ncas) { - for (const auto& [type_pair, nca] : nca_map) { - if (!nca) { - continue; - } - - const auto& [title_type, content_type] = type_pair; - - if (content_type == ContentRecordType::Meta) { - const auto subdirs = nca->GetSubdirectories(); - if (!subdirs.empty()) { - for (const auto& inner_file : subdirs[0]->GetFiles()) { - if (inner_file->GetExtension() == "cnmt") { - const CNMT cnmt(inner_file); - versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion(); - break; - } - } - } - } - - if (title_type == TitleType::Update && content_type == ContentRecordType::Control) { - const auto romfs = nca->GetRomFS(); - if (!romfs) { - continue; - } - - const auto extracted = ExtractRomFS(romfs); - if (!extracted) { - continue; - } - - auto nacp_file = extracted->GetFile("control.nacp"); - if (!nacp_file) { - nacp_file = extracted->GetFile("Control.nacp"); - } - if (!nacp_file) { - continue; - } - - const NACP nacp(nacp_file); - auto version_string = nacp.GetVersionString(); - if (!version_string.empty()) { - version_strings[title_id] = std::move(version_string); - } - } - } - } - - bool added_entries = false; - for (const auto& [title_id, nca_map] : ncas) { - if (base_program_id.has_value() && GetBaseTitleID(title_id) != *base_program_id) { - continue; - } - - for (const auto& [type_pair, nca] : nca_map) { - const auto& [title_type, content_type] = type_pair; - if (only_content && title_type != TitleType::Update && title_type != TitleType::AOC) { - continue; - } - - auto entry_file = nca ? nca->GetBaseFile() : nullptr; - if (!entry_file) { - continue; - } - - u32 version = 0; - std::string version_string; - - if (title_type == TitleType::Update) { - if (const auto version_it = versions.find(title_id); version_it != versions.end()) { - version = version_it->second; - } - - if (const auto version_str_it = version_strings.find(title_id); - version_str_it != version_strings.end()) { - version_string = version_str_it->second; - } - } - - on_entry(title_type, content_type, title_id, entry_file, version, version_string); - added_entries = true; - } - } - - return added_entries; -} - -static void UpsertExternalVersionEntry(std::vector& multi_version_entries, - u64 title_id, u32 version, - const std::string& version_string, - ContentRecordType content_type, const VirtualFile& file) { - auto it = std::find_if(multi_version_entries.begin(), multi_version_entries.end(), - [title_id, version](const ExternalUpdateEntry& entry) { - return entry.title_id == title_id && entry.version == version; - }); - - if (it == multi_version_entries.end()) { - ExternalUpdateEntry update_entry; - update_entry.title_id = title_id; - update_entry.version = version; - update_entry.version_string = version_string; - update_entry.files[static_cast(content_type)] = file; - multi_version_entries.push_back(std::move(update_entry)); - return; - } - - it->files[static_cast(content_type)] = file; - if (it->version_string.empty() && !version_string.empty()) { - it->version_string = version_string; - } -} - -template -static bool AddExternalEntriesFromContainer(const std::shared_ptr& nsp, EntryMap& entries, - VersionMap& versions, - std::vector& multi_version_entries) { - return ForEachContainerEntry( - nsp, true, std::nullopt, - [&entries, &versions, - &multi_version_entries](TitleType title_type, ContentRecordType content_type, u64 title_id, - const VirtualFile& file, u32 version, - const std::string& version_string) { - entries[{title_id, content_type, title_type}] = file; - - if (title_type == TitleType::Update) { - versions[title_id] = version; - UpsertExternalVersionEntry(multi_version_entries, title_id, version, version_string, - content_type, file); - } - }); -} - ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { switch (type) { case NCAContentType::Program: @@ -1208,26 +1008,6 @@ void ManualContentProvider::AddEntryWithVersion(TitleType title_type, ContentRec } } -bool ManualContentProvider::AddEntriesFromContainer(VirtualFile file, bool only_content, - std::optional base_program_id) { - const auto nsp = OpenContainerAsNsp(file, Loader::FileType::Unknown); - if (!nsp) { - return false; - } - - return ForEachContainerEntry( - nsp, only_content, base_program_id, - [this](TitleType title_type, ContentRecordType content_type, u64 title_id, - const VirtualFile& entry_file, u32 version, const std::string& version_string) { - if (title_type == TitleType::Update) { - AddEntryWithVersion(title_type, content_type, title_id, version, version_string, - entry_file); - } else { - AddEntry(title_type, content_type, title_id, entry_file); - } - }); -} - void ManualContentProvider::ClearAllEntries() { entries.clear(); multi_version_entries.clear(); @@ -1311,6 +1091,14 @@ VirtualFile ManualContentProvider::GetEntryForVersion(u64 title_id, ContentRecor return nullptr; } +bool ManualContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const { + size_t count = 0; + for (const auto& entry : multi_version_entries) + if (entry.title_id == title_id && entry.files[size_t(type)]) + ++count; + return count > 0; +} + ExternalContentProvider::ExternalContentProvider(std::vector load_directories) : load_dirs(std::move(load_directories)) { ExternalContentProvider::Refresh(); @@ -1371,22 +1159,247 @@ void ExternalContentProvider::ScanDirectory(const VirtualDir& dir) { } void ExternalContentProvider::ProcessNSP(const VirtualFile& file) { - const auto nsp = OpenContainerAsNsp(file, Loader::FileType::NSP); - if (!nsp) { + auto nsp = NSP(file); + if (nsp.GetStatus() != Loader::ResultStatus::Success) { return; } LOG_DEBUG(Service_FS, "Processing NSP file: {}", file->GetName()); - AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries); + + const auto ncas = nsp.GetNCAs(); + + std::map nsp_versions; + std::map nsp_version_strings; // title_id -> NACP version string + + for (const auto& [title_id, nca_map] : ncas) { + for (const auto& [type_pair, nca] : nca_map) { + const auto& [title_type, content_type] = type_pair; + + if (content_type == ContentRecordType::Meta) { + const auto subdirs = nca->GetSubdirectories(); + if (!subdirs.empty()) { + const auto section0 = subdirs[0]; + const auto files = section0->GetFiles(); + for (const auto& inner_file : files) { + if (inner_file->GetExtension() == "cnmt") { + const CNMT cnmt(inner_file); + const auto cnmt_title_id = cnmt.GetTitleID(); + const auto version = cnmt.GetTitleVersion(); + nsp_versions[cnmt_title_id] = version; + versions[cnmt_title_id] = version; + break; + } + } + } + } + + if (content_type == ContentRecordType::Control && title_type == TitleType::Update) { + auto romfs = nca->GetRomFS(); + if (romfs) { + auto extracted = ExtractRomFS(romfs); + if (extracted) { + auto nacp_file = extracted->GetFile("control.nacp"); + if (!nacp_file) { + nacp_file = extracted->GetFile("Control.nacp"); + } + if (nacp_file) { + NACP nacp(nacp_file); + auto ver_str = nacp.GetVersionString(); + if (!ver_str.empty()) { + nsp_version_strings[title_id] = ver_str; + } + } + } + } + } + } + } + + std::map, std::array> version_files; + + for (const auto& [title_id, nca_map] : ncas) { + for (const auto& [type_pair, nca] : nca_map) { + const auto& [title_type, content_type] = type_pair; + + if (title_type != TitleType::AOC && title_type != TitleType::Update) { + continue; + } + + auto nca_file = nsp.GetNCAFile(title_id, content_type, title_type); + if (nca_file != nullptr) { + entries[{title_id, content_type, title_type}] = nca_file; + + if (title_type == TitleType::Update) { + u32 version = 0; + auto ver_it = nsp_versions.find(title_id); + if (ver_it != nsp_versions.end()) { + version = ver_it->second; + } + + version_files[{title_id, version}][size_t(content_type)] = nca_file; + } + + LOG_DEBUG(Service_FS, "Added entry - Title ID: {:016X}, Type: {}, Content: {}", + title_id, static_cast(title_type), static_cast(content_type)); + } + } + } + + for (const auto& [key, files_map] : version_files) { + const auto& [title_id, version] = key; + + std::string ver_str; + auto str_it = nsp_version_strings.find(title_id); + if (str_it != nsp_version_strings.end()) { + ver_str = str_it->second; + } + + bool version_exists = false; + for (auto& existing : multi_version_entries) { + if (existing.title_id == title_id && existing.version == version) { + existing.files = files_map; + if (existing.version_string.empty() && !ver_str.empty()) { + existing.version_string = ver_str; + } + version_exists = true; + break; + } + } + + if (!version_exists && !files_map.empty()) { + ExternalUpdateEntry update_entry{ + .title_id = title_id, + .version = version, + .version_string = ver_str, + .files = files_map + }; + multi_version_entries.push_back(update_entry); + LOG_DEBUG(Service_FS, "Added multi-version update - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}", + title_id, version, ver_str, files_map.size()); + } + } } void ExternalContentProvider::ProcessXCI(const VirtualFile& file) { - const auto nsp = OpenContainerAsNsp(file, Loader::FileType::XCI); - if (!nsp) { + auto xci = XCI(file); + if (xci.GetStatus() != Loader::ResultStatus::Success) { return; } - AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries); + auto nsp = xci.GetSecurePartitionNSP(); + if (nsp == nullptr) { + return; + } + + const auto ncas = nsp->GetNCAs(); + + std::map xci_versions; + std::map xci_version_strings; + + for (const auto& [title_id, nca_map] : ncas) { + for (const auto& [type_pair, nca] : nca_map) { + const auto& [title_type, content_type] = type_pair; + + if (content_type == ContentRecordType::Meta) { + const auto subdirs = nca->GetSubdirectories(); + if (!subdirs.empty()) { + const auto section0 = subdirs[0]; + const auto files = section0->GetFiles(); + for (const auto& inner_file : files) { + if (inner_file->GetExtension() == "cnmt") { + const CNMT cnmt(inner_file); + const auto cnmt_title_id = cnmt.GetTitleID(); + const auto version = cnmt.GetTitleVersion(); + xci_versions[cnmt_title_id] = version; + versions[cnmt_title_id] = version; + break; + } + } + } + } + + if (content_type == ContentRecordType::Control && title_type == TitleType::Update) { + auto romfs = nca->GetRomFS(); + if (romfs) { + auto extracted = ExtractRomFS(romfs); + if (extracted) { + auto nacp_file = extracted->GetFile("control.nacp"); + if (!nacp_file) { + nacp_file = extracted->GetFile("Control.nacp"); + } + if (nacp_file) { + NACP nacp(nacp_file); + auto ver_str = nacp.GetVersionString(); + if (!ver_str.empty()) { + xci_version_strings[title_id] = ver_str; + } + } + } + } + } + } + } + + std::map, std::array> version_files; + + for (const auto& [title_id, nca_map] : ncas) { + for (const auto& [type_pair, nca] : nca_map) { + const auto& [title_type, content_type] = type_pair; + + if (title_type != TitleType::AOC && title_type != TitleType::Update) { + continue; + } + + auto nca_file = nsp->GetNCAFile(title_id, content_type, title_type); + if (nca_file != nullptr) { + entries[{title_id, content_type, title_type}] = nca_file; + + if (title_type == TitleType::Update) { + u32 version = 0; + auto ver_it = xci_versions.find(title_id); + if (ver_it != xci_versions.end()) { + version = ver_it->second; + } + + version_files[{title_id, version}][size_t(content_type)] = nca_file; + } + } + } + } + + for (const auto& [key, files_map] : version_files) { + const auto& [title_id, version] = key; + + std::string ver_str; + auto str_it = xci_version_strings.find(title_id); + if (str_it != xci_version_strings.end()) { + ver_str = str_it->second; + } + + bool version_exists = false; + for (auto& existing : multi_version_entries) { + if (existing.title_id == title_id && existing.version == version) { + existing.files = files_map; + if (existing.version_string.empty() && !ver_str.empty()) { + existing.version_string = ver_str; + } + version_exists = true; + break; + } + } + + if (!version_exists && !files_map.empty()) { + ExternalUpdateEntry update_entry{ + .title_id = title_id, + .version = version, + .version_string = ver_str, + .files = files_map + }; + multi_version_entries.push_back(update_entry); + LOG_DEBUG(Service_FS, "Added multi-version update from XCI - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}", + title_id, version, ver_str, files_map.size()); + } + } } bool ExternalContentProvider::HasEntry(u64 title_id, ContentRecordType type) const { @@ -1478,4 +1491,12 @@ VirtualFile ExternalContentProvider::GetEntryForVersion(u64 title_id, ContentRec return nullptr; } +bool ExternalContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const { + size_t count = 0; + for (const auto& entry : multi_version_entries) + if (entry.title_id == title_id && entry.files[size_t(type)]) + ++count; + return count > 1; +} + } // namespace FileSys diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 32134d1c48..2e39f74db8 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -263,8 +262,6 @@ public: VirtualFile file); void AddEntryWithVersion(TitleType title_type, ContentRecordType content_type, u64 title_id, u32 version, const std::string& version_string, VirtualFile file); - bool AddEntriesFromContainer(VirtualFile file, bool only_content = false, - std::optional base_program_id = std::nullopt); void ClearAllEntries(); void Refresh() override; @@ -279,6 +276,7 @@ public: std::vector ListUpdateVersions(u64 title_id) const; VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const; + bool HasMultipleVersions(u64 title_id, ContentRecordType type) const; private: std::map, VirtualFile> entries; @@ -305,6 +303,7 @@ public: std::vector ListUpdateVersions(u64 title_id) const; VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const; + bool HasMultipleVersions(u64 title_id, ContentRecordType type) const; private: void ScanDirectory(const VirtualDir& dir); diff --git a/src/core/hle/service/am/process_creation.cpp b/src/core/hle/service/am/process_creation.cpp index a20ac7de83..cd537062d7 100644 --- a/src/core/hle/service/am/process_creation.cpp +++ b/src/core/hle/service/am/process_creation.cpp @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/am/process_creation.h b/src/core/hle/service/am/process_creation.h index 57d8b8f815..40c3d36aff 100644 --- a/src/core/hle/service/am/process_creation.h +++ b/src/core/hle/service/am/process_creation.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/am/service/application_creator.cpp b/src/core/hle/service/am/service/application_creator.cpp index e8e4a103c2..4fece53119 100644 --- a/src/core/hle/service/am/service/application_creator.cpp +++ b/src/core/hle/service/am/service/application_creator.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/am/service/library_applet_creator.cpp b/src/core/hle/service/am/service/library_applet_creator.cpp index 9f0359ed2b..17db4b35d3 100644 --- a/src/core/hle/service/am/service/library_applet_creator.cpp +++ b/src/core/hle/service/am/service/library_applet_creator.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/audio/audio_out_manager.cpp b/src/core/hle/service/audio/audio_out_manager.cpp index 3b2087932c..a5a05a1b76 100644 --- a/src/core/hle/service/audio/audio_out_manager.cpp +++ b/src/core/hle/service/audio/audio_out_manager.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project diff --git a/src/core/hle/service/audio/audio_out_manager.h b/src/core/hle/service/audio/audio_out_manager.h index ec974aaba4..57d1eb1e58 100644 --- a/src/core/hle/service/audio/audio_out_manager.h +++ b/src/core/hle/service/audio/audio_out_manager.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project diff --git a/src/core/hle/service/audio/audio_renderer_manager.cpp b/src/core/hle/service/audio/audio_renderer_manager.cpp index 972e930a89..4f0bf0f3b9 100644 --- a/src/core/hle/service/audio/audio_renderer_manager.cpp +++ b/src/core/hle/service/audio/audio_renderer_manager.cpp @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/audio/audio_renderer_manager.h b/src/core/hle/service/audio/audio_renderer_manager.h index fdce8b6ffa..3f25ad6df1 100644 --- a/src/core/hle/service/audio/audio_renderer_manager.h +++ b/src/core/hle/service/audio/audio_renderer_manager.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/os/process.cpp b/src/core/hle/service/os/process.cpp index 2d5d7def64..ec00af1433 100644 --- a/src/core/hle/service/os/process.cpp +++ b/src/core/hle/service/os/process.cpp @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/os/process.h b/src/core/hle/service/os/process.h index ca10945f84..cec61c76c7 100644 --- a/src/core/hle/service/os/process.h +++ b/src/core/hle/service/os/process.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp index bfc5539903..4fc59d0e10 100644 --- a/src/core/hle/service/prepo/prepo.cpp +++ b/src/core/hle/service/prepo/prepo.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -28,10 +28,8 @@ public: {10101, &PlayReport::SaveReportWithUser, "SaveReportWithUserOld"}, {10102, &PlayReport::SaveReport, "SaveReportOld2"}, {10103, &PlayReport::SaveReportWithUser, "SaveReportWithUserOld2"}, - {10104, &PlayReport::SaveReport, "SaveReportOld3"}, - {10105, &PlayReport::SaveReportWithUser, "SaveReportWithUserOld3"}, - {10106, &PlayReport::SaveReport, "SaveReport"}, - {10107, &PlayReport::SaveReportWithUser, "SaveReportWithUser"}, + {10104, &PlayReport::SaveReport, "SaveReport"}, + {10105, &PlayReport::SaveReportWithUser, "SaveReportWithUser"}, {10200, &PlayReport::RequestImmediateTransmission, "RequestImmediateTransmission"}, {10300, &PlayReport::GetTransmissionStatus, "GetTransmissionStatus"}, {10400, &PlayReport::GetSystemSessionId, "GetSystemSessionId"}, diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index b4d50227d3..4379634d03 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -9,15 +9,11 @@ #include #include #include -#include #include "common/concepts.h" #include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/string_util.h" #include "core/core.h" -#include "core/file_sys/card_image.h" -#include "core/file_sys/common_funcs.h" -#include "core/file_sys/submission_package.h" #include "core/hle/kernel/k_process.h" #include "core/loader/deconstructed_rom_directory.h" #include "core/loader/kip.h" @@ -41,49 +37,6 @@ std::optional IdentifyFileLoader(FileSys::VirtualFile file) { return std::nullopt; } -std::shared_ptr OpenContainerAsNsp(FileSys::VirtualFile file, FileType type, - u64 program_id = 0, - std::size_t program_index = 0) { - if (!file) { - return nullptr; - } - - if (type == FileType::NSP) { - auto nsp = std::make_shared(file, program_id, program_index); - return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr; - } - - if (type == FileType::XCI) { - FileSys::XCI xci{file, program_id, program_index}; - if (xci.GetStatus() != ResultStatus::Success) { - return nullptr; - } - - auto secure_nsp = xci.GetSecurePartitionNSP(); - if (secure_nsp == nullptr || secure_nsp->GetStatus() != ResultStatus::Success) { - return nullptr; - } - - return secure_nsp; - } - - return nullptr; -} - -bool HasApplicationProgramContent(const std::shared_ptr& nsp) { - if (!nsp) { - return false; - } - - const auto& ncas = nsp->GetNCAs(); - return std::any_of(ncas.cbegin(), ncas.cend(), [](const auto& title_entry) { - const auto& nca_map = title_entry.second; - return nca_map.find( - {FileSys::TitleType::Application, FileSys::ContentRecordType::Program}) != - nca_map.end(); - }); -} - } // namespace FileType IdentifyFile(FileSys::VirtualFile file) { @@ -109,27 +62,6 @@ FileType IdentifyFile(FileSys::VirtualFile file) { } } -bool IsContainerType(FileType type) { - return type == FileType::NSP || type == FileType::XCI; -} - -bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 program_id, - std::size_t program_index) { - if (!file) { - return false; - } - - if (type == FileType::Unknown) { - type = IdentifyFile(file); - } - - if (!IsContainerType(type)) { - return false; - } - - return HasApplicationProgramContent(OpenContainerAsNsp(file, type, program_id, program_index)); -} - FileType GuessFromFilename(const std::string& name) { if (name == "main") return FileType::DeconstructedRomDirectory; diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 95ce638da0..f4e932cec9 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -49,29 +46,12 @@ enum class FileType { }; /** - * Identifies the type of a supported file/container based on its structure. + * Identifies the type of a bootable file based on the magic value in its header. * @param file open file * @return FileType of file */ FileType IdentifyFile(FileSys::VirtualFile file); -/** - * Returns whether the file type represents a container format that can bundle multiple titles - * (currently NSP/XCI). - */ -bool IsContainerType(FileType type); - -/** - * Returns whether a container file is bootable as a game (has Application/Program content). - * - * @param file open file - * @param type optional file type; if Unknown it is auto-detected. - * @param program_id optional program id hint for multi-program containers. - * @param program_index optional program index hint for multi-program containers. - */ -bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type = FileType::Unknown, - u64 program_id = 0, std::size_t program_index = 0); - /** * Guess the type of a bootable file from its name * @param name String name of bootable file diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 4333acb70c..3016d5f25f 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -58,30 +55,19 @@ AppLoader_NSP::~AppLoader_NSP() = default; FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) { const FileSys::NSP nsp(nsp_file); - if (nsp.GetStatus() != ResultStatus::Success) { - return FileType::Error; - } - - // Extracted Type case - if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr && - FileSys::IsDirectoryExeFS(nsp.GetExeFS())) { - return FileType::NSP; - } - - // Non-extracted NSPs can legitimately contain only update/DLC content. - // Identify the container format itself; bootability is validated by Load(). - if (!nsp.GetNCAs().empty()) { - return FileType::NSP; - } - - // Fallback when NCAs couldn't be parsed (e.g. missing keys) but the PFS still contains NCAs. - for (const auto& entry : nsp.GetFiles()) { - if (entry == nullptr) { - continue; + if (nsp.GetStatus() == ResultStatus::Success) { + // Extracted Type case + if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr && + FileSys::IsDirectoryExeFS(nsp.GetExeFS())) { + return FileType::NSP; } - const auto& name = entry->GetName(); - if (name.size() >= 4 && name.substr(name.size() - 4) == ".nca") { + // Non-Extracted Type case + const auto program_id = nsp.GetProgramTitleID(); + if (!nsp.IsExtractedType() && + nsp.GetNCA(program_id, FileSys::ContentRecordType::Program) != nullptr && + AppLoader_NCA::IdentifyType( + nsp.GetNCAFile(program_id, FileSys::ContentRecordType::Program)) == FileType::NCA) { return FileType::NSP; } } diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 983184a226..e9abb199a1 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -47,13 +44,10 @@ AppLoader_XCI::~AppLoader_XCI() = default; FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& xci_file) { const FileSys::XCI xci(xci_file); - if (xci.GetStatus() != ResultStatus::Success) { - return FileType::Error; - } - - // Identify XCI as a valid container even when it does not include a bootable Program NCA. - // Bootability is handled by AppLoader_XCI::Load(). - if (xci.GetSecurePartitionNSP() != nullptr) { + if (xci.GetStatus() == ResultStatus::Success && + xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr && + AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) == + FileType::NCA) { return FileType::XCI; } diff --git a/src/core/reporter.h b/src/core/reporter.h index 1eee8da31f..db1ca3ba0c 100644 --- a/src/core/reporter.h +++ b/src/core/reporter.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -56,7 +53,6 @@ public: enum class PlayReportType { Old, Old2, - Old3, New, System, }; diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index d1ed32134c..f49c43ee2a 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -425,9 +425,6 @@ std::unique_ptr InitializeTranslations(QObject* parent) "their resolution, details and supported controllers and depending on this setting.\n" "Setting to Handheld can help improve performance for low end systems.")); INSERT(Settings, current_user, QString(), QString()); - INSERT(Settings, serial_unit, tr("Unit Serial"), QString()); - INSERT(Settings, serial_battery, tr("Battery Serial"), QString()); - INSERT(Settings, debug_knobs, tr("Debug knobs"), QString()); // Controls diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 08524bd854..0596329392 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -14,11 +14,8 @@ #include #include #include -#include - #include -#include -#include +#include #include "common/common_types.h" #include "common/div_ceil.h" @@ -97,10 +94,10 @@ static constexpr Binding NULL_BINDING{ template struct HostBindings { - boost::container::static_vector buffers; - boost::container::static_vector offsets; - boost::container::static_vector sizes; - boost::container::static_vector strides; + boost::container::small_vector buffers; + boost::container::small_vector offsets; + boost::container::small_vector sizes; + boost::container::small_vector strides; u32 min_index{NUM_VERTEX_BUFFERS}; u32 max_index{0}; }; diff --git a/src/video_core/control/channel_state.cpp b/src/video_core/control/channel_state.cpp index d07c7e2a83..2539997d53 100644 --- a/src/video_core/control/channel_state.cpp +++ b/src/video_core/control/channel_state.cpp @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -22,12 +19,12 @@ ChannelState::ChannelState(s32 bind_id_) : bind_id{bind_id_}, initialized{} {} void ChannelState::Init(Core::System& system, GPU& gpu, u64 program_id_) { ASSERT(memory_manager); program_id = program_id_; - dma_pusher.emplace(system, gpu, *memory_manager, *this); - maxwell_3d.emplace(system, *memory_manager); - fermi_2d.emplace(*memory_manager); - kepler_compute.emplace(system, *memory_manager); - maxwell_dma.emplace(system, *memory_manager); - kepler_memory.emplace(system, *memory_manager); + dma_pusher = std::make_unique(system, gpu, *memory_manager, *this); + maxwell_3d = std::make_unique(system, *memory_manager); + fermi_2d = std::make_unique(*memory_manager); + kepler_compute = std::make_unique(system, *memory_manager); + maxwell_dma = std::make_unique(system, *memory_manager); + kepler_memory = std::make_unique(system, *memory_manager); initialized = true; } diff --git a/src/video_core/control/channel_state.h b/src/video_core/control/channel_state.h index 2984d2e09e..b385f4939f 100644 --- a/src/video_core/control/channel_state.h +++ b/src/video_core/control/channel_state.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -9,12 +6,6 @@ #include #include "common/common_types.h" -#include "video_core/engines/fermi_2d.h" -#include "video_core/engines/kepler_memory.h" -#include "video_core/engines/kepler_compute.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/engines/maxwell_dma.h" -#include "video_core/dma_pusher.h" namespace Core { class System; @@ -27,34 +18,49 @@ class RasterizerInterface; namespace Tegra { class GPU; + +namespace Engines { +class Puller; +class Fermi2D; +class Maxwell3D; +class MaxwellDMA; +class KeplerCompute; +class KeplerMemory; +} // namespace Engines + class MemoryManager; +class DmaPusher; namespace Control { struct ChannelState { explicit ChannelState(s32 bind_id); + ChannelState(const ChannelState& state) = delete; + ChannelState& operator=(const ChannelState&) = delete; + ChannelState(ChannelState&& other) noexcept = default; + ChannelState& operator=(ChannelState&& other) noexcept = default; void Init(Core::System& system, GPU& gpu, u64 program_id); void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); - /// 3D engine - std::optional maxwell_3d; - /// 2D engine - std::optional fermi_2d; - /// Compute engine - std::optional kepler_compute; - /// DMA engine - std::optional maxwell_dma; - /// Inline memory engine - std::optional kepler_memory; - /// NV01 Timer - std::optional nv01_timer; - std::optional dma_pusher; - std::shared_ptr memory_manager; - s32 bind_id = -1; u64 program_id = 0; + /// 3D engine + std::unique_ptr maxwell_3d; + /// 2D engine + std::unique_ptr fermi_2d; + /// Compute engine + std::unique_ptr kepler_compute; + /// DMA engine + std::unique_ptr maxwell_dma; + /// Inline memory engine + std::unique_ptr kepler_memory; + + std::shared_ptr memory_manager; + + std::unique_ptr dma_pusher; + bool initialized{}; }; diff --git a/src/video_core/engines/engine_interface.h b/src/video_core/engines/engine_interface.h index bf3bd66aca..e271ecab59 100644 --- a/src/video_core/engines/engine_interface.h +++ b/src/video_core/engines/engine_interface.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -15,7 +15,6 @@ namespace Tegra::Engines { enum class EngineTypes : u32 { - Nv01Timer, KeplerCompute, Maxwell3D, Fermi2D, diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index e48f294a5a..7dbb8f6617 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -26,15 +26,8 @@ namespace Tegra::Engines { constexpr u32 MacroRegistersStart = 0xE00; Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_) - : draw_manager{std::make_unique(this)}, system{system_} - , memory_manager{memory_manager_} -#ifdef ARCHITECTURE_x86_64 - , macro_engine(bool(Settings::values.disable_macro_jit)) -#else - , macro_engine(true) -#endif - , upload_state{memory_manager, regs.upload} -{ + : draw_manager{std::make_unique(this)}, system{system_}, + memory_manager{memory_manager_}, macro_engine{GetMacroEngine(*this)}, upload_state{memory_manager, regs.upload} { dirty.flags.flip(); InitializeRegisterDefaults(); execution_mask.reset(); @@ -335,9 +328,9 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume shadow_state.shadow_ram_control = static_cast(nonshadow_argument); return; case MAXWELL3D_REG_INDEX(load_mme.instruction_ptr): - return macro_engine.ClearCode(regs.load_mme.instruction_ptr); + return macro_engine->ClearCode(regs.load_mme.instruction_ptr); case MAXWELL3D_REG_INDEX(load_mme.instruction): - return macro_engine.AddCode(regs.load_mme.instruction_ptr, argument); + return macro_engine->AddCode(regs.load_mme.instruction_ptr, argument); case MAXWELL3D_REG_INDEX(load_mme.start_address): return ProcessMacroBind(argument); case MAXWELL3D_REG_INDEX(falcon[4]): @@ -405,7 +398,7 @@ void Maxwell3D::CallMacroMethod(u32 method, const std::vector& parameters) ((method - MacroRegistersStart) >> 1) % static_cast(macro_positions.size()); // Execute the current macro. - macro_engine.Execute(*this, macro_positions[entry], parameters); + macro_engine->Execute(macro_positions[entry], parameters); draw_manager->DrawDeferred(); } @@ -471,7 +464,7 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, } void Maxwell3D::ProcessMacroUpload(u32 data) { - macro_engine.AddCode(regs.load_mme.instruction_ptr++, data); + macro_engine->AddCode(regs.load_mme.instruction_ptr++, data); } void Maxwell3D::ProcessMacroBind(u32 data) { diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index b73082b7ef..5312c04b6f 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -2258,7 +2258,7 @@ public: /// Returns whether the vertex array specified by index is supposed to be /// accessed per instance or not. bool IsInstancingEnabled(std::size_t index) const { - return bool(is_instanced[index]); //FUCK YOU MSVC + return is_instanced[index]; } }; @@ -3203,7 +3203,7 @@ private: std::vector macro_params; /// Interpreter for the macro codes uploaded to the GPU. - MacroEngine macro_engine; + std::optional macro_engine; Upload::State upload_state; diff --git a/src/video_core/engines/nv01_timer.h b/src/video_core/engines/nv01_timer.h deleted file mode 100644 index a8e60f9f53..0000000000 --- a/src/video_core/engines/nv01_timer.h +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include "common/bit_field.h" -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/logging/log.h" -#include "video_core/engines/engine_interface.h" -#include "video_core/engines/engine_upload.h" - -namespace Core { -class System; -} - -namespace Tegra { -class MemoryManager; -} - -namespace Tegra::Engines { -class Nv01Timer final : public EngineInterface { -public: - explicit Nv01Timer(Core::System& system_, MemoryManager& memory_manager) - : system{system_} - {} - ~Nv01Timer() override; - - /// Write the value to the register identified by method. - void CallMethod(u32 method, u32 method_argument, bool is_last_call) override { - LOG_DEBUG(HW_GPU, "method={}, argument={}, is_last_call={}", method, method_argument, is_last_call); - } - - /// Write multiple values to the register identified by method. - void CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) override { - LOG_DEBUG(HW_GPU, "method={}, base_start={}, amount={}, pending={}", method, fmt::ptr(base_start), amount, methods_pending); - } - - struct Regs { - // No fucking idea - INSERT_PADDING_BYTES_NOINIT(0x48); - } regs{}; -private: - void ConsumeSinkImpl() override {} - Core::System& system; -}; -} diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp index b5b4e5d7fa..8dd34c04ab 100644 --- a/src/video_core/engines/puller.cpp +++ b/src/video_core/engines/puller.cpp @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -37,22 +34,24 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) { bound_engines[method_call.subchannel] = engine_id; switch (engine_id) { case EngineID::FERMI_TWOD_A: - dma_pusher.BindSubchannel(&*channel_state.fermi_2d, method_call.subchannel, EngineTypes::Fermi2D); + dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel, + EngineTypes::Fermi2D); break; case EngineID::MAXWELL_B: - dma_pusher.BindSubchannel(&*channel_state.maxwell_3d, method_call.subchannel, EngineTypes::Maxwell3D); + dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel, + EngineTypes::Maxwell3D); break; case EngineID::KEPLER_COMPUTE_B: - dma_pusher.BindSubchannel(&*channel_state.kepler_compute, method_call.subchannel, EngineTypes::KeplerCompute); + dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel, + EngineTypes::KeplerCompute); break; case EngineID::MAXWELL_DMA_COPY_A: - dma_pusher.BindSubchannel(&*channel_state.maxwell_dma, method_call.subchannel, EngineTypes::MaxwellDMA); + dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel, + EngineTypes::MaxwellDMA); break; case EngineID::KEPLER_INLINE_TO_MEMORY_B: - dma_pusher.BindSubchannel(&*channel_state.kepler_memory, method_call.subchannel, EngineTypes::KeplerMemory); - break; - case EngineID::NV01_TIMER: - dma_pusher.BindSubchannel(&*channel_state.nv01_timer, method_call.subchannel, EngineTypes::Nv01Timer); + dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel, + EngineTypes::KeplerMemory); break; default: UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); @@ -210,22 +209,24 @@ void Puller::CallEngineMethod(const MethodCall& method_call) { switch (engine) { case EngineID::FERMI_TWOD_A: - channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); + channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument, + method_call.IsLastCall()); break; case EngineID::MAXWELL_B: - channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); + channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument, + method_call.IsLastCall()); break; case EngineID::KEPLER_COMPUTE_B: - channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); + channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument, + method_call.IsLastCall()); break; case EngineID::MAXWELL_DMA_COPY_A: - channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); + channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument, + method_call.IsLastCall()); break; case EngineID::KEPLER_INLINE_TO_MEMORY_B: - channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); - break; - case EngineID::NV01_TIMER: - channel_state.nv01_timer->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); + channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument, + method_call.IsLastCall()); break; default: UNIMPLEMENTED_MSG("Unimplemented engine"); @@ -254,9 +255,6 @@ void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_s case EngineID::KEPLER_INLINE_TO_MEMORY_B: channel_state.kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending); break; - case EngineID::NV01_TIMER: - channel_state.nv01_timer->CallMultiMethod(method, base_start, amount, methods_pending); - break; default: UNIMPLEMENTED_MSG("Unimplemented engine"); break; diff --git a/src/video_core/engines/puller.h b/src/video_core/engines/puller.h index fe5102e3ed..d4175ee945 100644 --- a/src/video_core/engines/puller.h +++ b/src/video_core/engines/puller.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -23,7 +20,6 @@ class MemoryManager; class DmaPusher; enum class EngineID { - NV01_TIMER = 0x0004, FERMI_TWOD_A = 0x902D, // 2D Engine MAXWELL_B = 0xB197, // 3D Engine KEPLER_COMPUTE_B = 0xB1C0, diff --git a/src/video_core/macro.cpp b/src/video_core/macro.cpp index 0d1fe0a52b..3fe69be4dd 100644 --- a/src/video_core/macro.cpp +++ b/src/video_core/macro.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -10,7 +10,6 @@ #include #include -#include #ifdef ARCHITECTURE_x86_64 // xbyak hates human beings #ifdef __GNUC__ @@ -74,411 +73,601 @@ bool IsTopologySafe(Maxwell3D::Regs::PrimitiveTopology topology) { } } -} // Anonymous namespace +class HLEMacroImpl : public CachedMacro { +public: + explicit HLEMacroImpl(Maxwell3D& maxwell3d_) + : CachedMacro(maxwell3d_) + {} +}; -void HLE_DrawArraysIndirect::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { - auto topology = static_cast(parameters[0]); - if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { - Fallback(maxwell3d, parameters); - return; - } +/// @note: these macros have two versions, a normal and extended version, with the extended version +/// also assigning the base vertex/instance. +template +class HLE_DrawArraysIndirect final : public HLEMacroImpl { +public: + explicit HLE_DrawArraysIndirect(Maxwell3D& maxwell3d_) + : HLEMacroImpl(maxwell3d_) + {} - auto& params = maxwell3d.draw_manager->GetIndirectParams(); - params.is_byte_count = false; - params.is_indexed = false; - params.include_count = false; - params.count_start_address = 0; - params.indirect_start_address = maxwell3d.GetMacroAddress(1); - params.buffer_size = 4 * sizeof(u32); - params.max_draw_counts = 1; - params.stride = 0; + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + auto topology = static_cast(parameters[0]); + if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { + Fallback(parameters); + return; + } - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - } + auto& params = maxwell3d.draw_manager->GetIndirectParams(); + params.is_byte_count = false; + params.is_indexed = false; + params.include_count = false; + params.count_start_address = 0; + params.indirect_start_address = maxwell3d.GetMacroAddress(1); + params.buffer_size = 4 * sizeof(u32); + params.max_draw_counts = 1; + params.stride = 0; - maxwell3d.draw_manager->DrawArrayIndirect(topology); + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + } + + maxwell3d.draw_manager->DrawArrayIndirect(topology); - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } -} -void HLE_DrawArraysIndirect::Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters) { - SCOPE_EXIT { if (extended) { maxwell3d.engine_state = Maxwell3D::EngineHint::None; maxwell3d.replace_table.clear(); } - }; - maxwell3d.RefreshParameters(); - const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); - auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[0]); - const u32 vertex_first = parameters[3]; - const u32 vertex_count = parameters[1]; - if (!IsTopologySafe(topology) && size_t(maxwell3d.GetMaxCurrentVertices()) < size_t(vertex_first) + size_t(vertex_count)) { - ASSERT(false && "Faulty draw!"); - return; } - const u32 base_instance = parameters[4]; - if (extended) { + +private: + void Fallback(const std::vector& parameters) { + SCOPE_EXIT { + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } + }; + maxwell3d.RefreshParameters(); + const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); + + auto topology = static_cast(parameters[0]); + const u32 vertex_first = parameters[3]; + const u32 vertex_count = parameters[1]; + + if (!IsTopologySafe(topology) && size_t(maxwell3d.GetMaxCurrentVertices()) < size_t(vertex_first) + size_t(vertex_count)) { + ASSERT(false && "Faulty draw!"); + return; + } + + const u32 base_instance = parameters[4]; + if (extended) { + maxwell3d.regs.global_base_instance_index = base_instance; + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType( + 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + } + + maxwell3d.draw_manager->DrawArray(topology, vertex_first, vertex_count, base_instance, + instance_count); + + if (extended) { + maxwell3d.regs.global_base_instance_index = 0; + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } + } +}; + +/* + * @note: these macros have two versions, a normal and extended version, with the extended version + * also assigning the base vertex/instance. + */ +template +class HLE_DrawIndexedIndirect final : public HLEMacroImpl { +public: + explicit HLE_DrawIndexedIndirect(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + auto topology = static_cast(parameters[0]); + if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { + Fallback(parameters); + return; + } + + const u32 estimate = static_cast(maxwell3d.EstimateIndexBufferSize()); + const u32 element_base = parameters[4]; + const u32 base_instance = parameters[5]; + maxwell3d.regs.vertex_id_base = element_base; + maxwell3d.regs.global_base_vertex_index = element_base; maxwell3d.regs.global_base_instance_index = base_instance; - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - } - maxwell3d.draw_manager->DrawArray(topology, vertex_first, vertex_count, base_instance, instance_count); - if (extended) { - maxwell3d.regs.global_base_instance_index = 0; - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } -} - -void HLE_DrawIndexedIndirect::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { - auto topology = static_cast(parameters[0]); - if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { - Fallback(maxwell3d, parameters); - return; - } - - const u32 estimate = u32(maxwell3d.EstimateIndexBufferSize()); - const u32 element_base = parameters[4]; - const u32 base_instance = parameters[5]; - maxwell3d.regs.vertex_id_base = element_base; - maxwell3d.regs.global_base_vertex_index = element_base; - maxwell3d.regs.global_base_instance_index = base_instance; - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); - maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - } - auto& params = maxwell3d.draw_manager->GetIndirectParams(); - params.is_byte_count = false; - params.is_indexed = true; - params.include_count = false; - params.count_start_address = 0; - params.indirect_start_address = maxwell3d.GetMacroAddress(1); - params.buffer_size = 5 * sizeof(u32); - params.max_draw_counts = 1; - params.stride = 0; - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate); - maxwell3d.regs.vertex_id_base = 0x0; - maxwell3d.regs.global_base_vertex_index = 0x0; - maxwell3d.regs.global_base_instance_index = 0x0; - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } -} -void HLE_DrawIndexedIndirect::Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters) { - maxwell3d.RefreshParameters(); - const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); - const u32 element_base = parameters[4]; - const u32 base_instance = parameters[5]; - maxwell3d.regs.vertex_id_base = element_base; - maxwell3d.regs.global_base_vertex_index = element_base; - maxwell3d.regs.global_base_instance_index = base_instance; - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); - maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - } - maxwell3d.draw_manager->DrawIndex(Tegra::Maxwell3D::Regs::PrimitiveTopology(parameters[0]), parameters[3], parameters[1], element_base, base_instance, instance_count); - maxwell3d.regs.vertex_id_base = 0x0; - maxwell3d.regs.global_base_vertex_index = 0x0; - maxwell3d.regs.global_base_instance_index = 0x0; - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } -} -void HLE_MultiLayerClear::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { - maxwell3d.RefreshParameters(); - ASSERT(parameters.size() == 1); - - const Maxwell3D::Regs::ClearSurface clear_params{parameters[0]}; - const u32 rt_index = clear_params.RT; - const u32 num_layers = maxwell3d.regs.rt[rt_index].depth; - ASSERT(clear_params.layer == 0); - - maxwell3d.regs.clear_surface.raw = clear_params.raw; - maxwell3d.draw_manager->Clear(num_layers); -} -void HLE_MultiDrawIndexedIndirectCount::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { - const auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[2]); - if (!IsTopologySafe(topology)) { - Fallback(maxwell3d, parameters); - return; - } - - const u32 start_indirect = parameters[0]; - const u32 end_indirect = parameters[1]; - if (start_indirect >= end_indirect) { - // Nothing to do. - return; - } - - const u32 padding = parameters[3]; // padding is in words - - // size of each indirect segment - const u32 indirect_words = 5 + padding; - const u32 stride = indirect_words * sizeof(u32); - const std::size_t draw_count = end_indirect - start_indirect; - const u32 estimate = static_cast(maxwell3d.EstimateIndexBufferSize()); - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - auto& params = maxwell3d.draw_manager->GetIndirectParams(); - params.is_byte_count = false; - params.is_indexed = true; - params.include_count = true; - params.count_start_address = maxwell3d.GetMacroAddress(4); - params.indirect_start_address = maxwell3d.GetMacroAddress(5); - params.buffer_size = stride * draw_count; - params.max_draw_counts = draw_count; - params.stride = stride; - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); - maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - maxwell3d.SetHLEReplacementAttributeType(0, 0x648, Maxwell3D::HLEReplacementAttributeType::DrawID); - maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate); - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); -} -void HLE_MultiDrawIndexedIndirectCount::Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters) { - SCOPE_EXIT { - // Clean everything. - maxwell3d.regs.vertex_id_base = 0x0; - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - }; - maxwell3d.RefreshParameters(); - const u32 start_indirect = parameters[0]; - const u32 end_indirect = parameters[1]; - if (start_indirect >= end_indirect) { - // Nothing to do. - return; - } - const auto topology = static_cast(parameters[2]); - const u32 padding = parameters[3]; - const std::size_t max_draws = parameters[4]; - const u32 indirect_words = 5 + padding; - const std::size_t first_draw = start_indirect; - const std::size_t effective_draws = end_indirect - start_indirect; - const std::size_t last_draw = start_indirect + (std::min)(effective_draws, max_draws); - for (std::size_t index = first_draw; index < last_draw; index++) { - const std::size_t base = index * indirect_words + 5; - const u32 base_vertex = parameters[base + 3]; - const u32 base_instance = parameters[base + 4]; - maxwell3d.regs.vertex_id_base = base_vertex; - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); - maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - maxwell3d.CallMethod(0x8e3, 0x648, true); - maxwell3d.CallMethod(0x8e4, static_cast(index), true); maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - maxwell3d.draw_manager->DrawIndex(topology, parameters[base + 2], parameters[base], base_vertex, base_instance, parameters[base + 1]); - } -} -void HLE_DrawIndirectByteCount::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { - const bool force = maxwell3d.Rasterizer().HasDrawTransformFeedback(); - auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[0] & 0xFFFFU); - if (!force && (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology))) { - Fallback(maxwell3d, parameters); - return; - } - auto& params = maxwell3d.draw_manager->GetIndirectParams(); - params.is_byte_count = true; - params.is_indexed = false; - params.include_count = false; - params.count_start_address = 0; - params.indirect_start_address = maxwell3d.GetMacroAddress(2); - params.buffer_size = 4; - params.max_draw_counts = 1; - params.stride = parameters[1]; - maxwell3d.regs.draw.begin = parameters[0]; - maxwell3d.regs.draw_auto_stride = parameters[1]; - maxwell3d.regs.draw_auto_byte_count = parameters[2]; - maxwell3d.draw_manager->DrawArrayIndirect(topology); -} -void HLE_DrawIndirectByteCount::Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters) { - maxwell3d.RefreshParameters(); - - maxwell3d.regs.draw.begin = parameters[0]; - maxwell3d.regs.draw_auto_stride = parameters[1]; - maxwell3d.regs.draw_auto_byte_count = parameters[2]; - - maxwell3d.draw_manager->DrawArray( - maxwell3d.regs.draw.topology, 0, - maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1); -} -void HLE_C713C83D8F63CCF3::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { - maxwell3d.RefreshParameters(); - const u32 offset = (parameters[0] & 0x3FFFFFFF) << 2; - const u32 address = maxwell3d.regs.shadow_scratch[24]; - auto& const_buffer = maxwell3d.regs.const_buffer; - const_buffer.size = 0x7000; - const_buffer.address_high = (address >> 24) & 0xFF; - const_buffer.address_low = address << 8; - const_buffer.offset = offset; -} -void HLE_D7333D26E0A93EDE::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { - maxwell3d.RefreshParameters(); - const size_t index = parameters[0]; - const u32 address = maxwell3d.regs.shadow_scratch[42 + index]; - const u32 size = maxwell3d.regs.shadow_scratch[47 + index]; - auto& const_buffer = maxwell3d.regs.const_buffer; - const_buffer.size = size; - const_buffer.address_high = (address >> 24) & 0xFF; - const_buffer.address_low = address << 8; -} -void HLE_BindShader::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { - maxwell3d.RefreshParameters(); - auto& regs = maxwell3d.regs; - const u32 index = parameters[0]; - if ((parameters[1] - regs.shadow_scratch[28 + index]) == 0) { - return; + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); + maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + } + auto& params = maxwell3d.draw_manager->GetIndirectParams(); + params.is_byte_count = false; + params.is_indexed = true; + params.include_count = false; + params.count_start_address = 0; + params.indirect_start_address = maxwell3d.GetMacroAddress(1); + params.buffer_size = 5 * sizeof(u32); + params.max_draw_counts = 1; + params.stride = 0; + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate); + maxwell3d.regs.vertex_id_base = 0x0; + maxwell3d.regs.global_base_vertex_index = 0x0; + maxwell3d.regs.global_base_instance_index = 0x0; + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } } - regs.pipelines[index & 0xF].offset = parameters[2]; - maxwell3d.dirty.flags[VideoCommon::Dirty::Shaders] = true; - regs.shadow_scratch[28 + index] = parameters[1]; - regs.shadow_scratch[34 + index] = parameters[2]; +private: + void Fallback(const std::vector& parameters) { + maxwell3d.RefreshParameters(); + const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); + const u32 element_base = parameters[4]; + const u32 base_instance = parameters[5]; + maxwell3d.regs.vertex_id_base = element_base; + maxwell3d.regs.global_base_vertex_index = element_base; + maxwell3d.regs.global_base_instance_index = base_instance; + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); + maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + } - const u32 address = parameters[4]; - auto& const_buffer = regs.const_buffer; - const_buffer.size = 0x10000; - const_buffer.address_high = (address >> 24) & 0xFF; - const_buffer.address_low = address << 8; + maxwell3d.draw_manager->DrawIndex(Tegra::Maxwell3D::Regs::PrimitiveTopology(parameters[0]), parameters[3], parameters[1], element_base, base_instance, instance_count); - const size_t bind_group_id = parameters[3] & 0x7F; - auto& bind_group = regs.bind_groups[bind_group_id]; - bind_group.raw_config = 0x11; - maxwell3d.ProcessCBBind(bind_group_id); -} -void HLE_SetRasterBoundingBox::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { - maxwell3d.RefreshParameters(); - const u32 raster_mode = parameters[0]; - auto& regs = maxwell3d.regs; - const u32 raster_enabled = maxwell3d.regs.conservative_raster_enable; - const u32 scratch_data = maxwell3d.regs.shadow_scratch[52]; - regs.raster_bounding_box.raw = raster_mode & 0xFFFFF00F; - regs.raster_bounding_box.pad.Assign(scratch_data & raster_enabled); -} -void HLE_ClearConstBuffer::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { - static constexpr std::array zeroes{}; //must be bigger than either 7000 or 5F00 - maxwell3d.RefreshParameters(); - auto& regs = maxwell3d.regs; - regs.const_buffer.size = u32(base_size); - regs.const_buffer.address_high = parameters[0]; - regs.const_buffer.address_low = parameters[1]; - regs.const_buffer.offset = 0; - maxwell3d.ProcessCBMultiData(zeroes.data(), parameters[2] * 4); -} -void HLE_ClearMemory::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { - maxwell3d.RefreshParameters(); - const u32 needed_memory = parameters[2] / sizeof(u32); - if (needed_memory > zero_memory.size()) { - zero_memory.resize(needed_memory, 0); + maxwell3d.regs.vertex_id_base = 0x0; + maxwell3d.regs.global_base_vertex_index = 0x0; + maxwell3d.regs.global_base_instance_index = 0x0; + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } } - auto& regs = maxwell3d.regs; - regs.upload.line_length_in = parameters[2]; - regs.upload.line_count = 1; - regs.upload.dest.address_high = parameters[0]; - regs.upload.dest.address_low = parameters[1]; - maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); - maxwell3d.CallMultiMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), zero_memory.data(), needed_memory, needed_memory); -} -void HLE_TransformFeedbackSetup::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { - maxwell3d.RefreshParameters(); - auto& regs = maxwell3d.regs; - regs.transform_feedback_enabled = 1; - regs.transform_feedback.buffers[0].start_offset = 0; - regs.transform_feedback.buffers[1].start_offset = 0; - regs.transform_feedback.buffers[2].start_offset = 0; - regs.transform_feedback.buffers[3].start_offset = 0; - regs.upload.line_length_in = 4; - regs.upload.line_count = 1; - regs.upload.dest.address_high = parameters[0]; - regs.upload.dest.address_low = parameters[1]; - maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); - maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), regs.transform_feedback.controls[0].stride, true); - maxwell3d.Rasterizer().RegisterTransformFeedback(regs.upload.dest.Address()); -} +}; -#define HLE_MACRO_LIST \ - HLE_MACRO_ELEM(0x0D61FC9FAAC9FCADULL, HLE_DrawArraysIndirect, (false)) \ - HLE_MACRO_ELEM(0x8A4D173EB99A8603ULL, HLE_DrawArraysIndirect, (true)) \ - HLE_MACRO_ELEM(0x771BB18C62444DA0ULL, HLE_DrawIndexedIndirect, (false)) \ - HLE_MACRO_ELEM(0x0217920100488FF7ULL, HLE_DrawIndexedIndirect, (true)) \ - HLE_MACRO_ELEM(0x3F5E74B9C9A50164ULL, HLE_MultiDrawIndexedIndirectCount, ()) \ - HLE_MACRO_ELEM(0xEAD26C3E2109B06BULL, HLE_MultiLayerClear, ()) \ - HLE_MACRO_ELEM(0xC713C83D8F63CCF3ULL, HLE_C713C83D8F63CCF3, ()) \ - HLE_MACRO_ELEM(0xD7333D26E0A93EDEULL, HLE_D7333D26E0A93EDE, ()) \ - HLE_MACRO_ELEM(0xEB29B2A09AA06D38ULL, HLE_BindShader, ()) \ - HLE_MACRO_ELEM(0xDB1341DBEB4C8AF7ULL, HLE_SetRasterBoundingBox, ()) \ - HLE_MACRO_ELEM(0x6C97861D891EDf7EULL, HLE_ClearConstBuffer, (0x5F00)) \ - HLE_MACRO_ELEM(0xD246FDDF3A6173D7ULL, HLE_ClearConstBuffer, (0x7000)) \ - HLE_MACRO_ELEM(0xEE4D0004BEC8ECF4ULL, HLE_ClearMemory, ()) \ - HLE_MACRO_ELEM(0xFC0CF27F5FFAA661ULL, HLE_TransformFeedbackSetup, ()) \ - HLE_MACRO_ELEM(0xB5F74EDB717278ECULL, HLE_DrawIndirectByteCount, ()) \ +class HLE_MultiLayerClear final : public HLEMacroImpl { +public: + explicit HLE_MultiLayerClear(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} -// Allocates and returns a cached macro if the hash matches a known function. -[[nodiscard]] inline AnyCachedMacro GetHLEProgram(u64 hash) noexcept { + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + ASSERT(parameters.size() == 1); + + const Maxwell3D::Regs::ClearSurface clear_params{parameters[0]}; + const u32 rt_index = clear_params.RT; + const u32 num_layers = maxwell3d.regs.rt[rt_index].depth; + ASSERT(clear_params.layer == 0); + + maxwell3d.regs.clear_surface.raw = clear_params.raw; + maxwell3d.draw_manager->Clear(num_layers); + } +}; + +class HLE_MultiDrawIndexedIndirectCount final : public HLEMacroImpl { +public: + explicit HLE_MultiDrawIndexedIndirectCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + const auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[2]); + if (!IsTopologySafe(topology)) { + Fallback(parameters); + return; + } + + const u32 start_indirect = parameters[0]; + const u32 end_indirect = parameters[1]; + if (start_indirect >= end_indirect) { + // Nothing to do. + return; + } + + const u32 padding = parameters[3]; // padding is in words + + // size of each indirect segment + const u32 indirect_words = 5 + padding; + const u32 stride = indirect_words * sizeof(u32); + const std::size_t draw_count = end_indirect - start_indirect; + const u32 estimate = static_cast(maxwell3d.EstimateIndexBufferSize()); + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + auto& params = maxwell3d.draw_manager->GetIndirectParams(); + params.is_byte_count = false; + params.is_indexed = true; + params.include_count = true; + params.count_start_address = maxwell3d.GetMacroAddress(4); + params.indirect_start_address = maxwell3d.GetMacroAddress(5); + params.buffer_size = stride * draw_count; + params.max_draw_counts = draw_count; + params.stride = stride; + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType( + 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); + maxwell3d.SetHLEReplacementAttributeType( + 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + maxwell3d.SetHLEReplacementAttributeType(0, 0x648, + Maxwell3D::HLEReplacementAttributeType::DrawID); + maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate); + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } + +private: + void Fallback(const std::vector& parameters) { + SCOPE_EXIT { + // Clean everything. + maxwell3d.regs.vertex_id_base = 0x0; + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + }; + maxwell3d.RefreshParameters(); + const u32 start_indirect = parameters[0]; + const u32 end_indirect = parameters[1]; + if (start_indirect >= end_indirect) { + // Nothing to do. + return; + } + const auto topology = static_cast(parameters[2]); + const u32 padding = parameters[3]; + const std::size_t max_draws = parameters[4]; + + const u32 indirect_words = 5 + padding; + const std::size_t first_draw = start_indirect; + const std::size_t effective_draws = end_indirect - start_indirect; + const std::size_t last_draw = start_indirect + (std::min)(effective_draws, max_draws); + + for (std::size_t index = first_draw; index < last_draw; index++) { + const std::size_t base = index * indirect_words + 5; + const u32 base_vertex = parameters[base + 3]; + const u32 base_instance = parameters[base + 4]; + maxwell3d.regs.vertex_id_base = base_vertex; + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType( + 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); + maxwell3d.SetHLEReplacementAttributeType( + 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + maxwell3d.CallMethod(0x8e3, 0x648, true); + maxwell3d.CallMethod(0x8e4, static_cast(index), true); + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + maxwell3d.draw_manager->DrawIndex(topology, parameters[base + 2], parameters[base], + base_vertex, base_instance, parameters[base + 1]); + } + } +}; + +class HLE_DrawIndirectByteCount final : public HLEMacroImpl { +public: + explicit HLE_DrawIndirectByteCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + const bool force = maxwell3d.Rasterizer().HasDrawTransformFeedback(); + + auto topology = static_cast(parameters[0] & 0xFFFFU); + if (!force && (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology))) { + Fallback(parameters); + return; + } + auto& params = maxwell3d.draw_manager->GetIndirectParams(); + params.is_byte_count = true; + params.is_indexed = false; + params.include_count = false; + params.count_start_address = 0; + params.indirect_start_address = maxwell3d.GetMacroAddress(2); + params.buffer_size = 4; + params.max_draw_counts = 1; + params.stride = parameters[1]; + maxwell3d.regs.draw.begin = parameters[0]; + maxwell3d.regs.draw_auto_stride = parameters[1]; + maxwell3d.regs.draw_auto_byte_count = parameters[2]; + + maxwell3d.draw_manager->DrawArrayIndirect(topology); + } + +private: + void Fallback(const std::vector& parameters) { + maxwell3d.RefreshParameters(); + + maxwell3d.regs.draw.begin = parameters[0]; + maxwell3d.regs.draw_auto_stride = parameters[1]; + maxwell3d.regs.draw_auto_byte_count = parameters[2]; + + maxwell3d.draw_manager->DrawArray( + maxwell3d.regs.draw.topology, 0, + maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1); + } +}; + +class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl { +public: + explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + const u32 offset = (parameters[0] & 0x3FFFFFFF) << 2; + const u32 address = maxwell3d.regs.shadow_scratch[24]; + auto& const_buffer = maxwell3d.regs.const_buffer; + const_buffer.size = 0x7000; + const_buffer.address_high = (address >> 24) & 0xFF; + const_buffer.address_low = address << 8; + const_buffer.offset = offset; + } +}; + +class HLE_D7333D26E0A93EDE final : public HLEMacroImpl { +public: + explicit HLE_D7333D26E0A93EDE(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + const size_t index = parameters[0]; + const u32 address = maxwell3d.regs.shadow_scratch[42 + index]; + const u32 size = maxwell3d.regs.shadow_scratch[47 + index]; + auto& const_buffer = maxwell3d.regs.const_buffer; + const_buffer.size = size; + const_buffer.address_high = (address >> 24) & 0xFF; + const_buffer.address_low = address << 8; + } +}; + +class HLE_BindShader final : public HLEMacroImpl { +public: + explicit HLE_BindShader(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + auto& regs = maxwell3d.regs; + const u32 index = parameters[0]; + if ((parameters[1] - regs.shadow_scratch[28 + index]) == 0) { + return; + } + + regs.pipelines[index & 0xF].offset = parameters[2]; + maxwell3d.dirty.flags[VideoCommon::Dirty::Shaders] = true; + regs.shadow_scratch[28 + index] = parameters[1]; + regs.shadow_scratch[34 + index] = parameters[2]; + + const u32 address = parameters[4]; + auto& const_buffer = regs.const_buffer; + const_buffer.size = 0x10000; + const_buffer.address_high = (address >> 24) & 0xFF; + const_buffer.address_low = address << 8; + + const size_t bind_group_id = parameters[3] & 0x7F; + auto& bind_group = regs.bind_groups[bind_group_id]; + bind_group.raw_config = 0x11; + maxwell3d.ProcessCBBind(bind_group_id); + } +}; + +class HLE_SetRasterBoundingBox final : public HLEMacroImpl { +public: + explicit HLE_SetRasterBoundingBox(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + const u32 raster_mode = parameters[0]; + auto& regs = maxwell3d.regs; + const u32 raster_enabled = maxwell3d.regs.conservative_raster_enable; + const u32 scratch_data = maxwell3d.regs.shadow_scratch[52]; + regs.raster_bounding_box.raw = raster_mode & 0xFFFFF00F; + regs.raster_bounding_box.pad.Assign(scratch_data & raster_enabled); + } +}; + +template +class HLE_ClearConstBuffer final : public HLEMacroImpl { +public: + explicit HLE_ClearConstBuffer(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + static constexpr std::array zeroes{}; + auto& regs = maxwell3d.regs; + regs.const_buffer.size = u32(base_size); + regs.const_buffer.address_high = parameters[0]; + regs.const_buffer.address_low = parameters[1]; + regs.const_buffer.offset = 0; + maxwell3d.ProcessCBMultiData(zeroes.data(), parameters[2] * 4); + } +}; + +class HLE_ClearMemory final : public HLEMacroImpl { +public: + explicit HLE_ClearMemory(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + + const u32 needed_memory = parameters[2] / sizeof(u32); + if (needed_memory > zero_memory.size()) { + zero_memory.resize(needed_memory, 0); + } + auto& regs = maxwell3d.regs; + regs.upload.line_length_in = parameters[2]; + regs.upload.line_count = 1; + regs.upload.dest.address_high = parameters[0]; + regs.upload.dest.address_low = parameters[1]; + maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); + maxwell3d.CallMultiMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), zero_memory.data(), needed_memory, needed_memory); + } + +private: + std::vector zero_memory; +}; + +class HLE_TransformFeedbackSetup final : public HLEMacroImpl { +public: + explicit HLE_TransformFeedbackSetup(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} + + void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { + maxwell3d.RefreshParameters(); + + auto& regs = maxwell3d.regs; + regs.transform_feedback_enabled = 1; + regs.transform_feedback.buffers[0].start_offset = 0; + regs.transform_feedback.buffers[1].start_offset = 0; + regs.transform_feedback.buffers[2].start_offset = 0; + regs.transform_feedback.buffers[3].start_offset = 0; + + regs.upload.line_length_in = 4; + regs.upload.line_count = 1; + regs.upload.dest.address_high = parameters[0]; + regs.upload.dest.address_low = parameters[1]; + maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); + maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), regs.transform_feedback.controls[0].stride, true); + + maxwell3d.Rasterizer().RegisterTransformFeedback(regs.upload.dest.Address()); + } +}; + +} // Anonymous namespace + +HLEMacro::HLEMacro(Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {} + +HLEMacro::~HLEMacro() = default; + +std::unique_ptr HLEMacro::GetHLEProgram(u64 hash) const { // Compiler will make you a GREAT job at making an ad-hoc hash table :) switch (hash) { -#define HLE_MACRO_ELEM(HASH, TY, VAL) case HASH: return TY VAL; - HLE_MACRO_LIST -#undef HLE_MACRO_ELEM - default: return std::monostate{}; - } -} -[[nodiscard]] inline bool CanBeHLEProgram(u64 hash) noexcept { - switch (hash) { -#define HLE_MACRO_ELEM(HASH, TY, VAL) case HASH: return true; - HLE_MACRO_LIST -#undef HLE_MACRO_ELEM - default: return false; + case 0x0D61FC9FAAC9FCADULL: return std::make_unique>(maxwell3d); + case 0x8A4D173EB99A8603ULL: return std::make_unique>(maxwell3d); + case 0x771BB18C62444DA0ULL: return std::make_unique>(maxwell3d); + case 0x0217920100488FF7ULL: return std::make_unique>(maxwell3d); + case 0x3F5E74B9C9A50164ULL: return std::make_unique(maxwell3d); + case 0xEAD26C3E2109B06BULL: return std::make_unique(maxwell3d); + case 0xC713C83D8F63CCF3ULL: return std::make_unique(maxwell3d); + case 0xD7333D26E0A93EDEULL: return std::make_unique(maxwell3d); + case 0xEB29B2A09AA06D38ULL: return std::make_unique(maxwell3d); + case 0xDB1341DBEB4C8AF7ULL: return std::make_unique(maxwell3d); + case 0x6C97861D891EDf7EULL: return std::make_unique>(maxwell3d); + case 0xD246FDDF3A6173D7ULL: return std::make_unique>(maxwell3d); + case 0xEE4D0004BEC8ECF4ULL: return std::make_unique(maxwell3d); + case 0xFC0CF27F5FFAA661ULL: return std::make_unique(maxwell3d); + case 0xB5F74EDB717278ECULL: return std::make_unique(maxwell3d); + default: + return nullptr; } } -void MacroInterpreterImpl::Execute(Engines::Maxwell3D& maxwell3d, std::span params, u32 method) { +namespace { +class MacroInterpreterImpl final : public CachedMacro { +public: + explicit MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d_, const std::vector& code_) + : CachedMacro(maxwell3d_) + , code{code_} + {} + + void Execute(const std::vector& params, u32 method) override; + +private: + /// Resets the execution engine state, zeroing registers, etc. + void Reset(); + + /** + * Executes a single macro instruction located at the current program counter. Returns whether + * the interpreter should keep running. + * + * @param is_delay_slot Whether the current step is being executed due to a delay slot in a + * previous instruction. + */ + bool Step(bool is_delay_slot); + + /// Calculates the result of an ALU operation. src_a OP src_b; + u32 GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b); + + /// Performs the result operation on the input result and stores it in the specified register + /// (if necessary). + void ProcessResult(Macro::ResultOperation operation, u32 reg, u32 result); + + /// Evaluates the branch condition and returns whether the branch should be taken or not. + bool EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const; + + /// Reads an opcode at the current program counter location. + Macro::Opcode GetOpcode() const; + + /// Returns the specified register's value. Register 0 is hardcoded to always return 0. + u32 GetRegister(u32 register_id) const; + + /// Sets the register to the input value. + void SetRegister(u32 register_id, u32 value); + + /// Sets the method address to use for the next Send instruction. + void SetMethodAddress(u32 address); + + /// Calls a GPU Engine method with the input parameter. + void Send(u32 value); + + /// Reads a GPU register located at the method address. + u32 Read(u32 method) const; + + /// Returns the next parameter in the parameter queue. + u32 FetchParameter(); + + /// Current program counter + u32 pc{}; + /// Program counter to execute at after the delay slot is executed. + std::optional delayed_pc; + + /// General purpose macro registers. + std::array registers = {}; + + /// Method address to use for the next Send instruction. + Macro::MethodAddress method_address = {}; + + /// Input parameters of the current macro. + std::unique_ptr parameters; + std::size_t num_parameters = 0; + std::size_t parameters_capacity = 0; + /// Index of the next parameter that will be fetched by the 'parm' instruction. + u32 next_parameter_index = 0; + + bool carry_flag = false; + const std::vector& code; +}; + +void MacroInterpreterImpl::Execute(const std::vector& params, u32 method) { Reset(); registers[1] = params[0]; - parameters.resize(params.size()); - std::memcpy(parameters.data(), params.data(), params.size() * sizeof(u32)); + num_parameters = params.size(); + + if (num_parameters > parameters_capacity) { + parameters_capacity = num_parameters; + parameters = std::make_unique(num_parameters); + } + std::memcpy(parameters.get(), params.data(), num_parameters * sizeof(u32)); // Execute the code until we hit an exit condition. bool keep_executing = true; while (keep_executing) { - keep_executing = Step(maxwell3d, false); + keep_executing = Step(false); } // Assert the the macro used all the input parameters - ASSERT(next_parameter_index == parameters.size()); + ASSERT(next_parameter_index == num_parameters); } -/// Resets the execution engine state, zeroing registers, etc. void MacroInterpreterImpl::Reset() { registers = {}; pc = 0; delayed_pc = {}; method_address.raw = 0; - // Vector must hold its last indices otherwise wonky shit will happen + num_parameters = 0; // The next parameter index starts at 1, because $r1 already has the value of the first // parameter. next_parameter_index = 1; carry_flag = false; } -/// @brief Executes a single macro instruction located at the current program counter. Returns whether -/// the interpreter should keep running. -/// @param is_delay_slot Whether the current step is being executed due to a delay slot in a previous instruction. -bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slot) { +bool MacroInterpreterImpl::Step(bool is_delay_slot) { u32 base_address = pc; Macro::Opcode opcode = GetOpcode(); @@ -493,12 +682,14 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo switch (opcode.operation) { case Macro::Operation::ALU: { - u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a), GetRegister(opcode.src_b)); - ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result); + u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a), + GetRegister(opcode.src_b)); + ProcessResult(opcode.result_operation, opcode.dst, result); break; } case Macro::Operation::AddImmediate: { - ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, GetRegister(opcode.src_a) + opcode.immediate); + ProcessResult(opcode.result_operation, opcode.dst, + GetRegister(opcode.src_a) + opcode.immediate); break; } case Macro::Operation::ExtractInsert: { @@ -508,7 +699,7 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask(); dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit); dst |= src << opcode.bf_dst_bit; - ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, dst); + ProcessResult(opcode.result_operation, opcode.dst, dst); break; } case Macro::Operation::ExtractShiftLeftImmediate: { @@ -517,7 +708,7 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit; - ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result); + ProcessResult(opcode.result_operation, opcode.dst, result); break; } case Macro::Operation::ExtractShiftLeftRegister: { @@ -526,12 +717,12 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst; - ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result); + ProcessResult(opcode.result_operation, opcode.dst, result); break; } case Macro::Operation::Read: { - u32 result = Read(maxwell3d, GetRegister(opcode.src_a) + opcode.immediate); - ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result); + u32 result = Read(GetRegister(opcode.src_a) + opcode.immediate); + ProcessResult(opcode.result_operation, opcode.dst, result); break; } case Macro::Operation::Branch: { @@ -547,7 +738,7 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo delayed_pc = base_address + opcode.GetBranchTarget(); // Execute one more instruction due to the delay slot. - return Step(maxwell3d, true); + return Step(true); } break; } @@ -560,13 +751,13 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo // cause an exit if it's executed inside a delay slot. if (opcode.is_exit && !is_delay_slot) { // Exit has a delay slot, execute the next instruction - Step(maxwell3d, true); + Step(true); return false; } + return true; } -/// Calculates the result of an ALU operation. src_a OP src_b; u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b) { switch (operation) { case Macro::ALUOperation::Add: { @@ -606,8 +797,7 @@ u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a, } } -/// Performs the result operation on the input result and stores it in the specified register (if necessary). -void MacroInterpreterImpl::ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result) { +void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 reg, u32 result) { switch (operation) { case Macro::ResultOperation::IgnoreAndFetch: // Fetch parameter and ignore result. @@ -625,12 +815,12 @@ void MacroInterpreterImpl::ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::R case Macro::ResultOperation::FetchAndSend: // Fetch parameter and send result. SetRegister(reg, FetchParameter()); - Send(maxwell3d, result); + Send(result); break; case Macro::ResultOperation::MoveAndSend: // Move and send result. SetRegister(reg, result); - Send(maxwell3d, result); + Send(result); break; case Macro::ResultOperation::FetchAndSetMethod: // Fetch parameter and use result as Method Address. @@ -641,13 +831,13 @@ void MacroInterpreterImpl::ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::R // Move result and use as Method Address, then fetch and send parameter. SetRegister(reg, result); SetMethodAddress(result); - Send(maxwell3d, FetchParameter()); + Send(FetchParameter()); break; case Macro::ResultOperation::MoveAndSetMethodSend: // Move result and use as Method Address, then send bits 12:17 of result. SetRegister(reg, result); SetMethodAddress(result); - Send(maxwell3d, (result >> 12) & 0b111111); + Send((result >> 12) & 0b111111); break; default: UNIMPLEMENTED_MSG("Unimplemented result operation {}", operation); @@ -655,7 +845,6 @@ void MacroInterpreterImpl::ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::R } } -/// Evaluates the branch condition and returns whether the branch should be taken or not. bool MacroInterpreterImpl::EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const { switch (cond) { case Macro::BranchCondition::Zero: @@ -666,44 +855,46 @@ bool MacroInterpreterImpl::EvaluateBranchCondition(Macro::BranchCondition cond, UNREACHABLE(); } -/// Reads an opcode at the current program counter location. Macro::Opcode MacroInterpreterImpl::GetOpcode() const { ASSERT((pc % sizeof(u32)) == 0); ASSERT(pc < code.size() * sizeof(u32)); return {code[pc / sizeof(u32)]}; } -/// Returns the specified register's value. Register 0 is hardcoded to always return 0. u32 MacroInterpreterImpl::GetRegister(u32 register_id) const { - return registers[register_id]; + return registers.at(register_id); } -/// Sets the register to the input value. void MacroInterpreterImpl::SetRegister(u32 register_id, u32 value) { // Register 0 is hardwired as the zero register. // Ensure no writes to it actually occur. - if (register_id == 0) + if (register_id == 0) { return; - registers[register_id] = value; + } + + registers.at(register_id) = value; } -/// Calls a GPU Engine method with the input parameter. -void MacroInterpreterImpl::Send(Engines::Maxwell3D& maxwell3d, u32 value) { +void MacroInterpreterImpl::SetMethodAddress(u32 address) { + method_address.raw = address; +} + +void MacroInterpreterImpl::Send(u32 value) { maxwell3d.CallMethod(method_address.address, value, true); // Increment the method address by the method increment. - method_address.address.Assign(method_address.address.Value() + method_address.increment.Value()); + method_address.address.Assign(method_address.address.Value() + + method_address.increment.Value()); } -/// Reads a GPU register located at the method address. -u32 MacroInterpreterImpl::Read(Engines::Maxwell3D& maxwell3d, u32 method) const { +u32 MacroInterpreterImpl::Read(u32 method) const { return maxwell3d.GetRegisterValue(method); } -/// Returns the next parameter in the parameter queue. u32 MacroInterpreterImpl::FetchParameter() { - ASSERT(next_parameter_index < parameters.size()); + ASSERT(next_parameter_index < num_parameters); return parameters[next_parameter_index++]; } +} // Anonymous namespace #ifdef ARCHITECTURE_x86_64 namespace { @@ -739,15 +930,17 @@ static const auto default_cg_mode = Xbyak::DontSetProtectRWE; static const auto default_cg_mode = nullptr; //Allow RWE #endif -struct MacroJITx64Impl final : public Xbyak::CodeGenerator, public DynamicCachedMacro { - explicit MacroJITx64Impl(std::span code_) +class MacroJITx64Impl final : public Xbyak::CodeGenerator, public CachedMacro { +public: + explicit MacroJITx64Impl(Engines::Maxwell3D& maxwell3d_, const std::vector& code_) : Xbyak::CodeGenerator(MAX_CODE_SIZE, default_cg_mode) + , CachedMacro(maxwell3d_) , code{code_} { Compile(); } - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, u32 method) override; + void Execute(const std::vector& parameters, u32 method) override; void Compile_ALU(Macro::Opcode opcode); void Compile_AddImmediate(Macro::Opcode opcode); @@ -757,13 +950,18 @@ struct MacroJITx64Impl final : public Xbyak::CodeGenerator, public DynamicCached void Compile_Read(Macro::Opcode opcode); void Compile_Branch(Macro::Opcode opcode); +private: void Optimizer_ScanFlags(); + void Compile(); bool Compile_NextInstruction(); + Xbyak::Reg32 Compile_FetchParameter(); Xbyak::Reg32 Compile_GetRegister(u32 index, Xbyak::Reg32 dst); + void Compile_ProcessResult(Macro::ResultOperation operation, u32 reg); void Compile_Send(Xbyak::Reg32 value); + Macro::Opcode GetOpCode() const; struct JITState { @@ -783,17 +981,21 @@ struct MacroJITx64Impl final : public Xbyak::CodeGenerator, public DynamicCached bool enable_asserts{}; }; OptimizerState optimizer{}; + std::optional next_opcode{}; ProgramType program{nullptr}; + std::array labels; std::array delay_skip; Xbyak::Label end_of_code{}; + bool is_delay_slot{}; u32 pc{}; - std::span code; + + const std::vector& code; }; -void MacroJITx64Impl::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, u32 method) { +void MacroJITx64Impl::Execute(const std::vector& parameters, u32 method) { ASSERT_OR_EXECUTE(program != nullptr, { return; }); JITState state{}; state.maxwell3d = &maxwell3d; @@ -1029,7 +1231,7 @@ void MacroJITx64Impl::Compile_Read(Macro::Opcode opcode) { Compile_ProcessResult(opcode.result_operation, opcode.dst); } -static void MacroJIT_SendThunk(Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) { +void Send(Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) { maxwell3d->CallMethod(method_address.address, value, true); } @@ -1038,7 +1240,7 @@ void MacroJITx64Impl::Compile_Send(Xbyak::Reg32 value) { mov(Common::X64::ABI_PARAM1, qword[STATE]); mov(Common::X64::ABI_PARAM2.cvt32(), METHOD_ADDRESS); mov(Common::X64::ABI_PARAM3.cvt32(), value); - Common::X64::CallFarFunction(*this, &MacroJIT_SendThunk); + Common::X64::CallFarFunction(*this, &Send); Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); Xbyak::Label dont_process{}; @@ -1250,8 +1452,10 @@ bool MacroJITx64Impl::Compile_NextInstruction() { return true; } -static void MacroJIT_ErrorThunk(uintptr_t parameter, uintptr_t max_parameter) { - LOG_CRITICAL(HW_GPU, "Macro JIT: invalid parameter access 0x{:x} (0x{:x} is the last parameter)", parameter, max_parameter - sizeof(u32)); +static void WarnInvalidParameter(uintptr_t parameter, uintptr_t max_parameter) { + LOG_CRITICAL(HW_GPU, + "Macro JIT: invalid parameter access 0x{:x} (0x{:x} is the last parameter)", + parameter, max_parameter - sizeof(u32)); } Xbyak::Reg32 MacroJITx64Impl::Compile_FetchParameter() { @@ -1261,7 +1465,7 @@ Xbyak::Reg32 MacroJITx64Impl::Compile_FetchParameter() { Common::X64::ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); mov(Common::X64::ABI_PARAM1, PARAMETERS); mov(Common::X64::ABI_PARAM2, MAX_PARAMETER); - Common::X64::CallFarFunction(*this, &MacroJIT_ErrorThunk); + Common::X64::CallFarFunction(*this, &WarnInvalidParameter); Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); L(parameter_ok); mov(eax, dword[PARAMETERS]); @@ -1370,42 +1574,33 @@ static void Dump(u64 hash, std::span code, bool decompiled = false) { macro_file.write(reinterpret_cast(code.data()), code.size_bytes()); } -void MacroEngine::Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span parameters) { - auto const execute_variant = [&maxwell3d, ¶meters, method](AnyCachedMacro& acm) { - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if>(&acm)) - a->get()->Execute(maxwell3d, parameters, method); - }; - if (auto const it = macro_cache.find(method); it != macro_cache.end()) { - auto& ci = it->second; - if (!CanBeHLEProgram(ci.hash) || Settings::values.disable_macro_hle) - maxwell3d.RefreshParameters(); //LLE must reload parameters - execute_variant(ci.program); +MacroEngine::MacroEngine(Engines::Maxwell3D& maxwell3d_, bool is_interpreted_) + : hle_macros{std::make_optional(maxwell3d_)} + , maxwell3d{maxwell3d_} + , is_interpreted{is_interpreted_} +{} + +MacroEngine::~MacroEngine() = default; + +void MacroEngine::AddCode(u32 method, u32 data) { + uploaded_macro_code[method].push_back(data); +} + +void MacroEngine::ClearCode(u32 method) { + macro_cache.erase(method); + uploaded_macro_code.erase(method); +} + +void MacroEngine::Execute(u32 method, const std::vector& parameters) { + auto compiled_macro = macro_cache.find(method); + if (compiled_macro != macro_cache.end()) { + const auto& cache_info = compiled_macro->second; + if (cache_info.has_hle_program) { + cache_info.hle_program->Execute(parameters, method); + } else { + maxwell3d.RefreshParameters(); + cache_info.lle_program->Execute(parameters, method); + } } else { // Macro not compiled, check if it's uploaded and if so, compile it std::optional mid_method; @@ -1422,37 +1617,51 @@ void MacroEngine::Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::spansecond); + cache_info.hash = Common::HashValue(macro_code->second); + } else { const auto& macro_cached = uploaded_macro_code[mid_method.value()]; const auto rebased_method = method - mid_method.value(); auto& code = uploaded_macro_code[method]; code.resize(macro_cached.size() - rebased_method); std::memcpy(code.data(), macro_cached.data() + rebased_method, code.size() * sizeof(u32)); - ci.hash = Common::HashValue(code); - ci.program = Compile(maxwell3d, code); - } else { - ci.program = Compile(maxwell3d, macro_code->second); - ci.hash = Common::HashValue(macro_code->second); + cache_info.hash = Common::HashValue(code); + cache_info.lle_program = Compile(code); } - if (CanBeHLEProgram(ci.hash) && !Settings::values.disable_macro_hle) { - ci.program = GetHLEProgram(ci.hash); - } else { + + auto hle_program = hle_macros->GetHLEProgram(cache_info.hash); + if (!hle_program || Settings::values.disable_macro_hle) { maxwell3d.RefreshParameters(); + cache_info.lle_program->Execute(parameters, method); + } else { + cache_info.has_hle_program = true; + cache_info.hle_program = std::move(hle_program); + cache_info.hle_program->Execute(parameters, method); } - execute_variant(ci.program); + if (Settings::values.dump_macros) { - Dump(ci.hash, macro_code->second, !std::holds_alternative(ci.program)); + Dump(cache_info.hash, macro_code->second, cache_info.has_hle_program); } } } -AnyCachedMacro MacroEngine::Compile(Engines::Maxwell3D& maxwell3d, std::span code) { +std::unique_ptr MacroEngine::Compile(const std::vector& code) { #ifdef ARCHITECTURE_x86_64 if (!is_interpreted) - return std::make_unique(code); + return std::make_unique(maxwell3d, code); +#endif + return std::make_unique(maxwell3d, code); +} + +std::optional GetMacroEngine(Engines::Maxwell3D& maxwell3d) { +#ifdef ARCHITECTURE_x86_64 + return std::make_optional(maxwell3d, bool(Settings::values.disable_macro_jit)); +#else + return std::make_optional(maxwell3d, true); #endif - return MacroInterpreterImpl(code); } } // namespace Tegra diff --git a/src/video_core/macro.h b/src/video_core/macro.h index a9a8f2de04..9bdb4219ce 100644 --- a/src/video_core/macro.h +++ b/src/video_core/macro.h @@ -7,10 +7,8 @@ #pragma once #include -#include -#include -#include #include +#include #include "common/bit_field.h" #include "common/common_types.h" @@ -100,142 +98,62 @@ union MethodAddress { } // namespace Macro -struct HLEMacro { -}; -/// @note: these macros have two versions, a normal and extended version, with the extended version -/// also assigning the base vertex/instance. -struct HLE_DrawArraysIndirect final { - HLE_DrawArraysIndirect(bool extended_) noexcept : extended{extended_} {} - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); - void Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters); - bool extended; -}; -/// @note: these macros have two versions, a normal and extended version, with the extended version -/// also assigning the base vertex/instance. -struct HLE_DrawIndexedIndirect final { - explicit HLE_DrawIndexedIndirect(bool extended_) noexcept : extended{extended_} {} - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); - void Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters); - bool extended; -}; -struct HLE_MultiLayerClear final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); -}; -struct HLE_MultiDrawIndexedIndirectCount final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); - void Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters); -}; -struct HLE_DrawIndirectByteCount final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); - void Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters); -}; -struct HLE_C713C83D8F63CCF3 final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); -}; -struct HLE_D7333D26E0A93EDE final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); -}; -struct HLE_BindShader final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); -}; -struct HLE_SetRasterBoundingBox final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); -}; -struct HLE_ClearConstBuffer final { - HLE_ClearConstBuffer(size_t base_size_) noexcept : base_size{base_size_} {} - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); - size_t base_size; -}; -struct HLE_ClearMemory final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); - std::vector zero_memory; -}; -struct HLE_TransformFeedbackSetup final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); -}; -struct MacroInterpreterImpl final { - MacroInterpreterImpl() {} - MacroInterpreterImpl(std::span code_) : code{code_} {} - void Execute(Engines::Maxwell3D& maxwell3d, std::span params, u32 method); - void Reset(); - bool Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slot); - u32 GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b); - void ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result); - bool EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const; - Macro::Opcode GetOpcode() const; - u32 GetRegister(u32 register_id) const; - void SetRegister(u32 register_id, u32 value); - /// Sets the method address to use for the next Send instruction. - [[nodiscard]] inline void SetMethodAddress(u32 address) noexcept { - method_address.raw = address; - } - void Send(Engines::Maxwell3D& maxwell3d, u32 value); - u32 Read(Engines::Maxwell3D& maxwell3d, u32 method) const; - u32 FetchParameter(); - /// General purpose macro registers. - std::array registers = {}; - /// Input parameters of the current macro. - std::vector parameters; - std::span code; - /// Program counter to execute at after the delay slot is executed. - std::optional delayed_pc; - /// Method address to use for the next Send instruction. - Macro::MethodAddress method_address = {}; - /// Current program counter - u32 pc{}; - /// Index of the next parameter that will be fetched by the 'parm' instruction. - u32 next_parameter_index = 0; - bool carry_flag = false; -}; -struct DynamicCachedMacro { - virtual ~DynamicCachedMacro() = default; +class CachedMacro { +public: + CachedMacro(Engines::Maxwell3D& maxwell3d_) + : maxwell3d{maxwell3d_} + {} + virtual ~CachedMacro() = default; /// Executes the macro code with the specified input parameters. /// @param parameters The parameters of the macro /// @param method The method to execute - virtual void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, u32 method) = 0; + virtual void Execute(const std::vector& parameters, u32 method) = 0; + Engines::Maxwell3D& maxwell3d; }; -using AnyCachedMacro = std::variant< - std::monostate, - HLEMacro, - HLE_DrawArraysIndirect, - HLE_DrawIndexedIndirect, - HLE_MultiDrawIndexedIndirectCount, - HLE_MultiLayerClear, - HLE_C713C83D8F63CCF3, - HLE_D7333D26E0A93EDE, - HLE_BindShader, - HLE_SetRasterBoundingBox, - HLE_ClearConstBuffer, - HLE_ClearMemory, - HLE_TransformFeedbackSetup, - HLE_DrawIndirectByteCount, - MacroInterpreterImpl, - // Used for JIT x86 macro - std::unique_ptr ->; +class HLEMacro { +public: + explicit HLEMacro(Engines::Maxwell3D& maxwell3d_); + ~HLEMacro(); + // Allocates and returns a cached macro if the hash matches a known function. + // Returns nullptr otherwise. + [[nodiscard]] std::unique_ptr GetHLEProgram(u64 hash) const; +private: + Engines::Maxwell3D& maxwell3d; +}; + +class MacroEngine { +public: + explicit MacroEngine(Engines::Maxwell3D& maxwell3d, bool is_interpreted); + ~MacroEngine(); -struct MacroEngine { - MacroEngine(bool is_interpreted_) noexcept : is_interpreted{is_interpreted_} {} // Store the uploaded macro code to compile them when they're called. - inline void AddCode(u32 method, u32 data) noexcept { - uploaded_macro_code[method].push_back(data); - } + void AddCode(u32 method, u32 data); + // Clear the code associated with a method. - inline void ClearCode(u32 method) noexcept { - macro_cache.erase(method); - uploaded_macro_code.erase(method); - } + void ClearCode(u32 method); + // Compiles the macro if its not in the cache, and executes the compiled macro - void Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span parameters); - AnyCachedMacro Compile(Engines::Maxwell3D& maxwell3d, std::span code); + void Execute(u32 method, const std::vector& parameters); + +protected: + std::unique_ptr Compile(const std::vector& code); + +private: struct CacheInfo { - AnyCachedMacro program; + std::unique_ptr lle_program{}; + std::unique_ptr hle_program{}; u64 hash{}; + bool has_hle_program{}; }; + ankerl::unordered_dense::map macro_cache; ankerl::unordered_dense::map> uploaded_macro_code; + std::optional hle_macros; + Engines::Maxwell3D& maxwell3d; bool is_interpreted; }; +std::optional GetMacroEngine(Engines::Maxwell3D& maxwell3d); + } // namespace Tegra diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index 789f4da2ed..07611ef98c 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp @@ -1032,7 +1032,7 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend VkShaderModule frag_shader = *convert_float_to_depth_frag; const std::array stages = MakeStages(*full_screen_vert, frag_shader); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); - pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ + pipeline = device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -1062,7 +1062,7 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend VkShaderModule frag_shader = *convert_depth_to_float_frag; const std::array stages = MakeStages(*full_screen_vert, frag_shader); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); - pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ + pipeline = device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -1093,7 +1093,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren } const std::array stages = MakeStages(*full_screen_vert, *module); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); - pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ + pipeline = device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -1135,7 +1135,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende is_target_depth ? *convert_float_to_depth_frag : *convert_depth_to_float_frag; const std::array stages = MakeStages(*full_screen_vert, frag_shader); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); - pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ + pipeline = device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index e88b27b273..910e07a606 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -64,8 +61,7 @@ public: .pDescriptorUpdateEntries = entries.data(), .templateType = type, .descriptorSetLayout = descriptor_set_layout, - .pipelineBindPoint = - is_compute ? VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS, + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .pipelineLayout = pipeline_layout, .set = 0, }); @@ -126,7 +122,7 @@ private: }); ++binding; num_descriptors += descriptors[i].count; - offset += sizeof(DescriptorUpdateEntry) * descriptors[i].count; + offset += sizeof(DescriptorUpdateEntry); } } diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 1725bc8ccc..d1e607e75f 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -137,8 +137,14 @@ try memory_allocator, scheduler, swapchain, +#ifdef ANDROID surface) - , blit_swapchain(device_memory, + , +#else + *surface) + , +#endif + blit_swapchain(device_memory, device, memory_allocator, present_manager, diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index c842cce709..6256bc8bd8 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -10,7 +10,6 @@ #include #include -#include "video_core/buffer_cache/buffer_cache_base.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" @@ -109,14 +108,6 @@ VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat // Null buffer not supported, adjust offset and size offset = 0; size = 0; - } else { - // Align offset down to minTexelBufferOffsetAlignment - const u32 alignment = static_cast(device->GetMinTexelBufferOffsetAlignment()); - if (alignment > 1) { - const u32 aligned_offset = offset & ~(alignment - 1); - size += offset - aligned_offset; - offset = aligned_offset; - } } const auto it{std::ranges::find_if(views, [offset, size, format](const BufferView& view) { return offset == view.offset && size == view.size && format == view.format; @@ -584,18 +575,18 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset } void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) { - boost::container::static_vector buffer_handles(bindings.buffers.size()); - for (u32 i = 0; i < bindings.buffers.size(); ++i) { - auto handle = bindings.buffers[i]->Handle(); + boost::container::small_vector buffer_handles; + for (u32 index = 0; index < bindings.buffers.size(); ++index) { + auto handle = bindings.buffers[index]->Handle(); if (handle == VK_NULL_HANDLE) { - bindings.offsets[i] = 0; - bindings.sizes[i] = VK_WHOLE_SIZE; + bindings.offsets[index] = 0; + bindings.sizes[index] = VK_WHOLE_SIZE; if (!device.HasNullDescriptor()) { ReserveNullBuffer(); handle = *null_buffer; } } - buffer_handles[i] = handle; + buffer_handles.push_back(handle); } const u32 device_max = device.GetMaxVertexInputBindings(); const u32 min_binding = (std::min)(bindings.min_index, device_max); @@ -605,12 +596,19 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bi return; } if (device.IsExtExtendedDynamicStateSupported()) { - scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles), binding_count](vk::CommandBuffer cmdbuf) { - cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, binding_count, buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data(), bindings_.strides.data()); + scheduler.Record([bindings_ = std::move(bindings), + buffer_handles_ = std::move(buffer_handles), + binding_count](vk::CommandBuffer cmdbuf) { + cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, binding_count, buffer_handles_.data(), + bindings_.offsets.data(), bindings_.sizes.data(), + bindings_.strides.data()); }); } else { - scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles), binding_count](vk::CommandBuffer cmdbuf) { - cmdbuf.BindVertexBuffers(bindings_.min_index, binding_count, buffer_handles_.data(), bindings_.offsets.data()); + scheduler.Record([bindings_ = std::move(bindings), + buffer_handles_ = std::move(buffer_handles), + binding_count](vk::CommandBuffer cmdbuf) { + cmdbuf.BindVertexBuffers(bindings_.min_index, binding_count, buffer_handles_.data(), + bindings_.offsets.data()); }); } } @@ -641,21 +639,15 @@ void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings< // Already logged in the rasterizer return; } - boost::container::static_vector buffer_handles(bindings.buffers.size()); - for (u32 i = 0; i < bindings.buffers.size(); ++i) { - auto handle = bindings.buffers[i]->Handle(); - if (handle == VK_NULL_HANDLE) { - bindings.offsets[i] = 0; - bindings.sizes[i] = VK_WHOLE_SIZE; - if (!device.HasNullDescriptor()) { - ReserveNullBuffer(); - handle = *null_buffer; - } - } - buffer_handles[i] = handle; + boost::container::small_vector buffer_handles; + for (u32 index = 0; index < bindings.buffers.size(); ++index) { + buffer_handles.push_back(bindings.buffers[index]->Handle()); } - scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { - cmdbuf.BindTransformFeedbackBuffersEXT(0, u32(buffer_handles_.size()), buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data()); + scheduler.Record([bindings_ = std::move(bindings), + buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { + cmdbuf.BindTransformFeedbackBuffersEXT(0, static_cast(buffer_handles_.size()), + buffer_handles_.data(), bindings_.offsets.data(), + bindings_.sizes.data()); }); } diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index d45a57f7bb..22e646afe9 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -285,7 +285,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool, .requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U, }; bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size; - pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{ + pipeline = device.GetLogical().CreateComputePipeline({ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -299,7 +299,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool, .pSpecializationInfo = nullptr, }, .layout = *layout, - .basePipelineHandle = {}, + .basePipelineHandle = nullptr, .basePipelineIndex = 0, }); } @@ -944,7 +944,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_, .codeSize = static_cast(code.size_bytes()), .pCode = code.data(), }); - pipelines[i] = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{ + pipelines[i] = device.GetLogical().CreateComputePipeline({ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -958,7 +958,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_, .pSpecializationInfo = nullptr, }, .layout = *layout, - .basePipelineHandle = {}, + .basePipelineHandle = nullptr, .basePipelineIndex = 0, }); }; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 1a62324c95..51b5141a06 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -50,14 +50,11 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel DescriptorLayoutBuilder builder{device}; builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT); - uses_push_descriptor = builder.CanUsePushDescriptor(); - descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor); + descriptor_set_layout = builder.CreateDescriptorSetLayout(false); pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout); descriptor_update_template = - builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, uses_push_descriptor); - if (!uses_push_descriptor) { - descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info); - } + builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, false); + descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info); const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT, .pNext = nullptr, @@ -67,24 +64,26 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) { flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; } - pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{ - .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, - .pNext = nullptr, - .flags = flags, - .stage{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - .pNext = - device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr, - .flags = 0, - .stage = VK_SHADER_STAGE_COMPUTE_BIT, - .module = *spv_module, - .pName = "main", - .pSpecializationInfo = nullptr, + pipeline = device.GetLogical().CreateComputePipeline( + { + .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = flags, + .stage{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = + device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr, + .flags = 0, + .stage = VK_SHADER_STAGE_COMPUTE_BIT, + .module = *spv_module, + .pName = "main", + .pSpecializationInfo = nullptr, + }, + .layout = *pipeline_layout, + .basePipelineHandle = 0, + .basePipelineIndex = 0, }, - .layout = *pipeline_layout, - .basePipelineHandle = 0, - .basePipelineIndex = 0, - }, *pipeline_cache); + *pipeline_cache); // Log compute pipeline creation if (Settings::values.gpu_logging_enabled.GetValue()) { @@ -242,16 +241,11 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, RESCALING_LAYOUT_WORDS_OFFSET, sizeof(rescaling_data), rescaling_data.data()); } - if (uses_push_descriptor) { - cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout, - 0, descriptor_data); - } else { - const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()}; - const vk::Device& dev{device.GetLogical()}; - dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0, - descriptor_set, nullptr); - } + const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()}; + const vk::Device& dev{device.GetLogical()}; + dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0, + descriptor_set, nullptr); }); } diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index aa84c00e12..d1a1e2c46d 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -58,7 +55,6 @@ private: vk::ShaderModule spv_module; vk::DescriptorSetLayout descriptor_set_layout; - bool uses_push_descriptor{false}; DescriptorAllocator descriptor_allocator; vk::PipelineLayout pipeline_layout; vk::DescriptorUpdateTemplate descriptor_update_template; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index e989bf6b31..d156baa77b 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -474,7 +474,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { buffer_cache.BindHostStageBuffers(stage); PushImageDescriptors(texture_cache, guest_descriptor_queue, stage_infos[stage], rescaling, samplers_it, views_it); - const auto& info{stage_infos[stage]}; + const auto& info{stage_infos[0]}; if (info.uses_render_area) { render_area.uses_render_area = true; render_area.words = {static_cast(regs.surface_clip.width), @@ -946,27 +946,29 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; } - pipeline = device.GetLogical().CreateGraphicsPipeline({ - .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, - .pNext = nullptr, - .flags = flags, - .stageCount = static_cast(shader_stages.size()), - .pStages = shader_stages.data(), - .pVertexInputState = &vertex_input_ci, - .pInputAssemblyState = &input_assembly_ci, - .pTessellationState = &tessellation_ci, - .pViewportState = &viewport_ci, - .pRasterizationState = &rasterization_ci, - .pMultisampleState = &multisample_ci, - .pDepthStencilState = &depth_stencil_ci, - .pColorBlendState = &color_blend_ci, - .pDynamicState = &dynamic_state_ci, - .layout = *pipeline_layout, - .renderPass = render_pass, - .subpass = 0, - .basePipelineHandle = nullptr, - .basePipelineIndex = 0, - }, *pipeline_cache); + pipeline = device.GetLogical().CreateGraphicsPipeline( + { + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = flags, + .stageCount = static_cast(shader_stages.size()), + .pStages = shader_stages.data(), + .pVertexInputState = &vertex_input_ci, + .pInputAssemblyState = &input_assembly_ci, + .pTessellationState = &tessellation_ci, + .pViewportState = &viewport_ci, + .pRasterizationState = &rasterization_ci, + .pMultisampleState = &multisample_ci, + .pDepthStencilState = &depth_stencil_ci, + .pColorBlendState = &color_blend_ci, + .pDynamicState = &dynamic_state_ci, + .layout = *pipeline_layout, + .renderPass = render_pass, + .subpass = 0, + .basePipelineHandle = nullptr, + .basePipelineIndex = 0, + }, + *pipeline_cache); // Log graphics pipeline creation if (Settings::values.gpu_logging_enabled.GetValue()) { diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp index 80853362ad..3b5c2e3c01 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -101,14 +101,22 @@ PresentManager::PresentManager(const vk::Instance& instance_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_, Swapchain& swapchain_, +#ifdef ANDROID vk::SurfaceKHR& surface_) +#else + VkSurfaceKHR_T* surface_handle_) +#endif : instance{instance_} , render_window{render_window_} , device{device_} , memory_allocator{memory_allocator_} , scheduler{scheduler_} , swapchain{swapchain_} +#ifdef ANDROID , surface{surface_} +#else + , surface_handle{surface_handle_} +#endif , blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())} , use_present_thread{Settings::values.async_presentation.GetValue()} { @@ -291,7 +299,11 @@ void PresentManager::PresentThread(std::stop_token token) { } void PresentManager::RecreateSwapchain(Frame* frame) { +#ifndef ANDROID + swapchain.Create(surface_handle, frame->width, frame->height); // Pass raw pointer +#else swapchain.Create(*surface, frame->width, frame->height); // Pass raw pointer +#endif SetImageCount(); } diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h index c51f8ed77f..aacc9b025a 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.h +++ b/src/video_core/renderer_vulkan/vk_present_manager.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -15,6 +15,8 @@ #include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/vulkan_wrapper.h" +struct VkSurfaceKHR_T; + namespace Core::Frontend { class EmuWindow; } // namespace Core::Frontend @@ -44,7 +46,11 @@ public: MemoryAllocator& memory_allocator, Scheduler& scheduler, Swapchain& swapchain, +#ifdef ANDROID vk::SurfaceKHR& surface); +#else + VkSurfaceKHR_T* surface_handle); +#endif ~PresentManager(); /// Returns the last used presentation frame @@ -78,7 +84,11 @@ private: MemoryAllocator& memory_allocator; Scheduler& scheduler; Swapchain& swapchain; +#ifdef ANDROID vk::SurfaceKHR& surface; +#else + VkSurfaceKHR_T* surface_handle; +#endif vk::CommandPool cmdpool; std::vector frames; boost::container::deque present_queue; diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 7bb632cedc..818a73ec3d 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -1257,7 +1257,7 @@ void QueryCacheRuntime::EndHostConditionalRendering() { PauseHostConditionalRendering(); impl->hcr_is_set = false; impl->is_hcr_running = false; - impl->hcr_buffer = VkBuffer{}; + impl->hcr_buffer = nullptr; impl->hcr_offset = 0; } diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h index e2aa4d991e..b8dae9bc2d 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.h +++ b/src/video_core/renderer_vulkan/vk_query_cache.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -38,7 +35,7 @@ public: ~QueryCacheRuntime(); template - void SyncValues(std::span values, VkBuffer base_src_buffer = VkBuffer{}); + void SyncValues(std::span values, VkBuffer base_src_buffer = nullptr); void Barriers(bool is_prebarrier); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 947de6a80e..0a032cdae0 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -377,7 +377,7 @@ void Scheduler::EndRenderPass() VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images)); }); - state.renderpass = VkRenderPass{}; + state.renderpass = nullptr; num_renderpass_images = 0; } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 00a912f2cd..667f136ee6 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -44,10 +44,10 @@ public: ~Scheduler(); /// Sends the current execution context to the GPU. - u64 Flush(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {}); + u64 Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); /// Sends the current execution context to the GPU and waits for it to complete. - void Finish(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {}); + void Finish(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); /// Waits for the worker thread to finish executing everything. After this function returns it's /// safe to touch worker resources. @@ -237,8 +237,8 @@ private: }; struct State { - VkRenderPass renderpass{}; - VkFramebuffer framebuffer{}; + VkRenderPass renderpass = nullptr; + VkFramebuffer framebuffer = nullptr; VkExtent2D render_area = {0, 0}; GraphicsPipeline* graphics_pipeline = nullptr; bool is_rescaling = false; diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index cd8f948d8b..b89e981444 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -109,22 +109,38 @@ VkCompositeAlphaFlagBitsKHR ChooseAlphaFlags(const VkSurfaceCapabilitiesKHR& cap } // Anonymous namespace Swapchain::Swapchain( - VkSurfaceKHR_T* surface_, +#ifdef ANDROID + VkSurfaceKHR surface_, +#else + VkSurfaceKHR_T* surface_handle_, +#endif const Device& device_, Scheduler& scheduler_, u32 width_, u32 height_) +#ifdef ANDROID : surface(surface_) +#else + : surface_handle{surface_handle_} +#endif , device{device_} , scheduler{scheduler_} { +#ifdef ANDROID Create(surface, width_, height_); +#else + Create(surface_handle, width_, height_); +#endif } Swapchain::~Swapchain() = default; void Swapchain::Create( - VkSurfaceKHR_T* surface_, +#ifdef ANDROID + VkSurfaceKHR surface_, +#else + VkSurfaceKHR_T* surface_handle_, +#endif u32 width_, u32 height_) { @@ -132,10 +148,18 @@ void Swapchain::Create( is_suboptimal = false; width = width_; height = height_; +#ifdef ANDROID surface = surface_; +#else + surface_handle = surface_handle_; +#endif const auto physical_device = device.GetPhysical(); - const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(VkSurfaceKHR(surface))}; +#ifdef ANDROID + const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface)}; +#else + const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface_handle)}; +#endif if (capabilities.maxImageExtent.width == 0 || capabilities.maxImageExtent.height == 0) { return; } @@ -230,8 +254,14 @@ void Swapchain::Present(VkSemaphore render_semaphore) { void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) { const auto physical_device{device.GetPhysical()}; - const auto formats{physical_device.GetSurfaceFormatsKHR(VkSurfaceKHR(surface))}; - const auto present_modes = physical_device.GetSurfacePresentModesKHR(VkSurfaceKHR(surface)); + +#ifdef ANDROID + const auto formats{physical_device.GetSurfaceFormatsKHR(surface)}; + const auto present_modes = physical_device.GetSurfacePresentModesKHR(surface); +#else + const auto formats{physical_device.GetSurfaceFormatsKHR(surface_handle)}; + const auto present_modes = physical_device.GetSurfacePresentModesKHR(surface_handle); +#endif has_mailbox = std::find(present_modes.begin(), present_modes.end(), VK_PRESENT_MODE_MAILBOX_KHR) != present_modes.end(); @@ -260,7 +290,11 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .pNext = nullptr, .flags = 0, - .surface = VkSurfaceKHR(surface), +#ifdef ANDROID + .surface = surface, +#else + .surface = surface_handle, +#endif .minImageCount = requested_image_count, .imageFormat = surface_format.format, .imageColorSpace = surface_format.colorSpace, @@ -279,7 +313,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) { .compositeAlpha = alpha_flags, .presentMode = present_mode, .clipped = VK_FALSE, - .oldSwapchain = VkSwapchainKHR{}, + .oldSwapchain = nullptr, }; const u32 graphics_family{device.GetGraphicsFamily()}; const u32 present_family{device.GetPresentFamily()}; @@ -311,7 +345,11 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) { swapchain_ci.flags |= VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR; } // Request the size again to reduce the possibility of a TOCTOU race condition. - const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(VkSurfaceKHR(surface)); +#ifdef ANDROID + const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface); +#else + const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface_handle); +#endif swapchain_ci.imageExtent = ChooseSwapExtent(updated_capabilities, width, height); // Don't add code within this and the swapchain creation. swapchain = device.GetLogical().CreateSwapchainKHR(swapchain_ci); diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index d926cc118a..7e99bf8fa7 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -11,6 +11,8 @@ #include "common/common_types.h" #include "video_core/vulkan_common/vulkan_wrapper.h" +struct VkSurfaceKHR_T; + namespace Layout { struct FramebufferLayout; } @@ -23,7 +25,11 @@ class Scheduler; class Swapchain { public: explicit Swapchain( - VkSurfaceKHR_T* surface, +#ifdef ANDROID + VkSurfaceKHR surface, +#else + VkSurfaceKHR_T* surface_handle, +#endif const Device& device, Scheduler& scheduler, u32 width, @@ -32,7 +38,11 @@ public: /// Creates (or recreates) the swapchain with a given size. void Create( - VkSurfaceKHR_T* surface, +#ifdef ANDROID + VkSurfaceKHR surface, +#else + VkSurfaceKHR_T* surface_handle, +#endif u32 width, u32 height); @@ -118,7 +128,11 @@ private: bool NeedsPresentModeUpdate() const; - VkSurfaceKHR_T* surface; +#ifdef ANDROID + VkSurfaceKHR surface; +#else + VkSurfaceKHR_T* surface_handle; +#endif const Device& device; Scheduler& scheduler; diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.h b/src/video_core/renderer_vulkan/vk_update_descriptor.h index 1497108b16..3a1343e02e 100644 --- a/src/video_core/renderer_vulkan/vk_update_descriptor.h +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.h @@ -1,13 +1,10 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include -#include + #include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { diff --git a/src/video_core/vulkan_common/vulkan.h b/src/video_core/vulkan_common/vulkan.h index 2cc0f0d7f0..8d2e8e2a37 100644 --- a/src/video_core/vulkan_common/vulkan.h +++ b/src/video_core/vulkan_common/vulkan.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -40,6 +40,3 @@ #undef False #undef None #undef True - -// "Catch-all" handle for both Android and.. the rest of platforms -struct VkSurfaceKHR_T; diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index b51c57d380..a2ff3ee6ed 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -419,7 +419,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR : instance{instance_}, dld{dld_}, physical{physical_}, format_properties(GetFormatProperties(physical)) { // Get suitability and device properties. - const bool is_suitable = GetSuitability(surface != VkSurfaceKHR{}); + const bool is_suitable = GetSuitability(surface != nullptr); const VkDriverId driver_id = properties.driver.driverID; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index d29a8cd3f3..d3623d1186 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -318,11 +318,6 @@ public: return properties.properties.limits.minStorageBufferOffsetAlignment; } - /// Returns texel buffer offset alignment requirement. - VkDeviceSize GetMinTexelBufferOffsetAlignment() const { - return properties.properties.limits.minTexelBufferOffsetAlignment; - } - /// Returns the maximum range for storage buffers. VkDeviceSize GetMaxStorageBufferRange() const { return properties.properties.limits.maxStorageBufferRange; diff --git a/src/video_core/vulkan_common/vulkan_surface.cpp b/src/video_core/vulkan_common/vulkan_surface.cpp index 761b7759c8..dc65d3960a 100644 --- a/src/video_core/vulkan_common/vulkan_surface.cpp +++ b/src/video_core/vulkan_common/vulkan_surface.cpp @@ -15,7 +15,7 @@ vk::SurfaceKHR CreateSurface( const vk::Instance& instance, [[maybe_unused]] const Core::Frontend::EmuWindow::WindowSystemInfo& window_info) { [[maybe_unused]] const vk::InstanceDispatch& dld = instance.Dispatch(); - VkSurfaceKHR unsafe_surface = VkSurfaceKHR{}; + VkSurfaceKHR unsafe_surface = nullptr; #ifdef _WIN32 if (window_info.type == Core::Frontend::WindowSystemType::Windows) { diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 872fbd858e..5c04132f7b 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -404,13 +404,13 @@ public: /// Construct a handle transferring the ownership from another handle. Handle(Handle&& rhs) noexcept - : handle{std::exchange(rhs.handle, Type{})}, owner{rhs.owner}, dld{rhs.dld} {} + : handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, dld{rhs.dld} {} /// Assign the current handle transferring the ownership from another handle. /// Destroys any previously held object. Handle& operator=(Handle&& rhs) noexcept { Release(); - handle = std::exchange(rhs.handle, Type{}); + handle = std::exchange(rhs.handle, nullptr); owner = rhs.owner; dld = rhs.dld; return *this; @@ -424,7 +424,7 @@ public: /// Destroys any held object. void reset() noexcept { Release(); - handle = Type{}; + handle = nullptr; } /// Returns the address of the held object. @@ -440,7 +440,7 @@ public: /// Returns true when there's a held object. explicit operator bool() const noexcept { - return handle != Type{}; + return handle != nullptr; } #ifndef ANDROID @@ -455,7 +455,7 @@ public: #endif protected: - Type handle{}; + Type handle = nullptr; OwnerType owner = nullptr; const Dispatch* dld = nullptr; @@ -463,7 +463,7 @@ private: /// Destroys the held object if it exists. void Release() noexcept { if (handle) { - Destroy(OwnerType(owner), Type(handle), *dld); + Destroy(owner, handle, *dld); } } }; @@ -506,7 +506,7 @@ public: /// Destroys any held object. void reset() noexcept { Release(); - handle = {}; + handle = nullptr; } /// Returns the address of the held object. @@ -522,7 +522,7 @@ public: /// Returns true when there's a held object. explicit operator bool() const noexcept { - return handle != Type{}; + return handle != nullptr; } #ifndef ANDROID @@ -537,7 +537,7 @@ public: #endif protected: - Type handle{}; + Type handle = nullptr; const Dispatch* dld = nullptr; private: @@ -607,7 +607,7 @@ private: std::unique_ptr allocations; std::size_t num = 0; VkDevice device = nullptr; - PoolType pool{}; + PoolType pool = nullptr; const DeviceDispatch* dld = nullptr; }; @@ -669,12 +669,12 @@ public: Image& operator=(const Image&) = delete; Image(Image&& rhs) noexcept - : handle{std::exchange(rhs.handle, VkImage{})}, usage{rhs.usage}, owner{rhs.owner}, + : handle{std::exchange(rhs.handle, nullptr)}, usage{rhs.usage}, owner{rhs.owner}, allocator{rhs.allocator}, allocation{rhs.allocation}, dld{rhs.dld} {} Image& operator=(Image&& rhs) noexcept { Release(); - handle = std::exchange(rhs.handle, VkImage{}); + handle = std::exchange(rhs.handle, nullptr); usage = rhs.usage; owner = rhs.owner; allocator = rhs.allocator; @@ -693,11 +693,11 @@ public: void reset() noexcept { Release(); - handle = VkImage{}; + handle = nullptr; } explicit operator bool() const noexcept { - return handle != VkImage{}; + return handle != nullptr; } void SetObjectNameEXT(const char* name) const; @@ -709,7 +709,7 @@ public: private: void Release() const noexcept; - VkImage handle{}; + VkImage handle = nullptr; VkImageUsageFlags usage{}; VkDevice owner = nullptr; VmaAllocator allocator = nullptr; @@ -730,13 +730,13 @@ public: Buffer& operator=(const Buffer&) = delete; Buffer(Buffer&& rhs) noexcept - : handle{std::exchange(rhs.handle, VkBuffer{})}, owner{rhs.owner}, allocator{rhs.allocator}, + : handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, allocator{rhs.allocator}, allocation{rhs.allocation}, mapped{rhs.mapped}, is_coherent{rhs.is_coherent}, dld{rhs.dld} {} Buffer& operator=(Buffer&& rhs) noexcept { Release(); - handle = std::exchange(rhs.handle, VkBuffer{}); + handle = std::exchange(rhs.handle, nullptr); owner = rhs.owner; allocator = rhs.allocator; allocation = rhs.allocation; @@ -756,11 +756,11 @@ public: void reset() noexcept { Release(); - handle = VkBuffer{}; + handle = nullptr; } explicit operator bool() const noexcept { - return handle != VkBuffer{}; + return handle != nullptr; } /// Returns the host mapped memory, an empty span otherwise. @@ -786,7 +786,7 @@ public: private: void Release() const noexcept; - VkBuffer handle{}; + VkBuffer handle = nullptr; VkDevice owner = nullptr; VmaAllocator allocator = nullptr; VmaAllocation allocation = nullptr; @@ -1020,10 +1020,10 @@ public: [[nodiscard]] PipelineLayout CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci) const; [[nodiscard]] Pipeline CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci, - VkPipelineCache cache = {}) const; + VkPipelineCache cache = nullptr) const; [[nodiscard]] Pipeline CreateComputePipeline(const VkComputePipelineCreateInfo& ci, - VkPipelineCache cache = {}) const; + VkPipelineCache cache = nullptr) const; [[nodiscard]] Sampler CreateSampler(const VkSamplerCreateInfo& ci) const; diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 1d2d358672..bdff73a040 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -10,14 +10,13 @@ #include -#include #include #include #include -#include #include #include #include +#include #include "common/common_types.h" #include "common/fs/fs.h" @@ -43,7 +42,7 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p item_model = new QStandardItemModel(tree_view); tree_view->setModel(item_model); tree_view->setAlternatingRowColors(true); - tree_view->setSelectionMode(QHeaderView::ExtendedSelection); + tree_view->setSelectionMode(QHeaderView::MultiSelection); tree_view->setSelectionBehavior(QHeaderView::SelectRows); tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); @@ -249,11 +248,8 @@ void ConfigurePerGameAddons::AddonDeleteRequested(QList selected) { void ConfigurePerGameAddons::showContextMenu(const QPoint& pos) { const QModelIndex index = tree_view->indexAt(pos); - auto selected = tree_view->selectionModel()->selectedRows(); - if (index.isValid() && selected.empty()) { - QModelIndex idx = item_model->index(index.row(), 0); - if (idx.isValid()) selected << idx; - } + auto selected = tree_view->selectionModel()->selectedIndexes(); + if (index.isValid() && selected.empty()) selected = {index}; if (selected.empty()) return; @@ -264,15 +260,6 @@ void ConfigurePerGameAddons::showContextMenu(const QPoint& pos) { AddonDeleteRequested(selected); }); - if (selected.length() == 1) { - auto loc = selected.at(0).data(PATCH_LOCATION).toString(); - if (QFileInfo::exists(loc)) { - QAction* open = menu.addAction(tr("&Open in File Manager")); - connect(open, &QAction::triggered, this, - [selected, loc]() { QDesktopServices::openUrl(QUrl::fromLocalFile(loc)); }); - } - } - menu.exec(tree_view->viewport()->mapToGlobal(pos)); } diff --git a/src/yuzu/game/game_list_worker.cpp b/src/yuzu/game/game_list_worker.cpp index 81012e4374..c4504a0d5e 100644 --- a/src/yuzu/game/game_list_worker.cpp +++ b/src/yuzu/game/game_list_worker.cpp @@ -4,7 +4,6 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include #include #include #include @@ -17,17 +16,14 @@ #include "common/fs/fs.h" #include "common/fs/path_util.h" -#include "common/settings.h" #include "core/core.h" #include "core/file_sys/card_image.h" -#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/fs_filesystem.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" -#include "core/file_sys/romfs.h" #include "core/file_sys/submission_package.h" #include "core/loader/loader.h" #include "yuzu/compatibility_list.h" @@ -379,12 +375,6 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa return true; } - if (target == ScanTarget::PopulateGameList && - (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP) && - !Loader::IsBootableGameContainer(file, file_type)) { - return true; - } - u64 program_id = 0; const auto res2 = loader->ReadProgramId(program_id); @@ -393,10 +383,18 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa provider->AddEntry(FileSys::TitleType::Application, FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id, file); - } else if (Settings::values.ext_content_from_game_dirs.GetValue() && + } else if (res2 == Loader::ResultStatus::Success && (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { - void(provider->AddEntriesFromContainer(file)); + const auto nsp = file_type == Loader::FileType::NSP + ? std::make_shared(file) + : FileSys::XCI{file}.GetSecurePartitionNSP(); + for (const auto& title : nsp->GetNCAs()) { + for (const auto& entry : title.second) { + provider->AddEntry(entry.first.first, entry.first.second, title.first, + entry.second->GetBaseFile()); + } + } } } else { std::vector program_ids; diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index e02e02b413..6ead3c4130 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -2019,10 +2019,6 @@ void MainWindow::ConfigureFilesystemProvider(const std::string& filepath) { return; } - if (QtCommon::provider->AddEntriesFromContainer(file)) { - return; - } - auto loader = Loader::GetLoader(*QtCommon::system, file); if (!loader) { return; @@ -2037,8 +2033,19 @@ void MainWindow::ConfigureFilesystemProvider(const std::string& filepath) { const auto res2 = loader->ReadProgramId(program_id); if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { QtCommon::provider->AddEntry(FileSys::TitleType::Application, - FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), - program_id, file); + FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id, + file); + } else if (res2 == Loader::ResultStatus::Success && + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { + const auto nsp = file_type == Loader::FileType::NSP + ? std::make_shared(file) + : FileSys::XCI{file}.GetSecurePartitionNSP(); + for (const auto& title : nsp->GetNCAs()) { + for (const auto& entry : title.second) { + QtCommon::provider->AddEntry(entry.first.first, entry.first.second, title.first, + entry.second->GetBaseFile()); + } + } } }