Compare commits

...

17 commits

Author SHA1 Message Date
lizzie
cb66fcc088 fx 2026-03-07 04:18:30 +00:00
lizzie
6cdc613fb8 [audio_core, hle/services, video_core/compute] Inline std::unique_ptr<> allocs into std::optional<>
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-03-07 04:16:37 +00:00
xbzk
ddac8c8eb5
[vk] fix crash introduced in 9a07bd0570 (#3685)
Some checks are pending
tx-src / sources (push) Waiting to run
Check Strings / check-strings (push) Waiting to run
Fix for current crash on master.
Just reverted only the necessary stuff so that PresentManager can hold a reference to khr and resist death upon application hold/restore.
@Lizzie shall judge.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3685
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-06 19:52:17 +01:00
lizzie
c062931c9b
[qt] add translation table entry for debug_knobs,serial_battery and serial_unit (#3682)
trivial qt change

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3682
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-06 16:38:39 +01:00
crueter
e4122dae1d
[desktop] addons: open mod folder in rc menu (#3662)
also fixed the multiselection being absolutely horrendous

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3662
2026-03-06 16:38:21 +01:00
lizzie
b75e81af5e
[video_core/engines] implement stub NV01 timer, inline other channel engines (#3640)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3640
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-06 15:05:39 +01:00
lizzie
2ed1328c93
[vk] use static_vector instead of small_vector for TFB and other bindings (#3641)
MK8D is a big offender, taking up lots of time memcpy'ing and memmov'ing small_vector<> AND to add salt to the wound it doesn't even do heap allocations (no game does I think) - so basically useless waste of compute time in hot path for NO reason :^)

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3641
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-06 15:05:05 +01:00
lizzie
c70b857c4f
[video_core/engines] Macro HLE inline (#3653)
Should slightly boost perf on android, Desktop is mainly unaffected (for now)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3653
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-06 15:04:38 +01:00
MaranBr
23566a1f7d
[prepo] Add support for missing PlayReport commands (#3674)
This fixes:

`[ 433.095195] Debug <Critical> core\hle\service\service.cpp:operator ():69: Assertion Failed!
Unknown / unimplemented function '10107': port='prepo:u' cmd_buf={[0]=0x110006, [1]=0x80000014, [2]=0x1, [3]=0x0, [4]=0x0, [5]=0x191080, [6]=0x5A7350F8, [7]=0x112, [8]=0x5A735158}`

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3674
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-03-06 15:02:59 +01:00
xbzk
529b069499
[android,ui] fixed top disalignment between buttons of each column in settings fragment (#3675)
this silly little thing tickles obsessive compulsive disturbed fellas a lot hu3
was shipped along PR 3660, which was rediscussed for other reason, hence this tiny lonely PR.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3675
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-05 13:58:46 +01:00
lizzie
9a07bd0570
[vk] unify VkSurfaceKHR with Android and the rest of platforms; remove technically incorrect nullptr() ctor for handles (#2971)
Removes some odd #ifdef-ing that just can use a shrimple opaque type.

Also removes nullptr() ctor'ing for vulkan handles and such; it's not incorrect per se like how `void *p = 0;` isn't incorrect, just that, y'know, any static analyzer will go "woah". Also there isn't any guarantee that handles `sizeof(Handle) == sizeof(void*)` so may as well :)

Signed-off-by: lizzie lizzie@eden-emu.dev

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2971
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-05 07:32:18 +01:00
xbzk
05f6942bef
[android, ui] fixed setting reset to defaults infinite loop (#3659)
fixed a bug discovered by Pavel in which the settings' "reset to defaults" dialog would get stuck in a infinite loop, due to a recall prior to cleaning state.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3659
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-05 00:58:49 +01:00
nekle
70c1e9abed
[android] Try and fix SD Card storage mount points for external paths (#3436)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3436
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: nekle <nekle@protonmail.com>
Co-committed-by: nekle <nekle@protonmail.com>
2026-03-05 00:56:25 +01:00
wildcard
69e47bd2c0
[vulkan] Fix wrong stage index in prepare_stage render area check (#3672)
The OpenGL correctly uses const auto& info{stage_infos[stage]};

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3672
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2026-03-05 00:54:48 +01:00
wildcard
cdf9b556b2
[vulkan]fix vuid 02751 (#3573)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3573
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2026-03-05 00:54:26 +01:00
xbzk
7f5de6bcd6
[android/fs] external content loader + nca/xci patches (#3596)
Foreword: WHY DON'T EVERYBODY USE ONE FOLDER FOR EACH GAME+CONTENTS? AIN'T THIS THE FORMAT GAMES COME WHEN YOU BUE THEM? DO YOU LIVE WITH ALL YOUR FRIENDS AND HAVE A 2ND HOUSE FOR ALL THE CHILDREN?

Nice, i feel better now.

This feat extends Maufeat's work on external content loading. It harmonically additions:
"...also, if in each game folder X, you find a folder Y, and in this folder Y you detect ONLY a single game, then mount all external content for that game found in that folder Y and its subfolders."
Permanent (not toggleable). External Content folders are supported equally.

Also:
-Reworked several routines for preserving single source of truth between android and other systems;
-Fixed the annoying unknown format error for content files, by providing proper format detection.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3596
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-04 22:51:35 +01:00
wildcard
c682306788
[vulkan] Implement push descriptors for compute pipelines (#3666)
Implements push descriptor for compute pipelines along with a bug fix, the increment logic was, offset += sizeof(DescriptorUpdateEntry);
This only advances the byte offset by a single descriptor slot, regardless of the array's size (descriptorCount).Now suppose if a shader utilized an array of descriptors (eg, layout(binding = 0) uniform sampler2D textures[4]) and if this happened to fit within the MaxPushDescriptors limit, the template would consume 4 * sizeof(DescriptorUpdateEntry) bytes, but the offset for the next binding would only advance by 1 slot.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3666
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2026-03-04 22:46:55 +01:00
75 changed files with 1545 additions and 1617 deletions

View file

@ -607,6 +607,12 @@ object NativeLibrary {
*/ */
external fun addFileToFilesystemProvider(path: String) 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 * Clears all files added to the manual filesystem provider in our EmulationSession instance
*/ */

View file

@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.fetcher package org.yuzu.yuzu_emu.features.fetcher
import android.graphics.Rect import android.graphics.Rect
import android.view.View import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() { class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
@ -15,8 +16,20 @@ class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecorat
state: RecyclerView.State state: RecyclerView.State
) { ) {
outRect.bottom = spacing outRect.bottom = spacing
if (parent.getChildAdapterPosition(view) == 0) {
val position = parent.getChildAdapterPosition(view)
if (position == RecyclerView.NO_POSITION) return
if (position == 0) {
outRect.top = spacing 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
} }
} }
} }

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@ -68,7 +68,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.reset_setting_confirmation) .setMessage(R.string.reset_setting_confirmation)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
when (val item = settingsViewModel.clickedItem) { val item = settingsViewModel.clickedItem ?: return@setPositiveButton
clearDialogState()
when (item) {
is AnalogInputSetting -> { is AnalogInputSetting -> {
val stickParam = NativeInput.getStickParam( val stickParam = NativeInput.getStickParam(
item.playerIndex, item.playerIndex,
@ -107,12 +109,17 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
} }
else -> { else -> {
settingsViewModel.clickedItem!!.setting.reset() item.setting.reset()
settingsViewModel.setAdapterItemChanged(position) settingsViewModel.setAdapterItemChanged(position)
} }
} }
} }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel) { _: DialogInterface, _: Int ->
clearDialogState()
}
.setOnCancelListener {
clearDialogState()
}
.create() .create()
} }
@ -186,27 +193,6 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
updateButtonState(isValid) 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) { fun attachRepeat(button: View, delta: Int) {
val handler = Handler(Looper.getMainLooper()) val handler = Handler(Looper.getMainLooper())
var runnable: Runnable? = null var runnable: Runnable? = null
@ -439,9 +425,13 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
private fun closeDialog() { private fun closeDialog() {
settingsViewModel.setAdapterItemChanged(position) settingsViewModel.setAdapterItemChanged(position)
clearDialogState()
dismiss()
}
private fun clearDialogState() {
settingsViewModel.clickedItem = null settingsViewModel.clickedItem = null
settingsViewModel.setSliderProgress(-1f) settingsViewModel.setSliderProgress(-1f)
dismiss()
} }
private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int { private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {

View file

@ -1066,7 +1066,10 @@ class SettingsFragmentPresenter(
IntSetting.THEME.getValueAsString() IntSetting.THEME.getValueAsString()
override val defaultValue: Int = IntSetting.THEME.defaultValue override val defaultValue: Int = IntSetting.THEME.defaultValue
override fun reset() = IntSetting.THEME.setInt(defaultValue) override fun reset() {
IntSetting.THEME.setInt(defaultValue)
settingsViewModel.setShouldRecreate(true)
}
} }
add(HeaderSetting(R.string.app_settings)) add(HeaderSetting(R.string.app_settings))

View file

@ -127,10 +127,6 @@ class AddonViewModel : ViewModel() {
return 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( NativeConfig.setDisabledAddons(
game!!.programId, game!!.programId,
_patchList.value.mapNotNull { _patchList.value.mapNotNull {
@ -140,7 +136,7 @@ class AddonViewModel : ViewModel() {
if (PatchType.from(it.type) == PatchType.Update) { if (PatchType.from(it.type) == PatchType.Update) {
if (it.name.contains("(NAND)") || it.name.contains("(SDMC)")) { if (it.name.contains("(NAND)") || it.name.contains("(SDMC)")) {
it.name it.name
} else if (hasMultipleUpdates) { } else if (it.numericVersion != 0L) {
"Update@${it.numericVersion}" "Update@${it.numericVersion}"
} else { } else {
it.name it.name

View file

@ -424,7 +424,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
) )
val uriString = result.toString() val uriString = result.toString()
val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString } val folder = gamesViewModel.folders.value.firstOrNull {
it.uriString == uriString && it.type == org.yuzu.yuzu_emu.model.DirectoryType.EXTERNAL_CONTENT
}
if (folder != null) { if (folder != null) {
Toast.makeText( Toast.makeText(
applicationContext, applicationContext,

View file

@ -51,11 +51,24 @@ object GameHelper {
// Scan External Content directories and register all NSP/XCI files // Scan External Content directories and register all NSP/XCI files
val externalContentDirs = NativeConfig.getExternalContentDirs() val externalContentDirs = NativeConfig.getExternalContentDirs()
for (externalDir in externalContentDirs) { val uniqueExternalContentDirs = linkedSetOf<String>()
externalContentDirs.forEach { externalDir ->
if (externalDir.isNotEmpty()) {
uniqueExternalContentDirs.add(externalDir)
}
}
val mountedContainerUris = mutableSetOf<String>()
for (externalDir in uniqueExternalContentDirs) {
if (externalDir.isNotEmpty()) { if (externalDir.isNotEmpty()) {
val externalDirUri = externalDir.toUri() val externalDirUri = externalDir.toUri()
if (FileUtil.isTreeUriValid(externalDirUri)) { if (FileUtil.isTreeUriValid(externalDirUri)) {
scanExternalContentRecursive(FileUtil.listFiles(externalDirUri), 3) scanContentContainersRecursive(FileUtil.listFiles(externalDirUri), 3) {
val containerUri = it.uri.toString()
if (mountedContainerUris.add(containerUri)) {
NativeLibrary.addFileToFilesystemProvider(containerUri)
}
}
} }
} }
} }
@ -65,10 +78,13 @@ object GameHelper {
val gameDirUri = gameDir.uriString.toUri() val gameDirUri = gameDir.uriString.toUri()
val isValid = FileUtil.isTreeUriValid(gameDirUri) val isValid = FileUtil.isTreeUriValid(gameDirUri)
if (isValid) { if (isValid) {
val scanDepth = if (gameDir.deepScan) 3 else 1
addGamesRecursive( addGamesRecursive(
games, games,
FileUtil.listFiles(gameDirUri), FileUtil.listFiles(gameDirUri),
if (gameDir.deepScan) 3 else 1 scanDepth,
mountedContainerUris
) )
} else { } else {
badDirs.add(index) badDirs.add(index)
@ -103,9 +119,10 @@ object GameHelper {
// be done better imo. // be done better imo.
private val externalContentExtensions = setOf("nsp", "xci") private val externalContentExtensions = setOf("nsp", "xci")
private fun scanExternalContentRecursive( private fun scanContentContainersRecursive(
files: Array<MinimalDocumentFile>, files: Array<MinimalDocumentFile>,
depth: Int depth: Int,
onContainerFound: (MinimalDocumentFile) -> Unit
) { ) {
if (depth <= 0) { if (depth <= 0) {
return return
@ -113,14 +130,15 @@ object GameHelper {
files.forEach { files.forEach {
if (it.isDirectory) { if (it.isDirectory) {
scanExternalContentRecursive( scanContentContainersRecursive(
FileUtil.listFiles(it.uri), FileUtil.listFiles(it.uri),
depth - 1 depth - 1,
onContainerFound
) )
} else { } else {
val extension = FileUtil.getExtension(it.uri).lowercase() val extension = FileUtil.getExtension(it.uri).lowercase()
if (externalContentExtensions.contains(extension)) { if (externalContentExtensions.contains(extension)) {
NativeLibrary.addFileToFilesystemProvider(it.uri.toString()) onContainerFound(it)
} }
} }
} }
@ -129,7 +147,8 @@ object GameHelper {
private fun addGamesRecursive( private fun addGamesRecursive(
games: MutableList<Game>, games: MutableList<Game>,
files: Array<MinimalDocumentFile>, files: Array<MinimalDocumentFile>,
depth: Int depth: Int,
mountedContainerUris: MutableSet<String>
) { ) {
if (depth <= 0) { if (depth <= 0) {
return return
@ -140,11 +159,20 @@ object GameHelper {
addGamesRecursive( addGamesRecursive(
games, games,
FileUtil.listFiles(it.uri), FileUtil.listFiles(it.uri),
depth - 1 depth - 1,
mountedContainerUris
) )
} else { } else {
if (Game.extensions.contains(FileUtil.getExtension(it.uri))) { val extension = FileUtil.getExtension(it.uri).lowercase()
val game = getGame(it.uri, true) 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 != null) { if (game != null) {
games.add(game) games.add(game)
} }
@ -153,14 +181,20 @@ object GameHelper {
} }
} }
fun getGame(uri: Uri, addedToLibrary: Boolean): Game? { fun getGame(
uri: Uri,
addedToLibrary: Boolean,
registerFilesystemProvider: Boolean = true
): Game? {
val filePath = uri.toString() val filePath = uri.toString()
if (!GameMetadata.getIsValid(filePath)) { if (!GameMetadata.getIsValid(filePath)) {
return null return null
} }
if (registerFilesystemProvider) {
// Needed to update installed content information // Needed to update installed content information
NativeLibrary.addFileToFilesystemProvider(filePath) NativeLibrary.addFileToFilesystemProvider(filePath)
}
var name = GameMetadata.getTitle(filePath) var name = GameMetadata.getTitle(filePath)

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
@ -80,17 +80,15 @@ 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? { fun getRemovableStoragePath(idString: String): String? {
var pathFile: File val possibleMountPaths = listOf("/mnt/media_rw/$idString", "/storage/$idString")
pathFile = File("/mnt/media_rw/$idString"); for (mountPath in possibleMountPaths) {
val pathFile = File(mountPath);
if (pathFile.exists()) { if (pathFile.exists()) {
return pathFile.absolutePath return pathFile.absolutePath
} }
}
return null return null
} }

View file

@ -33,6 +33,12 @@ void AndroidConfig::ReadAndroidValues() {
if (global) { if (global) {
ReadAndroidUIValues(); ReadAndroidUIValues();
ReadUIValues(); 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(); ReadOverlayValues();
} }
ReadDriverValues(); ReadDriverValues();

View file

@ -96,6 +96,11 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobj
return false; return false;
} }
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) &&
!Loader::IsBootableGameContainer(file, file_type)) {
return false;
}
u64 program_id = 0; u64 program_id = 0;
Loader::ResultStatus res = loader->ReadProgramId(program_id); Loader::ResultStatus res = loader->ReadProgramId(program_id);
if (res != Loader::ResultStatus::Success) { if (res != Loader::ResultStatus::Success) {

View file

@ -217,108 +217,9 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
return; return;
} }
const auto extension = Common::ToLower(filepath.substr(filepath.find_last_of('.') + 1)); if (m_manual_provider->AddEntriesFromContainer(file)) {
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; 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); auto loader = Loader::GetLoader(m_system, file);
if (!loader) { if (!loader) {
@ -339,6 +240,13 @@ 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) { void EmulationSession::InitializeSystem(bool reload) {
if (!reload) { if (!reload) {
// Initialize logging system // Initialize logging system
@ -1609,6 +1517,12 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* e
Common::Android::GetJString(env, jpath)); 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) { void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, jobject jobj) {
EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries(); EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries();
} }

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -46,6 +49,7 @@ public:
const Core::PerfStatsResults& PerfStats(); const Core::PerfStatsResults& PerfStats();
int ShadersBuilding(); int ShadersBuilding();
void ConfigureFilesystemProvider(const std::string& filepath); void ConfigureFilesystemProvider(const std::string& filepath);
void ConfigureFilesystemProviderFromGameFolder(const std::string& filepath);
void InitializeSystem(bool reload); void InitializeSystem(bool reload);
void SetAppletId(int applet_id); void SetAppletId(int applet_id);
Core::SystemResultStatus InitializeEmulation(const std::string& filepath, Core::SystemResultStatus InitializeEmulation(const std::string& filepath,

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -8,10 +11,11 @@
namespace AudioCore { namespace AudioCore {
AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>()} { AudioCore::AudioCore(Core::System& system) {
audio_manager.emplace();
CreateSinks(); CreateSinks();
// Must be created after the sinks // Must be created after the sinks
adsp = std::make_unique<ADSP::ADSP>(system, *output_sink); adsp.emplace(system, *output_sink);
} }
AudioCore ::~AudioCore() { AudioCore ::~AudioCore() {

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -15,10 +18,7 @@ class System;
namespace AudioCore { namespace AudioCore {
class AudioManager; /// @brief Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP.
/**
* Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP.
*/
class AudioCore { class AudioCore {
public: public:
explicit AudioCore(Core::System& system); explicit AudioCore(Core::System& system);
@ -50,27 +50,22 @@ public:
*/ */
Sink::Sink& GetInputSink(); Sink::Sink& GetInputSink();
/** /// @brief Get the ADSP.
* Get the ADSP. /// @return Ref to the ADSP.
*
* @return Ref to the ADSP.
*/
ADSP::ADSP& ADSP(); ADSP::ADSP& ADSP();
private: private:
/** /// @brief Create the sinks on startup.
* Create the sinks on startup.
*/
void CreateSinks(); void CreateSinks();
/// Main audio manager for audio in/out /// Main audio manager for audio in/out
std::unique_ptr<AudioManager> audio_manager; std::optional<AudioManager> audio_manager;
/// Sink used for audio renderer and audio out /// Sink used for audio renderer and audio out
std::unique_ptr<Sink::Sink> output_sink; std::unique_ptr<Sink::Sink> output_sink;
/// Sink used for audio input /// Sink used for audio input
std::unique_ptr<Sink::Sink> input_sink; std::unique_ptr<Sink::Sink> input_sink;
/// The ADSP in the sysmodule /// The ADSP in the sysmodule
std::unique_ptr<ADSP::ADSP> adsp; std::optional<ADSP::ADSP> adsp;
}; };
} // namespace AudioCore } // namespace AudioCore

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -41,8 +44,7 @@ void Manager::ReleaseSessionId(const size_t session_id) {
Result Manager::LinkToManager() { Result Manager::LinkToManager() {
std::scoped_lock l{mutex}; std::scoped_lock l{mutex};
if (!linked_to_manager) { if (!linked_to_manager) {
AudioManager& manager{system.AudioCore().GetAudioManager()}; system.AudioCore().GetAudioManager().SetInManager(std::bind(&Manager::BufferReleaseAndRegister, this));
manager.SetInManager(std::bind(&Manager::BufferReleaseAndRegister, this));
linked_to_manager = true; linked_to_manager = true;
} }

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -40,8 +43,7 @@ void Manager::ReleaseSessionId(const size_t session_id) {
Result Manager::LinkToManager() { Result Manager::LinkToManager() {
std::scoped_lock l{mutex}; std::scoped_lock l{mutex};
if (!linked_to_manager) { if (!linked_to_manager) {
AudioManager& manager{system.AudioCore().GetAudioManager()}; system.AudioCore().GetAudioManager().SetOutManager(std::bind(&Manager::BufferReleaseAndRegister, this));
manager.SetOutManager(std::bind(&Manager::BufferReleaseAndRegister, this));
linked_to_manager = true; linked_to_manager = true;
} }

View file

@ -756,6 +756,8 @@ struct Values {
Category::DataStorage}; Category::DataStorage};
Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path", Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path",
Category::DataStorage}; 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; std::vector<std::string> external_content_dirs;
// Debugging // Debugging

View file

@ -347,7 +347,7 @@ struct System::Impl {
// Register with applet manager // Register with applet manager
// All threads are started, begin main process execution, now that we're in the clear // All threads are started, begin main process execution, now that we're in the clear
applet_manager.CreateAndInsertByFrontendAppletParameters(std::move(process), params); applet_manager.CreateAndInsertByFrontendAppletParameters(std::make_unique<Service::Process>(*std::move(process)), params);
if (Settings::values.gamecard_inserted) { if (Settings::values.gamecard_inserted) {
if (Settings::values.gamecard_current_game) { if (Settings::values.gamecard_current_game) {

View file

@ -117,6 +117,12 @@ void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
bool IsDirValidAndNonEmpty(const VirtualDir& dir) { bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty()); 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 } // Anonymous namespace
PatchManager::PatchManager(u64 title_id_, PatchManager::PatchManager(u64 title_id_,
@ -155,8 +161,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
if (!update_versions.empty()) { if (!update_versions.empty()) {
checked_external = true; checked_external = true;
for (const auto& update_entry : update_versions) { for (const auto& update_entry : update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version); if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
update_disabled = false; update_disabled = false;
enabled_version = update_entry.version; enabled_version = update_entry.version;
break; break;
@ -175,8 +180,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
if (!manual_update_versions.empty()) { if (!manual_update_versions.empty()) {
checked_manual = true; checked_manual = true;
for (const auto& update_entry : manual_update_versions) { for (const auto& update_entry : manual_update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version); if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
update_disabled = false; update_disabled = false;
enabled_version = update_entry.version; enabled_version = update_entry.version;
break; break;
@ -580,8 +584,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
if (!update_versions.empty()) { if (!update_versions.empty()) {
checked_external = true; checked_external = true;
for (const auto& update_entry : update_versions) { for (const auto& update_entry : update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version); if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
update_disabled = false; update_disabled = false;
enabled_version = update_entry.version; enabled_version = update_entry.version;
update_raw = external_provider->GetEntryForVersion(update_tid, type, update_entry.version); update_raw = external_provider->GetEntryForVersion(update_tid, type, update_entry.version);
@ -600,8 +603,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
if (!manual_update_versions.empty()) { if (!manual_update_versions.empty()) {
checked_manual = true; checked_manual = true;
for (const auto& update_entry : manual_update_versions) { for (const auto& update_entry : manual_update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version); if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
update_disabled = false; update_disabled = false;
enabled_version = update_entry.version; enabled_version = update_entry.version;
update_raw = manual_provider->GetEntryForVersion(update_tid, type, update_entry.version); update_raw = manual_provider->GetEntryForVersion(update_tid, type, update_entry.version);
@ -704,9 +706,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
version_str = FormatTitleVersion(update_entry.version); version_str = FormatTitleVersion(update_entry.version);
} }
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
const auto update_disabled = const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend(); IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
Patch update_patch = {.enabled = !update_disabled, Patch update_patch = {.enabled = !update_disabled,
.name = "Update", .name = "Update",
@ -732,9 +733,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
version_str = FormatTitleVersion(update_entry.version); version_str = FormatTitleVersion(update_entry.version);
} }
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
const auto update_disabled = const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend(); IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
Patch update_patch = {.enabled = !update_disabled, Patch update_patch = {.enabled = !update_disabled,
@ -771,7 +771,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
std::nullopt, std::nullopt, ContentRecordType::Program, update_tid); std::nullopt, std::nullopt, ContentRecordType::Program, update_tid);
for (const auto& [slot, entry] : all_updates) { for (const auto& [slot, entry] : all_updates) {
if (slot == ContentProviderUnionSlot::External) { if (slot == ContentProviderUnionSlot::External ||
slot == ContentProviderUnionSlot::FrontendManual) {
continue; continue;
} }

View file

@ -104,6 +104,206 @@ static std::string GetCNMTName(TitleType type, u64 title_id) {
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], 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) { ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
switch (type) { switch (type) {
case NCAContentType::Program: case NCAContentType::Program:
@ -1008,6 +1208,26 @@ 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() { void ManualContentProvider::ClearAllEntries() {
entries.clear(); entries.clear();
multi_version_entries.clear(); multi_version_entries.clear();
@ -1091,14 +1311,6 @@ VirtualFile ManualContentProvider::GetEntryForVersion(u64 title_id, ContentRecor
return nullptr; 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) ExternalContentProvider::ExternalContentProvider(std::vector<VirtualDir> load_directories)
: load_dirs(std::move(load_directories)) { : load_dirs(std::move(load_directories)) {
ExternalContentProvider::Refresh(); ExternalContentProvider::Refresh();
@ -1159,247 +1371,22 @@ void ExternalContentProvider::ScanDirectory(const VirtualDir& dir) {
} }
void ExternalContentProvider::ProcessNSP(const VirtualFile& file) { void ExternalContentProvider::ProcessNSP(const VirtualFile& file) {
auto nsp = NSP(file); const auto nsp = OpenContainerAsNsp(file, Loader::FileType::NSP);
if (nsp.GetStatus() != Loader::ResultStatus::Success) { if (!nsp) {
return; return;
} }
LOG_DEBUG(Service_FS, "Processing NSP file: {}", file->GetName()); 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) { void ExternalContentProvider::ProcessXCI(const VirtualFile& file) {
auto xci = XCI(file); const auto nsp = OpenContainerAsNsp(file, Loader::FileType::XCI);
if (xci.GetStatus() != Loader::ResultStatus::Success) { if (!nsp) {
return; return;
} }
auto nsp = xci.GetSecurePartitionNSP(); AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
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 { bool ExternalContentProvider::HasEntry(u64 title_id, ContentRecordType type) const {
@ -1491,12 +1478,4 @@ VirtualFile ExternalContentProvider::GetEntryForVersion(u64 title_id, ContentRec
return nullptr; 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 } // namespace FileSys

View file

@ -9,6 +9,7 @@
#include <array> #include <array>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
#include <ankerl/unordered_dense.h> #include <ankerl/unordered_dense.h>
@ -262,6 +263,8 @@ public:
VirtualFile file); VirtualFile file);
void AddEntryWithVersion(TitleType title_type, ContentRecordType content_type, u64 title_id, void AddEntryWithVersion(TitleType title_type, ContentRecordType content_type, u64 title_id,
u32 version, const std::string& version_string, VirtualFile file); 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 ClearAllEntries();
void Refresh() override; void Refresh() override;
@ -276,7 +279,6 @@ public:
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const; std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const; VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
private: private:
std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries; std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
@ -303,7 +305,6 @@ public:
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const; std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const; VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
private: private:
void ScanDirectory(const VirtualDir& dir); void ScanDirectory(const VirtualDir& dir);

View file

@ -268,7 +268,7 @@ void AppletManager::SetWindowSystem(WindowSystem* window_system) {
if (Settings::values.enable_overlay && m_window_system->GetOverlayDisplayApplet() == nullptr) { if (Settings::values.enable_overlay && m_window_system->GetOverlayDisplayApplet() == nullptr) {
if (auto overlay_process = CreateProcess(m_system, static_cast<u64>(AppletProgramId::OverlayDisplay), 0, 0)) { if (auto overlay_process = CreateProcess(m_system, static_cast<u64>(AppletProgramId::OverlayDisplay), 0, 0)) {
auto overlay_applet = std::make_shared<Applet>(m_system, std::move(overlay_process), false); auto overlay_applet = std::make_shared<Applet>(m_system, std::make_unique<Service::Process>(*std::move(overlay_process)), false);
overlay_applet->program_id = static_cast<u64>(AppletProgramId::OverlayDisplay); overlay_applet->program_id = static_cast<u64>(AppletProgramId::OverlayDisplay);
overlay_applet->applet_id = AppletId::OverlayDisplay; overlay_applet->applet_id = AppletId::OverlayDisplay;
overlay_applet->type = AppletType::OverlayApplet; overlay_applet->type = AppletType::OverlayApplet;

View file

@ -1,6 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <optional>
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/content_archive.h" #include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h" #include "core/file_sys/nca_metadata.h"
@ -36,31 +40,23 @@ FileSys::StorageId GetStorageIdForFrontendSlot(
} }
} }
std::unique_ptr<Process> CreateProcessImpl(std::unique_ptr<Loader::AppLoader>& out_loader, std::optional<Process> CreateProcessImpl(std::unique_ptr<Loader::AppLoader>& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index) {
Loader::ResultStatus& out_load_result,
Core::System& system, FileSys::VirtualFile file,
u64 program_id, u64 program_index) {
// Get the appropriate loader to parse this NCA. // Get the appropriate loader to parse this NCA.
out_loader = Loader::GetLoader(system, file, program_id, program_index); out_loader = Loader::GetLoader(system, file, program_id, program_index);
// Ensure we have a loader which can parse the NCA. // Ensure we have a loader which can parse the NCA.
if (!out_loader) { if (out_loader) {
return nullptr;
}
// Try to load the process. // Try to load the process.
auto process = std::make_unique<Process>(system); auto process = std::optional<Process>(system);
if (process->Initialize(*out_loader, out_load_result)) { if (process->Initialize(*out_loader, out_load_result)) {
return process; return process;
} }
}
return nullptr; return std::nullopt;
} }
} // Anonymous namespace } // Anonymous namespace
std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id, std::optional<Process> CreateProcess(Core::System& system, u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation) {
u8 minimum_key_generation, u8 maximum_key_generation) {
// Attempt to load program NCA. // Attempt to load program NCA.
FileSys::VirtualFile nca_raw{}; FileSys::VirtualFile nca_raw{};
@ -70,7 +66,7 @@ std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id,
// Ensure we retrieved a program NCA. // Ensure we retrieved a program NCA.
if (!nca_raw) { if (!nca_raw) {
return nullptr; return std::nullopt;
} }
// Ensure we have a suitable version. // Ensure we have a suitable version.
@ -79,9 +75,8 @@ std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id,
if (nca.GetStatus() == Loader::ResultStatus::Success && if (nca.GetStatus() == Loader::ResultStatus::Success &&
(nca.GetKeyGeneration() < minimum_key_generation || (nca.GetKeyGeneration() < minimum_key_generation ||
nca.GetKeyGeneration() > maximum_key_generation)) { nca.GetKeyGeneration() > maximum_key_generation)) {
LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id, LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id, nca.GetKeyGeneration());
nca.GetKeyGeneration()); return std::nullopt;
return nullptr;
} }
} }
@ -90,15 +85,10 @@ std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id,
return CreateProcessImpl(loader, status, system, nca_raw, program_id, 0); return CreateProcessImpl(loader, status, system, nca_raw, program_id, 0);
} }
std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control, std::optional<Process> CreateApplicationProcess(std::vector<u8>& out_control, std::unique_ptr<Loader::AppLoader>& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index) {
std::unique_ptr<Loader::AppLoader>& out_loader, auto process = CreateProcessImpl(out_loader, out_load_result, system, file, program_id, program_index);
Loader::ResultStatus& out_load_result,
Core::System& system, FileSys::VirtualFile file,
u64 program_id, u64 program_index) {
auto process =
CreateProcessImpl(out_loader, out_load_result, system, file, program_id, program_index);
if (!process) { if (!process) {
return nullptr; return std::nullopt;
} }
FileSys::NACP nacp; FileSys::NACP nacp;
@ -118,13 +108,10 @@ std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control,
// TODO(DarkLordZach): When FSController/Game Card Support is added, if // TODO(DarkLordZach): When FSController/Game Card Support is added, if
// current_process_game_card use correct StorageId // current_process_game_card use correct StorageId
launch.base_game_storage_id = GetStorageIdForFrontendSlot( launch.base_game_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program));
storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program)); launch.update_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program));
launch.update_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(
FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program));
system.GetARPManager().Register(launch.title_id, launch, out_control); system.GetARPManager().Register(launch.title_id, launch, out_control);
return process; return process;
} }

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -24,12 +27,7 @@ class Process;
namespace Service::AM { namespace Service::AM {
std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id, std::optional<Process> CreateProcess(Core::System& system, u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation);
u8 minimum_key_generation, u8 maximum_key_generation); std::optional<Process> CreateApplicationProcess(std::vector<u8>& out_control, std::unique_ptr<Loader::AppLoader>& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index);
std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control,
std::unique_ptr<Loader::AppLoader>& out_loader,
Loader::ResultStatus& out_load_result,
Core::System& system, FileSys::VirtualFile file,
u64 program_id, u64 program_index);
} // namespace Service::AM } // namespace Service::AM

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@ -21,8 +21,7 @@ namespace Service::AM {
namespace { namespace {
Result CreateGuestApplication(SharedPointer<IApplicationAccessor>* out_application_accessor, Result CreateGuestApplication(SharedPointer<IApplicationAccessor>* out_application_accessor, Core::System& system, WindowSystem& window_system, u64 program_id) {
Core::System& system, WindowSystem& window_system, u64 program_id) {
FileSys::VirtualFile nca_raw{}; FileSys::VirtualFile nca_raw{};
// Get the program NCA from storage. // Get the program NCA from storage.
@ -35,11 +34,10 @@ Result CreateGuestApplication(SharedPointer<IApplicationAccessor>* out_applicati
std::vector<u8> control; std::vector<u8> control;
std::unique_ptr<Loader::AppLoader> loader; std::unique_ptr<Loader::AppLoader> loader;
Loader::ResultStatus result; Loader::ResultStatus result;
auto process = auto process = CreateApplicationProcess(control, loader, result, system, nca_raw, program_id, 0);
CreateApplicationProcess(control, loader, result, system, nca_raw, program_id, 0); R_UNLESS(process != std::nullopt, ResultUnknown);
R_UNLESS(process != nullptr, ResultUnknown);
const auto applet = std::make_shared<Applet>(system, std::move(process), true); const auto applet = std::make_shared<Applet>(system, std::make_unique<Service::Process>(*std::move(process)), true);
applet->program_id = program_id; applet->program_id = program_id;
applet->applet_id = AppletId::Application; applet->applet_id = AppletId::Application;
applet->type = AppletType::Application; applet->type = AppletType::Application;
@ -47,8 +45,7 @@ Result CreateGuestApplication(SharedPointer<IApplicationAccessor>* out_applicati
window_system.TrackApplet(applet, true); window_system.TrackApplet(applet, true);
*out_application_accessor = *out_application_accessor = std::make_shared<IApplicationAccessor>(system, applet, window_system);
std::make_shared<IApplicationAccessor>(system, applet, window_system);
R_SUCCEED(); R_SUCCEED();
} }
@ -90,12 +87,10 @@ Result IApplicationCreator::CreateSystemApplication(
std::vector<u8> control; std::vector<u8> control;
std::unique_ptr<Loader::AppLoader> loader; std::unique_ptr<Loader::AppLoader> loader;
auto process = CreateProcess(system, application_id, 1, 21);
R_UNLESS(process != std::nullopt, ResultUnknown);
auto process = const auto applet = std::make_shared<Applet>(system, std::make_unique<Service::Process>(*std::move(process)), true);
CreateProcess(system, application_id, 1, 21);
R_UNLESS(process != nullptr, ResultUnknown);
const auto applet = std::make_shared<Applet>(system, std::move(process), true);
applet->program_id = application_id; applet->program_id = application_id;
applet->applet_id = AppletId::Starter; applet->applet_id = AppletId::Starter;
applet->type = AppletType::LibraryApplet; applet->type = AppletType::LibraryApplet;
@ -103,8 +98,7 @@ Result IApplicationCreator::CreateSystemApplication(
m_window_system.TrackApplet(applet, true); m_window_system.TrackApplet(applet, true);
*out_application_accessor = *out_application_accessor = std::make_shared<IApplicationAccessor>(system, applet, m_window_system);
std::make_shared<IApplicationAccessor>(system, applet, m_window_system);
Core::LaunchTimestampCache::SaveLaunchTimestamp(application_id); Core::LaunchTimestampCache::SaveLaunchTimestamp(application_id);
R_SUCCEED(); R_SUCCEED();
} }

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@ -121,12 +121,8 @@ std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
}; };
auto process = CreateProcess(system, program_id, Firmware1400, Firmware2100); auto process = CreateProcess(system, program_id, Firmware1400, Firmware2100);
if (!process) { if (process) {
// Couldn't initialize the guest process const auto applet = std::make_shared<Applet>(system, std::make_unique<Service::Process>(*std::move(process)), false);
return {};
}
const auto applet = std::make_shared<Applet>(system, std::move(process), false);
applet->program_id = program_id; applet->program_id = program_id;
applet->applet_id = applet_id; applet->applet_id = applet_id;
applet->type = AppletType::LibraryApplet; applet->type = AppletType::LibraryApplet;
@ -137,11 +133,12 @@ std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
applet->caller_applet = caller_applet; applet->caller_applet = caller_applet;
applet->caller_applet_broker = broker; applet->caller_applet_broker = broker;
caller_applet->child_applets.push_back(applet); caller_applet->child_applets.push_back(applet);
window_system.TrackApplet(applet, false); window_system.TrackApplet(applet, false);
return std::make_shared<ILibraryAppletAccessor>(system, broker, applet); return std::make_shared<ILibraryAppletAccessor>(system, broker, applet);
} }
// Couldn't initialize the guest process
return {};
}
std::shared_ptr<ILibraryAppletAccessor> CreateFrontendApplet(Core::System& system, std::shared_ptr<ILibraryAppletAccessor> CreateFrontendApplet(Core::System& system,
WindowSystem& window_system, WindowSystem& window_system,

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@ -14,7 +14,9 @@ namespace Service::Audio {
using namespace AudioCore::AudioOut; using namespace AudioCore::AudioOut;
IAudioOutManager::IAudioOutManager(Core::System& system_) IAudioOutManager::IAudioOutManager(Core::System& system_)
: ServiceFramework{system_, "audout:u"}, impl{std::make_unique<Manager>(system_)} { : ServiceFramework{system_, "audout:u"}
, impl(system_)
{
// clang-format off // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, D<&IAudioOutManager::ListAudioOuts>, "ListAudioOuts"}, {0, D<&IAudioOutManager::ListAudioOuts>, "ListAudioOuts"},

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@ -43,7 +43,7 @@ private:
AudioCore::AudioOut::AudioOutParameter parameter, AudioCore::AudioOut::AudioOutParameter parameter,
InCopyHandle<Kernel::KProcess> process_handle, ClientAppletResourceUserId aruid); InCopyHandle<Kernel::KProcess> process_handle, ClientAppletResourceUserId aruid);
std::unique_ptr<AudioCore::AudioOut::Manager> impl; std::optional<AudioCore::AudioOut::Manager> impl;
}; };
} // namespace Service::Audio } // namespace Service::Audio

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -15,7 +18,9 @@ namespace Service::Audio {
using namespace AudioCore::Renderer; using namespace AudioCore::Renderer;
IAudioRendererManager::IAudioRendererManager(Core::System& system_) IAudioRendererManager::IAudioRendererManager(Core::System& system_)
: ServiceFramework{system_, "audren:u"}, impl{std::make_unique<Manager>(system_)} { : ServiceFramework{system_, "audren:u"}
, impl(system_)
{
// clang-format off // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, D<&IAudioRendererManager::OpenAudioRenderer>, "OpenAudioRenderer"}, {0, D<&IAudioRendererManager::OpenAudioRenderer>, "OpenAudioRenderer"},

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -30,7 +33,7 @@ private:
Result GetAudioDeviceServiceWithRevisionInfo(Out<SharedPointer<IAudioDevice>> out_audio_device, Result GetAudioDeviceServiceWithRevisionInfo(Out<SharedPointer<IAudioDevice>> out_audio_device,
u32 revision, ClientAppletResourceUserId aruid); u32 revision, ClientAppletResourceUserId aruid);
std::unique_ptr<AudioCore::Renderer::Manager> impl; std::optional<AudioCore::Renderer::Manager> impl;
u32 num_audio_devices{0}; u32 num_audio_devices{0};
}; };

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -10,14 +13,6 @@
namespace Service { namespace Service {
Process::Process(Core::System& system)
: m_system(system), m_process(), m_main_thread_priority(), m_main_thread_stack_size(),
m_process_started() {}
Process::~Process() {
this->Finalize();
}
bool Process::Initialize(Loader::AppLoader& loader, Loader::ResultStatus& out_load_result) { bool Process::Initialize(Loader::AppLoader& loader, Loader::ResultStatus& out_load_result) {
// First, ensure we are not holding another process. // First, ensure we are not holding another process.
this->Finalize(); this->Finalize();

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -22,8 +25,8 @@ namespace Service {
class Process { class Process {
public: public:
explicit Process(Core::System& system); inline explicit Process(Core::System& system) noexcept : m_system(system) {}
~Process(); inline ~Process() { this->Finalize(); }
bool Initialize(Loader::AppLoader& loader, Loader::ResultStatus& out_load_result); bool Initialize(Loader::AppLoader& loader, Loader::ResultStatus& out_load_result);
void Finalize(); void Finalize();
@ -50,8 +53,8 @@ public:
private: private:
Core::System& m_system; Core::System& m_system;
Kernel::KProcess* m_process{}; Kernel::KProcess* m_process{};
s32 m_main_thread_priority{};
u64 m_main_thread_stack_size{}; u64 m_main_thread_stack_size{};
s32 m_main_thread_priority{};
bool m_process_started{}; bool m_process_started{};
}; };

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@ -28,8 +28,10 @@ public:
{10101, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old>, "SaveReportWithUserOld"}, {10101, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old>, "SaveReportWithUserOld"},
{10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old2>, "SaveReportOld2"}, {10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old2>, "SaveReportOld2"},
{10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old2>, "SaveReportWithUserOld2"}, {10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old2>, "SaveReportWithUserOld2"},
{10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"}, {10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old3>, "SaveReportOld3"},
{10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"}, {10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old3>, "SaveReportWithUserOld3"},
{10106, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
{10107, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
{10200, &PlayReport::RequestImmediateTransmission, "RequestImmediateTransmission"}, {10200, &PlayReport::RequestImmediateTransmission, "RequestImmediateTransmission"},
{10300, &PlayReport::GetTransmissionStatus, "GetTransmissionStatus"}, {10300, &PlayReport::GetTransmissionStatus, "GetTransmissionStatus"},
{10400, &PlayReport::GetSystemSessionId, "GetSystemSessionId"}, {10400, &PlayReport::GetSystemSessionId, "GetSystemSessionId"},

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@ -9,11 +9,15 @@
#include <ostream> #include <ostream>
#include <string> #include <string>
#include <concepts> #include <concepts>
#include <algorithm>
#include "common/concepts.h" #include "common/concepts.h"
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/core.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/hle/kernel/k_process.h"
#include "core/loader/deconstructed_rom_directory.h" #include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/kip.h" #include "core/loader/kip.h"
@ -37,6 +41,49 @@ std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
return std::nullopt; 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 } // namespace
FileType IdentifyFile(FileSys::VirtualFile file) { FileType IdentifyFile(FileSys::VirtualFile file) {
@ -62,6 +109,27 @@ 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) { FileType GuessFromFilename(const std::string& name) {
if (name == "main") if (name == "main")
return FileType::DeconstructedRomDirectory; return FileType::DeconstructedRomDirectory;

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -46,12 +49,29 @@ enum class FileType {
}; };
/** /**
* Identifies the type of a bootable file based on the magic value in its header. * Identifies the type of a supported file/container based on its structure.
* @param file open file * @param file open file
* @return FileType of file * @return FileType of file
*/ */
FileType IdentifyFile(FileSys::VirtualFile 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 * Guess the type of a bootable file from its name
* @param name String name of bootable file * @param name String name of bootable file

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -55,19 +58,30 @@ AppLoader_NSP::~AppLoader_NSP() = default;
FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) { FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) {
const FileSys::NSP nsp(nsp_file); const FileSys::NSP nsp(nsp_file);
if (nsp.GetStatus() == ResultStatus::Success) { if (nsp.GetStatus() != ResultStatus::Success) {
return FileType::Error;
}
// Extracted Type case // Extracted Type case
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr && if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) { FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
return FileType::NSP; return FileType::NSP;
} }
// Non-Extracted Type case // Non-extracted NSPs can legitimately contain only update/DLC content.
const auto program_id = nsp.GetProgramTitleID(); // Identify the container format itself; bootability is validated by Load().
if (!nsp.IsExtractedType() && if (!nsp.GetNCAs().empty()) {
nsp.GetNCA(program_id, FileSys::ContentRecordType::Program) != nullptr && return FileType::NSP;
AppLoader_NCA::IdentifyType( }
nsp.GetNCAFile(program_id, FileSys::ContentRecordType::Program)) == FileType::NCA) {
// 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;
}
const auto& name = entry->GetName();
if (name.size() >= 4 && name.substr(name.size() - 4) == ".nca") {
return FileType::NSP; return FileType::NSP;
} }
} }

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -44,10 +47,13 @@ AppLoader_XCI::~AppLoader_XCI() = default;
FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& xci_file) { FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& xci_file) {
const FileSys::XCI xci(xci_file); const FileSys::XCI xci(xci_file);
if (xci.GetStatus() == ResultStatus::Success && if (xci.GetStatus() != ResultStatus::Success) {
xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr && return FileType::Error;
AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) == }
FileType::NCA) {
// 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) {
return FileType::XCI; return FileType::XCI;
} }

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -53,6 +56,7 @@ public:
enum class PlayReportType { enum class PlayReportType {
Old, Old,
Old2, Old2,
Old3,
New, New,
System, System,
}; };

View file

@ -425,6 +425,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
"their resolution, details and supported controllers and depending on this setting.\n" "their resolution, details and supported controllers and depending on this setting.\n"
"Setting to Handheld can help improve performance for low end systems.")); "Setting to Handheld can help improve performance for low end systems."));
INSERT(Settings, current_user, QString(), QString()); INSERT(Settings, current_user, QString(), QString());
INSERT(Settings, serial_unit, tr("Unit Serial"), QString());
INSERT(Settings, serial_battery, tr("Battery Serial"), QString());
INSERT(Settings, debug_knobs, tr("Debug knobs"), QString());
// Controls // Controls

View file

@ -14,9 +14,12 @@
#include <mutex> #include <mutex>
#include <numeric> #include <numeric>
#include <span> #include <span>
#include <ankerl/unordered_dense.h>
#include <vector> #include <vector>
#include <ankerl/unordered_dense.h>
#include <boost/container/static_vector.hpp>
#include <boost/container/small_vector.hpp>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/div_ceil.h" #include "common/div_ceil.h"
#include "common/literals.h" #include "common/literals.h"
@ -94,10 +97,10 @@ static constexpr Binding NULL_BINDING{
template <typename Buffer> template <typename Buffer>
struct HostBindings { struct HostBindings {
boost::container::small_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers; boost::container::static_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers;
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets; boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> offsets;
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes; boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> sizes;
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides; boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> strides;
u32 min_index{NUM_VERTEX_BUFFERS}; u32 min_index{NUM_VERTEX_BUFFERS};
u32 max_index{0}; u32 max_index{0};
}; };

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@ -19,12 +22,12 @@ ChannelState::ChannelState(s32 bind_id_) : bind_id{bind_id_}, initialized{} {}
void ChannelState::Init(Core::System& system, GPU& gpu, u64 program_id_) { void ChannelState::Init(Core::System& system, GPU& gpu, u64 program_id_) {
ASSERT(memory_manager); ASSERT(memory_manager);
program_id = program_id_; program_id = program_id_;
dma_pusher = std::make_unique<Tegra::DmaPusher>(system, gpu, *memory_manager, *this); dma_pusher.emplace(system, gpu, *memory_manager, *this);
maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, *memory_manager); maxwell_3d.emplace(system, *memory_manager);
fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager); fermi_2d.emplace(*memory_manager);
kepler_compute = std::make_unique<Engines::KeplerCompute>(system, *memory_manager); kepler_compute.emplace(system, *memory_manager);
maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager); maxwell_dma.emplace(system, *memory_manager);
kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager); kepler_memory.emplace(system, *memory_manager);
initialized = true; initialized = true;
} }

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@ -6,6 +9,12 @@
#include <memory> #include <memory>
#include "common/common_types.h" #include "common/common_types.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/engines/kepler_memory.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/maxwell_dma.h"
#include "video_core/dma_pusher.h"
namespace Core { namespace Core {
class System; class System;
@ -18,49 +27,34 @@ class RasterizerInterface;
namespace Tegra { namespace Tegra {
class GPU; class GPU;
namespace Engines {
class Puller;
class Fermi2D;
class Maxwell3D;
class MaxwellDMA;
class KeplerCompute;
class KeplerMemory;
} // namespace Engines
class MemoryManager; class MemoryManager;
class DmaPusher;
namespace Control { namespace Control {
struct ChannelState { struct ChannelState {
explicit ChannelState(s32 bind_id); explicit ChannelState(s32 bind_id);
ChannelState(const ChannelState& state) = delete;
ChannelState& operator=(const ChannelState&) = delete;
ChannelState(ChannelState&& other) noexcept = default;
ChannelState& operator=(ChannelState&& other) noexcept = default;
void Init(Core::System& system, GPU& gpu, u64 program_id); void Init(Core::System& system, GPU& gpu, u64 program_id);
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
s32 bind_id = -1;
u64 program_id = 0;
/// 3D engine /// 3D engine
std::unique_ptr<Engines::Maxwell3D> maxwell_3d; std::optional<Engines::Maxwell3D> maxwell_3d;
/// 2D engine /// 2D engine
std::unique_ptr<Engines::Fermi2D> fermi_2d; std::optional<Engines::Fermi2D> fermi_2d;
/// Compute engine /// Compute engine
std::unique_ptr<Engines::KeplerCompute> kepler_compute; std::optional<Engines::KeplerCompute> kepler_compute;
/// DMA engine /// DMA engine
std::unique_ptr<Engines::MaxwellDMA> maxwell_dma; std::optional<Engines::MaxwellDMA> maxwell_dma;
/// Inline memory engine /// Inline memory engine
std::unique_ptr<Engines::KeplerMemory> kepler_memory; std::optional<Engines::KeplerMemory> kepler_memory;
/// NV01 Timer
std::optional<Engines::KeplerMemory> nv01_timer;
std::optional<DmaPusher> dma_pusher;
std::shared_ptr<MemoryManager> memory_manager; std::shared_ptr<MemoryManager> memory_manager;
std::unique_ptr<DmaPusher> dma_pusher; s32 bind_id = -1;
u64 program_id = 0;
bool initialized{}; bool initialized{};
}; };

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@ -15,6 +15,7 @@
namespace Tegra::Engines { namespace Tegra::Engines {
enum class EngineTypes : u32 { enum class EngineTypes : u32 {
Nv01Timer,
KeplerCompute, KeplerCompute,
Maxwell3D, Maxwell3D,
Fermi2D, Fermi2D,

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@ -26,8 +26,15 @@ namespace Tegra::Engines {
constexpr u32 MacroRegistersStart = 0xE00; constexpr u32 MacroRegistersStart = 0xE00;
Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_) Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_)
: draw_manager{std::make_unique<DrawManager>(this)}, system{system_}, : draw_manager{std::make_unique<DrawManager>(this)}, system{system_}
memory_manager{memory_manager_}, macro_engine{GetMacroEngine(*this)}, upload_state{memory_manager, regs.upload} { , memory_manager{memory_manager_}
#ifdef ARCHITECTURE_x86_64
, macro_engine(bool(Settings::values.disable_macro_jit))
#else
, macro_engine(true)
#endif
, upload_state{memory_manager, regs.upload}
{
dirty.flags.flip(); dirty.flags.flip();
InitializeRegisterDefaults(); InitializeRegisterDefaults();
execution_mask.reset(); execution_mask.reset();
@ -328,9 +335,9 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
shadow_state.shadow_ram_control = static_cast<Regs::ShadowRamControl>(nonshadow_argument); shadow_state.shadow_ram_control = static_cast<Regs::ShadowRamControl>(nonshadow_argument);
return; return;
case MAXWELL3D_REG_INDEX(load_mme.instruction_ptr): case MAXWELL3D_REG_INDEX(load_mme.instruction_ptr):
return macro_engine->ClearCode(regs.load_mme.instruction_ptr); return macro_engine.ClearCode(regs.load_mme.instruction_ptr);
case MAXWELL3D_REG_INDEX(load_mme.instruction): case MAXWELL3D_REG_INDEX(load_mme.instruction):
return macro_engine->AddCode(regs.load_mme.instruction_ptr, argument); return macro_engine.AddCode(regs.load_mme.instruction_ptr, argument);
case MAXWELL3D_REG_INDEX(load_mme.start_address): case MAXWELL3D_REG_INDEX(load_mme.start_address):
return ProcessMacroBind(argument); return ProcessMacroBind(argument);
case MAXWELL3D_REG_INDEX(falcon[4]): case MAXWELL3D_REG_INDEX(falcon[4]):
@ -398,7 +405,7 @@ void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters)
((method - MacroRegistersStart) >> 1) % static_cast<u32>(macro_positions.size()); ((method - MacroRegistersStart) >> 1) % static_cast<u32>(macro_positions.size());
// Execute the current macro. // Execute the current macro.
macro_engine->Execute(macro_positions[entry], parameters); macro_engine.Execute(*this, macro_positions[entry], parameters);
draw_manager->DrawDeferred(); draw_manager->DrawDeferred();
} }
@ -464,7 +471,7 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
} }
void Maxwell3D::ProcessMacroUpload(u32 data) { void Maxwell3D::ProcessMacroUpload(u32 data) {
macro_engine->AddCode(regs.load_mme.instruction_ptr++, data); macro_engine.AddCode(regs.load_mme.instruction_ptr++, data);
} }
void Maxwell3D::ProcessMacroBind(u32 data) { void Maxwell3D::ProcessMacroBind(u32 data) {

View file

@ -2258,7 +2258,7 @@ public:
/// Returns whether the vertex array specified by index is supposed to be /// Returns whether the vertex array specified by index is supposed to be
/// accessed per instance or not. /// accessed per instance or not.
bool IsInstancingEnabled(std::size_t index) const { bool IsInstancingEnabled(std::size_t index) const {
return is_instanced[index]; return bool(is_instanced[index]); //FUCK YOU MSVC
} }
}; };
@ -3203,7 +3203,7 @@ private:
std::vector<u32> macro_params; std::vector<u32> macro_params;
/// Interpreter for the macro codes uploaded to the GPU. /// Interpreter for the macro codes uploaded to the GPU.
std::optional<MacroEngine> macro_engine; MacroEngine macro_engine;
Upload::State upload_state; Upload::State upload_state;

View file

@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "video_core/engines/engine_interface.h"
#include "video_core/engines/engine_upload.h"
namespace Core {
class System;
}
namespace Tegra {
class MemoryManager;
}
namespace Tegra::Engines {
class Nv01Timer final : public EngineInterface {
public:
explicit Nv01Timer(Core::System& system_, MemoryManager& memory_manager)
: system{system_}
{}
~Nv01Timer() override;
/// Write the value to the register identified by method.
void CallMethod(u32 method, u32 method_argument, bool is_last_call) override {
LOG_DEBUG(HW_GPU, "method={}, argument={}, is_last_call={}", method, method_argument, is_last_call);
}
/// Write multiple values to the register identified by method.
void CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) override {
LOG_DEBUG(HW_GPU, "method={}, base_start={}, amount={}, pending={}", method, fmt::ptr(base_start), amount, methods_pending);
}
struct Regs {
// No fucking idea
INSERT_PADDING_BYTES_NOINIT(0x48);
} regs{};
private:
void ConsumeSinkImpl() override {}
Core::System& system;
};
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@ -34,24 +37,22 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) {
bound_engines[method_call.subchannel] = engine_id; bound_engines[method_call.subchannel] = engine_id;
switch (engine_id) { switch (engine_id) {
case EngineID::FERMI_TWOD_A: case EngineID::FERMI_TWOD_A:
dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel, dma_pusher.BindSubchannel(&*channel_state.fermi_2d, method_call.subchannel, EngineTypes::Fermi2D);
EngineTypes::Fermi2D);
break; break;
case EngineID::MAXWELL_B: case EngineID::MAXWELL_B:
dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel, dma_pusher.BindSubchannel(&*channel_state.maxwell_3d, method_call.subchannel, EngineTypes::Maxwell3D);
EngineTypes::Maxwell3D);
break; break;
case EngineID::KEPLER_COMPUTE_B: case EngineID::KEPLER_COMPUTE_B:
dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel, dma_pusher.BindSubchannel(&*channel_state.kepler_compute, method_call.subchannel, EngineTypes::KeplerCompute);
EngineTypes::KeplerCompute);
break; break;
case EngineID::MAXWELL_DMA_COPY_A: case EngineID::MAXWELL_DMA_COPY_A:
dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel, dma_pusher.BindSubchannel(&*channel_state.maxwell_dma, method_call.subchannel, EngineTypes::MaxwellDMA);
EngineTypes::MaxwellDMA);
break; break;
case EngineID::KEPLER_INLINE_TO_MEMORY_B: case EngineID::KEPLER_INLINE_TO_MEMORY_B:
dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel, dma_pusher.BindSubchannel(&*channel_state.kepler_memory, method_call.subchannel, EngineTypes::KeplerMemory);
EngineTypes::KeplerMemory); break;
case EngineID::NV01_TIMER:
dma_pusher.BindSubchannel(&*channel_state.nv01_timer, method_call.subchannel, EngineTypes::Nv01Timer);
break; break;
default: default:
UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
@ -209,24 +210,22 @@ void Puller::CallEngineMethod(const MethodCall& method_call) {
switch (engine) { switch (engine) {
case EngineID::FERMI_TWOD_A: case EngineID::FERMI_TWOD_A:
channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument, channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
method_call.IsLastCall());
break; break;
case EngineID::MAXWELL_B: case EngineID::MAXWELL_B:
channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument, channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
method_call.IsLastCall());
break; break;
case EngineID::KEPLER_COMPUTE_B: case EngineID::KEPLER_COMPUTE_B:
channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument, channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
method_call.IsLastCall());
break; break;
case EngineID::MAXWELL_DMA_COPY_A: case EngineID::MAXWELL_DMA_COPY_A:
channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument, channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
method_call.IsLastCall());
break; break;
case EngineID::KEPLER_INLINE_TO_MEMORY_B: case EngineID::KEPLER_INLINE_TO_MEMORY_B:
channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument, channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
method_call.IsLastCall()); break;
case EngineID::NV01_TIMER:
channel_state.nv01_timer->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
break; break;
default: default:
UNIMPLEMENTED_MSG("Unimplemented engine"); UNIMPLEMENTED_MSG("Unimplemented engine");
@ -255,6 +254,9 @@ void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_s
case EngineID::KEPLER_INLINE_TO_MEMORY_B: case EngineID::KEPLER_INLINE_TO_MEMORY_B:
channel_state.kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending); channel_state.kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending);
break; break;
case EngineID::NV01_TIMER:
channel_state.nv01_timer->CallMultiMethod(method, base_start, amount, methods_pending);
break;
default: default:
UNIMPLEMENTED_MSG("Unimplemented engine"); UNIMPLEMENTED_MSG("Unimplemented engine");
break; break;

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@ -20,6 +23,7 @@ class MemoryManager;
class DmaPusher; class DmaPusher;
enum class EngineID { enum class EngineID {
NV01_TIMER = 0x0004,
FERMI_TWOD_A = 0x902D, // 2D Engine FERMI_TWOD_A = 0x902D, // 2D Engine
MAXWELL_B = 0xB197, // 3D Engine MAXWELL_B = 0xB197, // 3D Engine
KEPLER_COMPUTE_B = 0xB1C0, KEPLER_COMPUTE_B = 0xB1C0,

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@ -10,6 +10,7 @@
#include <span> #include <span>
#include <fstream> #include <fstream>
#include <variant>
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
// xbyak hates human beings // xbyak hates human beings
#ifdef __GNUC__ #ifdef __GNUC__
@ -73,26 +74,12 @@ bool IsTopologySafe(Maxwell3D::Regs::PrimitiveTopology topology) {
} }
} }
class HLEMacroImpl : public CachedMacro { } // Anonymous namespace
public:
explicit HLEMacroImpl(Maxwell3D& maxwell3d_)
: CachedMacro(maxwell3d_)
{}
};
/// @note: these macros have two versions, a normal and extended version, with the extended version void HLE_DrawArraysIndirect::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
/// also assigning the base vertex/instance.
template <bool extended>
class HLE_DrawArraysIndirect final : public HLEMacroImpl {
public:
explicit HLE_DrawArraysIndirect(Maxwell3D& maxwell3d_)
: HLEMacroImpl(maxwell3d_)
{}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0]); auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0]);
if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) {
Fallback(parameters); Fallback(maxwell3d, parameters);
return; return;
} }
@ -118,9 +105,7 @@ public:
maxwell3d.replace_table.clear(); maxwell3d.replace_table.clear();
} }
} }
void HLE_DrawArraysIndirect::Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters) {
private:
void Fallback(const std::vector<u32>& parameters) {
SCOPE_EXIT { SCOPE_EXIT {
if (extended) { if (extended) {
maxwell3d.engine_state = Maxwell3D::EngineHint::None; maxwell3d.engine_state = Maxwell3D::EngineHint::None;
@ -129,52 +114,35 @@ private:
}; };
maxwell3d.RefreshParameters(); maxwell3d.RefreshParameters();
const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]);
auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[0]);
auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0]);
const u32 vertex_first = parameters[3]; const u32 vertex_first = parameters[3];
const u32 vertex_count = parameters[1]; const u32 vertex_count = parameters[1];
if (!IsTopologySafe(topology) && size_t(maxwell3d.GetMaxCurrentVertices()) < size_t(vertex_first) + size_t(vertex_count)) { if (!IsTopologySafe(topology) && size_t(maxwell3d.GetMaxCurrentVertices()) < size_t(vertex_first) + size_t(vertex_count)) {
ASSERT(false && "Faulty draw!"); ASSERT(false && "Faulty draw!");
return; return;
} }
const u32 base_instance = parameters[4]; const u32 base_instance = parameters[4];
if (extended) { if (extended) {
maxwell3d.regs.global_base_instance_index = base_instance; maxwell3d.regs.global_base_instance_index = base_instance;
maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro;
maxwell3d.SetHLEReplacementAttributeType( maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance);
0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance);
} }
maxwell3d.draw_manager->DrawArray(topology, vertex_first, vertex_count, base_instance, instance_count);
maxwell3d.draw_manager->DrawArray(topology, vertex_first, vertex_count, base_instance,
instance_count);
if (extended) { if (extended) {
maxwell3d.regs.global_base_instance_index = 0; maxwell3d.regs.global_base_instance_index = 0;
maxwell3d.engine_state = Maxwell3D::EngineHint::None; maxwell3d.engine_state = Maxwell3D::EngineHint::None;
maxwell3d.replace_table.clear(); maxwell3d.replace_table.clear();
} }
} }
};
/* void HLE_DrawIndexedIndirect::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
* @note: these macros have two versions, a normal and extended version, with the extended version
* also assigning the base vertex/instance.
*/
template <bool extended>
class HLE_DrawIndexedIndirect final : public HLEMacroImpl {
public:
explicit HLE_DrawIndexedIndirect(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0]); auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0]);
if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) {
Fallback(parameters); Fallback(maxwell3d, parameters);
return; return;
} }
const u32 estimate = static_cast<u32>(maxwell3d.EstimateIndexBufferSize()); const u32 estimate = u32(maxwell3d.EstimateIndexBufferSize());
const u32 element_base = parameters[4]; const u32 element_base = parameters[4];
const u32 base_instance = parameters[5]; const u32 base_instance = parameters[5];
maxwell3d.regs.vertex_id_base = element_base; maxwell3d.regs.vertex_id_base = element_base;
@ -205,9 +173,7 @@ public:
maxwell3d.replace_table.clear(); maxwell3d.replace_table.clear();
} }
} }
void HLE_DrawIndexedIndirect::Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters) {
private:
void Fallback(const std::vector<u32>& parameters) {
maxwell3d.RefreshParameters(); maxwell3d.RefreshParameters();
const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]);
const u32 element_base = parameters[4]; const u32 element_base = parameters[4];
@ -221,9 +187,7 @@ private:
maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex);
maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance);
} }
maxwell3d.draw_manager->DrawIndex(Tegra::Maxwell3D::Regs::PrimitiveTopology(parameters[0]), parameters[3], parameters[1], element_base, base_instance, instance_count); maxwell3d.draw_manager->DrawIndex(Tegra::Maxwell3D::Regs::PrimitiveTopology(parameters[0]), parameters[3], parameters[1], element_base, base_instance, instance_count);
maxwell3d.regs.vertex_id_base = 0x0; maxwell3d.regs.vertex_id_base = 0x0;
maxwell3d.regs.global_base_vertex_index = 0x0; maxwell3d.regs.global_base_vertex_index = 0x0;
maxwell3d.regs.global_base_instance_index = 0x0; maxwell3d.regs.global_base_instance_index = 0x0;
@ -232,13 +196,7 @@ private:
maxwell3d.replace_table.clear(); maxwell3d.replace_table.clear();
} }
} }
}; void HLE_MultiLayerClear::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
class HLE_MultiLayerClear final : public HLEMacroImpl {
public:
explicit HLE_MultiLayerClear(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
maxwell3d.RefreshParameters(); maxwell3d.RefreshParameters();
ASSERT(parameters.size() == 1); ASSERT(parameters.size() == 1);
@ -250,16 +208,10 @@ public:
maxwell3d.regs.clear_surface.raw = clear_params.raw; maxwell3d.regs.clear_surface.raw = clear_params.raw;
maxwell3d.draw_manager->Clear(num_layers); maxwell3d.draw_manager->Clear(num_layers);
} }
}; void HLE_MultiDrawIndexedIndirectCount::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
class HLE_MultiDrawIndexedIndirectCount final : public HLEMacroImpl {
public:
explicit HLE_MultiDrawIndexedIndirectCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
const auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[2]); const auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[2]);
if (!IsTopologySafe(topology)) { if (!IsTopologySafe(topology)) {
Fallback(parameters); Fallback(maxwell3d, parameters);
return; return;
} }
@ -289,19 +241,14 @@ public:
params.stride = stride; params.stride = stride;
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro;
maxwell3d.SetHLEReplacementAttributeType( maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex);
0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance);
maxwell3d.SetHLEReplacementAttributeType( maxwell3d.SetHLEReplacementAttributeType(0, 0x648, Maxwell3D::HLEReplacementAttributeType::DrawID);
0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance);
maxwell3d.SetHLEReplacementAttributeType(0, 0x648,
Maxwell3D::HLEReplacementAttributeType::DrawID);
maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate); maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate);
maxwell3d.engine_state = Maxwell3D::EngineHint::None; maxwell3d.engine_state = Maxwell3D::EngineHint::None;
maxwell3d.replace_table.clear(); maxwell3d.replace_table.clear();
} }
void HLE_MultiDrawIndexedIndirectCount::Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters) {
private:
void Fallback(const std::vector<u32>& parameters) {
SCOPE_EXIT { SCOPE_EXIT {
// Clean everything. // Clean everything.
maxwell3d.regs.vertex_id_base = 0x0; maxwell3d.regs.vertex_id_base = 0x0;
@ -318,41 +265,29 @@ private:
const auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[2]); const auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[2]);
const u32 padding = parameters[3]; const u32 padding = parameters[3];
const std::size_t max_draws = parameters[4]; const std::size_t max_draws = parameters[4];
const u32 indirect_words = 5 + padding; const u32 indirect_words = 5 + padding;
const std::size_t first_draw = start_indirect; const std::size_t first_draw = start_indirect;
const std::size_t effective_draws = end_indirect - start_indirect; const std::size_t effective_draws = end_indirect - start_indirect;
const std::size_t last_draw = start_indirect + (std::min)(effective_draws, max_draws); const std::size_t last_draw = start_indirect + (std::min)(effective_draws, max_draws);
for (std::size_t index = first_draw; index < last_draw; index++) { for (std::size_t index = first_draw; index < last_draw; index++) {
const std::size_t base = index * indirect_words + 5; const std::size_t base = index * indirect_words + 5;
const u32 base_vertex = parameters[base + 3]; const u32 base_vertex = parameters[base + 3];
const u32 base_instance = parameters[base + 4]; const u32 base_instance = parameters[base + 4];
maxwell3d.regs.vertex_id_base = base_vertex; maxwell3d.regs.vertex_id_base = base_vertex;
maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro;
maxwell3d.SetHLEReplacementAttributeType( maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex);
0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance);
maxwell3d.SetHLEReplacementAttributeType(
0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance);
maxwell3d.CallMethod(0x8e3, 0x648, true); maxwell3d.CallMethod(0x8e3, 0x648, true);
maxwell3d.CallMethod(0x8e4, static_cast<u32>(index), true); maxwell3d.CallMethod(0x8e4, static_cast<u32>(index), true);
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
maxwell3d.draw_manager->DrawIndex(topology, parameters[base + 2], parameters[base], maxwell3d.draw_manager->DrawIndex(topology, parameters[base + 2], parameters[base], base_vertex, base_instance, parameters[base + 1]);
base_vertex, base_instance, parameters[base + 1]);
} }
} }
}; void HLE_DrawIndirectByteCount::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
class HLE_DrawIndirectByteCount final : public HLEMacroImpl {
public:
explicit HLE_DrawIndirectByteCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
const bool force = maxwell3d.Rasterizer().HasDrawTransformFeedback(); const bool force = maxwell3d.Rasterizer().HasDrawTransformFeedback();
auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[0] & 0xFFFFU);
auto topology = static_cast<Maxwell3D::Regs::PrimitiveTopology>(parameters[0] & 0xFFFFU);
if (!force && (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology))) { if (!force && (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology))) {
Fallback(parameters); Fallback(maxwell3d, parameters);
return; return;
} }
auto& params = maxwell3d.draw_manager->GetIndirectParams(); auto& params = maxwell3d.draw_manager->GetIndirectParams();
@ -367,12 +302,9 @@ public:
maxwell3d.regs.draw.begin = parameters[0]; maxwell3d.regs.draw.begin = parameters[0];
maxwell3d.regs.draw_auto_stride = parameters[1]; maxwell3d.regs.draw_auto_stride = parameters[1];
maxwell3d.regs.draw_auto_byte_count = parameters[2]; maxwell3d.regs.draw_auto_byte_count = parameters[2];
maxwell3d.draw_manager->DrawArrayIndirect(topology); maxwell3d.draw_manager->DrawArrayIndirect(topology);
} }
void HLE_DrawIndirectByteCount::Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters) {
private:
void Fallback(const std::vector<u32>& parameters) {
maxwell3d.RefreshParameters(); maxwell3d.RefreshParameters();
maxwell3d.regs.draw.begin = parameters[0]; maxwell3d.regs.draw.begin = parameters[0];
@ -383,13 +315,7 @@ private:
maxwell3d.regs.draw.topology, 0, maxwell3d.regs.draw.topology, 0,
maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1); maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1);
} }
}; void HLE_C713C83D8F63CCF3::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl {
public:
explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
maxwell3d.RefreshParameters(); maxwell3d.RefreshParameters();
const u32 offset = (parameters[0] & 0x3FFFFFFF) << 2; const u32 offset = (parameters[0] & 0x3FFFFFFF) << 2;
const u32 address = maxwell3d.regs.shadow_scratch[24]; const u32 address = maxwell3d.regs.shadow_scratch[24];
@ -399,13 +325,7 @@ public:
const_buffer.address_low = address << 8; const_buffer.address_low = address << 8;
const_buffer.offset = offset; const_buffer.offset = offset;
} }
}; void HLE_D7333D26E0A93EDE::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
class HLE_D7333D26E0A93EDE final : public HLEMacroImpl {
public:
explicit HLE_D7333D26E0A93EDE(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
maxwell3d.RefreshParameters(); maxwell3d.RefreshParameters();
const size_t index = parameters[0]; const size_t index = parameters[0];
const u32 address = maxwell3d.regs.shadow_scratch[42 + index]; const u32 address = maxwell3d.regs.shadow_scratch[42 + index];
@ -415,13 +335,7 @@ public:
const_buffer.address_high = (address >> 24) & 0xFF; const_buffer.address_high = (address >> 24) & 0xFF;
const_buffer.address_low = address << 8; const_buffer.address_low = address << 8;
} }
}; void HLE_BindShader::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
class HLE_BindShader final : public HLEMacroImpl {
public:
explicit HLE_BindShader(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
maxwell3d.RefreshParameters(); maxwell3d.RefreshParameters();
auto& regs = maxwell3d.regs; auto& regs = maxwell3d.regs;
const u32 index = parameters[0]; const u32 index = parameters[0];
@ -445,13 +359,7 @@ public:
bind_group.raw_config = 0x11; bind_group.raw_config = 0x11;
maxwell3d.ProcessCBBind(bind_group_id); maxwell3d.ProcessCBBind(bind_group_id);
} }
}; void HLE_SetRasterBoundingBox::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
class HLE_SetRasterBoundingBox final : public HLEMacroImpl {
public:
explicit HLE_SetRasterBoundingBox(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
maxwell3d.RefreshParameters(); maxwell3d.RefreshParameters();
const u32 raster_mode = parameters[0]; const u32 raster_mode = parameters[0];
auto& regs = maxwell3d.regs; auto& regs = maxwell3d.regs;
@ -460,16 +368,9 @@ public:
regs.raster_bounding_box.raw = raster_mode & 0xFFFFF00F; regs.raster_bounding_box.raw = raster_mode & 0xFFFFF00F;
regs.raster_bounding_box.pad.Assign(scratch_data & raster_enabled); regs.raster_bounding_box.pad.Assign(scratch_data & raster_enabled);
} }
}; void HLE_ClearConstBuffer::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
static constexpr std::array<u32, 0x7000> zeroes{}; //must be bigger than either 7000 or 5F00
template <size_t base_size>
class HLE_ClearConstBuffer final : public HLEMacroImpl {
public:
explicit HLE_ClearConstBuffer(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
maxwell3d.RefreshParameters(); maxwell3d.RefreshParameters();
static constexpr std::array<u32, base_size> zeroes{};
auto& regs = maxwell3d.regs; auto& regs = maxwell3d.regs;
regs.const_buffer.size = u32(base_size); regs.const_buffer.size = u32(base_size);
regs.const_buffer.address_high = parameters[0]; regs.const_buffer.address_high = parameters[0];
@ -477,15 +378,8 @@ public:
regs.const_buffer.offset = 0; regs.const_buffer.offset = 0;
maxwell3d.ProcessCBMultiData(zeroes.data(), parameters[2] * 4); maxwell3d.ProcessCBMultiData(zeroes.data(), parameters[2] * 4);
} }
}; void HLE_ClearMemory::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
class HLE_ClearMemory final : public HLEMacroImpl {
public:
explicit HLE_ClearMemory(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
maxwell3d.RefreshParameters(); maxwell3d.RefreshParameters();
const u32 needed_memory = parameters[2] / sizeof(u32); const u32 needed_memory = parameters[2] / sizeof(u32);
if (needed_memory > zero_memory.size()) { if (needed_memory > zero_memory.size()) {
zero_memory.resize(needed_memory, 0); zero_memory.resize(needed_memory, 0);
@ -498,176 +392,93 @@ public:
maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true);
maxwell3d.CallMultiMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), zero_memory.data(), needed_memory, needed_memory); maxwell3d.CallMultiMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), zero_memory.data(), needed_memory, needed_memory);
} }
void HLE_TransformFeedbackSetup::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method) {
private:
std::vector<u32> zero_memory;
};
class HLE_TransformFeedbackSetup final : public HLEMacroImpl {
public:
explicit HLE_TransformFeedbackSetup(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {}
void Execute(const std::vector<u32>& parameters, [[maybe_unused]] u32 method) override {
maxwell3d.RefreshParameters(); maxwell3d.RefreshParameters();
auto& regs = maxwell3d.regs; auto& regs = maxwell3d.regs;
regs.transform_feedback_enabled = 1; regs.transform_feedback_enabled = 1;
regs.transform_feedback.buffers[0].start_offset = 0; regs.transform_feedback.buffers[0].start_offset = 0;
regs.transform_feedback.buffers[1].start_offset = 0; regs.transform_feedback.buffers[1].start_offset = 0;
regs.transform_feedback.buffers[2].start_offset = 0; regs.transform_feedback.buffers[2].start_offset = 0;
regs.transform_feedback.buffers[3].start_offset = 0; regs.transform_feedback.buffers[3].start_offset = 0;
regs.upload.line_length_in = 4; regs.upload.line_length_in = 4;
regs.upload.line_count = 1; regs.upload.line_count = 1;
regs.upload.dest.address_high = parameters[0]; regs.upload.dest.address_high = parameters[0];
regs.upload.dest.address_low = parameters[1]; regs.upload.dest.address_low = parameters[1];
maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true);
maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), regs.transform_feedback.controls[0].stride, true); maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), regs.transform_feedback.controls[0].stride, true);
maxwell3d.Rasterizer().RegisterTransformFeedback(regs.upload.dest.Address()); maxwell3d.Rasterizer().RegisterTransformFeedback(regs.upload.dest.Address());
} }
};
} // Anonymous namespace #define HLE_MACRO_LIST \
HLE_MACRO_ELEM(0x0D61FC9FAAC9FCADULL, HLE_DrawArraysIndirect, (false)) \
HLE_MACRO_ELEM(0x8A4D173EB99A8603ULL, HLE_DrawArraysIndirect, (true)) \
HLE_MACRO_ELEM(0x771BB18C62444DA0ULL, HLE_DrawIndexedIndirect, (false)) \
HLE_MACRO_ELEM(0x0217920100488FF7ULL, HLE_DrawIndexedIndirect, (true)) \
HLE_MACRO_ELEM(0x3F5E74B9C9A50164ULL, HLE_MultiDrawIndexedIndirectCount, ()) \
HLE_MACRO_ELEM(0xEAD26C3E2109B06BULL, HLE_MultiLayerClear, ()) \
HLE_MACRO_ELEM(0xC713C83D8F63CCF3ULL, HLE_C713C83D8F63CCF3, ()) \
HLE_MACRO_ELEM(0xD7333D26E0A93EDEULL, HLE_D7333D26E0A93EDE, ()) \
HLE_MACRO_ELEM(0xEB29B2A09AA06D38ULL, HLE_BindShader, ()) \
HLE_MACRO_ELEM(0xDB1341DBEB4C8AF7ULL, HLE_SetRasterBoundingBox, ()) \
HLE_MACRO_ELEM(0x6C97861D891EDf7EULL, HLE_ClearConstBuffer, (0x5F00)) \
HLE_MACRO_ELEM(0xD246FDDF3A6173D7ULL, HLE_ClearConstBuffer, (0x7000)) \
HLE_MACRO_ELEM(0xEE4D0004BEC8ECF4ULL, HLE_ClearMemory, ()) \
HLE_MACRO_ELEM(0xFC0CF27F5FFAA661ULL, HLE_TransformFeedbackSetup, ()) \
HLE_MACRO_ELEM(0xB5F74EDB717278ECULL, HLE_DrawIndirectByteCount, ()) \
HLEMacro::HLEMacro(Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {} // Allocates and returns a cached macro if the hash matches a known function.
[[nodiscard]] inline AnyCachedMacro GetHLEProgram(u64 hash) noexcept {
HLEMacro::~HLEMacro() = default;
std::unique_ptr<CachedMacro> HLEMacro::GetHLEProgram(u64 hash) const {
// Compiler will make you a GREAT job at making an ad-hoc hash table :) // Compiler will make you a GREAT job at making an ad-hoc hash table :)
switch (hash) { switch (hash) {
case 0x0D61FC9FAAC9FCADULL: return std::make_unique<HLE_DrawArraysIndirect<false>>(maxwell3d); #define HLE_MACRO_ELEM(HASH, TY, VAL) case HASH: return TY VAL;
case 0x8A4D173EB99A8603ULL: return std::make_unique<HLE_DrawArraysIndirect<true>>(maxwell3d); HLE_MACRO_LIST
case 0x771BB18C62444DA0ULL: return std::make_unique<HLE_DrawIndexedIndirect<false>>(maxwell3d); #undef HLE_MACRO_ELEM
case 0x0217920100488FF7ULL: return std::make_unique<HLE_DrawIndexedIndirect<true>>(maxwell3d); default: return std::monostate{};
case 0x3F5E74B9C9A50164ULL: return std::make_unique<HLE_MultiDrawIndexedIndirectCount>(maxwell3d); }
case 0xEAD26C3E2109B06BULL: return std::make_unique<HLE_MultiLayerClear>(maxwell3d); }
case 0xC713C83D8F63CCF3ULL: return std::make_unique<HLE_C713C83D8F63CCF3>(maxwell3d); [[nodiscard]] inline bool CanBeHLEProgram(u64 hash) noexcept {
case 0xD7333D26E0A93EDEULL: return std::make_unique<HLE_D7333D26E0A93EDE>(maxwell3d); switch (hash) {
case 0xEB29B2A09AA06D38ULL: return std::make_unique<HLE_BindShader>(maxwell3d); #define HLE_MACRO_ELEM(HASH, TY, VAL) case HASH: return true;
case 0xDB1341DBEB4C8AF7ULL: return std::make_unique<HLE_SetRasterBoundingBox>(maxwell3d); HLE_MACRO_LIST
case 0x6C97861D891EDf7EULL: return std::make_unique<HLE_ClearConstBuffer<0x5F00>>(maxwell3d); #undef HLE_MACRO_ELEM
case 0xD246FDDF3A6173D7ULL: return std::make_unique<HLE_ClearConstBuffer<0x7000>>(maxwell3d); default: return false;
case 0xEE4D0004BEC8ECF4ULL: return std::make_unique<HLE_ClearMemory>(maxwell3d);
case 0xFC0CF27F5FFAA661ULL: return std::make_unique<HLE_TransformFeedbackSetup>(maxwell3d);
case 0xB5F74EDB717278ECULL: return std::make_unique<HLE_DrawIndirectByteCount>(maxwell3d);
default:
return nullptr;
} }
} }
namespace { void MacroInterpreterImpl::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> params, u32 method) {
class MacroInterpreterImpl final : public CachedMacro {
public:
explicit MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d_, const std::vector<u32>& code_)
: CachedMacro(maxwell3d_)
, code{code_}
{}
void Execute(const std::vector<u32>& params, u32 method) override;
private:
/// Resets the execution engine state, zeroing registers, etc.
void Reset();
/**
* Executes a single macro instruction located at the current program counter. Returns whether
* the interpreter should keep running.
*
* @param is_delay_slot Whether the current step is being executed due to a delay slot in a
* previous instruction.
*/
bool Step(bool is_delay_slot);
/// Calculates the result of an ALU operation. src_a OP src_b;
u32 GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b);
/// Performs the result operation on the input result and stores it in the specified register
/// (if necessary).
void ProcessResult(Macro::ResultOperation operation, u32 reg, u32 result);
/// Evaluates the branch condition and returns whether the branch should be taken or not.
bool EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const;
/// Reads an opcode at the current program counter location.
Macro::Opcode GetOpcode() const;
/// Returns the specified register's value. Register 0 is hardcoded to always return 0.
u32 GetRegister(u32 register_id) const;
/// Sets the register to the input value.
void SetRegister(u32 register_id, u32 value);
/// Sets the method address to use for the next Send instruction.
void SetMethodAddress(u32 address);
/// Calls a GPU Engine method with the input parameter.
void Send(u32 value);
/// Reads a GPU register located at the method address.
u32 Read(u32 method) const;
/// Returns the next parameter in the parameter queue.
u32 FetchParameter();
/// Current program counter
u32 pc{};
/// Program counter to execute at after the delay slot is executed.
std::optional<u32> delayed_pc;
/// General purpose macro registers.
std::array<u32, Macro::NUM_MACRO_REGISTERS> registers = {};
/// Method address to use for the next Send instruction.
Macro::MethodAddress method_address = {};
/// Input parameters of the current macro.
std::unique_ptr<u32[]> parameters;
std::size_t num_parameters = 0;
std::size_t parameters_capacity = 0;
/// Index of the next parameter that will be fetched by the 'parm' instruction.
u32 next_parameter_index = 0;
bool carry_flag = false;
const std::vector<u32>& code;
};
void MacroInterpreterImpl::Execute(const std::vector<u32>& params, u32 method) {
Reset(); Reset();
registers[1] = params[0]; registers[1] = params[0];
num_parameters = params.size(); parameters.resize(params.size());
std::memcpy(parameters.data(), params.data(), params.size() * sizeof(u32));
if (num_parameters > parameters_capacity) {
parameters_capacity = num_parameters;
parameters = std::make_unique<u32[]>(num_parameters);
}
std::memcpy(parameters.get(), params.data(), num_parameters * sizeof(u32));
// Execute the code until we hit an exit condition. // Execute the code until we hit an exit condition.
bool keep_executing = true; bool keep_executing = true;
while (keep_executing) { while (keep_executing) {
keep_executing = Step(false); keep_executing = Step(maxwell3d, false);
} }
// Assert the the macro used all the input parameters // Assert the the macro used all the input parameters
ASSERT(next_parameter_index == num_parameters); ASSERT(next_parameter_index == parameters.size());
} }
/// Resets the execution engine state, zeroing registers, etc.
void MacroInterpreterImpl::Reset() { void MacroInterpreterImpl::Reset() {
registers = {}; registers = {};
pc = 0; pc = 0;
delayed_pc = {}; delayed_pc = {};
method_address.raw = 0; method_address.raw = 0;
num_parameters = 0; // Vector must hold its last indices otherwise wonky shit will happen
// The next parameter index starts at 1, because $r1 already has the value of the first // The next parameter index starts at 1, because $r1 already has the value of the first
// parameter. // parameter.
next_parameter_index = 1; next_parameter_index = 1;
carry_flag = false; carry_flag = false;
} }
bool MacroInterpreterImpl::Step(bool is_delay_slot) { /// @brief Executes a single macro instruction located at the current program counter. Returns whether
/// the interpreter should keep running.
/// @param is_delay_slot Whether the current step is being executed due to a delay slot in a previous instruction.
bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slot) {
u32 base_address = pc; u32 base_address = pc;
Macro::Opcode opcode = GetOpcode(); Macro::Opcode opcode = GetOpcode();
@ -682,14 +493,12 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) {
switch (opcode.operation) { switch (opcode.operation) {
case Macro::Operation::ALU: { case Macro::Operation::ALU: {
u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a), u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a), GetRegister(opcode.src_b));
GetRegister(opcode.src_b)); ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result);
ProcessResult(opcode.result_operation, opcode.dst, result);
break; break;
} }
case Macro::Operation::AddImmediate: { case Macro::Operation::AddImmediate: {
ProcessResult(opcode.result_operation, opcode.dst, ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, GetRegister(opcode.src_a) + opcode.immediate);
GetRegister(opcode.src_a) + opcode.immediate);
break; break;
} }
case Macro::Operation::ExtractInsert: { case Macro::Operation::ExtractInsert: {
@ -699,7 +508,7 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) {
src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask(); src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask();
dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit); dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit);
dst |= src << opcode.bf_dst_bit; dst |= src << opcode.bf_dst_bit;
ProcessResult(opcode.result_operation, opcode.dst, dst); ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, dst);
break; break;
} }
case Macro::Operation::ExtractShiftLeftImmediate: { case Macro::Operation::ExtractShiftLeftImmediate: {
@ -708,7 +517,7 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) {
u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit; u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit;
ProcessResult(opcode.result_operation, opcode.dst, result); ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result);
break; break;
} }
case Macro::Operation::ExtractShiftLeftRegister: { case Macro::Operation::ExtractShiftLeftRegister: {
@ -717,12 +526,12 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) {
u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst; u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst;
ProcessResult(opcode.result_operation, opcode.dst, result); ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result);
break; break;
} }
case Macro::Operation::Read: { case Macro::Operation::Read: {
u32 result = Read(GetRegister(opcode.src_a) + opcode.immediate); u32 result = Read(maxwell3d, GetRegister(opcode.src_a) + opcode.immediate);
ProcessResult(opcode.result_operation, opcode.dst, result); ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result);
break; break;
} }
case Macro::Operation::Branch: { case Macro::Operation::Branch: {
@ -738,7 +547,7 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) {
delayed_pc = base_address + opcode.GetBranchTarget(); delayed_pc = base_address + opcode.GetBranchTarget();
// Execute one more instruction due to the delay slot. // Execute one more instruction due to the delay slot.
return Step(true); return Step(maxwell3d, true);
} }
break; break;
} }
@ -751,13 +560,13 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) {
// cause an exit if it's executed inside a delay slot. // cause an exit if it's executed inside a delay slot.
if (opcode.is_exit && !is_delay_slot) { if (opcode.is_exit && !is_delay_slot) {
// Exit has a delay slot, execute the next instruction // Exit has a delay slot, execute the next instruction
Step(true); Step(maxwell3d, true);
return false; return false;
} }
return true; return true;
} }
/// Calculates the result of an ALU operation. src_a OP src_b;
u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b) { u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b) {
switch (operation) { switch (operation) {
case Macro::ALUOperation::Add: { case Macro::ALUOperation::Add: {
@ -797,7 +606,8 @@ u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a,
} }
} }
void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 reg, u32 result) { /// Performs the result operation on the input result and stores it in the specified register (if necessary).
void MacroInterpreterImpl::ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result) {
switch (operation) { switch (operation) {
case Macro::ResultOperation::IgnoreAndFetch: case Macro::ResultOperation::IgnoreAndFetch:
// Fetch parameter and ignore result. // Fetch parameter and ignore result.
@ -815,12 +625,12 @@ void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 r
case Macro::ResultOperation::FetchAndSend: case Macro::ResultOperation::FetchAndSend:
// Fetch parameter and send result. // Fetch parameter and send result.
SetRegister(reg, FetchParameter()); SetRegister(reg, FetchParameter());
Send(result); Send(maxwell3d, result);
break; break;
case Macro::ResultOperation::MoveAndSend: case Macro::ResultOperation::MoveAndSend:
// Move and send result. // Move and send result.
SetRegister(reg, result); SetRegister(reg, result);
Send(result); Send(maxwell3d, result);
break; break;
case Macro::ResultOperation::FetchAndSetMethod: case Macro::ResultOperation::FetchAndSetMethod:
// Fetch parameter and use result as Method Address. // Fetch parameter and use result as Method Address.
@ -831,13 +641,13 @@ void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 r
// Move result and use as Method Address, then fetch and send parameter. // Move result and use as Method Address, then fetch and send parameter.
SetRegister(reg, result); SetRegister(reg, result);
SetMethodAddress(result); SetMethodAddress(result);
Send(FetchParameter()); Send(maxwell3d, FetchParameter());
break; break;
case Macro::ResultOperation::MoveAndSetMethodSend: case Macro::ResultOperation::MoveAndSetMethodSend:
// Move result and use as Method Address, then send bits 12:17 of result. // Move result and use as Method Address, then send bits 12:17 of result.
SetRegister(reg, result); SetRegister(reg, result);
SetMethodAddress(result); SetMethodAddress(result);
Send((result >> 12) & 0b111111); Send(maxwell3d, (result >> 12) & 0b111111);
break; break;
default: default:
UNIMPLEMENTED_MSG("Unimplemented result operation {}", operation); UNIMPLEMENTED_MSG("Unimplemented result operation {}", operation);
@ -845,6 +655,7 @@ void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 r
} }
} }
/// Evaluates the branch condition and returns whether the branch should be taken or not.
bool MacroInterpreterImpl::EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const { bool MacroInterpreterImpl::EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const {
switch (cond) { switch (cond) {
case Macro::BranchCondition::Zero: case Macro::BranchCondition::Zero:
@ -855,46 +666,44 @@ bool MacroInterpreterImpl::EvaluateBranchCondition(Macro::BranchCondition cond,
UNREACHABLE(); UNREACHABLE();
} }
/// Reads an opcode at the current program counter location.
Macro::Opcode MacroInterpreterImpl::GetOpcode() const { Macro::Opcode MacroInterpreterImpl::GetOpcode() const {
ASSERT((pc % sizeof(u32)) == 0); ASSERT((pc % sizeof(u32)) == 0);
ASSERT(pc < code.size() * sizeof(u32)); ASSERT(pc < code.size() * sizeof(u32));
return {code[pc / sizeof(u32)]}; return {code[pc / sizeof(u32)]};
} }
/// Returns the specified register's value. Register 0 is hardcoded to always return 0.
u32 MacroInterpreterImpl::GetRegister(u32 register_id) const { u32 MacroInterpreterImpl::GetRegister(u32 register_id) const {
return registers.at(register_id); return registers[register_id];
} }
/// Sets the register to the input value.
void MacroInterpreterImpl::SetRegister(u32 register_id, u32 value) { void MacroInterpreterImpl::SetRegister(u32 register_id, u32 value) {
// Register 0 is hardwired as the zero register. // Register 0 is hardwired as the zero register.
// Ensure no writes to it actually occur. // Ensure no writes to it actually occur.
if (register_id == 0) { if (register_id == 0)
return; return;
registers[register_id] = value;
} }
registers.at(register_id) = value; /// Calls a GPU Engine method with the input parameter.
} void MacroInterpreterImpl::Send(Engines::Maxwell3D& maxwell3d, u32 value) {
void MacroInterpreterImpl::SetMethodAddress(u32 address) {
method_address.raw = address;
}
void MacroInterpreterImpl::Send(u32 value) {
maxwell3d.CallMethod(method_address.address, value, true); maxwell3d.CallMethod(method_address.address, value, true);
// Increment the method address by the method increment. // Increment the method address by the method increment.
method_address.address.Assign(method_address.address.Value() + method_address.address.Assign(method_address.address.Value() + method_address.increment.Value());
method_address.increment.Value());
} }
u32 MacroInterpreterImpl::Read(u32 method) const { /// Reads a GPU register located at the method address.
u32 MacroInterpreterImpl::Read(Engines::Maxwell3D& maxwell3d, u32 method) const {
return maxwell3d.GetRegisterValue(method); return maxwell3d.GetRegisterValue(method);
} }
/// Returns the next parameter in the parameter queue.
u32 MacroInterpreterImpl::FetchParameter() { u32 MacroInterpreterImpl::FetchParameter() {
ASSERT(next_parameter_index < num_parameters); ASSERT(next_parameter_index < parameters.size());
return parameters[next_parameter_index++]; return parameters[next_parameter_index++];
} }
} // Anonymous namespace
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
namespace { namespace {
@ -930,17 +739,15 @@ static const auto default_cg_mode = Xbyak::DontSetProtectRWE;
static const auto default_cg_mode = nullptr; //Allow RWE static const auto default_cg_mode = nullptr; //Allow RWE
#endif #endif
class MacroJITx64Impl final : public Xbyak::CodeGenerator, public CachedMacro { struct MacroJITx64Impl final : public Xbyak::CodeGenerator, public DynamicCachedMacro {
public: explicit MacroJITx64Impl(std::span<const u32> code_)
explicit MacroJITx64Impl(Engines::Maxwell3D& maxwell3d_, const std::vector<u32>& code_)
: Xbyak::CodeGenerator(MAX_CODE_SIZE, default_cg_mode) : Xbyak::CodeGenerator(MAX_CODE_SIZE, default_cg_mode)
, CachedMacro(maxwell3d_)
, code{code_} , code{code_}
{ {
Compile(); Compile();
} }
void Execute(const std::vector<u32>& parameters, u32 method) override; void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, u32 method) override;
void Compile_ALU(Macro::Opcode opcode); void Compile_ALU(Macro::Opcode opcode);
void Compile_AddImmediate(Macro::Opcode opcode); void Compile_AddImmediate(Macro::Opcode opcode);
@ -950,18 +757,13 @@ public:
void Compile_Read(Macro::Opcode opcode); void Compile_Read(Macro::Opcode opcode);
void Compile_Branch(Macro::Opcode opcode); void Compile_Branch(Macro::Opcode opcode);
private:
void Optimizer_ScanFlags(); void Optimizer_ScanFlags();
void Compile(); void Compile();
bool Compile_NextInstruction(); bool Compile_NextInstruction();
Xbyak::Reg32 Compile_FetchParameter(); Xbyak::Reg32 Compile_FetchParameter();
Xbyak::Reg32 Compile_GetRegister(u32 index, Xbyak::Reg32 dst); Xbyak::Reg32 Compile_GetRegister(u32 index, Xbyak::Reg32 dst);
void Compile_ProcessResult(Macro::ResultOperation operation, u32 reg); void Compile_ProcessResult(Macro::ResultOperation operation, u32 reg);
void Compile_Send(Xbyak::Reg32 value); void Compile_Send(Xbyak::Reg32 value);
Macro::Opcode GetOpCode() const; Macro::Opcode GetOpCode() const;
struct JITState { struct JITState {
@ -981,21 +783,17 @@ private:
bool enable_asserts{}; bool enable_asserts{};
}; };
OptimizerState optimizer{}; OptimizerState optimizer{};
std::optional<Macro::Opcode> next_opcode{}; std::optional<Macro::Opcode> next_opcode{};
ProgramType program{nullptr}; ProgramType program{nullptr};
std::array<Xbyak::Label, MAX_CODE_SIZE> labels; std::array<Xbyak::Label, MAX_CODE_SIZE> labels;
std::array<Xbyak::Label, MAX_CODE_SIZE> delay_skip; std::array<Xbyak::Label, MAX_CODE_SIZE> delay_skip;
Xbyak::Label end_of_code{}; Xbyak::Label end_of_code{};
bool is_delay_slot{}; bool is_delay_slot{};
u32 pc{}; u32 pc{};
std::span<const u32> code;
const std::vector<u32>& code;
}; };
void MacroJITx64Impl::Execute(const std::vector<u32>& parameters, u32 method) { void MacroJITx64Impl::Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, u32 method) {
ASSERT_OR_EXECUTE(program != nullptr, { return; }); ASSERT_OR_EXECUTE(program != nullptr, { return; });
JITState state{}; JITState state{};
state.maxwell3d = &maxwell3d; state.maxwell3d = &maxwell3d;
@ -1231,7 +1029,7 @@ void MacroJITx64Impl::Compile_Read(Macro::Opcode opcode) {
Compile_ProcessResult(opcode.result_operation, opcode.dst); Compile_ProcessResult(opcode.result_operation, opcode.dst);
} }
void Send(Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) { static void MacroJIT_SendThunk(Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) {
maxwell3d->CallMethod(method_address.address, value, true); maxwell3d->CallMethod(method_address.address, value, true);
} }
@ -1240,7 +1038,7 @@ void MacroJITx64Impl::Compile_Send(Xbyak::Reg32 value) {
mov(Common::X64::ABI_PARAM1, qword[STATE]); mov(Common::X64::ABI_PARAM1, qword[STATE]);
mov(Common::X64::ABI_PARAM2.cvt32(), METHOD_ADDRESS); mov(Common::X64::ABI_PARAM2.cvt32(), METHOD_ADDRESS);
mov(Common::X64::ABI_PARAM3.cvt32(), value); mov(Common::X64::ABI_PARAM3.cvt32(), value);
Common::X64::CallFarFunction(*this, &Send); Common::X64::CallFarFunction(*this, &MacroJIT_SendThunk);
Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
Xbyak::Label dont_process{}; Xbyak::Label dont_process{};
@ -1452,10 +1250,8 @@ bool MacroJITx64Impl::Compile_NextInstruction() {
return true; return true;
} }
static void WarnInvalidParameter(uintptr_t parameter, uintptr_t max_parameter) { static void MacroJIT_ErrorThunk(uintptr_t parameter, uintptr_t max_parameter) {
LOG_CRITICAL(HW_GPU, LOG_CRITICAL(HW_GPU, "Macro JIT: invalid parameter access 0x{:x} (0x{:x} is the last parameter)", parameter, max_parameter - sizeof(u32));
"Macro JIT: invalid parameter access 0x{:x} (0x{:x} is the last parameter)",
parameter, max_parameter - sizeof(u32));
} }
Xbyak::Reg32 MacroJITx64Impl::Compile_FetchParameter() { Xbyak::Reg32 MacroJITx64Impl::Compile_FetchParameter() {
@ -1465,7 +1261,7 @@ Xbyak::Reg32 MacroJITx64Impl::Compile_FetchParameter() {
Common::X64::ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); Common::X64::ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
mov(Common::X64::ABI_PARAM1, PARAMETERS); mov(Common::X64::ABI_PARAM1, PARAMETERS);
mov(Common::X64::ABI_PARAM2, MAX_PARAMETER); mov(Common::X64::ABI_PARAM2, MAX_PARAMETER);
Common::X64::CallFarFunction(*this, &WarnInvalidParameter); Common::X64::CallFarFunction(*this, &MacroJIT_ErrorThunk);
Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
L(parameter_ok); L(parameter_ok);
mov(eax, dword[PARAMETERS]); mov(eax, dword[PARAMETERS]);
@ -1574,33 +1370,42 @@ static void Dump(u64 hash, std::span<const u32> code, bool decompiled = false) {
macro_file.write(reinterpret_cast<const char*>(code.data()), code.size_bytes()); macro_file.write(reinterpret_cast<const char*>(code.data()), code.size_bytes());
} }
MacroEngine::MacroEngine(Engines::Maxwell3D& maxwell3d_, bool is_interpreted_) void MacroEngine::Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span<const u32> parameters) {
: hle_macros{std::make_optional<Tegra::HLEMacro>(maxwell3d_)} auto const execute_variant = [&maxwell3d, &parameters, method](AnyCachedMacro& acm) {
, maxwell3d{maxwell3d_} if (auto a = std::get_if<HLE_DrawArraysIndirect>(&acm))
, is_interpreted{is_interpreted_} a->Execute(maxwell3d, parameters, method);
{} if (auto a = std::get_if<HLE_DrawIndexedIndirect>(&acm))
a->Execute(maxwell3d, parameters, method);
MacroEngine::~MacroEngine() = default; if (auto a = std::get_if<HLE_MultiDrawIndexedIndirectCount>(&acm))
a->Execute(maxwell3d, parameters, method);
void MacroEngine::AddCode(u32 method, u32 data) { if (auto a = std::get_if<HLE_MultiLayerClear>(&acm))
uploaded_macro_code[method].push_back(data); a->Execute(maxwell3d, parameters, method);
} if (auto a = std::get_if<HLE_C713C83D8F63CCF3>(&acm))
a->Execute(maxwell3d, parameters, method);
void MacroEngine::ClearCode(u32 method) { if (auto a = std::get_if<HLE_D7333D26E0A93EDE>(&acm))
macro_cache.erase(method); a->Execute(maxwell3d, parameters, method);
uploaded_macro_code.erase(method); if (auto a = std::get_if<HLE_BindShader>(&acm))
} a->Execute(maxwell3d, parameters, method);
if (auto a = std::get_if<HLE_SetRasterBoundingBox>(&acm))
void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) { a->Execute(maxwell3d, parameters, method);
auto compiled_macro = macro_cache.find(method); if (auto a = std::get_if<HLE_ClearConstBuffer>(&acm))
if (compiled_macro != macro_cache.end()) { a->Execute(maxwell3d, parameters, method);
const auto& cache_info = compiled_macro->second; if (auto a = std::get_if<HLE_ClearMemory>(&acm))
if (cache_info.has_hle_program) { a->Execute(maxwell3d, parameters, method);
cache_info.hle_program->Execute(parameters, method); if (auto a = std::get_if<HLE_TransformFeedbackSetup>(&acm))
} else { a->Execute(maxwell3d, parameters, method);
maxwell3d.RefreshParameters(); if (auto a = std::get_if<HLE_DrawIndirectByteCount>(&acm))
cache_info.lle_program->Execute(parameters, method); a->Execute(maxwell3d, parameters, method);
} if (auto a = std::get_if<MacroInterpreterImpl>(&acm))
a->Execute(maxwell3d, parameters, method);
if (auto a = std::get_if<std::unique_ptr<DynamicCachedMacro>>(&acm))
a->get()->Execute(maxwell3d, parameters, method);
};
if (auto const it = macro_cache.find(method); it != macro_cache.end()) {
auto& ci = it->second;
if (!CanBeHLEProgram(ci.hash) || Settings::values.disable_macro_hle)
maxwell3d.RefreshParameters(); //LLE must reload parameters
execute_variant(ci.program);
} else { } else {
// Macro not compiled, check if it's uploaded and if so, compile it // Macro not compiled, check if it's uploaded and if so, compile it
std::optional<u32> mid_method; std::optional<u32> mid_method;
@ -1617,51 +1422,37 @@ void MacroEngine::Execute(u32 method, const std::vector<u32>& parameters) {
return; return;
} }
} }
auto& cache_info = macro_cache[method]; auto& ci = macro_cache[method];
if (mid_method) {
if (!mid_method.has_value()) {
cache_info.lle_program = Compile(macro_code->second);
cache_info.hash = Common::HashValue(macro_code->second);
} else {
const auto& macro_cached = uploaded_macro_code[mid_method.value()]; const auto& macro_cached = uploaded_macro_code[mid_method.value()];
const auto rebased_method = method - mid_method.value(); const auto rebased_method = method - mid_method.value();
auto& code = uploaded_macro_code[method]; auto& code = uploaded_macro_code[method];
code.resize(macro_cached.size() - rebased_method); code.resize(macro_cached.size() - rebased_method);
std::memcpy(code.data(), macro_cached.data() + rebased_method, code.size() * sizeof(u32)); std::memcpy(code.data(), macro_cached.data() + rebased_method, code.size() * sizeof(u32));
cache_info.hash = Common::HashValue(code); ci.hash = Common::HashValue(code);
cache_info.lle_program = Compile(code); ci.program = Compile(maxwell3d, code);
}
auto hle_program = hle_macros->GetHLEProgram(cache_info.hash);
if (!hle_program || Settings::values.disable_macro_hle) {
maxwell3d.RefreshParameters();
cache_info.lle_program->Execute(parameters, method);
} else { } else {
cache_info.has_hle_program = true; ci.program = Compile(maxwell3d, macro_code->second);
cache_info.hle_program = std::move(hle_program); ci.hash = Common::HashValue(macro_code->second);
cache_info.hle_program->Execute(parameters, method);
} }
if (CanBeHLEProgram(ci.hash) && !Settings::values.disable_macro_hle) {
ci.program = GetHLEProgram(ci.hash);
} else {
maxwell3d.RefreshParameters();
}
execute_variant(ci.program);
if (Settings::values.dump_macros) { if (Settings::values.dump_macros) {
Dump(cache_info.hash, macro_code->second, cache_info.has_hle_program); Dump(ci.hash, macro_code->second, !std::holds_alternative<std::monostate>(ci.program));
} }
} }
} }
std::unique_ptr<CachedMacro> MacroEngine::Compile(const std::vector<u32>& code) { AnyCachedMacro MacroEngine::Compile(Engines::Maxwell3D& maxwell3d, std::span<const u32> code) {
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
if (!is_interpreted) if (!is_interpreted)
return std::make_unique<MacroJITx64Impl>(maxwell3d, code); return std::make_unique<MacroJITx64Impl>(code);
#endif
return std::make_unique<MacroInterpreterImpl>(maxwell3d, code);
}
std::optional<MacroEngine> GetMacroEngine(Engines::Maxwell3D& maxwell3d) {
#ifdef ARCHITECTURE_x86_64
return std::make_optional<MacroEngine>(maxwell3d, bool(Settings::values.disable_macro_jit));
#else
return std::make_optional<MacroEngine>(maxwell3d, true);
#endif #endif
return MacroInterpreterImpl(code);
} }
} // namespace Tegra } // namespace Tegra

View file

@ -7,8 +7,10 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <ankerl/unordered_dense.h> #include <span>
#include <variant>
#include <vector> #include <vector>
#include <ankerl/unordered_dense.h>
#include "common/bit_field.h" #include "common/bit_field.h"
#include "common/common_types.h" #include "common/common_types.h"
@ -98,62 +100,142 @@ union MethodAddress {
} // namespace Macro } // namespace Macro
class CachedMacro { struct HLEMacro {
public: };
CachedMacro(Engines::Maxwell3D& maxwell3d_) /// @note: these macros have two versions, a normal and extended version, with the extended version
: maxwell3d{maxwell3d_} /// also assigning the base vertex/instance.
{} struct HLE_DrawArraysIndirect final {
virtual ~CachedMacro() = default; HLE_DrawArraysIndirect(bool extended_) noexcept : extended{extended_} {}
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
bool extended;
};
/// @note: these macros have two versions, a normal and extended version, with the extended version
/// also assigning the base vertex/instance.
struct HLE_DrawIndexedIndirect final {
explicit HLE_DrawIndexedIndirect(bool extended_) noexcept : extended{extended_} {}
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
bool extended;
};
struct HLE_MultiLayerClear final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
};
struct HLE_MultiDrawIndexedIndirectCount final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
};
struct HLE_DrawIndirectByteCount final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
};
struct HLE_C713C83D8F63CCF3 final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
};
struct HLE_D7333D26E0A93EDE final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
};
struct HLE_BindShader final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
};
struct HLE_SetRasterBoundingBox final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
};
struct HLE_ClearConstBuffer final {
HLE_ClearConstBuffer(size_t base_size_) noexcept : base_size{base_size_} {}
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
size_t base_size;
};
struct HLE_ClearMemory final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
std::vector<u32> zero_memory;
};
struct HLE_TransformFeedbackSetup final {
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
};
struct MacroInterpreterImpl final {
MacroInterpreterImpl() {}
MacroInterpreterImpl(std::span<const u32> code_) : code{code_} {}
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> params, u32 method);
void Reset();
bool Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slot);
u32 GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b);
void ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result);
bool EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const;
Macro::Opcode GetOpcode() const;
u32 GetRegister(u32 register_id) const;
void SetRegister(u32 register_id, u32 value);
/// Sets the method address to use for the next Send instruction.
[[nodiscard]] inline void SetMethodAddress(u32 address) noexcept {
method_address.raw = address;
}
void Send(Engines::Maxwell3D& maxwell3d, u32 value);
u32 Read(Engines::Maxwell3D& maxwell3d, u32 method) const;
u32 FetchParameter();
/// General purpose macro registers.
std::array<u32, Macro::NUM_MACRO_REGISTERS> registers = {};
/// Input parameters of the current macro.
std::vector<u32> parameters;
std::span<const u32> code;
/// Program counter to execute at after the delay slot is executed.
std::optional<u32> delayed_pc;
/// Method address to use for the next Send instruction.
Macro::MethodAddress method_address = {};
/// Current program counter
u32 pc{};
/// Index of the next parameter that will be fetched by the 'parm' instruction.
u32 next_parameter_index = 0;
bool carry_flag = false;
};
struct DynamicCachedMacro {
virtual ~DynamicCachedMacro() = default;
/// Executes the macro code with the specified input parameters. /// Executes the macro code with the specified input parameters.
/// @param parameters The parameters of the macro /// @param parameters The parameters of the macro
/// @param method The method to execute /// @param method The method to execute
virtual void Execute(const std::vector<u32>& parameters, u32 method) = 0; virtual void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, u32 method) = 0;
Engines::Maxwell3D& maxwell3d;
}; };
class HLEMacro { using AnyCachedMacro = std::variant<
public: std::monostate,
explicit HLEMacro(Engines::Maxwell3D& maxwell3d_); HLEMacro,
~HLEMacro(); HLE_DrawArraysIndirect,
// Allocates and returns a cached macro if the hash matches a known function. HLE_DrawIndexedIndirect,
// Returns nullptr otherwise. HLE_MultiDrawIndexedIndirectCount,
[[nodiscard]] std::unique_ptr<CachedMacro> GetHLEProgram(u64 hash) const; HLE_MultiLayerClear,
private: HLE_C713C83D8F63CCF3,
Engines::Maxwell3D& maxwell3d; HLE_D7333D26E0A93EDE,
}; HLE_BindShader,
HLE_SetRasterBoundingBox,
class MacroEngine { HLE_ClearConstBuffer,
public: HLE_ClearMemory,
explicit MacroEngine(Engines::Maxwell3D& maxwell3d, bool is_interpreted); HLE_TransformFeedbackSetup,
~MacroEngine(); HLE_DrawIndirectByteCount,
MacroInterpreterImpl,
// Used for JIT x86 macro
std::unique_ptr<DynamicCachedMacro>
>;
struct MacroEngine {
MacroEngine(bool is_interpreted_) noexcept : is_interpreted{is_interpreted_} {}
// Store the uploaded macro code to compile them when they're called. // Store the uploaded macro code to compile them when they're called.
void AddCode(u32 method, u32 data); inline void AddCode(u32 method, u32 data) noexcept {
uploaded_macro_code[method].push_back(data);
}
// Clear the code associated with a method. // Clear the code associated with a method.
void ClearCode(u32 method); inline void ClearCode(u32 method) noexcept {
macro_cache.erase(method);
uploaded_macro_code.erase(method);
}
// Compiles the macro if its not in the cache, and executes the compiled macro // Compiles the macro if its not in the cache, and executes the compiled macro
void Execute(u32 method, const std::vector<u32>& parameters); void Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span<const u32> parameters);
AnyCachedMacro Compile(Engines::Maxwell3D& maxwell3d, std::span<const u32> code);
protected:
std::unique_ptr<CachedMacro> Compile(const std::vector<u32>& code);
private:
struct CacheInfo { struct CacheInfo {
std::unique_ptr<CachedMacro> lle_program{}; AnyCachedMacro program;
std::unique_ptr<CachedMacro> hle_program{};
u64 hash{}; u64 hash{};
bool has_hle_program{};
}; };
ankerl::unordered_dense::map<u32, CacheInfo> macro_cache; ankerl::unordered_dense::map<u32, CacheInfo> macro_cache;
ankerl::unordered_dense::map<u32, std::vector<u32>> uploaded_macro_code; ankerl::unordered_dense::map<u32, std::vector<u32>> uploaded_macro_code;
std::optional<HLEMacro> hle_macros;
Engines::Maxwell3D& maxwell3d;
bool is_interpreted; bool is_interpreted;
}; };
std::optional<MacroEngine> GetMacroEngine(Engines::Maxwell3D& maxwell3d);
} // namespace Tegra } // namespace Tegra

View file

@ -1032,7 +1032,7 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend
VkShaderModule frag_shader = *convert_float_to_depth_frag; VkShaderModule frag_shader = *convert_float_to_depth_frag;
const std::array stages = MakeStages(*full_screen_vert, frag_shader); const std::array stages = MakeStages(*full_screen_vert, frag_shader);
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
pipeline = device.GetLogical().CreateGraphicsPipeline({ pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
.flags = 0, .flags = 0,
@ -1062,7 +1062,7 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend
VkShaderModule frag_shader = *convert_depth_to_float_frag; VkShaderModule frag_shader = *convert_depth_to_float_frag;
const std::array stages = MakeStages(*full_screen_vert, frag_shader); const std::array stages = MakeStages(*full_screen_vert, frag_shader);
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
pipeline = device.GetLogical().CreateGraphicsPipeline({ pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
.flags = 0, .flags = 0,
@ -1093,7 +1093,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren
} }
const std::array stages = MakeStages(*full_screen_vert, *module); const std::array stages = MakeStages(*full_screen_vert, *module);
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
pipeline = device.GetLogical().CreateGraphicsPipeline({ pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
.flags = 0, .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; is_target_depth ? *convert_float_to_depth_frag : *convert_depth_to_float_frag;
const std::array stages = MakeStages(*full_screen_vert, frag_shader); const std::array stages = MakeStages(*full_screen_vert, frag_shader);
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
pipeline = device.GetLogical().CreateGraphicsPipeline({ pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
.flags = 0, .flags = 0,

View file

@ -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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -61,7 +64,8 @@ public:
.pDescriptorUpdateEntries = entries.data(), .pDescriptorUpdateEntries = entries.data(),
.templateType = type, .templateType = type,
.descriptorSetLayout = descriptor_set_layout, .descriptorSetLayout = descriptor_set_layout,
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .pipelineBindPoint =
is_compute ? VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS,
.pipelineLayout = pipeline_layout, .pipelineLayout = pipeline_layout,
.set = 0, .set = 0,
}); });
@ -122,7 +126,7 @@ private:
}); });
++binding; ++binding;
num_descriptors += descriptors[i].count; num_descriptors += descriptors[i].count;
offset += sizeof(DescriptorUpdateEntry); offset += sizeof(DescriptorUpdateEntry) * descriptors[i].count;
} }
} }

View file

@ -137,14 +137,8 @@ try
memory_allocator, memory_allocator,
scheduler, scheduler,
swapchain, swapchain,
#ifdef ANDROID
surface) surface)
, , blit_swapchain(device_memory,
#else
*surface)
,
#endif
blit_swapchain(device_memory,
device, device,
memory_allocator, memory_allocator,
present_manager, present_manager,

View file

@ -10,6 +10,7 @@
#include <span> #include <span>
#include <vector> #include <vector>
#include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h"
@ -108,6 +109,14 @@ VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat
// Null buffer not supported, adjust offset and size // Null buffer not supported, adjust offset and size
offset = 0; offset = 0;
size = 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) { const auto it{std::ranges::find_if(views, [offset, size, format](const BufferView& view) {
return offset == view.offset && size == view.size && format == view.format; return offset == view.offset && size == view.size && format == view.format;
@ -575,18 +584,18 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
} }
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) { void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
boost::container::small_vector<VkBuffer, 32> buffer_handles; boost::container::static_vector<VkBuffer, VideoCommon::NUM_VERTEX_BUFFERS> buffer_handles(bindings.buffers.size());
for (u32 index = 0; index < bindings.buffers.size(); ++index) { for (u32 i = 0; i < bindings.buffers.size(); ++i) {
auto handle = bindings.buffers[index]->Handle(); auto handle = bindings.buffers[i]->Handle();
if (handle == VK_NULL_HANDLE) { if (handle == VK_NULL_HANDLE) {
bindings.offsets[index] = 0; bindings.offsets[i] = 0;
bindings.sizes[index] = VK_WHOLE_SIZE; bindings.sizes[i] = VK_WHOLE_SIZE;
if (!device.HasNullDescriptor()) { if (!device.HasNullDescriptor()) {
ReserveNullBuffer(); ReserveNullBuffer();
handle = *null_buffer; handle = *null_buffer;
} }
} }
buffer_handles.push_back(handle); buffer_handles[i] = handle;
} }
const u32 device_max = device.GetMaxVertexInputBindings(); const u32 device_max = device.GetMaxVertexInputBindings();
const u32 min_binding = (std::min)(bindings.min_index, device_max); const u32 min_binding = (std::min)(bindings.min_index, device_max);
@ -596,19 +605,12 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
return; return;
} }
if (device.IsExtExtendedDynamicStateSupported()) { if (device.IsExtExtendedDynamicStateSupported()) {
scheduler.Record([bindings_ = std::move(bindings), scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles), binding_count](vk::CommandBuffer cmdbuf) {
buffer_handles_ = std::move(buffer_handles), cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, binding_count, buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data(), bindings_.strides.data());
binding_count](vk::CommandBuffer cmdbuf) {
cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, binding_count, buffer_handles_.data(),
bindings_.offsets.data(), bindings_.sizes.data(),
bindings_.strides.data());
}); });
} else { } else {
scheduler.Record([bindings_ = std::move(bindings), scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles), binding_count](vk::CommandBuffer cmdbuf) {
buffer_handles_ = std::move(buffer_handles), cmdbuf.BindVertexBuffers(bindings_.min_index, binding_count, buffer_handles_.data(), bindings_.offsets.data());
binding_count](vk::CommandBuffer cmdbuf) {
cmdbuf.BindVertexBuffers(bindings_.min_index, binding_count, buffer_handles_.data(),
bindings_.offsets.data());
}); });
} }
} }
@ -639,15 +641,21 @@ void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<
// Already logged in the rasterizer // Already logged in the rasterizer
return; return;
} }
boost::container::small_vector<VkBuffer, 4> buffer_handles; boost::container::static_vector<VkBuffer, VideoCommon::NUM_VERTEX_BUFFERS> buffer_handles(bindings.buffers.size());
for (u32 index = 0; index < bindings.buffers.size(); ++index) { for (u32 i = 0; i < bindings.buffers.size(); ++i) {
buffer_handles.push_back(bindings.buffers[index]->Handle()); auto handle = bindings.buffers[i]->Handle();
if (handle == VK_NULL_HANDLE) {
bindings.offsets[i] = 0;
bindings.sizes[i] = VK_WHOLE_SIZE;
if (!device.HasNullDescriptor()) {
ReserveNullBuffer();
handle = *null_buffer;
} }
scheduler.Record([bindings_ = std::move(bindings), }
buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { buffer_handles[i] = handle;
cmdbuf.BindTransformFeedbackBuffersEXT(0, static_cast<u32>(buffer_handles_.size()), }
buffer_handles_.data(), bindings_.offsets.data(), scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
bindings_.sizes.data()); cmdbuf.BindTransformFeedbackBuffersEXT(0, u32(buffer_handles_.size()), buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data());
}); });
} }

View file

@ -285,7 +285,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
.requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U, .requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U,
}; };
bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size; bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size;
pipeline = device.GetLogical().CreateComputePipeline({ pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
.flags = 0, .flags = 0,
@ -299,7 +299,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
.pSpecializationInfo = nullptr, .pSpecializationInfo = nullptr,
}, },
.layout = *layout, .layout = *layout,
.basePipelineHandle = nullptr, .basePipelineHandle = {},
.basePipelineIndex = 0, .basePipelineIndex = 0,
}); });
} }
@ -944,7 +944,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
.codeSize = static_cast<u32>(code.size_bytes()), .codeSize = static_cast<u32>(code.size_bytes()),
.pCode = code.data(), .pCode = code.data(),
}); });
pipelines[i] = device.GetLogical().CreateComputePipeline({ pipelines[i] = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
.flags = 0, .flags = 0,
@ -958,7 +958,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
.pSpecializationInfo = nullptr, .pSpecializationInfo = nullptr,
}, },
.layout = *layout, .layout = *layout,
.basePipelineHandle = nullptr, .basePipelineHandle = {},
.basePipelineIndex = 0, .basePipelineIndex = 0,
}); });
}; };

View file

@ -50,11 +50,14 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
DescriptorLayoutBuilder builder{device}; DescriptorLayoutBuilder builder{device};
builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT); builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT);
descriptor_set_layout = builder.CreateDescriptorSetLayout(false); uses_push_descriptor = builder.CanUsePushDescriptor();
descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor);
pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout); pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout);
descriptor_update_template = descriptor_update_template =
builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, false); builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, uses_push_descriptor);
if (!uses_push_descriptor) {
descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info); descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info);
}
const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{ const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT, .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT,
.pNext = nullptr, .pNext = nullptr,
@ -64,8 +67,7 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) { if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) {
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
} }
pipeline = device.GetLogical().CreateComputePipeline( pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
{
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
.flags = flags, .flags = flags,
@ -82,8 +84,7 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
.layout = *pipeline_layout, .layout = *pipeline_layout,
.basePipelineHandle = 0, .basePipelineHandle = 0,
.basePipelineIndex = 0, .basePipelineIndex = 0,
}, }, *pipeline_cache);
*pipeline_cache);
// Log compute pipeline creation // Log compute pipeline creation
if (Settings::values.gpu_logging_enabled.GetValue()) { if (Settings::values.gpu_logging_enabled.GetValue()) {
@ -241,11 +242,16 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
RESCALING_LAYOUT_WORDS_OFFSET, sizeof(rescaling_data), RESCALING_LAYOUT_WORDS_OFFSET, sizeof(rescaling_data),
rescaling_data.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 VkDescriptorSet descriptor_set{descriptor_allocator.Commit()};
const vk::Device& dev{device.GetLogical()}; const vk::Device& dev{device.GetLogical()};
dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data); dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0, cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0,
descriptor_set, nullptr); descriptor_set, nullptr);
}
}); });
} }

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -55,6 +58,7 @@ private:
vk::ShaderModule spv_module; vk::ShaderModule spv_module;
vk::DescriptorSetLayout descriptor_set_layout; vk::DescriptorSetLayout descriptor_set_layout;
bool uses_push_descriptor{false};
DescriptorAllocator descriptor_allocator; DescriptorAllocator descriptor_allocator;
vk::PipelineLayout pipeline_layout; vk::PipelineLayout pipeline_layout;
vk::DescriptorUpdateTemplate descriptor_update_template; vk::DescriptorUpdateTemplate descriptor_update_template;

View file

@ -474,7 +474,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
buffer_cache.BindHostStageBuffers(stage); buffer_cache.BindHostStageBuffers(stage);
PushImageDescriptors(texture_cache, guest_descriptor_queue, stage_infos[stage], rescaling, PushImageDescriptors(texture_cache, guest_descriptor_queue, stage_infos[stage], rescaling,
samplers_it, views_it); samplers_it, views_it);
const auto& info{stage_infos[0]}; const auto& info{stage_infos[stage]};
if (info.uses_render_area) { if (info.uses_render_area) {
render_area.uses_render_area = true; render_area.uses_render_area = true;
render_area.words = {static_cast<float>(regs.surface_clip.width), render_area.words = {static_cast<float>(regs.surface_clip.width),
@ -946,8 +946,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
} }
pipeline = device.GetLogical().CreateGraphicsPipeline( pipeline = device.GetLogical().CreateGraphicsPipeline({
{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
.flags = flags, .flags = flags,
@ -967,8 +966,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
.subpass = 0, .subpass = 0,
.basePipelineHandle = nullptr, .basePipelineHandle = nullptr,
.basePipelineIndex = 0, .basePipelineIndex = 0,
}, }, *pipeline_cache);
*pipeline_cache);
// Log graphics pipeline creation // Log graphics pipeline creation
if (Settings::values.gpu_logging_enabled.GetValue()) { if (Settings::values.gpu_logging_enabled.GetValue()) {

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@ -101,22 +101,14 @@ PresentManager::PresentManager(const vk::Instance& instance_,
MemoryAllocator& memory_allocator_, MemoryAllocator& memory_allocator_,
Scheduler& scheduler_, Scheduler& scheduler_,
Swapchain& swapchain_, Swapchain& swapchain_,
#ifdef ANDROID
vk::SurfaceKHR& surface_) vk::SurfaceKHR& surface_)
#else
VkSurfaceKHR_T* surface_handle_)
#endif
: instance{instance_} : instance{instance_}
, render_window{render_window_} , render_window{render_window_}
, device{device_} , device{device_}
, memory_allocator{memory_allocator_} , memory_allocator{memory_allocator_}
, scheduler{scheduler_} , scheduler{scheduler_}
, swapchain{swapchain_} , swapchain{swapchain_}
#ifdef ANDROID
, surface{surface_} , surface{surface_}
#else
, surface_handle{surface_handle_}
#endif
, blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())} , blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())}
, use_present_thread{Settings::values.async_presentation.GetValue()} , use_present_thread{Settings::values.async_presentation.GetValue()}
{ {
@ -299,11 +291,7 @@ void PresentManager::PresentThread(std::stop_token token) {
} }
void PresentManager::RecreateSwapchain(Frame* frame) { void PresentManager::RecreateSwapchain(Frame* frame) {
#ifndef ANDROID
swapchain.Create(surface_handle, frame->width, frame->height); // Pass raw pointer
#else
swapchain.Create(*surface, frame->width, frame->height); // Pass raw pointer swapchain.Create(*surface, frame->width, frame->height); // Pass raw pointer
#endif
SetImageCount(); SetImageCount();
} }

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@ -15,8 +15,6 @@
#include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/vulkan_common/vulkan_wrapper.h"
struct VkSurfaceKHR_T;
namespace Core::Frontend { namespace Core::Frontend {
class EmuWindow; class EmuWindow;
} // namespace Core::Frontend } // namespace Core::Frontend
@ -46,11 +44,7 @@ public:
MemoryAllocator& memory_allocator, MemoryAllocator& memory_allocator,
Scheduler& scheduler, Scheduler& scheduler,
Swapchain& swapchain, Swapchain& swapchain,
#ifdef ANDROID
vk::SurfaceKHR& surface); vk::SurfaceKHR& surface);
#else
VkSurfaceKHR_T* surface_handle);
#endif
~PresentManager(); ~PresentManager();
/// Returns the last used presentation frame /// Returns the last used presentation frame
@ -84,11 +78,7 @@ private:
MemoryAllocator& memory_allocator; MemoryAllocator& memory_allocator;
Scheduler& scheduler; Scheduler& scheduler;
Swapchain& swapchain; Swapchain& swapchain;
#ifdef ANDROID
vk::SurfaceKHR& surface; vk::SurfaceKHR& surface;
#else
VkSurfaceKHR_T* surface_handle;
#endif
vk::CommandPool cmdpool; vk::CommandPool cmdpool;
std::vector<Frame> frames; std::vector<Frame> frames;
boost::container::deque<Frame*> present_queue; boost::container::deque<Frame*> present_queue;

View file

@ -126,16 +126,14 @@ public:
current_query = nullptr; current_query = nullptr;
amend_value = 0; amend_value = 0;
accumulation_value = 0; accumulation_value = 0;
queries_prefix_scan_pass = std::make_unique<QueriesPrefixScanPass>( queries_prefix_scan_pass.emplace(device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
const VkBufferCreateInfo buffer_ci = { const VkBufferCreateInfo buffer_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
.flags = 0, .flags = 0,
.size = 8, .size = 8,
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE, .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0, .queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr, .pQueueFamilyIndices = nullptr,
@ -592,8 +590,7 @@ private:
VideoCommon::HostQueryBase* current_query; VideoCommon::HostQueryBase* current_query;
bool has_started{}; bool has_started{};
std::mutex flush_guard; std::mutex flush_guard;
std::optional<QueriesPrefixScanPass> queries_prefix_scan_pass;
std::unique_ptr<QueriesPrefixScanPass> queries_prefix_scan_pass;
}; };
// Transform feedback queries // Transform feedback queries
@ -1176,35 +1173,21 @@ private:
} // namespace } // namespace
struct QueryCacheRuntimeImpl { struct QueryCacheRuntimeImpl {
QueryCacheRuntimeImpl(QueryCacheRuntime& runtime, VideoCore::RasterizerInterface* rasterizer_, QueryCacheRuntimeImpl(QueryCacheRuntime& runtime, VideoCore::RasterizerInterface* rasterizer_, Tegra::MaxwellDeviceMemoryManager& device_memory_, Vulkan::BufferCache& buffer_cache_, const Device& device_, const MemoryAllocator& memory_allocator_, Scheduler& scheduler_, StagingBufferPool& staging_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue, DescriptorPool& descriptor_pool, TextureCache& texture_cache_)
Tegra::MaxwellDeviceMemoryManager& device_memory_, : rasterizer{rasterizer_}, device_memory{device_memory_}, buffer_cache{buffer_cache_}
Vulkan::BufferCache& buffer_cache_, const Device& device_, , device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_}
const MemoryAllocator& memory_allocator_, Scheduler& scheduler_, , staging_pool{staging_pool_}, guest_streamer(0, runtime)
StagingBufferPool& staging_pool_, , sample_streamer(size_t(QueryType::ZPassPixelCount64), runtime, rasterizer, texture_cache_, device, scheduler, memory_allocator, compute_pass_descriptor_queue, descriptor_pool)
ComputePassDescriptorQueue& compute_pass_descriptor_queue, , tfb_streamer(size_t(QueryType::StreamingByteCount), runtime, device, scheduler, memory_allocator, staging_pool)
DescriptorPool& descriptor_pool, TextureCache& texture_cache_) , primitives_succeeded_streamer(size_t(QueryType::StreamingPrimitivesSucceeded), runtime, tfb_streamer, device_memory_)
: rasterizer{rasterizer_}, device_memory{device_memory_}, buffer_cache{buffer_cache_}, , primitives_needed_minus_succeeded_streamer(size_t(QueryType::StreamingPrimitivesNeededMinusSucceeded), runtime, 0u)
device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_}, , hcr_setup{}, hcr_is_set{}, is_hcr_running{}, maxwell3d{} {
staging_pool{staging_pool_}, guest_streamer(0, runtime),
sample_streamer(static_cast<size_t>(QueryType::ZPassPixelCount64), runtime, rasterizer,
texture_cache_, device, scheduler, memory_allocator,
compute_pass_descriptor_queue, descriptor_pool),
tfb_streamer(static_cast<size_t>(QueryType::StreamingByteCount), runtime, device,
scheduler, memory_allocator, staging_pool),
primitives_succeeded_streamer(
static_cast<size_t>(QueryType::StreamingPrimitivesSucceeded), runtime, tfb_streamer,
device_memory_),
primitives_needed_minus_succeeded_streamer(
static_cast<size_t>(QueryType::StreamingPrimitivesNeededMinusSucceeded), runtime, 0u),
hcr_setup{}, hcr_is_set{}, is_hcr_running{}, maxwell3d{} {
hcr_setup.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT; hcr_setup.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT;
hcr_setup.pNext = nullptr; hcr_setup.pNext = nullptr;
hcr_setup.flags = 0; hcr_setup.flags = 0;
conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>( conditional_resolve_pass.emplace(device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
const VkBufferCreateInfo buffer_ci = { const VkBufferCreateInfo buffer_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
@ -1241,7 +1224,7 @@ struct QueryCacheRuntimeImpl {
std::vector<std::vector<VkBufferCopy>> copies_setup; std::vector<std::vector<VkBufferCopy>> copies_setup;
// Host conditional rendering data // Host conditional rendering data
std::unique_ptr<ConditionalRenderingResolvePass> conditional_resolve_pass; std::optional<ConditionalRenderingResolvePass> conditional_resolve_pass;
vk::Buffer hcr_resolve_buffer; vk::Buffer hcr_resolve_buffer;
VkConditionalRenderingBeginInfoEXT hcr_setup; VkConditionalRenderingBeginInfoEXT hcr_setup;
VkBuffer hcr_buffer; VkBuffer hcr_buffer;
@ -1253,13 +1236,7 @@ struct QueryCacheRuntimeImpl {
Maxwell3D* maxwell3d; Maxwell3D* maxwell3d;
}; };
QueryCacheRuntime::QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer, QueryCacheRuntime::QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer, Tegra::MaxwellDeviceMemoryManager& device_memory_, Vulkan::BufferCache& buffer_cache_, const Device& device_, const MemoryAllocator& memory_allocator_, Scheduler& scheduler_, StagingBufferPool& staging_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue, DescriptorPool& descriptor_pool, TextureCache& texture_cache_) {
Tegra::MaxwellDeviceMemoryManager& device_memory_,
Vulkan::BufferCache& buffer_cache_, const Device& device_,
const MemoryAllocator& memory_allocator_,
Scheduler& scheduler_, StagingBufferPool& staging_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue,
DescriptorPool& descriptor_pool, TextureCache& texture_cache_) {
impl = std::make_unique<QueryCacheRuntimeImpl>( impl = std::make_unique<QueryCacheRuntimeImpl>(
*this, rasterizer, device_memory_, buffer_cache_, device_, memory_allocator_, scheduler_, *this, rasterizer, device_memory_, buffer_cache_, device_, memory_allocator_, scheduler_,
staging_pool_, compute_pass_descriptor_queue, descriptor_pool, texture_cache_); staging_pool_, compute_pass_descriptor_queue, descriptor_pool, texture_cache_);
@ -1280,7 +1257,7 @@ void QueryCacheRuntime::EndHostConditionalRendering() {
PauseHostConditionalRendering(); PauseHostConditionalRendering();
impl->hcr_is_set = false; impl->hcr_is_set = false;
impl->is_hcr_running = false; impl->is_hcr_running = false;
impl->hcr_buffer = nullptr; impl->hcr_buffer = VkBuffer{};
impl->hcr_offset = 0; impl->hcr_offset = 0;
} }
@ -1484,13 +1461,11 @@ void QueryCacheRuntime::Barriers(bool is_prebarrier) {
impl->scheduler.RequestOutsideRenderPassOperationContext(); impl->scheduler.RequestOutsideRenderPassOperationContext();
if (is_prebarrier) { if (is_prebarrier) {
impl->scheduler.Record([](vk::CommandBuffer cmdbuf) { impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
}); });
} else { } else {
impl->scheduler.Record([](vk::CommandBuffer cmdbuf) { impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
}); });
} }
} }
@ -1583,8 +1558,7 @@ void QueryCacheRuntime::SyncValues(std::span<SyncValuesType> values, VkBuffer ba
} }
impl->scheduler.RequestOutsideRenderPassOperationContext(); impl->scheduler.RequestOutsideRenderPassOperationContext();
impl->scheduler.Record([src_buffer, dst_buffers = std::move(impl->buffers_to_upload_to), impl->scheduler.Record([src_buffer, dst_buffers = std::move(impl->buffers_to_upload_to), vk_copies = std::move(impl->copies_setup)](vk::CommandBuffer cmdbuf) {
vk_copies = std::move(impl->copies_setup)](vk::CommandBuffer cmdbuf) {
size_t size = dst_buffers.size(); size_t size = dst_buffers.size();
for (size_t i = 0; i < size; i++) { for (size_t i = 0; i < size; i++) {
cmdbuf.CopyBuffer(src_buffer, dst_buffers[i].first, vk_copies[i]); cmdbuf.CopyBuffer(src_buffer, dst_buffers[i].first, vk_copies[i]);

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@ -35,7 +38,7 @@ public:
~QueryCacheRuntime(); ~QueryCacheRuntime();
template <typename SyncValuesType> template <typename SyncValuesType>
void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = nullptr); void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = VkBuffer{});
void Barriers(bool is_prebarrier); void Barriers(bool is_prebarrier);

View file

@ -377,7 +377,7 @@ void Scheduler::EndRenderPass()
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images)); VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images));
}); });
state.renderpass = nullptr; state.renderpass = VkRenderPass{};
num_renderpass_images = 0; num_renderpass_images = 0;
} }

View file

@ -44,10 +44,10 @@ public:
~Scheduler(); ~Scheduler();
/// Sends the current execution context to the GPU. /// Sends the current execution context to the GPU.
u64 Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); u64 Flush(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {});
/// Sends the current execution context to the GPU and waits for it to complete. /// Sends the current execution context to the GPU and waits for it to complete.
void Finish(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); void Finish(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {});
/// Waits for the worker thread to finish executing everything. After this function returns it's /// Waits for the worker thread to finish executing everything. After this function returns it's
/// safe to touch worker resources. /// safe to touch worker resources.
@ -237,8 +237,8 @@ private:
}; };
struct State { struct State {
VkRenderPass renderpass = nullptr; VkRenderPass renderpass{};
VkFramebuffer framebuffer = nullptr; VkFramebuffer framebuffer{};
VkExtent2D render_area = {0, 0}; VkExtent2D render_area = {0, 0};
GraphicsPipeline* graphics_pipeline = nullptr; GraphicsPipeline* graphics_pipeline = nullptr;
bool is_rescaling = false; bool is_rescaling = false;

View file

@ -109,38 +109,22 @@ VkCompositeAlphaFlagBitsKHR ChooseAlphaFlags(const VkSurfaceCapabilitiesKHR& cap
} // Anonymous namespace } // Anonymous namespace
Swapchain::Swapchain( Swapchain::Swapchain(
#ifdef ANDROID VkSurfaceKHR_T* surface_,
VkSurfaceKHR surface_,
#else
VkSurfaceKHR_T* surface_handle_,
#endif
const Device& device_, const Device& device_,
Scheduler& scheduler_, Scheduler& scheduler_,
u32 width_, u32 width_,
u32 height_) u32 height_)
#ifdef ANDROID
: surface(surface_) : surface(surface_)
#else
: surface_handle{surface_handle_}
#endif
, device{device_} , device{device_}
, scheduler{scheduler_} , scheduler{scheduler_}
{ {
#ifdef ANDROID
Create(surface, width_, height_); Create(surface, width_, height_);
#else
Create(surface_handle, width_, height_);
#endif
} }
Swapchain::~Swapchain() = default; Swapchain::~Swapchain() = default;
void Swapchain::Create( void Swapchain::Create(
#ifdef ANDROID VkSurfaceKHR_T* surface_,
VkSurfaceKHR surface_,
#else
VkSurfaceKHR_T* surface_handle_,
#endif
u32 width_, u32 width_,
u32 height_) u32 height_)
{ {
@ -148,18 +132,10 @@ void Swapchain::Create(
is_suboptimal = false; is_suboptimal = false;
width = width_; width = width_;
height = height_; height = height_;
#ifdef ANDROID
surface = surface_; surface = surface_;
#else
surface_handle = surface_handle_;
#endif
const auto physical_device = device.GetPhysical(); const auto physical_device = device.GetPhysical();
#ifdef ANDROID const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(VkSurfaceKHR(surface))};
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) { if (capabilities.maxImageExtent.width == 0 || capabilities.maxImageExtent.height == 0) {
return; return;
} }
@ -254,14 +230,8 @@ void Swapchain::Present(VkSemaphore render_semaphore) {
void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) { void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
const auto physical_device{device.GetPhysical()}; const auto physical_device{device.GetPhysical()};
const auto formats{physical_device.GetSurfaceFormatsKHR(VkSurfaceKHR(surface))};
#ifdef ANDROID const auto present_modes = physical_device.GetSurfacePresentModesKHR(VkSurfaceKHR(surface));
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) has_mailbox = std::find(present_modes.begin(), present_modes.end(), VK_PRESENT_MODE_MAILBOX_KHR)
!= present_modes.end(); != present_modes.end();
@ -290,11 +260,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.pNext = nullptr, .pNext = nullptr,
.flags = 0, .flags = 0,
#ifdef ANDROID .surface = VkSurfaceKHR(surface),
.surface = surface,
#else
.surface = surface_handle,
#endif
.minImageCount = requested_image_count, .minImageCount = requested_image_count,
.imageFormat = surface_format.format, .imageFormat = surface_format.format,
.imageColorSpace = surface_format.colorSpace, .imageColorSpace = surface_format.colorSpace,
@ -313,7 +279,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
.compositeAlpha = alpha_flags, .compositeAlpha = alpha_flags,
.presentMode = present_mode, .presentMode = present_mode,
.clipped = VK_FALSE, .clipped = VK_FALSE,
.oldSwapchain = nullptr, .oldSwapchain = VkSwapchainKHR{},
}; };
const u32 graphics_family{device.GetGraphicsFamily()}; const u32 graphics_family{device.GetGraphicsFamily()};
const u32 present_family{device.GetPresentFamily()}; const u32 present_family{device.GetPresentFamily()};
@ -345,11 +311,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
swapchain_ci.flags |= VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR; swapchain_ci.flags |= VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR;
} }
// Request the size again to reduce the possibility of a TOCTOU race condition. // Request the size again to reduce the possibility of a TOCTOU race condition.
#ifdef ANDROID const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(VkSurfaceKHR(surface));
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); swapchain_ci.imageExtent = ChooseSwapExtent(updated_capabilities, width, height);
// Don't add code within this and the swapchain creation. // Don't add code within this and the swapchain creation.
swapchain = device.GetLogical().CreateSwapchainKHR(swapchain_ci); swapchain = device.GetLogical().CreateSwapchainKHR(swapchain_ci);

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@ -11,8 +11,6 @@
#include "common/common_types.h" #include "common/common_types.h"
#include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/vulkan_common/vulkan_wrapper.h"
struct VkSurfaceKHR_T;
namespace Layout { namespace Layout {
struct FramebufferLayout; struct FramebufferLayout;
} }
@ -25,11 +23,7 @@ class Scheduler;
class Swapchain { class Swapchain {
public: public:
explicit Swapchain( explicit Swapchain(
#ifdef ANDROID VkSurfaceKHR_T* surface,
VkSurfaceKHR surface,
#else
VkSurfaceKHR_T* surface_handle,
#endif
const Device& device, const Device& device,
Scheduler& scheduler, Scheduler& scheduler,
u32 width, u32 width,
@ -38,11 +32,7 @@ public:
/// Creates (or recreates) the swapchain with a given size. /// Creates (or recreates) the swapchain with a given size.
void Create( void Create(
#ifdef ANDROID VkSurfaceKHR_T* surface,
VkSurfaceKHR surface,
#else
VkSurfaceKHR_T* surface_handle,
#endif
u32 width, u32 width,
u32 height); u32 height);
@ -128,11 +118,7 @@ private:
bool NeedsPresentModeUpdate() const; bool NeedsPresentModeUpdate() const;
#ifdef ANDROID VkSurfaceKHR_T* surface;
VkSurfaceKHR surface;
#else
VkSurfaceKHR_T* surface_handle;
#endif
const Device& device; const Device& device;
Scheduler& scheduler; Scheduler& scheduler;

View file

@ -1,10 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include <array> #include <array>
#include <variant>
#include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan { namespace Vulkan {
@ -12,21 +15,16 @@ namespace Vulkan {
class Device; class Device;
class Scheduler; class Scheduler;
struct DescriptorUpdateEntry { union DescriptorUpdateEntry {
struct Empty {};
DescriptorUpdateEntry() = default; DescriptorUpdateEntry() = default;
DescriptorUpdateEntry(VkDescriptorImageInfo image_) : image{image_} {} DescriptorUpdateEntry(VkDescriptorImageInfo image_) : image{image_} {}
DescriptorUpdateEntry(VkDescriptorBufferInfo buffer_) : buffer{buffer_} {} DescriptorUpdateEntry(VkDescriptorBufferInfo buffer_) : buffer{buffer_} {}
DescriptorUpdateEntry(VkBufferView texel_buffer_) : texel_buffer{texel_buffer_} {} DescriptorUpdateEntry(VkBufferView texel_buffer_) : texel_buffer{texel_buffer_} {}
std::monostate empty{};
union {
Empty empty{};
VkDescriptorImageInfo image; VkDescriptorImageInfo image;
VkDescriptorBufferInfo buffer; VkDescriptorBufferInfo buffer;
VkBufferView texel_buffer; VkBufferView texel_buffer;
}; };
};
class UpdateDescriptorQueue final { class UpdateDescriptorQueue final {
// This should be plenty for the vast majority of cases. Most desktop platforms only // This should be plenty for the vast majority of cases. Most desktop platforms only

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@ -40,3 +40,6 @@
#undef False #undef False
#undef None #undef None
#undef True #undef True
// "Catch-all" handle for both Android and.. the rest of platforms
struct VkSurfaceKHR_T;

View file

@ -419,7 +419,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
: instance{instance_}, dld{dld_}, physical{physical_}, : instance{instance_}, dld{dld_}, physical{physical_},
format_properties(GetFormatProperties(physical)) { format_properties(GetFormatProperties(physical)) {
// Get suitability and device properties. // Get suitability and device properties.
const bool is_suitable = GetSuitability(surface != nullptr); const bool is_suitable = GetSuitability(surface != VkSurfaceKHR{});
const VkDriverId driver_id = properties.driver.driverID; const VkDriverId driver_id = properties.driver.driverID;

View file

@ -318,6 +318,11 @@ public:
return properties.properties.limits.minStorageBufferOffsetAlignment; 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. /// Returns the maximum range for storage buffers.
VkDeviceSize GetMaxStorageBufferRange() const { VkDeviceSize GetMaxStorageBufferRange() const {
return properties.properties.limits.maxStorageBufferRange; return properties.properties.limits.maxStorageBufferRange;

View file

@ -15,7 +15,7 @@ vk::SurfaceKHR CreateSurface(
const vk::Instance& instance, const vk::Instance& instance,
[[maybe_unused]] const Core::Frontend::EmuWindow::WindowSystemInfo& window_info) { [[maybe_unused]] const Core::Frontend::EmuWindow::WindowSystemInfo& window_info) {
[[maybe_unused]] const vk::InstanceDispatch& dld = instance.Dispatch(); [[maybe_unused]] const vk::InstanceDispatch& dld = instance.Dispatch();
VkSurfaceKHR unsafe_surface = nullptr; VkSurfaceKHR unsafe_surface = VkSurfaceKHR{};
#ifdef _WIN32 #ifdef _WIN32
if (window_info.type == Core::Frontend::WindowSystemType::Windows) { if (window_info.type == Core::Frontend::WindowSystemType::Windows) {

View file

@ -404,13 +404,13 @@ public:
/// Construct a handle transferring the ownership from another handle. /// Construct a handle transferring the ownership from another handle.
Handle(Handle&& rhs) noexcept Handle(Handle&& rhs) noexcept
: handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, dld{rhs.dld} {} : handle{std::exchange(rhs.handle, Type{})}, owner{rhs.owner}, dld{rhs.dld} {}
/// Assign the current handle transferring the ownership from another handle. /// Assign the current handle transferring the ownership from another handle.
/// Destroys any previously held object. /// Destroys any previously held object.
Handle& operator=(Handle&& rhs) noexcept { Handle& operator=(Handle&& rhs) noexcept {
Release(); Release();
handle = std::exchange(rhs.handle, nullptr); handle = std::exchange(rhs.handle, Type{});
owner = rhs.owner; owner = rhs.owner;
dld = rhs.dld; dld = rhs.dld;
return *this; return *this;
@ -424,7 +424,7 @@ public:
/// Destroys any held object. /// Destroys any held object.
void reset() noexcept { void reset() noexcept {
Release(); Release();
handle = nullptr; handle = Type{};
} }
/// Returns the address of the held object. /// Returns the address of the held object.
@ -440,7 +440,7 @@ public:
/// Returns true when there's a held object. /// Returns true when there's a held object.
explicit operator bool() const noexcept { explicit operator bool() const noexcept {
return handle != nullptr; return handle != Type{};
} }
#ifndef ANDROID #ifndef ANDROID
@ -455,7 +455,7 @@ public:
#endif #endif
protected: protected:
Type handle = nullptr; Type handle{};
OwnerType owner = nullptr; OwnerType owner = nullptr;
const Dispatch* dld = nullptr; const Dispatch* dld = nullptr;
@ -463,7 +463,7 @@ private:
/// Destroys the held object if it exists. /// Destroys the held object if it exists.
void Release() noexcept { void Release() noexcept {
if (handle) { if (handle) {
Destroy(owner, handle, *dld); Destroy(OwnerType(owner), Type(handle), *dld);
} }
} }
}; };
@ -506,7 +506,7 @@ public:
/// Destroys any held object. /// Destroys any held object.
void reset() noexcept { void reset() noexcept {
Release(); Release();
handle = nullptr; handle = {};
} }
/// Returns the address of the held object. /// Returns the address of the held object.
@ -522,7 +522,7 @@ public:
/// Returns true when there's a held object. /// Returns true when there's a held object.
explicit operator bool() const noexcept { explicit operator bool() const noexcept {
return handle != nullptr; return handle != Type{};
} }
#ifndef ANDROID #ifndef ANDROID
@ -537,7 +537,7 @@ public:
#endif #endif
protected: protected:
Type handle = nullptr; Type handle{};
const Dispatch* dld = nullptr; const Dispatch* dld = nullptr;
private: private:
@ -607,7 +607,7 @@ private:
std::unique_ptr<AllocationType[]> allocations; std::unique_ptr<AllocationType[]> allocations;
std::size_t num = 0; std::size_t num = 0;
VkDevice device = nullptr; VkDevice device = nullptr;
PoolType pool = nullptr; PoolType pool{};
const DeviceDispatch* dld = nullptr; const DeviceDispatch* dld = nullptr;
}; };
@ -669,12 +669,12 @@ public:
Image& operator=(const Image&) = delete; Image& operator=(const Image&) = delete;
Image(Image&& rhs) noexcept Image(Image&& rhs) noexcept
: handle{std::exchange(rhs.handle, nullptr)}, usage{rhs.usage}, owner{rhs.owner}, : handle{std::exchange(rhs.handle, VkImage{})}, usage{rhs.usage}, owner{rhs.owner},
allocator{rhs.allocator}, allocation{rhs.allocation}, dld{rhs.dld} {} allocator{rhs.allocator}, allocation{rhs.allocation}, dld{rhs.dld} {}
Image& operator=(Image&& rhs) noexcept { Image& operator=(Image&& rhs) noexcept {
Release(); Release();
handle = std::exchange(rhs.handle, nullptr); handle = std::exchange(rhs.handle, VkImage{});
usage = rhs.usage; usage = rhs.usage;
owner = rhs.owner; owner = rhs.owner;
allocator = rhs.allocator; allocator = rhs.allocator;
@ -693,11 +693,11 @@ public:
void reset() noexcept { void reset() noexcept {
Release(); Release();
handle = nullptr; handle = VkImage{};
} }
explicit operator bool() const noexcept { explicit operator bool() const noexcept {
return handle != nullptr; return handle != VkImage{};
} }
void SetObjectNameEXT(const char* name) const; void SetObjectNameEXT(const char* name) const;
@ -709,7 +709,7 @@ public:
private: private:
void Release() const noexcept; void Release() const noexcept;
VkImage handle = nullptr; VkImage handle{};
VkImageUsageFlags usage{}; VkImageUsageFlags usage{};
VkDevice owner = nullptr; VkDevice owner = nullptr;
VmaAllocator allocator = nullptr; VmaAllocator allocator = nullptr;
@ -730,13 +730,13 @@ public:
Buffer& operator=(const Buffer&) = delete; Buffer& operator=(const Buffer&) = delete;
Buffer(Buffer&& rhs) noexcept Buffer(Buffer&& rhs) noexcept
: handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, allocator{rhs.allocator}, : handle{std::exchange(rhs.handle, VkBuffer{})}, owner{rhs.owner}, allocator{rhs.allocator},
allocation{rhs.allocation}, mapped{rhs.mapped}, allocation{rhs.allocation}, mapped{rhs.mapped},
is_coherent{rhs.is_coherent}, dld{rhs.dld} {} is_coherent{rhs.is_coherent}, dld{rhs.dld} {}
Buffer& operator=(Buffer&& rhs) noexcept { Buffer& operator=(Buffer&& rhs) noexcept {
Release(); Release();
handle = std::exchange(rhs.handle, nullptr); handle = std::exchange(rhs.handle, VkBuffer{});
owner = rhs.owner; owner = rhs.owner;
allocator = rhs.allocator; allocator = rhs.allocator;
allocation = rhs.allocation; allocation = rhs.allocation;
@ -756,11 +756,11 @@ public:
void reset() noexcept { void reset() noexcept {
Release(); Release();
handle = nullptr; handle = VkBuffer{};
} }
explicit operator bool() const noexcept { explicit operator bool() const noexcept {
return handle != nullptr; return handle != VkBuffer{};
} }
/// Returns the host mapped memory, an empty span otherwise. /// Returns the host mapped memory, an empty span otherwise.
@ -786,7 +786,7 @@ public:
private: private:
void Release() const noexcept; void Release() const noexcept;
VkBuffer handle = nullptr; VkBuffer handle{};
VkDevice owner = nullptr; VkDevice owner = nullptr;
VmaAllocator allocator = nullptr; VmaAllocator allocator = nullptr;
VmaAllocation allocation = nullptr; VmaAllocation allocation = nullptr;
@ -1020,10 +1020,10 @@ public:
[[nodiscard]] PipelineLayout CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci) const; [[nodiscard]] PipelineLayout CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci) const;
[[nodiscard]] Pipeline CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci, [[nodiscard]] Pipeline CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci,
VkPipelineCache cache = nullptr) const; VkPipelineCache cache = {}) const;
[[nodiscard]] Pipeline CreateComputePipeline(const VkComputePipelineCreateInfo& ci, [[nodiscard]] Pipeline CreateComputePipeline(const VkComputePipelineCreateInfo& ci,
VkPipelineCache cache = nullptr) const; VkPipelineCache cache = {}) const;
[[nodiscard]] Sampler CreateSampler(const VkSamplerCreateInfo& ci) const; [[nodiscard]] Sampler CreateSampler(const VkSamplerCreateInfo& ci) const;

View file

@ -10,13 +10,14 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <QDesktopServices>
#include <QHeaderView> #include <QHeaderView>
#include <QMenu> #include <QMenu>
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QStandardPaths>
#include <QString> #include <QString>
#include <QTimer> #include <QTimer>
#include <QTreeView> #include <QTreeView>
#include <QStandardPaths>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/fs/fs.h" #include "common/fs/fs.h"
@ -42,7 +43,7 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p
item_model = new QStandardItemModel(tree_view); item_model = new QStandardItemModel(tree_view);
tree_view->setModel(item_model); tree_view->setModel(item_model);
tree_view->setAlternatingRowColors(true); tree_view->setAlternatingRowColors(true);
tree_view->setSelectionMode(QHeaderView::MultiSelection); tree_view->setSelectionMode(QHeaderView::ExtendedSelection);
tree_view->setSelectionBehavior(QHeaderView::SelectRows); tree_view->setSelectionBehavior(QHeaderView::SelectRows);
tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
@ -248,8 +249,11 @@ void ConfigurePerGameAddons::AddonDeleteRequested(QList<QModelIndex> selected) {
void ConfigurePerGameAddons::showContextMenu(const QPoint& pos) { void ConfigurePerGameAddons::showContextMenu(const QPoint& pos) {
const QModelIndex index = tree_view->indexAt(pos); const QModelIndex index = tree_view->indexAt(pos);
auto selected = tree_view->selectionModel()->selectedIndexes(); auto selected = tree_view->selectionModel()->selectedRows();
if (index.isValid() && selected.empty()) selected = {index}; if (index.isValid() && selected.empty()) {
QModelIndex idx = item_model->index(index.row(), 0);
if (idx.isValid()) selected << idx;
}
if (selected.empty()) return; if (selected.empty()) return;
@ -260,6 +264,15 @@ void ConfigurePerGameAddons::showContextMenu(const QPoint& pos) {
AddonDeleteRequested(selected); AddonDeleteRequested(selected);
}); });
if (selected.length() == 1) {
auto loc = selected.at(0).data(PATCH_LOCATION).toString();
if (QFileInfo::exists(loc)) {
QAction* open = menu.addAction(tr("&Open in File Manager"));
connect(open, &QAction::triggered, this,
[selected, loc]() { QDesktopServices::openUrl(QUrl::fromLocalFile(loc)); });
}
}
menu.exec(tree_view->viewport()->mapToGlobal(pos)); menu.exec(tree_view->viewport()->mapToGlobal(pos));
} }

View file

@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <filesystem>
#include <memory> #include <memory>
#include <string> #include <string>
#include <utility> #include <utility>
@ -16,14 +17,17 @@
#include "common/fs/fs.h" #include "common/fs/fs.h"
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "common/settings.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/card_image.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/content_archive.h"
#include "core/file_sys/control_metadata.h" #include "core/file_sys/control_metadata.h"
#include "core/file_sys/fs_filesystem.h" #include "core/file_sys/fs_filesystem.h"
#include "core/file_sys/nca_metadata.h" #include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h" #include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h" #include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/submission_package.h" #include "core/file_sys/submission_package.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
#include "yuzu/compatibility_list.h" #include "yuzu/compatibility_list.h"
@ -375,6 +379,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
return true; 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; u64 program_id = 0;
const auto res2 = loader->ReadProgramId(program_id); const auto res2 = loader->ReadProgramId(program_id);
@ -383,18 +393,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
provider->AddEntry(FileSys::TitleType::Application, provider->AddEntry(FileSys::TitleType::Application,
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
program_id, file); program_id, file);
} else if (res2 == Loader::ResultStatus::Success && } else if (Settings::values.ext_content_from_game_dirs.GetValue() &&
(file_type == Loader::FileType::XCI || (file_type == Loader::FileType::XCI ||
file_type == Loader::FileType::NSP)) { file_type == Loader::FileType::NSP)) {
const auto nsp = file_type == Loader::FileType::NSP void(provider->AddEntriesFromContainer(file));
? 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 { } else {
std::vector<u64> program_ids; std::vector<u64> program_ids;

View file

@ -2019,6 +2019,10 @@ void MainWindow::ConfigureFilesystemProvider(const std::string& filepath) {
return; return;
} }
if (QtCommon::provider->AddEntriesFromContainer(file)) {
return;
}
auto loader = Loader::GetLoader(*QtCommon::system, file); auto loader = Loader::GetLoader(*QtCommon::system, file);
if (!loader) { if (!loader) {
return; return;
@ -2033,19 +2037,8 @@ void MainWindow::ConfigureFilesystemProvider(const std::string& filepath) {
const auto res2 = loader->ReadProgramId(program_id); const auto res2 = loader->ReadProgramId(program_id);
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
QtCommon::provider->AddEntry(FileSys::TitleType::Application, QtCommon::provider->AddEntry(FileSys::TitleType::Application,
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id, FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
file); 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());
}
}
} }
} }