mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-15 13:16:43 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b54224d07 | |||
| 2a105c2869 | |||
| 4b243b17df | |||
| c0a34acc5c | |||
| ca5a3b6bbd | |||
| 12a9e9bd7c |
@@ -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
|
||||
*/
|
||||
|
||||
+2
-15
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
-16
@@ -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 {
|
||||
|
||||
+1
-4
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -51,24 +51,11 @@ object GameHelper {
|
||||
|
||||
// Scan External Content directories and register all NSP/XCI files
|
||||
val externalContentDirs = NativeConfig.getExternalContentDirs()
|
||||
val uniqueExternalContentDirs = linkedSetOf<String>()
|
||||
externalContentDirs.forEach { externalDir ->
|
||||
if (externalDir.isNotEmpty()) {
|
||||
uniqueExternalContentDirs.add(externalDir)
|
||||
}
|
||||
}
|
||||
|
||||
val mountedContainerUris = mutableSetOf<String>()
|
||||
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<MinimalDocumentFile>,
|
||||
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<Game>,
|
||||
files: Array<MinimalDocumentFile>,
|
||||
depth: Int,
|
||||
mountedContainerUris: MutableSet<String>
|
||||
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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<FileSys::NSP>(file);
|
||||
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
|
||||
std::map<u64, u32> nsp_versions;
|
||||
std::map<u64, std::string> 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<FileSys::NCA>(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<int>(title_type), static_cast<int>(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();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -557,9 +557,6 @@ struct Values {
|
||||
SwitchableSetting<bool> fix_bloom_effects{linkage, false, "fix_bloom_effects",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
|
||||
Category::RendererHacks};
|
||||
|
||||
@@ -756,8 +753,6 @@ struct Values {
|
||||
Category::DataStorage};
|
||||
Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path",
|
||||
Category::DataStorage};
|
||||
Setting<bool> ext_content_from_game_dirs{linkage, true, "ext_content_from_game_dirs",
|
||||
Category::DataStorage};
|
||||
std::vector<std::string> external_content_dirs;
|
||||
|
||||
// Debugging
|
||||
|
||||
@@ -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<std::string>& 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<Patch> 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<Patch> 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<Patch> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<NSP> 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<NSP>(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<NSP>(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 <typename Callback>
|
||||
bool ForEachContainerEntry(const std::shared_ptr<NSP>& nsp, bool only_content,
|
||||
std::optional<u64> base_program_id, Callback&& on_entry) {
|
||||
if (!nsp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& ncas = nsp->GetNCAs();
|
||||
if (ncas.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<u64, u32> versions;
|
||||
std::map<u64, std::string> 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<ExternalUpdateEntry>& 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<std::size_t>(content_type)] = file;
|
||||
multi_version_entries.push_back(std::move(update_entry));
|
||||
return;
|
||||
}
|
||||
|
||||
it->files[static_cast<std::size_t>(content_type)] = file;
|
||||
if (it->version_string.empty() && !version_string.empty()) {
|
||||
it->version_string = version_string;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename EntryMap, typename VersionMap>
|
||||
static bool AddExternalEntriesFromContainer(const std::shared_ptr<NSP>& nsp, EntryMap& entries,
|
||||
VersionMap& versions,
|
||||
std::vector<ExternalUpdateEntry>& 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<u64> 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<VirtualDir> 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<u64, u32> nsp_versions;
|
||||
std::map<u64, std::string> 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::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> 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<int>(title_type), static_cast<int>(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<u64, u32> xci_versions;
|
||||
std::map<u64, std::string> 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::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> 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
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
@@ -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<u64> base_program_id = std::nullopt);
|
||||
void ClearAllEntries();
|
||||
|
||||
void Refresh() override;
|
||||
@@ -279,6 +276,7 @@ public:
|
||||
|
||||
std::vector<ExternalUpdateEntry> 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<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
|
||||
@@ -305,6 +303,7 @@ public:
|
||||
|
||||
std::vector<ExternalUpdateEntry> 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);
|
||||
|
||||
@@ -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 <ostream>
|
||||
#include <string>
|
||||
#include <concepts>
|
||||
#include <algorithm>
|
||||
#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<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::NSP> 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<FileSys::NSP>(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<FileSys::NSP>& 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;
|
||||
|
||||
@@ -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
|
||||
|
||||
+11
-25
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-10
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -559,15 +556,10 @@ private:
|
||||
|
||||
class InputFromMotion final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_, bool invert_x_, bool invert_y_, bool invert_z_, InputEngine* input_engine_)
|
||||
: identifier(identifier_)
|
||||
, motion_sensor(motion_sensor_)
|
||||
, gyro_threshold(gyro_threshold_)
|
||||
, input_engine(input_engine_)
|
||||
, invert_x{invert_x_}
|
||||
, invert_y{invert_y_}
|
||||
, invert_z{invert_z_}
|
||||
{
|
||||
explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_,
|
||||
InputEngine* input_engine_)
|
||||
: identifier(identifier_), motion_sensor(motion_sensor_), gyro_threshold(gyro_threshold_),
|
||||
input_engine(input_engine_) {
|
||||
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||
const InputIdentifier input_identifier{
|
||||
.identifier = identifier,
|
||||
@@ -583,19 +575,7 @@ public:
|
||||
}
|
||||
|
||||
Common::Input::MotionStatus GetStatus() const {
|
||||
auto basic_motion = input_engine->GetMotion(identifier, motion_sensor);
|
||||
if (invert_x) {
|
||||
basic_motion.accel_x = -basic_motion.accel_x;
|
||||
basic_motion.gyro_x = -basic_motion.gyro_x;
|
||||
}
|
||||
if (invert_y) {
|
||||
basic_motion.accel_y = -basic_motion.accel_y;
|
||||
basic_motion.gyro_y = -basic_motion.gyro_y;
|
||||
}
|
||||
if (invert_z) {
|
||||
basic_motion.accel_z = -basic_motion.accel_z;
|
||||
basic_motion.gyro_z = -basic_motion.gyro_z;
|
||||
}
|
||||
const auto basic_motion = input_engine->GetMotion(identifier, motion_sensor);
|
||||
Common::Input::MotionStatus status{};
|
||||
const Common::Input::AnalogProperties properties = {
|
||||
.deadzone = 0.0f,
|
||||
@@ -628,9 +608,6 @@ private:
|
||||
const float gyro_threshold;
|
||||
int callback_key;
|
||||
InputEngine* input_engine;
|
||||
bool invert_x : 1;
|
||||
bool invert_y : 1;
|
||||
bool invert_z : 1;
|
||||
};
|
||||
|
||||
class InputFromAxisMotion final : public Common::Input::InputDevice {
|
||||
@@ -1089,15 +1066,13 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
|
||||
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||
};
|
||||
|
||||
auto const invert_x = params.Get("invert_x", "+") == "-";
|
||||
auto const invert_y = params.Get("invert_y", "+") != "+";
|
||||
auto const invert_z = params.Get("invert_z", "+") != "+";
|
||||
if (params.Has("motion")) {
|
||||
const auto motion_sensor = params.Get("motion", 0);
|
||||
const auto gyro_threshold = params.Get("threshold", 0.007f);
|
||||
input_engine->PreSetController(identifier);
|
||||
input_engine->PreSetMotion(identifier, motion_sensor);
|
||||
return std::make_unique<InputFromMotion>(identifier, motion_sensor, gyro_threshold, invert_x, invert_y, invert_z, input_engine.get());
|
||||
return std::make_unique<InputFromMotion>(identifier, motion_sensor, gyro_threshold,
|
||||
input_engine.get());
|
||||
}
|
||||
|
||||
const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f);
|
||||
@@ -1110,7 +1085,7 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
|
||||
.range = range,
|
||||
.threshold = threshold,
|
||||
.offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
|
||||
.inverted = invert_x,
|
||||
.inverted = params.Get("invert_x", "+") == "-",
|
||||
};
|
||||
|
||||
const auto axis_y = params.Get("axis_y", 1);
|
||||
@@ -1119,7 +1094,7 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
|
||||
.range = range,
|
||||
.threshold = threshold,
|
||||
.offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
|
||||
.inverted = invert_y,
|
||||
.inverted = params.Get("invert_y", "+") != "+",
|
||||
};
|
||||
|
||||
const auto axis_z = params.Get("axis_z", 1);
|
||||
@@ -1128,13 +1103,14 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
|
||||
.range = range,
|
||||
.threshold = threshold,
|
||||
.offset = std::clamp(params.Get("offset_z", 0.0f), -1.0f, 1.0f),
|
||||
.inverted = invert_z,
|
||||
.inverted = params.Get("invert_z", "+") != "+",
|
||||
};
|
||||
input_engine->PreSetController(identifier);
|
||||
input_engine->PreSetAxis(identifier, axis_x);
|
||||
input_engine->PreSetAxis(identifier, axis_y);
|
||||
input_engine->PreSetAxis(identifier, axis_z);
|
||||
return std::make_unique<InputFromAxisMotion>(identifier, axis_x, axis_y, axis_z, properties_x, properties_y, properties_z, input_engine.get());
|
||||
return std::make_unique<InputFromAxisMotion>(identifier, axis_x, axis_y, axis_z, properties_x,
|
||||
properties_y, properties_z, input_engine.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateCameraDevice(
|
||||
|
||||
@@ -356,12 +356,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
||||
tr("Fix bloom effects"),
|
||||
tr("Removes bloom in Burnout."));
|
||||
|
||||
INSERT(Settings,
|
||||
rescale_hack,
|
||||
tr("Enable Legacy Rescale Pass"),
|
||||
tr("May fix rescale issues in some games by relying on behavior from the previous implementation.\n"
|
||||
"Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3."));
|
||||
|
||||
// Renderer (Extensions)
|
||||
INSERT(Settings, dyna_state, tr("Extended Dynamic State"),
|
||||
tr("Controls the number of features that can be used in Extended Dynamic State.\n"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -94,17 +91,6 @@ void AlphaTest(EmitContext& ctx) {
|
||||
} // Anonymous namespace
|
||||
|
||||
void EmitPrologue(EmitContext& ctx) {
|
||||
if (ctx.stage == Stage::Fragment && ctx.runtime_info.dual_source_blend) {
|
||||
// Initialize dual-source blending outputs - prevents MoltenVK crash.
|
||||
const Id zero{ctx.Const(0.0f)};
|
||||
const Id one{ctx.Const(1.0f)};
|
||||
const Id default_color{ctx.ConstantComposite(ctx.F32[4], zero, zero, zero, one)};
|
||||
for (u32 i = 0; i < 2; ++i) {
|
||||
if (Sirit::ValidId(ctx.frag_color[i])) {
|
||||
ctx.OpStore(ctx.frag_color[i], default_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx.stage == Stage::VertexB) {
|
||||
const Id zero{ctx.Const(0.0f)};
|
||||
const Id one{ctx.Const(1.0f)};
|
||||
|
||||
@@ -1670,22 +1670,13 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
|
||||
break;
|
||||
case Stage::Fragment:
|
||||
for (u32 index = 0; index < 8; ++index) {
|
||||
const bool need_dual_source = runtime_info.dual_source_blend && index <= 1;
|
||||
if (!need_dual_source && !info.stores_frag_color[index] &&
|
||||
!profile.need_declared_frag_colors) {
|
||||
if (!info.stores_frag_color[index] && !profile.need_declared_frag_colors) {
|
||||
continue;
|
||||
}
|
||||
const Id type{GetAttributeType(*this, runtime_info.color_output_types[index])};
|
||||
frag_color[index] = DefineOutput(*this, type, std::nullopt);
|
||||
// Correct mapping for dual-source blending
|
||||
if (runtime_info.dual_source_blend && index <= 1) {
|
||||
Decorate(frag_color[index], spv::Decoration::Location, 0u);
|
||||
Decorate(frag_color[index], spv::Decoration::Index, index);
|
||||
Name(frag_color[index], index == 0 ? "frag_color0" : "frag_color0_secondary");
|
||||
} else {
|
||||
Decorate(frag_color[index], spv::Decoration::Location, index);
|
||||
Name(frag_color[index], fmt::format("frag_color{}", index));
|
||||
}
|
||||
Decorate(frag_color[index], spv::Decoration::Location, index);
|
||||
Name(frag_color[index], fmt::format("frag_color{}", index));
|
||||
}
|
||||
if (info.stores_frag_depth) {
|
||||
frag_depth = DefineOutput(*this, F32[1], std::nullopt);
|
||||
|
||||
@@ -304,7 +304,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
|
||||
Optimization::GlobalMemoryToStorageBufferPass(program, host_info);
|
||||
Optimization::TexturePass(env, program, host_info);
|
||||
|
||||
if (Settings::values.resolution_info.active || Settings::values.rescale_hack.GetValue()) {
|
||||
if (Settings::values.resolution_info.active || Optimization::FragmentShaderNeedsRescalingPass(program)) {
|
||||
Optimization::RescalingPass(program);
|
||||
}
|
||||
Optimization::DeadCodeEliminationPass(program);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// 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
|
||||
|
||||
@@ -22,6 +25,7 @@ void LowerFp64ToFp32(IR::Program& program);
|
||||
void LowerFp16ToFp32(IR::Program& program);
|
||||
void LowerInt64ToInt32(IR::Program& program);
|
||||
void RescalingPass(IR::Program& program);
|
||||
bool FragmentShaderNeedsRescalingPass(const IR::Program& program);
|
||||
void SsaRewritePass(IR::Program& program);
|
||||
void PositionPass(Environment& env, IR::Program& program);
|
||||
void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo& host_info);
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
|
||||
void VisitMark(IR::Block& block, IR::Inst& inst) {
|
||||
void VisitMark(IR::Block& block, IR::Inst& inst, bool needs_hack) {
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::Opcode::ShuffleIndex:
|
||||
case IR::Opcode::ShuffleUp:
|
||||
@@ -54,20 +54,16 @@ void VisitMark(IR::Block& block, IR::Inst& inst) {
|
||||
bool must_patch_outside = false;
|
||||
if (bitcast_inst->GetOpcode() == IR::Opcode::GetAttribute) {
|
||||
const IR::Attribute attr{bitcast_inst->Arg(0).Attribute()};
|
||||
switch (attr) {
|
||||
case IR::Attribute::PositionX:
|
||||
case IR::Attribute::PositionY:
|
||||
if (attr >= IR::Attribute::PositionX && attr <= IR::Attribute::PositionW) {
|
||||
bitcast_inst->SetFlags<u32>(0xDEADBEEF);
|
||||
must_patch_outside = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (must_patch_outside) {
|
||||
const auto it{IR::Block::InstructionList::s_iterator_to(inst)};
|
||||
IR::IREmitter ir{block, it};
|
||||
if (Settings::values.rescale_hack.GetValue()) {
|
||||
|
||||
if (needs_hack) {
|
||||
const IR::F32 new_inst{&*block.PrependNewInst(it, inst)};
|
||||
const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())};
|
||||
const IR::Value converted{ir.FPMul(new_inst, up_factor)};
|
||||
@@ -347,12 +343,49 @@ void Visit(const IR::Program& program, IR::Block& block, IR::Inst& inst) {
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
bool FragmentShaderNeedsRescalingPass(const IR::Program& program) {
|
||||
#ifdef __ANDROID__
|
||||
// Disable this workaround on Android to preserve performance
|
||||
return false;
|
||||
#endif
|
||||
|
||||
if (program.stage != Stage::Fragment) return false;
|
||||
|
||||
for (const IR::Block* block : program.post_order_blocks) {
|
||||
for (const IR::Inst& inst : block->Instructions()) {
|
||||
const auto op = inst.GetOpcode();
|
||||
if (op != IR::Opcode::ShuffleIndex && op != IR::Opcode::ShuffleUp &&
|
||||
op != IR::Opcode::ShuffleDown && op != IR::Opcode::ShuffleButterfly) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inst.Arg(0).IsImmediate()) continue;
|
||||
const IR::Inst* arg_inst = inst.Arg(0).InstRecursive();
|
||||
|
||||
if (arg_inst->GetOpcode() != IR::Opcode::BitCastU32F32 || arg_inst->Arg(0).IsImmediate()) continue;
|
||||
const IR::Inst* bitcast_inst = arg_inst->Arg(0).InstRecursive();
|
||||
|
||||
if (bitcast_inst->GetOpcode() == IR::Opcode::GetAttribute) {
|
||||
const auto attr = bitcast_inst->Arg(0).Attribute();
|
||||
if (attr >= IR::Attribute::PositionX && attr <= IR::Attribute::PositionW) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RescalingPass(IR::Program& program) {
|
||||
const bool is_fragment_shader{program.stage == Stage::Fragment};
|
||||
const bool needs_hack{FragmentShaderNeedsRescalingPass(program)};
|
||||
if (needs_hack) {
|
||||
LOG_WARNING(Shader, "F32/U32 bitcast detected. Applying rescaling workaround.");
|
||||
}
|
||||
if (is_fragment_shader) {
|
||||
for (IR::Block* const block : program.post_order_blocks) {
|
||||
for (IR::Inst& inst : block->Instructions()) {
|
||||
VisitMark(*block, inst);
|
||||
VisitMark(*block, inst, needs_hack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,9 +110,6 @@ struct RuntimeInfo {
|
||||
|
||||
/// Output types for each color attachment
|
||||
std::array<AttributeType, 8> color_output_types{};
|
||||
|
||||
/// Dual source blending
|
||||
bool dual_source_blend{};
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,8 +137,14 @@ try
|
||||
memory_allocator,
|
||||
scheduler,
|
||||
swapchain,
|
||||
#ifdef ANDROID
|
||||
surface)
|
||||
,
|
||||
#else
|
||||
*surface)
|
||||
, blit_swapchain(device_memory,
|
||||
,
|
||||
#endif
|
||||
blit_swapchain(device_memory,
|
||||
device,
|
||||
memory_allocator,
|
||||
present_manager,
|
||||
|
||||
@@ -108,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<u32>(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;
|
||||
|
||||
@@ -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<u32>(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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<float>(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<u32>(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<u32>(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()) {
|
||||
|
||||
@@ -237,38 +237,11 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
|
||||
}
|
||||
info.convert_depth_mode = gl_ndc;
|
||||
break;
|
||||
case Shader::Stage::Fragment: {
|
||||
case Shader::Stage::Fragment:
|
||||
info.alpha_test_func = MaxwellToCompareFunction(
|
||||
key.state.UnpackComparisonOp(key.state.alpha_test_func.Value()));
|
||||
info.alpha_test_reference = std::bit_cast<float>(key.state.alpha_test_ref);
|
||||
|
||||
// Check for dual source blending
|
||||
const auto& blend0 = key.state.attachments[0];
|
||||
if (blend0.enable != 0) {
|
||||
using F = Maxwell::Blend::Factor;
|
||||
const auto src_rgb = blend0.SourceRGBFactor();
|
||||
const auto dst_rgb = blend0.DestRGBFactor();
|
||||
const auto src_a = blend0.SourceAlphaFactor();
|
||||
const auto dst_a = blend0.DestAlphaFactor();
|
||||
info.dual_source_blend =
|
||||
src_rgb == F::Source1Color_D3D || src_rgb == F::OneMinusSource1Color_D3D ||
|
||||
src_rgb == F::Source1Alpha_D3D || src_rgb == F::OneMinusSource1Alpha_D3D ||
|
||||
src_rgb == F::Source1Color_GL || src_rgb == F::OneMinusSource1Color_GL ||
|
||||
src_rgb == F::Source1Alpha_GL || src_rgb == F::OneMinusSource1Alpha_GL ||
|
||||
dst_rgb == F::Source1Color_D3D || dst_rgb == F::OneMinusSource1Color_D3D ||
|
||||
dst_rgb == F::Source1Alpha_D3D || dst_rgb == F::OneMinusSource1Alpha_D3D ||
|
||||
dst_rgb == F::Source1Color_GL || dst_rgb == F::OneMinusSource1Color_GL ||
|
||||
dst_rgb == F::Source1Alpha_GL || dst_rgb == F::OneMinusSource1Alpha_GL ||
|
||||
src_a == F::Source1Color_D3D || src_a == F::OneMinusSource1Color_D3D ||
|
||||
src_a == F::Source1Alpha_D3D || src_a == F::OneMinusSource1Alpha_D3D ||
|
||||
src_a == F::Source1Color_GL || src_a == F::OneMinusSource1Color_GL ||
|
||||
src_a == F::Source1Alpha_GL || src_a == F::OneMinusSource1Alpha_GL ||
|
||||
dst_a == F::Source1Color_D3D || dst_a == F::OneMinusSource1Color_D3D ||
|
||||
dst_a == F::Source1Alpha_D3D || dst_a == F::OneMinusSource1Alpha_D3D ||
|
||||
dst_a == F::Source1Color_GL || dst_a == F::OneMinusSource1Color_GL ||
|
||||
dst_a == F::Source1Alpha_GL || dst_a == F::OneMinusSource1Alpha_GL;
|
||||
}
|
||||
|
||||
if (device.IsMoltenVK()) {
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
const auto format = static_cast<Tegra::RenderTargetFormat>(key.state.color_formats[i]);
|
||||
@@ -285,7 +258,6 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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_,
|
||||
VkSurfaceKHR_T* surface_)
|
||||
#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) {
|
||||
swapchain.Create(surface, frame->width, frame->height); // Pass raw pointer
|
||||
#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();
|
||||
}
|
||||
|
||||
@@ -310,7 +322,7 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
|
||||
// Recreate surface and swapchain if needed.
|
||||
if (requires_recreation) {
|
||||
#ifdef ANDROID
|
||||
surface = *CreateSurface(instance, render_window.GetWindowInfo()).address();
|
||||
surface = CreateSurface(instance, render_window.GetWindowInfo());
|
||||
#endif
|
||||
RecreateSwapchain(frame);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
VkSurfaceKHR_T* surface);
|
||||
#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;
|
||||
VkSurfaceKHR_T* surface;
|
||||
#ifdef ANDROID
|
||||
vk::SurfaceKHR& surface;
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle;
|
||||
#endif
|
||||
vk::CommandPool cmdpool;
|
||||
std::vector<Frame> frames;
|
||||
boost::container::deque<Frame*> present_queue;
|
||||
|
||||
@@ -1280,7 +1280,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <typename SyncValuesType>
|
||||
void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = VkBuffer{});
|
||||
void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = nullptr);
|
||||
|
||||
void Barriers(bool is_prebarrier);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -586,20 +586,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
if (is_amd_driver) {
|
||||
// AMD drivers need a higher amount of Sets per Pool in certain circumstances like in XC2.
|
||||
sets_per_pool = 96;
|
||||
|
||||
// Disable VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT on AMD GCN4 and lower as it is broken.
|
||||
if (!features.shader_float16_int8.shaderFloat16) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"AMD GCN4 and earlier have broken VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT");
|
||||
has_broken_cube_compatibility = true;
|
||||
}
|
||||
|
||||
// AMD drivers (2026+) have broken float16 math on DKCR
|
||||
if (features.shader_float16_int8.shaderFloat16) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"AMD drivers (2026+) have broken float16 math");
|
||||
features.shader_float16_int8.shaderFloat16 = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_qualcomm) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<AllocationType[]> 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;
|
||||
|
||||
|
||||
@@ -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: 2016 Citra Emulator Project
|
||||
@@ -465,40 +465,30 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
|
||||
|
||||
button->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
connect(button, &QPushButton::customContextMenuRequested, [=, this](const QPoint& menu_location) {
|
||||
QMenu context_menu;
|
||||
Common::ParamPackage param = emulated_controller->GetMotionParam(motion_id);
|
||||
context_menu.addAction(tr("Clear"), [&] {
|
||||
emulated_controller->SetMotionParam(motion_id, {});
|
||||
motion_map[motion_id]->setText(tr("[not set]"));
|
||||
});
|
||||
if (param.Has("motion")) {
|
||||
context_menu.addAction(tr("Set gyro threshold"), [&] {
|
||||
const int gyro_threshold = int(param.Get("threshold", 0.007f) * 1000.0f);
|
||||
const int new_threshold = QInputDialog::getInt(
|
||||
this, tr("Set threshold"), tr("Choose a value between 0% and 100%"),
|
||||
gyro_threshold, 0, 100);
|
||||
param.Set("threshold", new_threshold / 1000.0f);
|
||||
emulated_controller->SetMotionParam(motion_id, param);
|
||||
connect(button, &QPushButton::customContextMenuRequested,
|
||||
[=, this](const QPoint& menu_location) {
|
||||
QMenu context_menu;
|
||||
Common::ParamPackage param = emulated_controller->GetMotionParam(motion_id);
|
||||
context_menu.addAction(tr("Clear"), [&] {
|
||||
emulated_controller->SetMotionParam(motion_id, {});
|
||||
motion_map[motion_id]->setText(tr("[not set]"));
|
||||
});
|
||||
if (param.Has("motion")) {
|
||||
context_menu.addAction(tr("Set gyro threshold"), [&] {
|
||||
const int gyro_threshold =
|
||||
static_cast<int>(param.Get("threshold", 0.007f) * 1000.0f);
|
||||
const int new_threshold = QInputDialog::getInt(
|
||||
this, tr("Set threshold"), tr("Choose a value between 0% and 100%"),
|
||||
gyro_threshold, 0, 100);
|
||||
param.Set("threshold", new_threshold / 1000.0f);
|
||||
emulated_controller->SetMotionParam(motion_id, param);
|
||||
});
|
||||
context_menu.addAction(tr("Calibrate sensor"), [&] {
|
||||
emulated_controller->StartMotionCalibration();
|
||||
});
|
||||
}
|
||||
context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location));
|
||||
});
|
||||
context_menu.addAction(tr("Invert X"), [&] {
|
||||
param.Set("invert_x", param.Get("invert_x", "+") == "-" ? "+" : "-");
|
||||
emulated_controller->SetMotionParam(motion_id, param);
|
||||
});
|
||||
context_menu.addAction(tr("Invert Y"), [&] {
|
||||
param.Set("invert_y", param.Get("invert_y", "+") == "-" ? "+" : "-");
|
||||
emulated_controller->SetMotionParam(motion_id, param);
|
||||
});
|
||||
context_menu.addAction(tr("Invert Z"), [&] {
|
||||
param.Set("invert_z", param.Get("invert_z", "+") == "-" ? "+" : "-");
|
||||
emulated_controller->SetMotionParam(motion_id, param);
|
||||
});
|
||||
context_menu.addAction(tr("Calibrate sensor"), [&] {
|
||||
emulated_controller->StartMotionCalibration();
|
||||
});
|
||||
}
|
||||
context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location));
|
||||
});
|
||||
}
|
||||
|
||||
connect(ui->sliderZLThreshold, &QSlider::valueChanged, [=, this] {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -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<FileSys::NSP>(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<u64> program_ids;
|
||||
|
||||
@@ -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<FileSys::NSP>(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user