Compare commits

..

24 Commits

Author SHA1 Message Date
CamilleLaVey 4e20cd98a7 [TEST] Another increased on windows size 2026-08-07 20:58:31 -04:00
CamilleLaVey 6a3adefd46 Increased windows size 2026-08-07 20:50:03 -04:00
CamilleLaVey 0fc813332f [TEST] Increased window limiter on AHB 2026-08-07 20:09:33 -04:00
CamilleLaVey 95c78a206c [TEST] Adjustment AHB to tiled-gpu-v2 state 2026-08-07 15:54:08 -04:00
CamilleLaVey 24cb761852 [memory, vulkan] Initial implementation for Unified Memory 2026-08-07 00:38:36 -04:00
CamilleLaVey 23d0ad417d [hw_composer] Removal of visible composite 2026-08-06 18:39:07 -04:00
CamilleLaVey 2fa88c4868 Fix strings and some minor adjustment to settings 2026-08-06 16:10:24 -04:00
CamilleLaVey a9461e23b3 [threads] Improve mantenance on the current threading ops 2026-08-06 03:47:33 -04:00
CamilleLaVey 8476fbfcfe [TEST] Set on fps limiters with adfp 2026-08-06 09:16:24 +02:00
CamilleLaVey ad854018df [TEST] Other pair of tests on adpf affinity threads 2026-08-06 09:16:24 +02:00
CamilleLaVey b748f0ad5f [thread, adpf] Adjustments on current affinitty cascade falls 2026-08-06 09:16:24 +02:00
CamilleLaVey b2c9ccfc7e Fix license headers 2026-08-06 09:16:24 +02:00
CamilleLaVey 13f48a56ad [TEST] Miscellaneous changes on Vsycing 2026-08-06 09:16:24 +02:00
CamilleLaVey 63e33f37f6 [gpu] Adjustments on the frame composer 2026-08-06 09:16:24 +02:00
CamilleLaVey 41cd784835 [common, settings] Adjusted CPU and GPU clocks settings 2026-08-06 09:16:24 +02:00
CamilleLaVey c41cadf3f4 [core/ threads] Initial refactor for Multithreading 2026-08-06 09:16:24 +02:00
lizzie a43664c0fd [audio_core] Fix jamboree crash due to OOB access (#4252)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

PLEASE test diablo 3 or ANY game that's sensitive to audio changes
Just TEST that there's NO regressions on the audio front

this fixes a rare Jamboree crash with audio DSP

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4252
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
2026-08-05 21:56:18 +02:00
lizzie a0f1cd1baf [hid_core] fix NPad crash on pikmin 4 due to nullref (#4249)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4249
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-08-05 20:55:01 +02:00
CamilleLaVey 49a0ca6d5d [vulkan] Initial implementation on Bindless Buffer/Descriptors (#4251)
Adds a VK_EXT_descriptor_buffer, VK_KHR_device_buffer_address path alongside push descriptors and descriptor sets. The ring is chunked and sized from the device limits, so drivers with a small sampler range (QCOM reports 128 KiB) get more, chunks per frame instead of losing the feature. A pipeline uses one path or the other, never both, since some drivers do not support bufferless push descriptors. The chunk binding is only re-emitted when it changes, and a draw that repeats the previous payload reuses its allocation instead of rewriting it. This whole change will help to reduce the constant use of descriptors on the slower path, improves the "slow implementation" on QCOM drivers (A7xx and older) for push descriptors, improves latency when compiling pipelines. The implementation on indexing descriptors was also refined.

_Special Thanks_

1.- Meowly The Bindless Smol (@Gidoly)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4251
2026-08-05 16:58:43 +02:00
CamilleLaVey 5e1d5e82dc [vulkan] Re-adjusting ASTC resolve (#4250)
This is an adjustment made due to problems caused by my ASTC changes some pr's earlier; added sanity to decoded blocks, removes the inconditional storage view for ASTC formats (verified against MaxwellToVk logic), added support to VK_EXT_astc_decode_mode which will ensure a better way to resolve RGFA16/RGFA32 on tilers, simplified ASTC HDR decoding into LDR valid formats.

This fixes some issues with SteamDeck, Fire Emblem: Engage washed colors and other problems related to ASTC.

_Special Thanks_

1.- Meowly The Smol (@Gidoly)
2.- @simply0001 for the actual idea on the redundant storage views on the transcoded ASTC path.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4250
2026-08-05 09:24:35 +02:00
lizzie ba9130fbf9 Revert "[externals] remove SPIRV-Headers and SPIRV-Tools (#3989)" (#4247)
This reverts commit ee197e6222.

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4247
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-04 02:03:18 +02:00
lizzie 7d5f390ffb [android] fix crash when loader for invalid file is found (#4003)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4003
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-04 01:52:32 +02:00
lizzie 8fe1e6efa2 [core/file_sys] implement IApplicationFunctions::GetPseudoDeviceId; common PatchManager::GetNACP for base|updates (#4159)
a bit of refactoring so there's less code duplication...
probably a good idea to make it a lambda anyways
maybe this is why QLauncher didn't have proper DLCs and updates?

either way "GetPseudoDeviceId" is now implemented (still stubbed) with a proper hash instead of just 0

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4159
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-04 01:50:54 +02:00
lizzie ee197e6222 [externals] remove SPIRV-Headers and SPIRV-Tools (#3989)
only dep that uses it is sirit, but sirit already has it's own bundled SPIRV-Headers... so

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3989
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-08-03 00:00:55 +02:00
140 changed files with 3716 additions and 2644 deletions
+3
View File
@@ -188,6 +188,7 @@ android {
create("mainline") {
dimension = "version"
isDefault = true
minSdk = 33
manifestPlaceholders += mapOf("appNameBase" to "Eden")
resValue("string", "app_name_suffixed", "Eden")
@@ -199,6 +200,7 @@ android {
create("genshinSpoof") {
dimension = "version"
minSdk = 35
manifestPlaceholders += mapOf("appNameBase" to "Eden Optimized")
resValue("string", "app_name_suffixed", "Eden Optimized")
applicationId = "com.miHoYo.Yuanshen"
@@ -216,6 +218,7 @@ android {
create("legacy") {
dimension = "version"
minSdk = 29
manifestPlaceholders += mapOf("appNameBase" to "Eden Legacy")
resValue("string", "app_name_suffixed", "Eden Legacy")
applicationId = "dev.legacy.eden_emulator"
@@ -218,6 +218,8 @@ object NativeLibrary {
external fun logSettings()
external fun refreshThreadPolicies()
external fun getDebugKnobAt(index: Int): Boolean
/**
@@ -27,6 +27,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"),
RENDERER_ASYNC_PRESENTATION("async_presentation"),
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
RENDERER_UNIFIED_MEMORY("use_unified_memory"),
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
@@ -50,7 +50,6 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
GPU_UNSWIZZLE_TEXTURE_SIZE("gpu_unswizzle_texture_size"),
GPU_UNSWIZZLE_STREAM_SIZE("gpu_unswizzle_stream_size"),
GPU_UNSWIZZLE_CHUNK_SIZE("gpu_unswizzle_chunk_size"),
HOMEBREW_NXLINK_SERVER_MODE("homebrew_nxlink_server_mode"),
BAT_TEMPERATURE_UNIT("bat_temperature_unit"),
CABINET_APPLET("cabinet_applet_mode"),
CONTROLLER_APPLET("controller_applet_mode"),
@@ -132,15 +132,6 @@ abstract class SettingsItem(
descriptionId = R.string.program_args_description
)
)
put(
SingleChoiceSetting(
IntSetting.HOMEBREW_NXLINK_SERVER_MODE,
titleId = R.string.nxlink_server_mode,
descriptionId = R.string.nxlink_server_mode_description,
choicesId = R.array.nxlinkServerModeEntries,
valuesId = R.array.nxlinkServerModeValues
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
@@ -603,7 +594,7 @@ abstract class SettingsItem(
IntSetting.ANDROID_PIPELINE_WORKERS,
titleId = R.string.pipeline_worker_cores,
descriptionId = R.string.pipeline_worker_cores_description,
min = 4,
min = 2,
max = 8,
units = "cores"
)
@@ -694,6 +685,13 @@ abstract class SettingsItem(
descriptionId = R.string.renderer_asynchronous_shaders_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_UNIFIED_MEMORY,
titleId = R.string.renderer_unified_memory,
descriptionId = R.string.renderer_unified_memory_description
)
)
put(
SingleChoiceSetting(
IntSetting.FAST_GPU_TIME,
@@ -250,8 +250,9 @@ class SettingsFragmentPresenter(
add(BooleanSetting.USE_CUSTOM_RTC.key)
add(LongSetting.CUSTOM_RTC.key)
add(HeaderSetting(R.string.cpu))
add(HeaderSetting(R.string.clocks))
add(IntSetting.FAST_CPU_TIME.key)
add(IntSetting.FAST_GPU_TIME.key)
add(BooleanSetting.CORE_SYNC_CORE_SPEED.key)
add(IntSetting.MEMORY_LAYOUT.key)
@@ -298,12 +299,12 @@ class SettingsFragmentPresenter(
add(HeaderSetting(R.string.hacks))
add(IntSetting.FAST_GPU_TIME.key)
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
add(BooleanSetting.EMULATE_BGR565.key)
add(BooleanSetting.RESCALE_HACK.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
add(BooleanSetting.RENDERER_UNIFIED_MEMORY.key)
add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
@@ -1288,7 +1289,6 @@ class SettingsFragmentPresenter(
add(ShortSetting.DEBUG_KNOBS.key)
add(StringSetting.PROGRAM_ARGS.key)
add(IntSetting.HOMEBREW_NXLINK_SERVER_MODE.key)
if (!NativeConfig.isPerGameConfigLoaded()) {
add(HeaderSetting(R.string.gpu_logging_header))
@@ -1451,6 +1451,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun onResume() {
super.onResume()
NativeLibrary.refreshThreadPolicies()
val b = _binding ?: return
updateStatsPosition(IntSetting.PERF_OVERLAY_POSITION.getInt())
updateSocPosition(IntSetting.SOC_OVERLAY_POSITION.getInt())
+61 -97
View File
@@ -20,132 +20,96 @@ struct RomMetadata {
std::vector<u8> icon;
bool isHomebrew;
};
static ankerl::unordered_dense::map<std::string, RomMetadata> m_rom_metadata_cache;
ankerl::unordered_dense::map<std::string, RomMetadata> m_rom_metadata_cache;
static RomMetadata CacheRomMetadata(const std::string& path) {
auto& instance = EmulationSession::GetInstance();
const auto file = Core::GetGameFileFromPath(instance.System().GetFilesystem(), path);
if (auto loader = Loader::GetLoader(instance.System(), file, 0, 0); loader) {
RomMetadata entry;
loader->ReadTitle(entry.title);
loader->ReadProgramId(entry.programId);
loader->ReadIcon(entry.icon);
RomMetadata CacheRomMetadata(const std::string& path) {
const auto file =
Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path);
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
const FileSys::PatchManager pm{
entry.programId,
instance.System().GetFileSystemController(),
instance.System().GetContentProvider()
};
const auto control = pm.GetControlMetadata();
RomMetadata entry;
loader->ReadTitle(entry.title);
loader->ReadProgramId(entry.programId);
loader->ReadIcon(entry.icon);
const FileSys::PatchManager pm{
entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(),
EmulationSession::GetInstance().System().GetContentProvider()};
const auto control = pm.GetControlMetadata();
if (control.first != nullptr) {
entry.developer = control.first->GetDeveloperName();
entry.version = control.first->GetVersionString();
} else {
FileSys::NACP nacp;
if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) {
entry.developer = nacp.GetDeveloperName();
if (control.first != nullptr) {
entry.developer = control.first->GetDeveloperName();
entry.version = control.first->GetVersionString();
} else {
entry.developer = "";
FileSys::NACP nacp{};
entry.developer = loader->ReadControlData(nacp) == Loader::ResultStatus::Success
? nacp.GetDeveloperName()
: "";
entry.version = "1.0.0";
}
entry.version = "1.0.0";
if (loader->GetFileType() == Loader::FileType::NRO) {
auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
entry.isHomebrew = loader_nro->IsHomebrew();
} else {
entry.isHomebrew = false;
}
m_rom_metadata_cache[path] = entry;
return entry;
}
if (loader->GetFileType() == Loader::FileType::NRO) {
auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
entry.isHomebrew = loader_nro->IsHomebrew();
} else {
entry.isHomebrew = false;
}
m_rom_metadata_cache[path] = entry;
return entry;
return {};
}
RomMetadata GetRomMetadata(const std::string& path, bool reload = false) {
if (reload) {
static RomMetadata GetRomMetadata(const std::string& path, bool reload = false) {
if (reload)
return CacheRomMetadata(path);
}
if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
return search->second;
}
if (auto it = m_rom_metadata_cache.find(path); it != m_rom_metadata_cache.end())
return it->second;
return CacheRomMetadata(path);
}
extern "C" {
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj,
jstring jpath) {
const auto file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(
Common::Android::GetJString(env, jpath), FileSys::OpenMode::Read);
if (!file) {
return false;
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj, jstring jpath) {
if (auto const file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(Common::Android::GetJString(env, jpath), FileSys::OpenMode::Read); file) {
if (auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file); loader) {
auto const file_type = loader->GetFileType();
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error)
return false;
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) && !Loader::IsBootableGameContainer(file, file_type))
return false;
u64 program_id = 0;
return loader->ReadProgramId(program_id) == Loader::ResultStatus::Success;
}
}
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file);
if (!loader) {
return false;
}
const auto file_type = loader->GetFileType();
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
return false;
}
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) &&
!Loader::IsBootableGameContainer(file, file_type)) {
return false;
}
u64 program_id = 0;
Loader::ResultStatus res = loader->ReadProgramId(program_id);
if (res != Loader::ResultStatus::Success) {
return false;
}
return true;
return false;
}
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj,
jstring jpath) {
return Common::Android::ToJString(
env, GetRomMetadata(Common::Android::GetJString(env, jpath)).title);
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj, jstring jpath) {
return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath)).title);
}
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj,
jstring jpath) {
return Common::Android::ToJString(
env, std::to_string(GetRomMetadata(Common::Android::GetJString(env, jpath)).programId));
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj, jstring jpath) {
return Common::Android::ToJString(env, std::to_string(GetRomMetadata(Common::Android::GetJString(env, jpath)).programId));
}
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj,
jstring jpath) {
return Common::Android::ToJString(
env, GetRomMetadata(Common::Android::GetJString(env, jpath)).developer);
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj, jstring jpath) {
return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath)).developer);
}
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj,
jstring jpath, jboolean jreload) {
return Common::Android::ToJString(
env, GetRomMetadata(Common::Android::GetJString(env, jpath), jreload).version);
jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj, jstring jpath, jboolean jreload) {
return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath), jreload).version);
}
jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj,
jstring jpath) {
jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj, jstring jpath) {
auto icon_data = GetRomMetadata(Common::Android::GetJString(env, jpath)).icon;
jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
reinterpret_cast<jbyte*>(icon_data.data()));
jbyteArray icon = env->NewByteArray(jsize(icon_data.size()));
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), reinterpret_cast<jbyte*>(icon_data.data()));
return icon;
}
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj,
jstring jpath) {
return static_cast<jboolean>(
GetRomMetadata(Common::Android::GetJString(env, jpath)).isHomebrew);
jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj, jstring jpath) {
return jboolean(GetRomMetadata(Common::Android::GetJString(env, jpath)).isHomebrew);
}
void Java_org_yuzu_yuzu_1emu_utils_GameMetadata_resetMetadata(JNIEnv* env, jobject obj) {
+5
View File
@@ -50,6 +50,7 @@ extern "C" {
#include "common/scope_exit.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "common/thread.h"
#include "frontend_common/play_time_manager.h"
#include "core/constants.h"
#include "core/core.h"
@@ -1182,6 +1183,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj
Settings::LogSettings();
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jobject jobj) {
Common::RefreshThreadPolicies();
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
}
@@ -958,7 +958,6 @@
<string name="clock_fast">سريع (2000MHz)</string>
<!-- GPU overclock factors -->
<string name="off">تعطيل</string>
<string name="fast_gpu_medium">متوسط (256)</string>
<string name="fast_gpu_high">مرتفع (512)</string>
@@ -884,7 +884,6 @@ Wirklich fortfahren?</string>
<string name="clock_fast">Schnell (2000MHz)</string>
<!-- GPU overclock factors -->
<string name="off">Aus</string>
<string name="fast_gpu_medium">Mittel (256)</string>
<string name="fast_gpu_high">Hoch (512)</string>
@@ -950,7 +950,6 @@
<string name="clock_fast">Rápido (2000MHz)</string>
<!-- GPU overclock factors -->
<string name="off">Desactivado</string>
<string name="fast_gpu_medium">Medio (256)</string>
<string name="fast_gpu_high">Alto (512)</string>
@@ -897,7 +897,6 @@
<string name="clock_fast">Rapide (2000MHz)</string>
<!-- GPU overclock factors -->
<string name="off">Désactivé</string>
<string name="fast_gpu_medium">Moyen (256)</string>
<string name="fast_gpu_high">Élevé (512)</string>
@@ -871,7 +871,6 @@
<string name="clock_fast">Szybkie (2000MHz)</string>
<!-- GPU overclock factors -->
<string name="off">Wyłączone</string>
<string name="fast_gpu_medium">Średnie (256)</string>
<string name="fast_gpu_high">Wysokie (512)</string>
@@ -954,7 +954,6 @@
<string name="clock_fast">Быстрая (2000MHz)</string>
<!-- GPU overclock factors -->
<string name="off">Выкл.</string>
<string name="fast_gpu_medium">Среднее (256)</string>
<string name="fast_gpu_high">Высокое (512)</string>
@@ -943,7 +943,6 @@
<string name="clock_fast">Швидко (2000 МГц)</string>
<!-- GPU overclock factors -->
<string name="off">Вимкнено</string>
<string name="fast_gpu_medium">Середньо (256)</string>
<string name="fast_gpu_high">Високо (512)</string>
@@ -948,7 +948,6 @@
<string name="clock_fast">快速 (2000MHz)</string>
<!-- GPU overclock factors -->
<string name="off">关闭</string>
<string name="fast_gpu_medium">中 (256)</string>
<string name="fast_gpu_high">高 (512)</string>
+2 -12
View File
@@ -34,7 +34,7 @@
</integer-array>
<string-array name="clockNames">
<item>@string/off</item>
<item>@string/clock_normal</item>
<item>@string/clock_boost</item>
<item>@string/clock_fast</item>
</string-array>
@@ -547,7 +547,7 @@
</integer-array>
<string-array name="gpuEntries">
<item>@string/off</item>
<item>@string/fast_gpu_normal</item>
<item>@string/fast_gpu_medium</item>
<item>@string/fast_gpu_high</item>
</string-array>
@@ -630,16 +630,6 @@
<item>3</item>
</integer-array>
<string-array name="nxlinkServerModeEntries">
<item>Disabled</item>
<item>Eden Log</item>
</string-array>
<integer-array name="nxlinkServerModeValues">
<item>0</item>
<item>1</item>
</integer-array>
<string-array name="installKeysResults">
<item>""</item>
<item>""</item>
+15 -13
View File
@@ -436,8 +436,6 @@
<string name="program_args">Homebrew Args</string>
<string name="program_args_description">Command-line arguments passed to homebrew at launch (e.g. -noglsl).</string>
<string name="nxlink_server_mode">nxlink Server</string>
<string name="nxlink_server_mode_description">Starts a local nxlink server for homebrew stdout/stderr streams.</string>
<!-- System settings strings -->
<string name="device_name">Device name</string>
@@ -452,8 +450,8 @@
<string name="set_custom_rtc">Set custom RTC</string>
<!-- CPU -->
<string name="fast_cpu_time">CPU Overclock</string>
<string name="fast_cpu_time_description">Forces the emulated CPU to run at a higher clock, reducing certain FPS limiters. Use Boost (1700MHz) to run at the Switch\'s highest native clock, or Fast (2000MHz) to run at 2x clock.</string>
<string name="fast_cpu_time">CPU Clocks</string>
<string name="fast_cpu_time_description">Raises the clock the emulated CPU reports, which removes some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly.</string>
<string name="custom_cpu_ticks">Custom CPU Ticks</string>
<string name="custom_cpu_ticks_description">Set a custom value of CPU ticks. Higher values can increase performance, but may also cause the game to freeze. A range of 7721000 is recommended.</string>
<string name="cpu_ticks">Ticks</string>
@@ -514,8 +512,8 @@
<string name="hacks">Hacks</string>
<string name="fast_gpu_time">Fast GPU Time</string>
<string name="fast_gpu_time_description">Forces most games to run at their highest native resolution. Use 256 for maximal performance and 512 for maximal graphics fidelity.</string>
<string name="fast_gpu_time">GPU Clocks</string>
<string name="fast_gpu_time_description">Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch\'s clocks.</string>
<string name="skip_cpu_inner_invalidation">Skip CPU Inner Invalidation</string>
<string name="skip_cpu_inner_invalidation_description">Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games.</string>
<string name="fix_bloom_effects">Fix Bloom Effects</string>
@@ -526,6 +524,8 @@
<string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string>
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
<string name="renderer_unified_memory">Unified memory access</string>
<string name="renderer_unified_memory_description">Allows GPU write buffer readbacks directly into guest memory, skipping the CPU staging copy.</string>
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
<string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string>
<string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string>
@@ -562,6 +562,7 @@
<!-- Debug settings strings -->
<string name="cpu">CPU</string>
<string name="clocks">Clocks</string>
<string name="use_auto_stub">Use Auto Stub</string>
<string name="use_auto_stub_description">Automatically stub missing services and functions. This may improve compatibility but can cause crashes and stability issues.</string>
@@ -970,14 +971,15 @@
<string name="memory_6gb">6GB (Unsafe)</string>
<string name="memory_8gb">8GB (Unsafe)</string>
<!--CPU clock speeds-->
<string name="clock_boost">Boost (1700MHz)</string>
<string name="clock_fast">Fast (2000MHz)</string>
<!-- CPU clock levels -->
<string name="clock_normal">Normal</string>
<string name="clock_boost">Boost</string>
<string name="clock_fast">Overclock</string>
<!-- GPU overclock factors -->
<string name="off">Off</string>
<string name="fast_gpu_medium">Medium (256)</string>
<string name="fast_gpu_high">High (512)</string>
<!-- GPU clock levels -->
<string name="fast_gpu_normal">Normal</string>
<string name="fast_gpu_medium">Boost</string>
<string name="fast_gpu_high">Overclock</string>
<!-- GPU swizzle texture size -->
<string name="gpu_texturesizeswizzle_verysmall">Very Small (16 MB)</string>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<game-mode-config
xmlns:android="http://schemas.android.com/apk/res/android"
android:supportsBatteryGameMode="true"
android:supportsBatteryGameMode="false"
android:supportsPerformanceGameMode="true"
android:allowGameDownscaling="false"
android:allowGameFpsOverride="false"/>
@@ -18,20 +18,15 @@
namespace AudioCore::Renderer {
constexpr u32 TempBufferSize = 0x3F00;
constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
/**
* Decode PCM data. Only s16 or f32 is supported.
*
* @tparam T - Type to decode. Only s16 and f32 are supported.
* @param memory - Core memory for reading samples.
* @param out_buffer - Output mix buffer to receive the samples.
* @param req - Information for how to decode.
* @return Number of samples decoded.
*/
/// @brief Decode PCM data. Only s16 or f32 is supported.
/// @tparam T - Type to decode. Only s16 and f32 are supported.
/// @param memory - Core memory for reading samples.
/// @param out_buffer - Output mix buffer to receive the samples.
/// @param req - Information for how to decode.
/// @return Number of samples decoded.
template <typename T>
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
const DecodeArg& req) {
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, const DecodeArg& req) {
constexpr s32 min{(std::numeric_limits<s16>::min)()};
constexpr s32 max{(std::numeric_limits<s16>::max)()};
@@ -94,16 +89,12 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
return samples_to_decode;
}
/**
* Decode ADPCM data.
*
* @param memory - Core memory for reading samples.
* @param out_buffer - Output mix buffer to receive the samples.
* @param req - Information for how to decode.
* @return Number of samples decoded.
*/
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
const DecodeArg& req) {
/// @brief Decode ADPCM data.
/// @param memory - Core memory for reading samples.
/// @param out_buffer - Output mix buffer to receive the samples.
/// @param req - Information for how to decode.
/// @return Number of samples decoded.
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, const DecodeArg& req) {
constexpr u32 SamplesPerFrame{14};
constexpr u32 NibblesPerFrame{16};
@@ -115,8 +106,7 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
return 0;
}
auto end{(req.end_offset % SamplesPerFrame) +
NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
auto end{(req.end_offset % SamplesPerFrame) + NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
if (req.end_offset % SamplesPerFrame) {
end += 3;
} else {
@@ -133,52 +123,49 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
return 0;
}
auto samples_to_read{samples_to_process};
auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
samples_remaining_in_frame};
auto samples_to_read = samples_to_process;
auto samples_remaining_in_frame = start_pos % SamplesPerFrame;
auto position_in_frame = (start_pos / SamplesPerFrame) * NibblesPerFrame + samples_remaining_in_frame;
if (samples_remaining_in_frame) {
position_in_frame += 2;
}
const auto size{(std::max)((samples_to_process / 8U) * SamplesPerFrame, 8U)};
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> wavebuffer(
memory, req.buffer + position_in_frame / 2, size);
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> wavebuffer(memory, req.buffer + position_in_frame / 2, size);
auto context{req.adpcm_context};
auto header{context->header};
u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)};
u8 scale{static_cast<u8>(header & 0xFU)};
s32 coeff0{req.coefficients[coeff_index * 2 + 0]};
s32 coeff1{req.coefficients[coeff_index * 2 + 1]};
auto context = req.adpcm_context;
auto header = context->header;
u8 scale = u8(header & 0xfU);
u8 coeff_index = u8((header >> 4U) & 0x7u);
s32 coeff0 = req.coefficients[coeff_index * 2 + 0];
s32 coeff1 = req.coefficients[coeff_index * 2 + 1];
auto yn0{context->yn0};
auto yn1{context->yn1};
static constexpr std::array<s32, 16> Steps{
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
auto yn0 = context->yn0;
auto yn1 = context->yn1;
auto const get_step = [](u32 index) {
// Emulates the following table
// 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
constexpr u64 steps_table = 0x1234567876543210ull;
constexpr u64 steps_sign = 0b1111111100000000ull;
auto const r = s32((steps_table >> (index * 4)) & 0xf);
return ((steps_sign >> index) & 1) == 0 ? -r : r;
};
const auto decode_sample = [&](const s32 code) -> s16 {
auto const decode_sample = [&](const s32 code) -> s16 {
const auto xn = code * (1 << scale);
const auto prediction = coeff0 * yn0 + coeff1 * yn1;
const auto sample = ((xn << 11) + 0x400 + prediction) >> 11;
const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF);
yn1 = yn0;
yn0 = static_cast<s16>(saturated);
return yn0;
return yn0 = s16(saturated);
};
u32 read_index{0};
u32 write_index{0};
while (samples_to_read > 0) {
u32 read_index = 0;
for (u32 write_index = 0; samples_to_read > 0 && write_index < out_buffer.size(); ) {
// Are we at a new frame?
if ((position_in_frame % NibblesPerFrame) == 0) {
header = wavebuffer[read_index++];
coeff_index = (header >> 4) & 0xF;
scale = header & 0xF;
scale = header & 0xFu;
coeff_index = (header >> 4) & 0x7u;
coeff0 = req.coefficients[coeff_index * 2 + 0];
coeff1 = req.coefficients[coeff_index * 2 + 1];
position_in_frame += 2;
@@ -187,14 +174,12 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
if (samples_to_read >= SamplesPerFrame) {
// Can grab all samples until the next header
for (u32 i = 0; i < SamplesPerFrame / 2; i++) {
auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]};
auto code1{Steps[wavebuffer[read_index] & 0xF]};
read_index++;
auto code0 = get_step((wavebuffer[read_index + i] >> 4) & 0xF);
auto code1 = get_step(wavebuffer[read_index + i] & 0xF);
out_buffer[write_index++] = decode_sample(code0);
out_buffer[write_index++] = decode_sample(code1);
}
read_index += SamplesPerFrame / 2;
position_in_frame += SamplesPerFrame;
samples_to_read -= SamplesPerFrame;
continue;
@@ -202,15 +187,14 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
}
// Decode a single sample
auto code{wavebuffer[read_index]};
auto code = wavebuffer[read_index];
if (position_in_frame & 1) {
code &= 0xF;
read_index++;
} else {
code >>= 4;
}
out_buffer[write_index++] = decode_sample(Steps[code]);
out_buffer[write_index++] = decode_sample(get_step(code));
position_in_frame++;
samples_to_read--;
@@ -219,27 +203,21 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
context->header = header;
context->yn0 = yn0;
context->yn1 = yn1;
return samples_to_process;
}
/**
* Decode implementation.
* Decode wavebuffers according to the given args.
*
* @param memory - Core memory to read data from.
* @param args - The wavebuffer data, and information for how to decode it.
*/
/// @brief Decode implementation.
/// Decode wavebuffers according to the given args.
///
/// @param memory - Core memory to read data from.
/// @param args - The wavebuffer data, and information for how to decode it.
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index,
auto& played_samples, auto& consumed) -> void {
constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index, auto& played_samples, auto& consumed) -> void {
voice_state.wave_buffer_valid[index] = false;
voice_state.loop_count = 0;
if (wavebuffer.stream_ended) {
played_samples = 0;
}
index = (index + 1) % MaxWaveBuffers;
consumed++;
};
@@ -255,8 +233,9 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
return;
}
auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]};
if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) {
// 0 -> 4, 1 -> 8, 2 -> 4
auto pitch = u32((0x040804ul >> (u32(args.src_quality) * 8)) & 0xfful);
if (u32(pitch + size_required.to_int_floor()) > TempBufferSize) {
return;
}
@@ -272,7 +251,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
bool is_buffer_starved{false};
u32 offset{voice_state.offset};
auto output_buffer{args.output};
auto output_buffer = args.output;
std::array<s16, TempBufferSize> temp_buffer{};
while (remaining_sample_count > 0) {
@@ -294,8 +273,8 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
if (wavebuffer_index >= MaxWaveBuffers) {
LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index);
wavebuffer_index = 0;
voice_state.wave_buffer_valid.fill(false);
wavebuffers_consumed = MaxWaveBuffers;
voice_state.wave_buffer_valid.fill(false);
}
if (!voice_state.wave_buffer_valid[wavebuffer_index]) {
@@ -303,12 +282,9 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
break;
}
auto& wavebuffer{args.wave_buffers[wavebuffer_index]};
if (offset == 0 && args.sample_format == SampleFormat::Adpcm &&
wavebuffer.context != 0) {
memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context,
wavebuffer.context_size);
auto& wavebuffer = args.wave_buffers[wavebuffer_index];
if (offset == 0 && args.sample_format == SampleFormat::Adpcm && wavebuffer.context != 0) {
memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context, wavebuffer.context_size);
}
auto start_offset{wavebuffer.start_offset};
@@ -351,9 +327,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
case SampleFormat::Adpcm: {
decode_arg.adpcm_context = &voice_state.adpcm_context;
memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size);
samples_decoded = DecodeAdpcm(
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
decode_arg);
samples_decoded = DecodeAdpcm( memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, decode_arg);
} break;
default:
@@ -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-License-Identifier: GPL-2.0-or-later
@@ -5,21 +8,13 @@
namespace AudioCore::Renderer {
static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input,
const Common::FixedPoint<49, 15>& sample_rate_ratio,
Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
if (sample_rate_ratio == 1.0f) {
for (u32 i = 0; i < samples_to_write; i++) {
output[i] = input[i];
}
} else {
u32 read_index{0};
for (u32 i = 0; i < samples_to_write; i++) {
output[i] = input[read_index + (fraction >= 0.5f)];
fraction += sample_rate_ratio;
read_index += static_cast<u32>(fraction.to_int_floor());
fraction.clear_int();
}
static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input, const Common::FixedPoint<49, 15>& sample_rate_ratio, Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
u32 read_index{0};
for (u32 i = 0; i < samples_to_write; i++) {
output[i] = input[read_index + (fraction >= 0.5f)];
fraction += sample_rate_ratio;
read_index += u32(fraction.to_int_floor());
fraction.clear_int();
}
}
@@ -295,7 +290,7 @@ static void ResampleNormalQuality(std::span<s32> output, std::span<const s16> in
auto lut{get_lut()};
u32 read_index{0};
for (u32 i = 0; i < samples_to_write; i++) {
const auto lut_index{(fraction.get_frac() >> 8) * 4};
const auto lut_index = ((fraction.get_frac() >> 8) << 2) & 511;
const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
@@ -845,7 +840,7 @@ static void ResampleHighQuality(std::span<s32> output, std::span<const s16> inpu
auto lut{get_lut()};
u32 read_index{0};
for (u32 i = 0; i < samples_to_write; i++) {
const auto lut_index{(fraction.get_frac() >> 8) * 8};
const auto lut_index = ((fraction.get_frac() >> 8) << 3) & 1023;
const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
+2
View File
@@ -121,6 +121,8 @@ add_library(
swap.h
thread.cpp
thread.h
adpf.cpp
adpf.h
thread_queue_list.h
thread_worker.h
threadsafe_queue.h
+15 -19
View File
@@ -336,22 +336,22 @@ ALLOC_MEMBER(VaType)::Allocate(VaType size) {
current_linear_alloc_end = alloc_start + size;
} else { // If linear allocation overflows the AS then find a gap
if (this->blocks.size() <= 2) {
alloc_start = virt_start;
ASSERT_MSG(false, "Unexpected allocator state!");
}
auto search_predecessor{std::next(this->blocks.begin())};
auto search_successor{std::next(search_predecessor)};
while (search_successor != this->blocks.end() &&
(search_successor->virt - search_predecessor->virt < size ||
search_predecessor->Mapped())) {
search_predecessor = search_successor++;
}
if (search_successor != this->blocks.end()) {
alloc_start = search_predecessor->virt;
} else {
auto search_predecessor{std::next(this->blocks.begin())};
auto search_successor{std::next(search_predecessor)};
while (search_successor != this->blocks.end() &&
(search_successor->virt - search_predecessor->virt < size ||
search_predecessor->Mapped())) {
search_predecessor = search_successor++;
}
if (search_successor != this->blocks.end()) {
alloc_start = search_predecessor->virt;
} else {
return {}; // AS is full
}
return {}; // AS is full
}
}
@@ -364,10 +364,6 @@ ALLOC_MEMBER(void)::AllocateFixed(VaType virt, VaType size) {
}
ALLOC_MEMBER(void)::Free(VaType virt, VaType size) {
const VaType virt_end = virt + size;
this->Unmap(virt, size);
if (virt_end >= virt && virt_end == current_linear_alloc_end) {
current_linear_alloc_end = virt < virt_start ? virt_start : virt;
}
}
} // namespace Common
+317
View File
@@ -0,0 +1,317 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "common/adpf.h"
#ifdef __ANDROID__
#include <algorithm>
#include <array>
#include <atomic>
#include <cstdint>
#include <mutex>
#include <vector>
#include <dlfcn.h>
#include <unistd.h>
#include "common/logging.h"
namespace Common::ADPF {
namespace {
constexpr std::chrono::nanoseconds DEFAULT_TARGET = std::chrono::nanoseconds{16'666'667};
struct AHintManager;
struct AHintSession;
using PFN_GetManager = AHintManager* (*)();
using PFN_CreateSession = AHintSession* (*)(AHintManager*, const s32*, size_t, s64);
using PFN_CloseSession = void (*)(AHintSession*);
using PFN_UpdateTarget = int (*)(AHintSession*, s64);
using PFN_ReportActual = int (*)(AHintSession*, s64);
using PFN_SetThreads = int (*)(AHintSession*, const pid_t*, size_t);
using PFN_SetPowerEfficiency = int (*)(AHintSession*, bool);
struct Api {
PFN_GetManager get_manager = nullptr;
PFN_CreateSession create_session = nullptr;
PFN_CloseSession close_session = nullptr;
PFN_UpdateTarget update_target = nullptr;
PFN_ReportActual report_actual = nullptr;
PFN_SetThreads set_threads = nullptr;
PFN_SetPowerEfficiency set_power_efficiency = nullptr;
AHintManager* manager = nullptr;
bool usable = false;
};
const Api& Resolve() {
static const Api api = [] {
Api resolved;
void* library = dlopen("libandroid.so", RTLD_NOW);
if (library == nullptr) {
LOG_INFO(Common, "libandroid.so unavailable, ADPF is disabled");
return resolved;
}
const auto load = [library](const char* name) { return dlsym(library, name); };
resolved.get_manager = reinterpret_cast<PFN_GetManager>(load("APerformanceHint_getManager"));
resolved.create_session =
reinterpret_cast<PFN_CreateSession>(load("APerformanceHint_createSession"));
resolved.close_session =
reinterpret_cast<PFN_CloseSession>(load("APerformanceHint_closeSession"));
resolved.update_target =
reinterpret_cast<PFN_UpdateTarget>(load("APerformanceHint_updateTargetWorkDuration"));
resolved.report_actual =
reinterpret_cast<PFN_ReportActual>(load("APerformanceHint_reportActualWorkDuration"));
resolved.set_threads =
reinterpret_cast<PFN_SetThreads>(load("APerformanceHint_setThreads"));
resolved.set_power_efficiency = reinterpret_cast<PFN_SetPowerEfficiency>(
load("APerformanceHint_setPreferPowerEfficiency"));
if (resolved.get_manager == nullptr || resolved.create_session == nullptr ||
resolved.close_session == nullptr || resolved.update_target == nullptr ||
resolved.report_actual == nullptr) {
LOG_INFO(Common, "Performance hint API not exported, ADPF is disabled");
return resolved;
}
resolved.manager = resolved.get_manager();
if (resolved.manager == nullptr) {
LOG_INFO(Common, "Device does not provide a performance hint manager");
return resolved;
}
resolved.usable = true;
LOG_INFO(Common, "ADPF available, setThreads {}, power efficiency {}",
resolved.set_threads != nullptr ? "yes" : "no",
resolved.set_power_efficiency != nullptr ? "yes" : "no");
return resolved;
}();
return api;
}
struct SessionState {
AHintSession* handle = nullptr;
std::vector<pid_t> threads;
bool unsupported = false;
};
std::mutex g_mutex;
std::array<SessionState, 2> g_sessions;
constexpr s64 MAX_REPORTED_TARGETS = 4;
std::atomic<s64> g_target_ns{DEFAULT_TARGET.count()};
thread_local std::chrono::steady_clock::time_point t_last_frame{};
SessionState& StateOf(Session session) {
return g_sessions[static_cast<size_t>(session)];
}
bool IsBackgroundUsable(const Api& api) {
return api.set_power_efficiency != nullptr;
}
void CloseLocked(SessionState& state) {
if (state.handle != nullptr) {
Resolve().close_session(state.handle);
state.handle = nullptr;
}
}
AHintSession* CreateSessionFor(Session session, const std::vector<pid_t>& threads) {
const Api& api = Resolve();
const s64 target = session == Session::Render ? g_target_ns.load(std::memory_order_relaxed) : 0;
std::vector<s32> ids;
ids.reserve(threads.size());
for (const pid_t tid : threads) {
ids.push_back(static_cast<s32>(tid));
}
AHintSession* handle = api.create_session(api.manager, ids.data(), ids.size(), target);
if (handle == nullptr && target == 0) {
handle = api.create_session(api.manager, ids.data(), ids.size(), DEFAULT_TARGET.count());
}
if (handle == nullptr) {
return nullptr;
}
if (session == Session::Background && api.set_power_efficiency != nullptr) {
api.set_power_efficiency(handle, true);
}
return handle;
}
bool SyncLocked(Session session, SessionState& state) {
if (state.threads.empty()) {
CloseLocked(state);
return false;
}
const Api& api = Resolve();
if (state.handle != nullptr && api.set_threads != nullptr) {
std::vector<pid_t> ids = state.threads;
if (api.set_threads(state.handle, ids.data(), ids.size()) == 0) {
return true;
}
}
AHintSession* const replacement = CreateSessionFor(session, state.threads);
if (replacement == nullptr) {
if (state.handle == nullptr) {
state.unsupported = true;
}
LOG_WARNING(Common, "Could not open a performance hint session for {} threads, falling back",
state.threads.size());
return false;
}
CloseLocked(state);
state.handle = replacement;
return true;
}
} // Anonymous namespace
bool IsSessionSupported(Session session) {
const Api& api = Resolve();
if (!api.usable) {
return false;
}
if (session == Session::Background && !IsBackgroundUsable(api)) {
return false;
}
std::scoped_lock lock{g_mutex};
return !StateOf(session).unsupported;
}
bool AddCurrentThread(Session session) {
if (!IsSessionSupported(session)) {
return false;
}
const pid_t tid = gettid();
std::scoped_lock lock{g_mutex};
for (size_t i = 0; i < g_sessions.size(); ++i) {
SessionState& state = g_sessions[i];
if (static_cast<size_t>(session) == i) {
continue;
}
const auto it = std::find(state.threads.begin(), state.threads.end(), tid);
if (it != state.threads.end()) {
state.threads.erase(it);
SyncLocked(static_cast<Session>(i), state);
}
}
SessionState& state = StateOf(session);
const bool added =
std::find(state.threads.begin(), state.threads.end(), tid) == state.threads.end();
if (added) {
state.threads.push_back(tid);
}
if (!SyncLocked(session, state)) {
if (added) {
std::erase(state.threads, tid);
}
return false;
}
return true;
}
void RemoveCurrentThread() {
if (!Resolve().usable) {
return;
}
const pid_t tid = gettid();
std::scoped_lock lock{g_mutex};
for (size_t i = 0; i < g_sessions.size(); ++i) {
SessionState& state = g_sessions[i];
if (std::erase(state.threads, tid) != 0) {
SyncLocked(static_cast<Session>(i), state);
}
}
}
void SetTargetWorkDuration(std::chrono::nanoseconds target) {
const Api& api = Resolve();
if (!api.usable || target.count() <= 0) {
return;
}
if (g_target_ns.exchange(target.count(), std::memory_order_relaxed) == target.count()) {
return;
}
std::scoped_lock lock{g_mutex};
SessionState& state = StateOf(Session::Render);
if (state.handle != nullptr) {
api.update_target(state.handle, target.count());
}
}
void ReportFrameInterval() {
const Api& api = Resolve();
if (!api.usable) {
return;
}
const auto now = std::chrono::steady_clock::now();
const auto previous = t_last_frame;
t_last_frame = now;
if (previous.time_since_epoch().count() == 0) {
return;
}
s64 actual = std::chrono::duration_cast<std::chrono::nanoseconds>(now - previous).count();
if (actual <= 0) {
return;
}
const s64 ceiling = g_target_ns.load(std::memory_order_relaxed) * MAX_REPORTED_TARGETS;
actual = (std::min)(actual, ceiling);
std::scoped_lock lock{g_mutex};
SessionState& state = StateOf(Session::Render);
if (state.handle != nullptr) {
api.report_actual(state.handle, actual);
}
}
void Shutdown() {
if (!Resolve().usable) {
return;
}
std::scoped_lock lock{g_mutex};
for (SessionState& state : g_sessions) {
CloseLocked(state);
state.threads.clear();
state.unsupported = false;
}
}
} // namespace Common::ADPF
#else
namespace Common::ADPF {
bool IsSessionSupported(Session) {
return false;
}
bool AddCurrentThread(Session) {
return false;
}
void RemoveCurrentThread() {}
void SetTargetWorkDuration(std::chrono::nanoseconds) {}
void ReportFrameInterval() {}
void Shutdown() {}
} // namespace Common::ADPF
#endif
+26
View File
@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <chrono>
namespace Common::ADPF {
enum class Session {
Render,
Background,
};
bool IsSessionSupported(Session session);
bool AddCurrentThread(Session session);
void RemoveCurrentThread();
void SetTargetWorkDuration(std::chrono::nanoseconds target);
void ReportFrameInterval();
void Shutdown();
} // namespace Common::ADPF
+1 -62
View File
@@ -4,8 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cerrno>
#include <cstdint>
#include <vector>
#include "common/assert.h"
@@ -17,10 +15,8 @@
#include "common/logging.h"
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#include <share.h>
#include <windows.h>
#else
#include <unistd.h>
#endif
@@ -99,65 +95,10 @@ namespace {
case FileShareFlag::ShareWriteOnly:
return _SH_DENYRD;
case FileShareFlag::ShareReadWrite:
case FileShareFlag::ShareReadWriteDelete:
return _SH_DENYNO;
}
}
[[nodiscard]] std::FILE* OpenWithWindowsShareDelete(const fs::path& path, FileAccessMode mode,
FileType type) {
DWORD desired_access{};
DWORD creation_disposition{OPEN_EXISTING};
int open_flags = type == FileType::BinaryFile ? _O_BINARY : _O_TEXT;
switch (mode) {
case FileAccessMode::Read:
desired_access = GENERIC_READ;
open_flags |= _O_RDONLY;
break;
case FileAccessMode::Write:
desired_access = GENERIC_WRITE;
creation_disposition = CREATE_ALWAYS;
open_flags |= _O_WRONLY;
break;
case FileAccessMode::Append:
desired_access = GENERIC_WRITE;
creation_disposition = OPEN_ALWAYS;
open_flags |= _O_WRONLY | _O_APPEND;
break;
case FileAccessMode::ReadWrite:
desired_access = GENERIC_READ | GENERIC_WRITE;
open_flags |= _O_RDWR;
break;
case FileAccessMode::ReadAppend:
desired_access = GENERIC_READ | GENERIC_WRITE;
creation_disposition = OPEN_ALWAYS;
open_flags |= _O_RDWR | _O_APPEND;
break;
}
const auto handle =
CreateFileW(path.c_str(), desired_access,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
if (handle == INVALID_HANDLE_VALUE) {
errno = EACCES;
return nullptr;
}
const auto fd = _open_osfhandle(reinterpret_cast<intptr_t>(handle), open_flags);
if (fd == -1) {
CloseHandle(handle);
return nullptr;
}
auto* const file = _wfdopen(fd, AccessModeToWStr(mode, type));
if (file == nullptr) {
_close(fd);
}
return file;
}
#else
/**
@@ -313,9 +254,7 @@ void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, File
errno = 0;
#ifdef _WIN32
if (flag == FileShareFlag::ShareReadWriteDelete) {
file = OpenWithWindowsShareDelete(path, mode, type);
} else if (flag != FileShareFlag::ShareNone) {
if (flag != FileShareFlag::ShareNone) {
file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
} else {
_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
+5 -6
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -49,11 +49,10 @@ enum class FileType {
};
enum class FileShareFlag {
ShareNone, // Provides exclusive access to the file.
ShareReadOnly, // Provides read only shared access to the file.
ShareWriteOnly, // Provides write only shared access to the file.
ShareReadWrite, // Provides read and write shared access to the file.
ShareReadWriteDelete, // Provides read, write, and delete shared access to the file.
ShareNone, // Provides exclusive access to the file.
ShareReadOnly, // Provides read only shared access to the file.
ShareWriteOnly, // Provides write only shared access to the file.
ShareReadWrite, // Provides read and write shared access to the file.
};
enum class DirEntryFilter {
+3
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-License-Identifier: GPL-2.0-or-later
+340 -6
View File
@@ -51,14 +51,45 @@
#endif // ^^^ POSIX ^^^
#include <atomic>
#include <mutex>
#include <random>
#include <vector>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/free_region_manager.h"
#include "common/host_memory.h"
#include "common/logging.h"
#include "common/memory_detect.h"
#include "common/settings.h"
#ifdef __ANDROID__
#include <dlfcn.h>
#include <android/hardware_buffer.h>
namespace {
struct NativeHandle {
int version;
int numFds;
int numInts;
int data[1];
};
using PFN_AHardwareBuffer_getNativeHandle = const NativeHandle* (*)(const AHardwareBuffer*);
PFN_AHardwareBuffer_getNativeHandle ResolveGetNativeHandle() {
void* const lib = dlopen("libnativewindow.so", RTLD_NOW);
if (lib == nullptr) {
return nullptr;
}
return reinterpret_cast<PFN_AHardwareBuffer_getNativeHandle>(
dlsym(lib, "AHardwareBuffer_getNativeHandle"));
}
} // namespace
#endif
#if defined(__ANDROID__) && __ANDROID_API__ < 30
#include <sys/syscall.h>
@@ -75,6 +106,12 @@ namespace Common {
[[maybe_unused]] constexpr size_t PageAlignment = 0x1000;
[[maybe_unused]] constexpr size_t HugePageSize = 0x200000;
static std::atomic<u64> committed_backing_size{};
u64 GetCommittedBackingSize() noexcept {
return committed_backing_size.load(std::memory_order_relaxed);
}
#ifdef _WIN32
// Manually imported for MinGW compatibility
@@ -123,7 +160,7 @@ static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn
class HostMemory::Impl {
public:
explicit Impl(size_t backing_size_, size_t virtual_size_)
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t)
: backing_size{backing_size_}
, virtual_size{virtual_size_}
, process{GetCurrentProcess()}
@@ -229,6 +266,10 @@ public:
UNREACHABLE();
}
bool IsBackingShared() const noexcept {
return true;
}
const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
@@ -501,9 +542,10 @@ static int shm_open_anon(int flags, mode_t mode) {
class HostMemory::Impl {
public:
explicit Impl(size_t backing_size_, size_t virtual_size_)
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
: backing_size{backing_size_}
, virtual_size{virtual_size_}
, preferred_offset{preferred_offset_}
{}
bool Init() {
@@ -543,10 +585,15 @@ public:
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
if (fd > 0) {
fd = -1;
close(fd);
}
fd = -1;
} else {
#ifdef __ANDROID__
if (InitAhbBacking()) {
return InitVirtual();
}
#endif
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
}
if (backing_base == MAP_FAILED) {
@@ -554,7 +601,10 @@ public:
return false;
}
// Virtual memory initialization
return InitVirtual();
}
bool InitVirtual() {
virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
if (virtual_base == MAP_FAILED) {
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
@@ -567,6 +617,222 @@ public:
return true;
}
#ifdef __ANDROID__
static AHardwareBuffer_Desc MakeBlobDesc(size_t len) {
return AHardwareBuffer_Desc{
.width = static_cast<u32>(len),
.height = 1,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_BLOB,
.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER,
.stride = 0,
.rfu0 = 0,
.rfu1 = 0,
};
}
static bool ProbeAhbBacking(PFN_AHardwareBuffer_getNativeHandle get_native_handle) {
const AHardwareBuffer_Desc desc = MakeBlobDesc(PageAlignment * 2);
AHardwareBuffer* buffer{};
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
return false;
}
const NativeHandle* const handle = get_native_handle(buffer);
if (handle == nullptr || handle->numFds < 1) {
AHardwareBuffer_release(buffer);
return false;
}
const int probe_fd = handle->data[0];
bool ok = true;
const auto try_map = [&](int prot, off_t offset, const char* what) {
if (!ok) {
return;
}
void* const ptr = mmap(nullptr, PageAlignment, prot, MAP_SHARED, probe_fd, offset);
if (ptr == MAP_FAILED) {
ok = false;
return;
}
munmap(ptr, PageAlignment);
};
try_map(PROT_READ | PROT_WRITE, 0, "shared mappings");
try_map(PROT_READ | PROT_WRITE, static_cast<off_t>(PageAlignment), "mappings at an offset");
#ifdef ARCHITECTURE_arm64
try_map(PROT_READ | PROT_EXEC, 0, "executable mappings");
#endif
AHardwareBuffer_release(buffer);
return ok;
}
size_t ComputeAhbBudget(size_t window_size) const {
const u64 total_physical = Common::GetMemInfo().TotalPhysicalMemory;
if (total_physical == 0) {
return 0;
}
constexpr u64 MinimumTotalPhysical = 7ULL << 30;
if (total_physical < MinimumTotalPhysical) {
return 0;
}
const u64 max_map_count = Common::GetMaxMapCount();
constexpr u64 ReservedMaps = 24576;
if (max_map_count == 0 || max_map_count <= ReservedMaps) {
return 0;
}
u64 budget = total_physical / 6;
budget = (std::min)(budget, (max_map_count - ReservedMaps) * PageAlignment);
const u64 available = Common::GetAvailablePhysicalMemory();
if (available != 0) {
constexpr u64 Headroom = 2ULL << 30;
budget = (std::min)(budget, available > Headroom ? available - Headroom : 0);
}
budget = (std::min)(budget, static_cast<u64>(backing_size));
budget = Common::AlignDown(budget, window_size);
constexpr u64 MinimumBudget = 256ULL << 20;
if (budget < MinimumBudget) {
return 0;
}
return static_cast<size_t>(budget);
}
bool InitAhbBacking() {
if (!Settings::values.use_unified_memory.GetValue()) {
return false;
}
static const PFN_AHardwareBuffer_getNativeHandle get_native_handle =
ResolveGetNativeHandle();
if (get_native_handle == nullptr) {
return false;
}
constexpr size_t window_size = 512ULL << 20;
const AHardwareBuffer_Desc window_desc = MakeBlobDesc(window_size);
if (AHardwareBuffer_isSupported(&window_desc) == 0) {
return false;
}
const size_t budget = ComputeAhbBudget(window_size);
if (budget == 0) {
return false;
}
if (!ProbeAhbBacking(get_native_handle)) {
return false;
}
const size_t aligned_backing = Common::AlignDown(backing_size, window_size);
const size_t region_size = (std::min)(budget, aligned_backing);
const size_t region_base = Common::AlignDown(
(std::min)(preferred_offset, aligned_backing - region_size), window_size);
const size_t num_windows = region_size / window_size;
std::vector<AHardwareBuffer*> buffers;
std::vector<int> buffer_fds;
const auto cleanup = [&] {
for (AHardwareBuffer* buffer : buffers) {
AHardwareBuffer_release(buffer);
}
buffers.clear();
buffer_fds.clear();
};
for (size_t i = 0; i < num_windows; ++i) {
const AHardwareBuffer_Desc desc = MakeBlobDesc(window_size);
AHardwareBuffer* buffer{};
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
cleanup();
return false;
}
buffers.push_back(buffer);
const NativeHandle* const handle = get_native_handle(buffer);
if (handle == nullptr || handle->numFds < 1) {
cleanup();
return false;
}
const int buffer_fd = handle->data[0];
const off_t buffer_len = lseek(buffer_fd, 0, SEEK_END);
if (buffer_len < static_cast<off_t>(window_size)) {
cleanup();
return false;
}
buffer_fds.push_back(buffer_fd);
}
u8* const base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
if (base == MAP_FAILED) {
cleanup();
return false;
}
const auto map_over_reservation = [&](size_t offset, size_t len, int map_fd,
off_t map_offset) {
if (len == 0) {
return true;
}
if (mmap(base + offset, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, map_fd,
map_offset) == MAP_FAILED) {
munmap(base, backing_size);
cleanup();
return false;
}
return true;
};
if (!map_over_reservation(0, region_base, fd, 0)) {
return false;
}
for (size_t i = 0; i < num_windows; ++i) {
if (!map_over_reservation(region_base + i * window_size, window_size, buffer_fds[i],
0)) {
return false;
}
}
const size_t tail_offset = region_base + region_size;
if (!map_over_reservation(tail_offset, backing_size - tail_offset, fd,
static_cast<off_t>(tail_offset))) {
return false;
}
backing_base = base;
ahb_windows = std::move(buffers);
ahb_fds = std::move(buffer_fds);
ahb_window_size = window_size;
ahb_base = region_base;
ahb_bytes = region_size;
committed_backing_size.store(region_size, std::memory_order_relaxed);
return true;
}
void MapBackingRange(size_t virtual_offset, size_t host_offset, size_t length, int prot_flags) {
while (length > 0) {
int map_fd = fd;
off_t map_offset = static_cast<off_t>(host_offset);
size_t chunk = length;
if (host_offset < ahb_base) {
chunk = (std::min)(chunk, ahb_base - host_offset);
} else if (host_offset < ahb_base + ahb_bytes) {
const size_t relative = host_offset - ahb_base;
const size_t window = relative / ahb_window_size;
const size_t local = relative % ahb_window_size;
map_fd = ahb_fds[window];
map_offset = static_cast<off_t>(local);
chunk = (std::min)(chunk, ahb_window_size - local);
}
void* const ret = mmap(virtual_base + virtual_offset, chunk, prot_flags,
MAP_SHARED | MAP_FIXED, map_fd, map_offset);
ASSERT_MSG(ret != MAP_FAILED, "mmap: {}", strerror(errno));
virtual_offset += chunk;
host_offset += chunk;
length -= chunk;
}
}
std::span<AHardwareBuffer* const> AhbWindows() const noexcept {
return ahb_windows;
}
size_t AhbWindowSize() const noexcept {
return ahb_bytes != 0 ? ahb_window_size : 0;
}
size_t AhbBase() const noexcept {
return ahb_base;
}
#endif
~Impl() {
Release();
}
@@ -587,6 +853,12 @@ public:
#ifdef ARCHITECTURE_arm64
if (True(perms & MemoryPermission::Execute))
prot_flags |= PROT_EXEC;
#endif
#ifdef __ANDROID__
if (ahb_bytes != 0) {
MapBackingRange(virtual_offset, host_offset, length, prot_flags);
return;
}
#endif
int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
@@ -632,8 +904,18 @@ public:
virtual_base = nullptr;
}
bool IsBackingShared() const noexcept {
#ifdef __ANDROID__
if (ahb_bytes != 0) {
return true;
}
#endif
return fd >= 0;
}
const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
const size_t preferred_offset;
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
@@ -656,6 +938,18 @@ private:
int ret = close(fd);
ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno));
}
#ifdef __ANDROID__
for (AHardwareBuffer* buffer : ahb_windows) {
AHardwareBuffer_release(buffer);
}
ahb_windows.clear();
ahb_fds.clear();
if (ahb_bytes != 0) {
committed_backing_size.store(0, std::memory_order_relaxed);
ahb_bytes = 0;
}
#endif
}
void AdjustMap(size_t* virtual_offset, size_t* length) {
@@ -681,11 +975,19 @@ private:
int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
FreeRegionManager free_manager{};
#ifdef __ANDROID__
std::vector<AHardwareBuffer*> ahb_windows;
std::vector<int> ahb_fds;
size_t ahb_window_size{};
size_t ahb_base{};
size_t ahb_bytes{};
#endif
};
#endif // ^^^ POSIX ^^^
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
: backing_size(backing_size_)
, virtual_size(virtual_size_)
{
@@ -697,7 +999,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
#else
// Try to allocate a fastmem arena.
// The implementation will fail with std::bad_alloc on errors.
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize);
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize, preferred_offset_);
if (impl->Init()) {
backing_base = impl->backing_base;
virtual_base = impl->virtual_base;
@@ -767,6 +1069,38 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f
std::memset(backing_base + physical_offset, fill_value, length);
}
std::span<AHardwareBuffer* const> HostMemory::BackingHardwareBuffers() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbWindows() : std::span<AHardwareBuffer* const>{};
#else
return {};
#endif
}
size_t HostMemory::BackingHardwareBufferWindowSize() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbWindowSize() : 0;
#else
return 0;
#endif
}
bool HostMemory::IsBackingShared() const noexcept {
#if defined(__OPENORBIS__) || defined(__managarm__)
return false;
#else
return impl && impl->IsBackingShared();
#endif
}
size_t HostMemory::BackingHardwareBufferBase() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbBase() : 0;
#else
return 0;
#endif
}
void HostMemory::EnableDirectMappedAddress() {
#if !(defined(__OPENORBIS__) || defined(__managarm__))
if (impl) {
+18 -1
View File
@@ -8,12 +8,17 @@
#include <memory>
#include <optional>
#include <span>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/virtual_buffer.h"
struct AHardwareBuffer;
namespace Common {
[[nodiscard]] u64 GetCommittedBackingSize() noexcept;
enum class MemoryPermission : u32 {
Read = 1 << 0,
Write = 1 << 1,
@@ -28,7 +33,7 @@ DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
*/
class HostMemory {
public:
explicit HostMemory(size_t backing_size_, size_t virtual_size_);
explicit HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_ = 0);
~HostMemory();
/**
@@ -62,6 +67,18 @@ public:
return backing_base;
}
[[nodiscard]] size_t BackingSize() const noexcept {
return backing_size;
}
[[nodiscard]] std::span<AHardwareBuffer* const> BackingHardwareBuffers() const noexcept;
[[nodiscard]] size_t BackingHardwareBufferWindowSize() const noexcept;
[[nodiscard]] size_t BackingHardwareBufferBase() const noexcept;
[[nodiscard]] bool IsBackingShared() const noexcept;
[[nodiscard]] u8* VirtualBasePointer() noexcept {
return virtual_base;
}
+55
View File
@@ -17,6 +17,10 @@
#endif
#endif
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "common/memory_detect.h"
namespace Common {
@@ -69,4 +73,55 @@ const MemoryInfo& GetMemInfo() {
return mem_info;
}
u64 GetAvailablePhysicalMemory() {
#ifdef _WIN32
MEMORYSTATUSEX memorystatus;
memorystatus.dwLength = sizeof(memorystatus);
if (GlobalMemoryStatusEx(&memorystatus)) {
return memorystatus.ullAvailPhys;
}
return 0;
#elif defined(__linux__)
if (std::FILE* const file = std::fopen("/proc/meminfo", "re")) {
char line[256];
u64 available = 0;
while (std::fgets(line, sizeof(line), file) != nullptr) {
if (std::strncmp(line, "MemAvailable:", 13) == 0) {
available = std::strtoull(line + 13, nullptr, 10) * 1024ULL;
break;
}
}
std::fclose(file);
if (available != 0) {
return available;
}
}
struct sysinfo info;
if (sysinfo(&info) == 0) {
const u64 unit = info.mem_unit != 0 ? info.mem_unit : 1ULL;
return (static_cast<u64>(info.freeram) + static_cast<u64>(info.bufferram)) * unit;
}
return 0;
#else
return 0;
#endif
}
u64 GetMaxMapCount() {
#ifdef __linux__
if (std::FILE* const file = std::fopen("/proc/sys/vm/max_map_count", "re")) {
char line[32];
u64 count = 0;
if (std::fgets(line, sizeof(line), file) != nullptr) {
count = std::strtoull(line, nullptr, 10);
}
std::fclose(file);
return count;
}
return 0;
#else
return 0;
#endif
}
} // namespace Common
+4
View File
@@ -18,4 +18,8 @@ struct MemoryInfo {
*/
[[nodiscard]] const MemoryInfo& GetMemInfo();
[[nodiscard]] u64 GetAvailablePhysicalMemory();
[[nodiscard]] u64 GetMaxMapCount();
} // namespace Common
+14 -22
View File
@@ -260,10 +260,10 @@ struct Values {
Category::Cpu};
SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto,
"cpu_accuracy", Category::Cpu};
SwitchableSetting<CpuClock> fast_cpu_time{linkage,
CpuClock::Off,
SwitchableSetting<CpuClock> cpu_clock{linkage,
CpuClock::Normal,
"fast_cpu_time",
Category::Cpu,
Category::System,
Specialization::Default,
true,
true};
@@ -540,13 +540,13 @@ struct Values {
#endif
// Renderer Hacks //
SwitchableSetting<GpuOverclock> fast_gpu_time{linkage,
GpuOverclock::Medium,
"fast_gpu_time",
Category::RendererHacks,
Specialization::Default,
true,
true};
SwitchableSetting<GpuClock> gpu_clock{linkage,
GpuClock::Boost,
"fast_gpu_time",
Category::System,
Specialization::Default,
true,
true};
SwitchableSetting<bool> skip_cpu_inner_invalidation{linkage,
false,
@@ -587,6 +587,9 @@ struct Values {
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererHacks};
SwitchableSetting<bool> use_unified_memory{linkage, false, "use_unified_memory",
Category::RendererHacks};
SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage,
GpuUnswizzleSize::Large,
"gpu_unswizzle_texture_size",
@@ -702,15 +705,7 @@ struct Values {
// Controls
InputSetting<std::array<PlayerInput, 10>> players;
Setting<bool> disable_wgi_xinput{
linkage, false, "disable_wgi_xinput", Category::Controls, Specialization::Default,
// Only read/write disable_wgi_xinput on Windows platforms
#ifdef _WIN32
true
#else
false
#endif
};
Setting<bool> enable_raw_input{
linkage, false, "enable_raw_input", Category::Controls, Specialization::Default,
// Only read/write enable_raw_input on Windows platforms
@@ -840,9 +835,6 @@ struct Values {
Setting<bool> gpu_log_driver_debug{linkage, true, "gpu_log_driver_debug", Category::Debugging};
Setting<s32> gpu_log_ring_buffer_size{linkage, 512, "gpu_log_ring_buffer_size",
Category::Debugging};
Setting<HomebrewNxlinkServerMode> homebrew_nxlink_server_mode{
linkage, HomebrewNxlinkServerMode::Disabled, "homebrew_nxlink_server_mode",
Category::Debugging};
SwitchableSetting<u16, true> debug_knobs{linkage,
0,
+2 -3
View File
@@ -140,7 +140,7 @@ ENUM(DmaAccuracy, Default, Unsafe, Safe);
ENUM(GpuFenceBehavior, Default, Immediate, Balanced, Accurate, Strict);
ENUM(CpuBackend, Dynarmic, Nce);
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid, Debugging);
ENUM(CpuClock, Off, Boost, Fast)
ENUM(CpuClock, Normal, Boost, Overclock)
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb, Memory_10Gb, Memory_12Gb);
ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
ENUM(FullscreenMode, Borderless, Exclusive);
@@ -152,14 +152,13 @@ ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
ENUM(ConsoleMode, Handheld, Docked);
ENUM(AppletMode, HLE, LLE);
ENUM(SpirvOptimizeMode, Never, OnLoad, Always);
ENUM(GpuOverclock, Normal, Medium, High)
ENUM(GpuClock, Normal, Boost, Overclock)
ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge)
ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High)
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
ENUM(TemperatureUnits, Celsius, Fahrenheit)
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
ENUM(HomebrewNxlinkServerMode, Disabled, EdenLog, HostStdout, File)
ENUM(GameListMode, TreeView, GridView, CarouselView);
ENUM(SpeedMode, Standard, Turbo, Slow);
+508 -36
View File
@@ -1,5 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -9,6 +10,7 @@
#include <string>
#include <thread>
#include "common/adpf.h"
#include "common/error.h"
#include "common/logging.h"
#include "common/assert.h"
@@ -39,6 +41,402 @@
#include <unistd.h>
#endif
#ifdef __linux__
#include <sys/resource.h>
#include <algorithm>
namespace {
constexpr int NICE_AUDIO = -16;
constexpr int NICE_URGENT_DISPLAY = -8;
constexpr int NICE_DISPLAY = -4;
constexpr int NICE_DEFAULT = 0;
constexpr int NICE_BACKGROUND = 10;
int LowestAllowedNice() {
static const int lowest = [] {
rlimit limit{};
if (getrlimit(RLIMIT_NICE, &limit) != 0) {
return 0;
}
if (limit.rlim_cur >= 40) {
return -20;
}
return 20 - static_cast<int>(limit.rlim_cur);
}();
return lowest;
}
int NiceValueForPriority(Common::ThreadPriority priority) {
const int wanted = [priority] {
switch (priority) {
case Common::ThreadPriority::Low: return NICE_BACKGROUND;
case Common::ThreadPriority::Normal: return NICE_DEFAULT;
case Common::ThreadPriority::High: return NICE_DISPLAY;
case Common::ThreadPriority::VeryHigh: return NICE_URGENT_DISPLAY;
case Common::ThreadPriority::Critical: return NICE_AUDIO;
default: return NICE_DEFAULT;
}
}();
return (std::max)(wanted, (std::min)(NICE_DEFAULT, LowestAllowedNice()));
}
} // Anonymous namespace
#endif
#ifdef __ANDROID__
#include <sys/utsname.h>
#include <atomic>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <mutex>
#include <vector>
namespace {
constexpr size_t ANDROID_MINIMUM_PERFORMANCE_CORES = 4;
// A core counts as a performance core while it is within this much of the fastest one.
constexpr s64 ANDROID_PERFORMANCE_CAPACITY_PERCENT = 50;
constexpr std::chrono::nanoseconds ANDROID_POLICY_POLL_INTERVAL = std::chrono::milliseconds{500};
enum class CoreGroup {
Unrestricted,
Performance,
Efficiency,
};
struct ThreadPolicy {
pid_t tid;
CoreGroup group;
s32 nice_value;
bool has_nice;
};
struct CoreInfo {
s64 weight;
u64 midr;
s32 cpu;
};
struct CpuTopologyState {
std::mutex topology_mutex;
cpu_set_t allowed{};
cpu_set_t performance{};
cpu_set_t efficiency{};
bool separated = false;
bool initialized = false;
pid_t canary_tid = 0;
cpu_set_t canary_mask{};
bool canary_valid = false;
std::atomic<s64> next_poll_ns{0};
std::mutex policy_mutex;
std::vector<ThreadPolicy> policies;
};
CpuTopologyState& State() {
static CpuTopologyState* const state = new CpuTopologyState();
return *state;
}
struct PolicyRegistration {
~PolicyRegistration() {
const pid_t tid = gettid();
::Common::ADPF::RemoveCurrentThread();
CpuTopologyState& state = State();
std::scoped_lock lock{state.policy_mutex};
std::erase_if(state.policies,
[tid](const ThreadPolicy& policy) { return policy.tid == tid; });
}
};
thread_local PolicyRegistration t_policy_registration;
s32 PossibleCpuCount() {
std::ifstream file("/sys/devices/system/cpu/possible");
std::string list;
if (file && std::getline(file, list) && !list.empty()) {
s64 highest = -1;
const char* cursor = list.c_str();
while (*cursor != '\0') {
char* end = nullptr;
const s64 value = std::strtol(cursor, &end, 10);
if (end == cursor) {
break;
}
highest = (std::max)(highest, value);
cursor = end;
while (*cursor == '-' || *cursor == ',') {
++cursor;
}
}
if (highest >= 0) {
return static_cast<s32>((std::min<s64>)(highest + 1, CPU_SETSIZE));
}
}
const s64 configured = sysconf(_SC_NPROCESSORS_CONF);
if (configured > 0) {
return static_cast<s32>((std::min<s64>)(configured, CPU_SETSIZE));
}
return static_cast<s32>((std::min<s64>)(std::thread::hardware_concurrency(), CPU_SETSIZE));
}
s64 ReadCpuScalar(s32 cpu, const char* node) {
s64 value = 0;
std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) + "/" + node);
if (!file || !(file >> value) || value <= 0) {
return 0;
}
return value;
}
u64 ReadCpuMidr(s32 cpu) {
u64 midr = 0;
std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) +
"/regs/identification/midr_el1");
if (!file || !(file >> std::hex >> midr)) {
return 0;
}
return midr;
}
std::vector<CoreInfo> CollectCores(const cpu_set_t& allowed, s32 total, const char* node,
bool require_all) {
std::vector<CoreInfo> cores;
for (s32 cpu = 0; cpu < total; ++cpu) {
if (!CPU_ISSET(cpu, &allowed)) {
continue;
}
const s64 weight = ReadCpuScalar(cpu, node);
if (weight <= 0) {
if (require_all) {
return {};
}
LOG_WARNING(Common, "Could not read {} for CPU {}, treating it as an efficiency core",
node, cpu);
continue;
}
cores.push_back(CoreInfo{weight, ReadCpuMidr(cpu), cpu});
}
return cores;
}
bool WeightsAreUniform(const std::vector<CoreInfo>& cores) {
return std::all_of(cores.begin(), cores.end(),
[&](const CoreInfo& core) { return core.weight == cores.front().weight; });
}
bool MidrsAreDistinct(const std::vector<CoreInfo>& cores) {
return std::none_of(cores.begin(), cores.end(),
[](const CoreInfo& core) { return core.midr == 0; }) &&
std::any_of(cores.begin(), cores.end(),
[&](const CoreInfo& core) { return core.midr != cores.front().midr; });
}
void ComputeTopologyLocked(CpuTopologyState& state) {
state.initialized = true;
state.separated = false;
CPU_ZERO(&state.allowed);
CPU_ZERO(&state.performance);
CPU_ZERO(&state.efficiency);
if (sched_getaffinity(getpid(), sizeof(state.allowed), &state.allowed) != 0) {
LOG_WARNING(Common, "Could not query process CPU affinity: {}",
::Common::GetLastErrorMsg());
return;
}
const s32 total = PossibleCpuCount();
auto cores = CollectCores(state.allowed, total, "cpu_capacity", true);
if (cores.empty() || (WeightsAreUniform(cores) && MidrsAreDistinct(cores))) {
auto by_frequency = CollectCores(state.allowed, total, "cpufreq/cpuinfo_max_freq", false);
if (!by_frequency.empty()) {
cores = std::move(by_frequency);
}
}
if (cores.empty()) {
LOG_WARNING(Common, "Could not determine CPU topology, thread placement is disabled");
return;
}
if (WeightsAreUniform(cores)) {
if (MidrsAreDistinct(cores)) {
LOG_WARNING(Common, "CPU clusters differ but rank identically, thread placement is "
"disabled");
} else {
LOG_INFO(Common, "CPU cores are symmetric, thread placement is disabled");
}
return;
}
std::sort(cores.begin(), cores.end(), [](const CoreInfo& lhs, const CoreInfo& rhs) {
if (lhs.weight != rhs.weight) {
return lhs.weight > rhs.weight;
}
return lhs.cpu < rhs.cpu;
});
const s64 fastest = cores.front().weight;
size_t taken = 0;
for (const auto& core : cores) {
const bool fast_enough =
core.weight * 100 >= fastest * ANDROID_PERFORMANCE_CAPACITY_PERCENT;
if (!fast_enough && taken >= ANDROID_MINIMUM_PERFORMANCE_CORES) {
break;
}
CPU_SET(core.cpu, &state.performance);
++taken;
}
if (taken == 0) {
return;
}
for (s32 cpu = 0; cpu < total; ++cpu) {
if (CPU_ISSET(cpu, &state.allowed) && !CPU_ISSET(cpu, &state.performance)) {
CPU_SET(cpu, &state.efficiency);
}
}
state.separated = CPU_COUNT(&state.efficiency) > 0;
LOG_INFO(Common, "CPU topology: {} performance cores, {} efficiency cores, separation {}",
CPU_COUNT(&state.performance), CPU_COUNT(&state.efficiency),
state.separated ? "enabled" : "unavailable");
}
void EnsureTopologyLocked(CpuTopologyState& state) {
if (!state.initialized) {
ComputeTopologyLocked(state);
}
}
void RefreshTopologyLocked(CpuTopologyState& state) {
if (!state.initialized) {
ComputeTopologyLocked(state);
return;
}
cpu_set_t current;
CPU_ZERO(&current);
if (sched_getaffinity(getpid(), sizeof(current), &current) != 0) {
return;
}
if (std::memcmp(&current, &state.allowed, sizeof(current)) != 0) {
ComputeTopologyLocked(state);
}
}
bool ApplyCoreGroupLocked(CpuTopologyState& state, pid_t tid, CoreGroup group,
bool* gone = nullptr) {
const bool restrict_group = group != CoreGroup::Unrestricted && state.separated;
const cpu_set_t* mask = &state.allowed;
if (restrict_group) {
mask = group == CoreGroup::Performance ? &state.performance : &state.efficiency;
}
if (CPU_COUNT(mask) == 0) {
return false;
}
if (sched_setaffinity(tid, sizeof(*mask), mask) != 0) {
if (gone != nullptr && errno == ESRCH) {
*gone = true;
return false;
}
LOG_WARNING(Common, "Could not restrict thread {} to its core group: {}", tid,
::Common::GetLastErrorMsg());
return false;
}
return true;
}
bool KernelPreservesRequestedAffinity() {
utsname info{};
if (uname(&info) != 0) {
return false;
}
s32 major = 0;
s32 minor = 0;
if (std::sscanf(info.release, "%d.%d", &major, &minor) != 2) {
return false;
}
return major > 6 || (major == 6 && minor >= 2);
}
void SnapshotCanaryLocked(CpuTopologyState& state) {
state.canary_valid = false;
if (!state.separated) {
return;
}
for (const auto& policy : state.policies) {
if (policy.group == CoreGroup::Unrestricted) {
continue;
}
cpu_set_t mask;
CPU_ZERO(&mask);
if (sched_getaffinity(policy.tid, sizeof(mask), &mask) != 0) {
continue;
}
state.canary_tid = policy.tid;
state.canary_mask = mask;
state.canary_valid = true;
return;
}
}
bool DueForPoll(CpuTopologyState& state) {
const auto now = std::chrono::steady_clock::now().time_since_epoch();
const s64 now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
s64 next = state.next_poll_ns.load(std::memory_order_relaxed);
if (now_ns < next) {
return false;
}
return state.next_poll_ns.compare_exchange_strong(
next, now_ns + ANDROID_POLICY_POLL_INTERVAL.count(), std::memory_order_relaxed);
}
ThreadPolicy& AcquirePolicyLocked(CpuTopologyState& state, pid_t tid) {
for (auto& policy : state.policies) {
if (policy.tid == tid) {
return policy;
}
}
return state.policies.emplace_back(ThreadPolicy{tid, CoreGroup::Unrestricted, 0, false});
}
void SetCurrentThreadCoreGroup(CoreGroup group) {
const pid_t tid = gettid();
(void)&t_policy_registration;
CoreGroup effective = group;
if (tid == getpid() && group != CoreGroup::Unrestricted) {
LOG_WARNING(Common, "Refusing to place the main thread: the CPU topology is read from it");
effective = CoreGroup::Unrestricted;
}
CpuTopologyState& state = State();
std::scoped_lock topology_lock{state.topology_mutex};
EnsureTopologyLocked(state);
ApplyCoreGroupLocked(state, tid, effective);
std::scoped_lock policy_lock{state.policy_mutex};
AcquirePolicyLocked(state, tid).group = effective;
if (!state.canary_valid) {
SnapshotCanaryLocked(state);
}
}
void RememberCurrentThreadNice(pid_t tid, s32 nice_value) {
(void)&t_policy_registration;
CpuTopologyState& state = State();
std::scoped_lock lock{state.policy_mutex};
ThreadPolicy& policy = AcquirePolicyLocked(state, tid);
policy.nice_value = nice_value;
policy.has_nice = true;
}
} // Anonymous namespace
#endif
#include "common/cpu_features.h"
#ifdef ARCHITECTURE_x86_64
#ifdef _MSC_VER
@@ -48,7 +446,6 @@
#endif
#include "common/x64/rdtsc.h"
#endif
#include "core/core_timing.h"
namespace Common {
@@ -78,21 +475,31 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
}
}();
set_thread_priority(find_thread(NULL), priority);
#else
pthread_t this_thread = pthread_self();
const auto scheduling_type = SCHED_OTHER;
s32 max_prio = sched_get_priority_max(scheduling_type);
s32 min_prio = sched_get_priority_min(scheduling_type);
u32 level = (std::max)(u32(new_priority) + 1, 4U);
struct sched_param params;
if (max_prio > min_prio) {
params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
} else {
params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
#elif defined(__ANDROID__)
const int nice_value = NiceValueForPriority(new_priority);
const pid_t tid = gettid();
if (setpriority(PRIO_PROCESS, static_cast<id_t>(tid), nice_value) != 0) {
LOG_WARNING(Common, "Could not set thread nice value to {}: {}", nice_value,
GetLastErrorMsg());
return;
}
RememberCurrentThreadNice(tid, nice_value);
#elif defined(__linux__)
const int nice_value = NiceValueForPriority(new_priority);
if (setpriority(PRIO_PROCESS, 0, nice_value) != 0) {
LOG_DEBUG(Common, "Could not set thread nice value to {}: {}", nice_value,
GetLastErrorMsg());
}
#else
const s32 max_prio = sched_get_priority_max(SCHED_OTHER);
const s32 min_prio = sched_get_priority_min(SCHED_OTHER);
if (max_prio > min_prio) {
const u32 level = (std::min)(static_cast<u32>(new_priority), 4U);
sched_param params{};
params.sched_priority =
min_prio + static_cast<s32>(static_cast<u32>(max_prio - min_prio) * level) / 4;
pthread_setschedparam(pthread_self(), SCHED_OTHER, &params);
}
pthread_setschedparam(this_thread, scheduling_type, &params);
#endif
}
@@ -132,29 +539,94 @@ void SetCurrentThreadName(const char* name) {
#endif
}
void PinCurrentThreadToPerformanceCore(size_t core_id) {
ASSERT(core_id < 4);
// If we set a flag for a CPU that doesn't exist, the thread may not be allowed to
// run in ANY processor!
auto const total_cores = std::thread::hardware_concurrency();
if (core_id < total_cores) {
void SetCurrentThreadToPerformanceCores() {
#if defined(__ANDROID__)
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(core_id, &set);
sched_setaffinity(pthread_self(), sizeof(set), &set);
#elif defined(__linux__) || defined(__FreeBSD__)
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(core_id, &set);
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
#elif defined(_WIN32)
DWORD set = 1UL << core_id;
SetThreadAffinityMask(GetCurrentThread(), set);
#else
// No pin functionality implemented
#endif
if (ADPF::AddCurrentThread(ADPF::Session::Render)) {
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
return;
}
SetCurrentThreadCoreGroup(CoreGroup::Performance);
#endif
}
void SetCurrentThreadToEfficiencyCores() {
#if defined(__ANDROID__)
if (ADPF::AddCurrentThread(ADPF::Session::Background)) {
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
return;
}
SetCurrentThreadCoreGroup(CoreGroup::Efficiency);
#endif
}
void SetCurrentThreadToBackgroundWork() {
#if defined(__ANDROID__)
ADPF::AddCurrentThread(ADPF::Session::Background);
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
#endif
}
void SetCurrentThreadToAllCores() {
#if defined(__ANDROID__)
ADPF::RemoveCurrentThread();
SetCurrentThreadCoreGroup(CoreGroup::Unrestricted);
#endif
}
void RefreshThreadPolicies() {
#if defined(__ANDROID__)
CpuTopologyState& state = State();
std::scoped_lock topology_lock{state.topology_mutex};
RefreshTopologyLocked(state);
std::scoped_lock policy_lock{state.policy_mutex};
std::erase_if(state.policies, [&state](const ThreadPolicy& policy) {
bool gone = false;
if (policy.has_nice &&
setpriority(PRIO_PROCESS, static_cast<id_t>(policy.tid), policy.nice_value) != 0 &&
errno == ESRCH) {
gone = true;
}
if (!gone) {
ApplyCoreGroupLocked(state, policy.tid, policy.group, &gone);
}
return gone;
});
SnapshotCanaryLocked(state);
#endif
}
void PollThreadPolicies() {
#if defined(__ANDROID__)
static const bool needed = !KernelPreservesRequestedAffinity();
if (!needed) {
return;
}
CpuTopologyState& state = State();
if (!DueForPoll(state)) {
return;
}
pid_t tid;
cpu_set_t expected;
{
std::scoped_lock lock{state.topology_mutex};
if (!state.canary_valid) {
return;
}
tid = state.canary_tid;
expected = state.canary_mask;
}
cpu_set_t current;
CPU_ZERO(&current);
if (sched_getaffinity(tid, sizeof(current), &current) == 0 &&
std::memcmp(&current, &expected, sizeof(current)) == 0) {
return;
}
RefreshThreadPolicies();
#endif
}
#ifdef ARCHITECTURE_x86_64
+13 -1
View File
@@ -99,8 +99,20 @@ enum class ThreadPriority : u32 {
Critical = 4,
};
enum class ThreadPlacement : u32 {
Default = 0,
Background = 1,
Efficiency = 2,
};
void SetCurrentThreadPriority(ThreadPriority new_priority);
void SetCurrentThreadName(const char* name);
void PinCurrentThreadToPerformanceCore(size_t core_id);
void SetCurrentThreadToPerformanceCores();
void SetCurrentThreadToEfficiencyCores();
void SetCurrentThreadToBackgroundWork();
void SetCurrentThreadToAllCores();
void RefreshThreadPolicies();
void PollThreadPolicies();
} // namespace Common
+18 -3
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-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -37,10 +37,25 @@ class StatefulThreadWorker {
using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>;
public:
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {})
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {},
ThreadPlacement placement = ThreadPlacement::Default)
: workers_queued{num_workers}, thread_name{std::move(name)} {
const auto lambda = [this, func](std::stop_token stop_token) {
const auto lambda = [this, func, placement](std::stop_token stop_token) {
Common::SetCurrentThreadName(thread_name.c_str());
if (placement != ThreadPlacement::Default) {
Common::SetCurrentThreadPriority(ThreadPriority::Low);
}
switch (placement) {
case ThreadPlacement::Efficiency:
Common::SetCurrentThreadToEfficiencyCores();
break;
case ThreadPlacement::Background:
Common::SetCurrentThreadToBackgroundWork();
break;
default:
Common::SetCurrentThreadToAllCores();
break;
}
{
[[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func()};
while (!stop_token.stop_requested()) {
+8
View File
@@ -208,4 +208,12 @@ UUID UUID::MakeRandomRFC4122V4() {
return uuid;
}
UUID UUID::MakeRFC4122V5(std::span<u8, 20> sha1) {
UUID uuid{};
std::memcpy(&uuid.uuid, sha1.data(), sizeof(UUID));
uuid.uuid[8] = 0x80 | (uuid.uuid[8] & 0x3F);
uuid.uuid[6] = 0x50 | (uuid.uuid[6] & 0xF);
return uuid;
}
} // namespace Common
+16 -20
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-License-Identifier: GPL-2.0-or-later
@@ -5,6 +8,7 @@
#include <array>
#include <functional>
#include <span>
#include <string>
#include "common/common_types.h"
@@ -86,28 +90,20 @@ struct UUID {
};
}
/**
* Creates a random UUID.
*
* @returns A random UUID.
*/
static UUID MakeRandom();
/// @brief Creates a random UUID.
/// @returns A random UUID.
[[nodiscard]] static UUID MakeRandom();
/**
* Creates a random UUID with a seed.
*
* @param seed A seed to initialize the Mersenne-Twister RNG
*
* @returns A random UUID.
*/
static UUID MakeRandomWithSeed(u32 seed);
/// @brief Creates a random UUID with a seed.
/// @param seed A seed to initialize the Mersenne-Twister RNG
/// @returns A random UUID.
[[nodiscard]] static UUID MakeRandomWithSeed(u32 seed);
/**
* Creates a random UUID. The generated UUID is RFC 4122 Version 4 compliant.
*
* @returns A random UUID that is RFC 4122 Version 4 compliant.
*/
static UUID MakeRandomRFC4122V4();
/// @brief Creates a random UUID. The generated UUID is RFC 4122 Version 4 compliant.
/// @returns A random UUID that is RFC 4122 Version 4 compliant.
[[nodiscard]] static UUID MakeRandomRFC4122V4();
[[nodiscard]] static UUID MakeRFC4122V5(std::span<u8, 20> sha1);
friend constexpr bool operator==(const UUID& lhs, const UUID& rhs) = default;
};
-2
View File
@@ -1139,8 +1139,6 @@ add_library(core STATIC
launch_timestamp_cache.h
loader/deconstructed_rom_directory.cpp
loader/deconstructed_rom_directory.h
loader/homebrew_nxlink.cpp
loader/homebrew_nxlink.h
loader/kip.cpp
loader/kip.h
loader/loader.cpp
+7 -3
View File
@@ -10,6 +10,7 @@
#include "audio_core/audio_core.h"
#include "common/fs/fs.h"
#include "common/logging.h"
#include "common/adpf.h"
#include "common/settings.h"
#include "common/settings_enums.h"
#include "common/string_util.h"
@@ -52,7 +53,6 @@
#include "core/hle/service/set/system_settings_server.h"
#include "core/hle/service/sm/sm.h"
#include "core/internal_network/network.h"
#include "core/loader/homebrew_nxlink.h"
#include "core/loader/loader.h"
#include "core/memory.h"
#include "core/memory/cheat_engine.h"
@@ -119,6 +119,7 @@ struct System::Impl {
is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
unified_memory = Settings::values.use_unified_memory.GetValue();
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
@@ -146,7 +147,8 @@ struct System::Impl {
!device_memory.has_value() ||
is_multicore != Settings::values.use_multi_core.GetValue() ||
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
Settings::MemoryLayout::Memory_4Gb);
Settings::MemoryLayout::Memory_4Gb) ||
unified_memory != Settings::values.use_unified_memory.GetValue();
if (!must_reinitialize) {
return;
@@ -157,6 +159,7 @@ struct System::Impl {
is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout =
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
unified_memory = Settings::values.use_unified_memory.GetValue();
Initialize(system);
}
@@ -386,6 +389,7 @@ struct System::Impl {
void ShutdownMainProcess() {
SetShuttingDown(true);
Common::ADPF::Shutdown();
// Reset per-game flags
Settings::values.use_squashed_iterated_blend = false;
@@ -398,7 +402,6 @@ struct System::Impl {
stop_event.request_stop();
core_timing.SyncPause(false);
Loader::HomebrewNxlink::StopServer();
Network::CancelPendingSocketOperations();
kernel.SuspendEmulation(true);
kernel.CloseServices();
@@ -503,6 +506,7 @@ struct System::Impl {
std::atomic_bool is_powered_on{};
bool is_multicore : 1 = false;
bool extended_memory_layout : 1 = false;
bool unified_memory : 1 = false;
bool exit_locked : 1 = false;
bool exit_requested : 1 = false;
bool nvdec_active : 1 = false;
+20 -3
View File
@@ -23,6 +23,21 @@ namespace Core::Timing {
constexpr s64 MAX_SLICE_LENGTH = 10000;
constexpr u32 CPU_CLOCK_BASE_MHZ = 1020;
constexpr u32 CPU_CLOCK_BOOST_MHZ = 1734;
constexpr u32 CPU_CLOCK_OVERCLOCK_MHZ = 2040;
constexpr u32 CpuClockTargetMhz(Settings::CpuClock clock) {
switch (clock) {
case Settings::CpuClock::Boost:
return CPU_CLOCK_BOOST_MHZ;
case Settings::CpuClock::Overclock:
return CPU_CLOCK_OVERCLOCK_MHZ;
default:
return CPU_CLOCK_BASE_MHZ;
}
}
std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) {
return std::make_shared<EventType>(std::move(callback), std::move(name));
}
@@ -58,7 +73,8 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
if (is_multicore) {
timer_thread = std::jthread([this](std::stop_token stop_token) {
Common::SetCurrentThreadName("HostTiming");
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
Common::SetCurrentThreadToPerformanceCores();
on_thread_init();
has_started = true;
@@ -209,8 +225,9 @@ void CoreTiming::ResetTicks() {
u64 CoreTiming::GetClockTicks() const {
u64 fres = is_multicore ? Common::g_wall_clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
if (auto const overclock = Settings::values.fast_cpu_time.GetValue(); overclock != Settings::CpuClock::Off) {
fres = u64(f64(fres) * (1.7 + 0.3 * u32(overclock)));
if (const u32 target = CpuClockTargetMhz(Settings::values.cpu_clock.GetValue());
target != CPU_CLOCK_BASE_MHZ) {
fres = fres * target / CPU_CLOCK_BASE_MHZ;
}
if (::Settings::values.sync_core_speed.GetValue()) {
auto const ticks = f64(fres);
+1 -6
View File
@@ -174,12 +174,7 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) {
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
#ifdef __ANDROID__
// Aimed specifically for Snapdragon 8 Elite devices
// This kills performance on desktop, but boosts perf for UMA devices
// like the S8E. Mediatek and Mali likely won't suffer.
Common::PinCurrentThreadToPerformanceCore(core);
#endif
Common::SetCurrentThreadToPerformanceCores();
auto& data = core_data[core];
data.host_context = Common::Fiber::ThreadToFiber();
+10 -1
View File
@@ -12,9 +12,18 @@ constexpr size_t VirtualReserveSize = 1ULL << 38;
constexpr size_t VirtualReserveSize = 1ULL << 39;
#endif
namespace {
size_t ApplicationPoolOffset() {
using Init = Kernel::Board::Nintendo::Nx::KSystemControl::Init;
const size_t dram_size = Init::GetIntendedMemorySize();
const size_t application_pool_size = Init::GetApplicationPoolSize();
return dram_size > application_pool_size ? dram_size - application_pool_size : 0;
}
}
DeviceMemory::DeviceMemory()
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
VirtualReserveSize} {}
VirtualReserveSize, ApplicationPoolOffset()} {}
DeviceMemory::~DeviceMemory() = default;
+35
View File
@@ -20,6 +20,8 @@
#include "common/scratch_buffer.h"
#include "common/virtual_buffer.h"
struct AHardwareBuffer;
namespace Core {
constexpr size_t DEVICE_PAGEBITS = 12ULL;
@@ -95,6 +97,34 @@ public:
ApplyOpOnPAddr(address, buffer, operation);
}
u8* GetPhysicalBase() noexcept {
return reinterpret_cast<u8*>(physical_base);
}
const u8* GetPhysicalBase() const noexcept {
return reinterpret_cast<const u8*>(physical_base);
}
size_t GetPhysicalSize() const noexcept {
return physical_size;
}
std::span<AHardwareBuffer* const> GetBackingHardwareBuffers() const noexcept {
return ahb_windows;
}
size_t GetBackingHardwareBufferWindowSize() const noexcept {
return ahb_window_size;
}
size_t GetBackingHardwareBufferBase() const noexcept {
return ahb_base;
}
bool IsBackingShared() const noexcept {
return backing_is_shared;
}
PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const {
PAddr subbits = PAddr(address & page_mask);
auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr;
@@ -171,6 +201,11 @@ private:
std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl;
const uintptr_t physical_base;
const size_t physical_size;
const std::span<AHardwareBuffer* const> ahb_windows;
const size_t ahb_window_size;
const size_t ahb_base;
const bool backing_is_shared;
DeviceInterface* device_inter;
struct TrackedEntry {
+5
View File
@@ -171,6 +171,11 @@ struct DeviceMemoryManagerAllocator {
template <typename Traits>
DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_)
: physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())}
, physical_size{device_memory_.buffer.BackingSize()}
, ahb_windows{device_memory_.buffer.BackingHardwareBuffers()}
, ahb_window_size{device_memory_.buffer.BackingHardwareBufferWindowSize()}
, ahb_base{device_memory_.buffer.BackingHardwareBufferBase()}
, backing_is_shared{device_memory_.buffer.IsBackingShared()}
, device_inter{nullptr}
, compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS))
, tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS)
+15 -16
View File
@@ -274,23 +274,22 @@ public:
explicit NACP(VirtualFile file);
~NACP();
const LanguageEntry& GetLanguageEntry() const;
std::string GetApplicationName() const;
std::string GetDeveloperName() const;
u64 GetTitleId() const;
u64 GetDLCBaseTitleId() const;
std::string GetVersionString() const;
u64 GetDefaultNormalSaveSize() const;
u64 GetDefaultJournalSaveSize() const;
u32 GetSupportedLanguages() const;
std::vector<std::string> GetApplicationNames() const;
std::vector<u8> GetRawBytes() const;
bool GetUserAccountSwitchLock() const;
u64 GetDeviceSaveDataSize() const;
u32 GetParentalControlFlag() const;
const std::array<u8, 0x20>& GetRatingAge() const;
[[nodiscard]] const LanguageEntry& GetLanguageEntry() const;
[[nodiscard]] std::string GetApplicationName() const;
[[nodiscard]] std::string GetDeveloperName() const;
[[nodiscard]] u64 GetTitleId() const;
[[nodiscard]] u64 GetDLCBaseTitleId() const;
[[nodiscard]] std::string GetVersionString() const;
[[nodiscard]] u64 GetDefaultNormalSaveSize() const;
[[nodiscard]] u64 GetDefaultJournalSaveSize() const;
[[nodiscard]] u32 GetSupportedLanguages() const;
[[nodiscard]] std::vector<std::string> GetApplicationNames() const;
[[nodiscard]] std::vector<u8> GetRawBytes() const;
[[nodiscard]] bool GetUserAccountSwitchLock() const;
[[nodiscard]] u64 GetDeviceSaveDataSize() const;
[[nodiscard]] u32 GetParentalControlFlag() const;
[[nodiscard]] const std::array<u8, 0x20>& GetRatingAge() const;
private:
RawNACP raw{};
std::vector<LanguageEntry> language_entries;
};
-1
View File
@@ -12,7 +12,6 @@
namespace FileSys {
enum class OpenMode : u32 {
Default = 0,
Read = (1 << 0),
Write = (1 << 1),
AllowAppend = (1 << 2),
+10
View File
@@ -1156,4 +1156,14 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
return {std::move(nacp), icon_file};
}
[[nodiscard]] PatchManager::Metadata PatchManager::GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept {
const FileSys::PatchManager pm{application_id, system.GetFileSystemController(), system.GetContentProvider()};
auto metadata = pm.GetControlMetadata();
if (metadata.first != nullptr)
return metadata;
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(application_id), system.GetFileSystemController(), system.GetContentProvider()};
return pm_update.GetControlMetadata();
}
} // namespace FileSys
+3
View File
@@ -105,6 +105,9 @@ public:
// Version of GetControlMetadata that takes an arbitrary NCA
[[nodiscard]] Metadata ParseControlNCA(const NCA& nca) const;
/// @brief Gets NACP metadata (accounting for any patches or updates)
[[nodiscard]] static PatchManager::Metadata GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept;
private:
[[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) const;
+4 -7
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -40,7 +40,7 @@ VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
VirtualFile VfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) {
const auto path = Common::FS::SanitizePath(path_);
return root->GetFileRelative(path, perms);
return root->GetFileRelative(path);
}
VirtualFile VfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) {
@@ -201,7 +201,7 @@ std::string VfsFile::GetFullPath() const {
return GetContainingDirectory()->GetFullPath() + '/' + GetName();
}
VirtualFile VfsDirectory::GetFileRelative(std::string_view path, OpenMode perms) const {
VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
auto vec = Common::FS::SplitPathComponents(path);
if (vec.empty()) {
return nullptr;
@@ -224,10 +224,7 @@ VirtualFile VfsDirectory::GetFileRelative(std::string_view path, OpenMode perms)
return nullptr;
}
if (perms == OpenMode::Default) {
return dir->GetFile(vec.back());
}
return dir->GetFileRelative(vec.back(), perms);
return dir->GetFile(vec.back());
}
VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const {
+1 -2
View File
@@ -201,8 +201,7 @@ public:
// Retrieves the file located at path as if the current directory was root. Returns nullptr if
// not found.
virtual VirtualFile GetFileRelative(std::string_view path,
OpenMode perms = OpenMode::Default) const;
virtual VirtualFile GetFileRelative(std::string_view path) const;
// Calls GetFileRelative(path) on the root of the current directory.
virtual VirtualFile GetFileAbsolute(std::string_view path) const;
+2 -2
View File
@@ -27,9 +27,9 @@ VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dir
return VirtualDir(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
}
VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path, OpenMode perms) const {
VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
for (const auto& layer : dirs) {
const auto file = layer->GetFileRelative(path, perms);
const auto file = layer->GetFileRelative(path);
if (file != nullptr)
return file;
}
+1 -5
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -23,8 +20,7 @@ public:
/// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = "");
VirtualFile GetFileRelative(std::string_view path,
OpenMode perms = OpenMode::Default) const override;
VirtualFile GetFileRelative(std::string_view path) const override;
VirtualDir GetDirectoryRelative(std::string_view path) const override;
VirtualFile GetFile(std::string_view file_name) const override;
VirtualDir GetSubdirectory(std::string_view subdir_name) const override;
+23 -51
View File
@@ -12,6 +12,7 @@
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging.h"
#include "core/file_sys/vfs/vfs.h"
#include "core/file_sys/vfs/vfs_real.h"
@@ -45,11 +46,17 @@ bool IsWithinRoot(std::string_view root, std::string_view full_path) {
}
constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) {
if (True(mode & OpenMode::Write) || True(mode & OpenMode::AllowAppend)) {
switch (mode) {
case OpenMode::Read:
return FS::FileAccessMode::Read;
case OpenMode::Write:
case OpenMode::ReadWrite:
case OpenMode::AllowAppend:
case OpenMode::All:
return FS::FileAccessMode::ReadWrite;
default:
return {};
}
return FS::FileAccessMode::Read;
}
} // Anonymous namespace
@@ -87,11 +94,9 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op
std::optional<std::string> parent_path,
OpenMode perms) {
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
const auto open_perms = perms == OpenMode::Default ? OpenMode::Read : perms;
std::scoped_lock lk{list_lock};
const CacheKey cache_key{path, open_perms};
if (auto it = cache.find(cache_key); it != cache.end()) {
if (auto it = cache.find(path); it != cache.end()) {
if (auto file = it->second.lock(); file) {
return file;
}
@@ -105,9 +110,8 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op
this->InsertReferenceIntoListLocked(*reference);
auto file = std::shared_ptr<RealVfsFile>(
new RealVfsFile(*this, std::move(reference), path, open_perms, size,
std::move(parent_path)));
cache[cache_key] = file;
new RealVfsFile(*this, std::move(reference), path, perms, size, std::move(parent_path)));
cache[path] = file;
return file;
}
@@ -120,7 +124,7 @@ VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, OpenMode perms
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
{
std::scoped_lock lk{list_lock};
CloseCachedFileReferenceLocked(path);
cache.erase(path);
}
// Current usages of CreateFile expect to delete the contents of an existing file.
@@ -153,8 +157,8 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_
const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
{
std::scoped_lock lk{list_lock};
CloseCachedFileReferenceLocked(old_path);
CloseCachedFileReferenceLocked(new_path);
cache.erase(old_path);
cache.erase(new_path);
}
if (!FS::RenameFile(old_path, new_path)) {
return nullptr;
@@ -166,15 +170,14 @@ bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
{
std::scoped_lock lk{list_lock};
CloseCachedFileReferenceLocked(path);
cache.erase(path);
}
return FS::RemoveFile(path);
}
VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) {
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
return std::shared_ptr<RealVfsDirectory>(
new RealVfsDirectory(*this, path, perms == OpenMode::Default ? OpenMode::Read : perms));
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
}
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) {
@@ -219,8 +222,8 @@ std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::stri
if (!reference.file) {
this->EvictSingleReferenceLocked();
reference.file = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms),
FS::FileType::BinaryFile, FS::FileShareFlag::ShareReadWriteDelete);
reference.file =
FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile);
if (reference.file) {
num_open_files++;
}
@@ -294,45 +297,15 @@ RealVfsFile::~RealVfsFile() {
base.DropReference(std::move(reference));
}
void RealVfsFilesystem::CloseCachedFileReferenceLocked(const std::string& path) {
for (auto it = cache.lower_bound(CacheKey{path, OpenMode::Default});
it != cache.end() && it->first.first == path;) {
const auto cached_file = it->second.lock();
if (cached_file) {
auto* const real_file = static_cast<RealVfsFile*>(cached_file.get());
auto& reference = real_file->reference;
if (reference && reference->file) {
RemoveReferenceFromListLocked(*reference);
reference->file.reset();
num_open_files--;
InsertReferenceIntoListLocked(*reference);
}
}
it = cache.erase(it);
}
}
std::string RealVfsFile::GetName() const {
#ifdef __ANDROID__
if (!path.empty() && path[0] != '/') {
if (path[0] != '/') {
return FS::Android::GetFilename(path);
}
#endif
return path_components.empty() ? "" : std::string(path_components.back());
}
std::string RealVfsFile::GetFullPath() const {
#ifdef __ANDROID__
if (!path.empty() && path[0] != '/') {
auto out = path;
std::replace(out.begin(), out.end(), '\\', '/');
return out;
}
#endif
return VfsFile::GetFullPath();
}
std::size_t RealVfsFile::GetSize() const {
if (size) {
return *size;
@@ -438,14 +411,13 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
RealVfsDirectory::~RealVfsDirectory() = default;
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path,
OpenMode open_perms) const {
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const {
const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
if (!FS::Exists(full_path) || FS::IsDir(full_path)
|| !IsWithinRoot(FS::SanitizePath(path), full_path)) {
return nullptr;
}
return base.OpenFile(full_path, open_perms == OpenMode::Default ? perms : open_perms);
return base.OpenFile(full_path, perms);
}
VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const {
+2 -7
View File
@@ -10,7 +10,6 @@
#include <mutex>
#include <optional>
#include <string_view>
#include <utility>
#include "common/intrusive_list.h"
#include "core/file_sys/fs_filesystem.h"
#include "core/file_sys/vfs/vfs.h"
@@ -50,9 +49,8 @@ public:
bool DeleteDirectory(std::string_view path) override;
private:
using CacheKey = std::pair<std::string, OpenMode>;
using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType;
std::map<CacheKey, std::weak_ptr<VfsFile>, std::less<>> cache;
std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache;
ReferenceListType open_references;
ReferenceListType closed_references;
std::mutex list_lock;
@@ -65,7 +63,6 @@ private:
std::unique_lock<std::mutex> RefreshReference(const std::string& path, OpenMode perms,
FileReference& reference);
void DropReference(std::unique_ptr<FileReference>&& reference);
void CloseCachedFileReferenceLocked(const std::string& path);
private:
friend class RealVfsDirectory;
@@ -88,7 +85,6 @@ public:
~RealVfsFile() override;
std::string GetName() const override;
std::string GetFullPath() const override;
std::size_t GetSize() const override;
bool Resize(std::size_t new_size) override;
VirtualDir GetContainingDirectory() const override;
@@ -119,8 +115,7 @@ class RealVfsDirectory : public VfsDirectory {
public:
~RealVfsDirectory() override;
VirtualFile GetFileRelative(std::string_view relative_path,
OpenMode perms = OpenMode::Default) const override;
VirtualFile GetFileRelative(std::string_view relative_path) const override;
VirtualDir GetDirectoryRelative(std::string_view relative_path) const override;
VirtualFile GetFile(std::string_view name) const override;
VirtualDir GetSubdirectory(std::string_view name) const override;
-5
View File
@@ -670,11 +670,6 @@ public:
size_t GetHeapRegionSize() const {
return m_heap_region_end - m_heap_region_start;
}
size_t GetCurrentHeapSize() const {
KScopedLightLock lk(m_general_lock);
return m_current_heap_end - m_heap_region_start;
}
size_t GetAliasRegionSize() const {
return m_alias_region_end - m_alias_region_start;
}
-11
View File
@@ -210,11 +210,6 @@ Result KProcess::Initialize(KernelCore& kernel, const Svc::CreateProcessParamete
m_arg_pointer = 0;
m_arg_return_address = 0;
m_main_thread_handle_addr = 0;
m_process_handle_addr = 0;
m_homebrew_next_load_path_addr = 0;
m_homebrew_next_load_argv_addr = 0;
m_is_homebrew_in_place_next_load = false;
m_has_homebrew_nxlink_argv_marker = false;
m_code_size = params.code_num_pages * PageSize;
m_is_application = True(params.flags & Svc::CreateProcessFlag::IsApplication);
@@ -952,7 +947,6 @@ Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) {
stack_top = stack_bottom + stack_size;
m_main_thread_stack_size = stack_size;
m_main_thread_stack_top = stack_top;
}
// Ensure our stack is safe to clean up on exit.
@@ -1011,11 +1005,6 @@ Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) {
if (GetInteger(m_main_thread_handle_addr) != 0) {
this->GetMemory().Write32(m_main_thread_handle_addr, thread_handle);
}
if (GetInteger(m_process_handle_addr) != 0) {
Handle process_handle;
R_TRY(m_handle_table.Add(kernel, std::addressof(process_handle), this));
this->GetMemory().Write32(m_process_handle_addr, process_handle);
}
} else {
main_thread->GetContext().r[0] = 0;
main_thread->GetContext().r[1] = thread_handle;
-52
View File
@@ -6,9 +6,7 @@
#pragma once
#include <array>
#include <map>
#include <string_view>
#include "core/arm/arm_interface.h"
#include "core/file_sys/program_metadata.h"
@@ -89,10 +87,6 @@ private:
KProcessAddress m_arg_pointer{};
KProcessAddress m_arg_return_address{};
KProcessAddress m_main_thread_handle_addr{};
KProcessAddress m_process_handle_addr{};
KProcessAddress m_homebrew_next_load_path_addr{};
KProcessAddress m_homebrew_next_load_argv_addr{};
std::array<char, 16> m_homebrew_nxlink_argv_marker{};
KHandleTable m_handle_table;
KProcessAddress m_plr_address{};
ThreadList m_thread_list{};
@@ -118,7 +112,6 @@ private:
size_t m_code_size{};
size_t m_main_thread_stack_size{};
KProcessAddress m_main_thread_stack_top{};
size_t m_max_process_memory{};
size_t m_memory_release_hint{};
s64 m_schedule_count{};
@@ -146,8 +139,6 @@ private:
bool m_is_suspended : 1 = false;
bool m_is_immortal : 1 = false;
bool m_is_handle_table_initialized : 1 = false;
bool m_is_homebrew_in_place_next_load : 1 = false;
bool m_has_homebrew_nxlink_argv_marker : 1 = false;
private:
Result StartTermination(KernelCore& kernel);
@@ -240,49 +231,6 @@ public:
void SetMainThreadHandleAddr(KProcessAddress addr) {
m_main_thread_handle_addr = addr;
}
void SetProcessHandleAddr(KProcessAddress addr) {
m_process_handle_addr = addr;
}
void SetHomebrewNextLoadBufferAddrs(KProcessAddress path_addr, KProcessAddress argv_addr) {
m_homebrew_next_load_path_addr = path_addr;
m_homebrew_next_load_argv_addr = argv_addr;
}
void SetHomebrewInPlaceNextLoad(bool enabled) {
m_is_homebrew_in_place_next_load = enabled;
}
void SetHomebrewNxlinkArgvMarker(std::string_view marker) {
if (marker.size() != m_homebrew_nxlink_argv_marker.size()) {
m_has_homebrew_nxlink_argv_marker = false;
return;
}
marker.copy(m_homebrew_nxlink_argv_marker.data(), m_homebrew_nxlink_argv_marker.size());
m_has_homebrew_nxlink_argv_marker = true;
}
void ClearHomebrewNxlinkArgvMarker() {
m_has_homebrew_nxlink_argv_marker = false;
}
std::string_view GetHomebrewNxlinkArgvMarker() const {
if (!m_has_homebrew_nxlink_argv_marker) {
return {};
}
return {m_homebrew_nxlink_argv_marker.data(), m_homebrew_nxlink_argv_marker.size()};
}
bool IsHomebrewInPlaceNextLoad() const {
return m_is_homebrew_in_place_next_load;
}
KProcessAddress GetHomebrewNextLoadPathAddr() const {
return m_homebrew_next_load_path_addr;
}
KProcessAddress GetHomebrewNextLoadArgvAddr() const {
return m_homebrew_next_load_argv_addr;
}
size_t GetCodeSize() const {
return m_code_size;
}
KProcessAddress GetMainThreadStackTop() const {
return m_main_thread_stack_top;
}
size_t GetMainStackSize() const {
return m_main_thread_stack_size;
+4 -5
View File
@@ -1,8 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-License-Identifier: GPL-2.0-or-late
// This file is automatically generated using svc_generator.py.
// DO NOT MODIFY IT MANUALLY
@@ -129,7 +128,7 @@ static void SvcWrap_QueryMemory64From32(Core::System& system, std::span<uint64_t
}
static void SvcWrap_ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args) {
ExitProcess64From32(system, args);
ExitProcess64From32(system);
}
static void SvcWrap_CreateThread64From32(Core::System& system, std::span<uint64_t, 8> args) {
@@ -1299,7 +1298,7 @@ static void SvcWrap_QueryMemory64(Core::System& system, std::span<uint64_t, 8> a
}
static void SvcWrap_ExitProcess64(Core::System& system, std::span<uint64_t, 8> args) {
ExitProcess64(system, args);
ExitProcess64(system);
}
static void SvcWrap_CreateThread64(Core::System& system, std::span<uint64_t, 8> args) {
+5 -6
View File
@@ -1,8 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-License-Identifier: GPL-2.0-or-late
// This file is automatically generated using svc_generator.py.
// DO NOT MODIFY IT MANUALLY
@@ -26,7 +25,7 @@ Result SetMemoryAttribute(Core::System& system, uint64_t address, uint64_t size,
Result MapMemory(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
Result UnmapMemory(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
Result QueryMemory(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info, uint64_t address);
void ExitProcess(Core::System& system, std::span<uint64_t, 8> args);
void ExitProcess(Core::System& system);
Result CreateThread(Core::System& system, Handle* out_handle, uint64_t func, uint64_t arg, uint64_t stack_bottom, int32_t priority, int32_t core_id);
Result StartThread(Core::System& system, Handle thread_handle);
void ExitThread(Core::System& system);
@@ -147,7 +146,7 @@ Result SetMemoryAttribute64From32(Core::System& system, uint32_t address, uint32
Result MapMemory64From32(Core::System& system, uint32_t dst_address, uint32_t src_address, uint32_t size);
Result UnmapMemory64From32(Core::System& system, uint32_t dst_address, uint32_t src_address, uint32_t size);
Result QueryMemory64From32(Core::System& system, uint32_t out_memory_info, PageInfo* out_page_info, uint32_t address);
void ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args);
void ExitProcess64From32(Core::System& system);
Result CreateThread64From32(Core::System& system, Handle* out_handle, uint32_t func, uint32_t arg, uint32_t stack_bottom, int32_t priority, int32_t core_id);
Result StartThread64From32(Core::System& system, Handle thread_handle);
void ExitThread64From32(Core::System& system);
@@ -268,7 +267,7 @@ Result SetMemoryAttribute64(Core::System& system, uint64_t address, uint64_t siz
Result MapMemory64(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
Result UnmapMemory64(Core::System& system, uint64_t dst_address, uint64_t src_address, uint64_t size);
Result QueryMemory64(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info, uint64_t address);
void ExitProcess64(Core::System& system, std::span<uint64_t, 8> args);
void ExitProcess64(Core::System& system);
Result CreateThread64(Core::System& system, Handle* out_handle, uint64_t func, uint64_t arg, uint64_t stack_bottom, int32_t priority, int32_t core_id);
Result StartThread64(Core::System& system, Handle thread_handle);
void ExitThread64(Core::System& system);
+6 -215
View File
@@ -4,9 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <vector>
#include "core/core.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/svc.h"
@@ -25,179 +22,6 @@ constexpr bool IsValidSetMemoryPermission(MemoryPermission perm) {
}
}
bool IsHomebrewInPlaceNextLoadCodeRange(const KProcess& process, u64 address, u64 size) {
if (!process.IsHomebrewInPlaceNextLoad()) {
return false;
}
const u64 code_start = GetInteger(process.GetEntryPoint());
const size_t code_size = process.GetCodeSize();
if (code_start == 0 || code_size == 0 || size > code_size || address < code_start) {
return false;
}
return address - code_start <= code_size - size;
}
struct HomebrewInPlaceMemoryBlock {
u64 address;
u64 size;
KMemoryState state;
KMemoryPermission permission;
KMemoryAttribute attribute;
bool use_process_permission;
};
Result SetHomebrewInPlaceMemoryPermissionByBlocks(KProcess& process, u64 address, u64 size,
MemoryPermission perm, Result original_result) {
auto& page_table = process.GetPageTable();
const u64 end = address + size;
const auto requested_permission = ConvertToKMemoryPermission(perm);
std::vector<HomebrewInPlaceMemoryBlock> blocks;
for (u64 cursor = address; cursor < end;) {
KMemoryInfo info;
PageInfo page_info;
const auto query_result = page_table.QueryInfo(std::addressof(info),
std::addressof(page_info), cursor);
if (query_result.IsError()) {
LOG_WARNING(Kernel_SVC,
"NextLoad in-place: split permission query failed "
"address=0x{:016X}, result={:#X}, original={:#X}",
cursor, query_result.raw, original_result.raw);
R_RETURN(original_result);
}
const u64 block_end = (std::min<u64>)(info.GetEndAddress(), end);
if (block_end <= cursor) {
LOG_WARNING(Kernel_SVC,
"NextLoad in-place: split permission walk stalled "
"cursor=0x{:016X}, block=0x{:016X}/0x{:X}, original={:#X}",
cursor, info.GetAddress(), info.GetSize(), original_result.raw);
R_RETURN(original_result);
}
const bool can_reprotect = True(info.GetState() & KMemoryState::FlagCanReprotect);
const bool can_process_reprotect = True(info.GetState() & KMemoryState::FlagCode);
if (!can_reprotect && !can_process_reprotect) {
LOG_WARNING(Kernel_SVC,
"NextLoad in-place: split permission unsupported block "
"address=0x{:016X}, size=0x{:X}, state=0x{:08X}, svc_state={}, "
"perm=0x{:08X}, attr=0x{:08X}, original={:#X}",
cursor, block_end - cursor, static_cast<u32>(info.GetState()),
static_cast<u32>(info.GetSvcState()),
static_cast<u32>(info.GetPermission()),
static_cast<u32>(info.GetAttribute()), original_result.raw);
R_RETURN(original_result);
}
blocks.push_back({
.address = cursor,
.size = block_end - cursor,
.state = info.GetState(),
.permission = info.GetPermission(),
.attribute = info.GetAttribute(),
.use_process_permission = !can_reprotect && can_process_reprotect,
});
cursor = block_end;
}
for (const auto& block : blocks) {
if (block.permission == requested_permission) {
continue;
}
const auto block_result =
block.use_process_permission
? page_table.SetProcessMemoryPermission(block.address, block.size, perm)
: page_table.SetMemoryPermission(block.address, block.size, perm);
if (block_result.IsError()) {
LOG_WARNING(Kernel_SVC,
"NextLoad in-place: split permission block failed "
"address=0x{:016X}, size=0x{:X}, state=0x{:08X}, perm=0x{:08X}, "
"attr=0x{:08X}, result={:#X}, original={:#X}",
block.address, block.size, static_cast<u32>(block.state),
static_cast<u32>(block.permission), static_cast<u32>(block.attribute),
block_result.raw, original_result.raw);
R_RETURN(block_result);
}
}
R_SUCCEED();
}
struct HomebrewInPlaceDeviceSharedBlock {
u64 address;
u64 size;
u16 device_use_count;
};
Result UnlockHomebrewInPlaceDeviceSharedSource(KProcess& process, u64 address, u64 size,
Result original_result) {
auto& page_table = process.GetPageTable();
const u64 end = address + size;
std::vector<HomebrewInPlaceDeviceSharedBlock> blocks;
for (u64 cursor = address; cursor < end;) {
KMemoryInfo info;
PageInfo page_info;
const auto query_result = page_table.QueryInfo(std::addressof(info),
std::addressof(page_info), cursor);
if (query_result.IsError()) {
R_RETURN(original_result);
}
const u64 block_end = (std::min<u64>)(info.GetEndAddress(), end);
if (block_end <= cursor) {
R_RETURN(original_result);
}
const bool can_device_map = True(info.GetState() & KMemoryState::FlagCanDeviceMap);
const bool is_clean_memory =
can_device_map && info.GetPermission() == KMemoryPermission::UserReadWrite &&
info.GetAttribute() == KMemoryAttribute::None && info.m_device_use_count == 0;
const bool is_stale_device_shared =
can_device_map && info.GetPermission() == KMemoryPermission::UserReadWrite &&
info.GetAttribute() == KMemoryAttribute::DeviceShared && info.m_device_use_count > 0;
if (is_clean_memory) {
cursor = block_end;
continue;
}
if (!is_stale_device_shared) {
R_RETURN(original_result);
}
blocks.push_back({
.address = cursor,
.size = block_end - cursor,
.device_use_count = info.m_device_use_count,
});
cursor = block_end;
}
if (blocks.empty()) {
R_RETURN(original_result);
}
for (const auto& block : blocks) {
for (u16 unlock = 0; unlock < block.device_use_count; unlock++) {
const auto unlock_result =
page_table.UnlockForDeviceAddressSpace(block.address, block.size);
if (unlock_result.IsError()) {
LOG_WARNING(Kernel_SVC,
"NextLoad in-place: device-shared source unlock failed "
"address=0x{:016X}, size=0x{:X}, remaining={}, result={:#X}, "
"original={:#X}",
block.address, block.size, block.device_use_count - unlock - 1,
unlock_result.raw, original_result.raw);
R_RETURN(original_result);
}
}
}
R_SUCCEED();
}
// Checks if address + size is greater than the given address
// This can return false if the size causes an overflow of a 64-bit type
// or if the given size is zero.
@@ -268,19 +92,11 @@ Result SetMemoryPermission(Core::System& system, u64 address, u64 size, MemoryPe
R_UNLESS(IsValidSetMemoryPermission(perm), ResultInvalidNewMemoryPermission);
// Validate that the region is in range for the current process.
auto& process = GetCurrentProcess(system.Kernel());
auto& page_table = process.GetPageTable();
auto& page_table = GetCurrentProcess(system.Kernel()).GetPageTable();
R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory);
// Set the memory attribute.
const auto result = page_table.SetMemoryPermission(address, size, perm);
if (result.raw == ResultInvalidCurrentMemory.raw &&
IsHomebrewInPlaceNextLoadCodeRange(process, address, size)) {
R_RETURN(
SetHomebrewInPlaceMemoryPermissionByBlocks(process, address, size, perm, result));
}
R_RETURN(result);
R_RETURN(page_table.SetMemoryPermission(address, size, perm));
}
Result SetMemoryAttribute(Core::System& system, u64 address, u64 size, u32 mask, u32 attr) {
@@ -319,30 +135,14 @@ Result MapMemory(Core::System& system, u64 dst_addr, u64 src_addr, u64 size) {
LOG_TRACE(Kernel_SVC, "called, dst_addr={:#x}, src_addr={:#x}, size={:#x}", dst_addr,
src_addr, size);
auto& process = GetCurrentProcess(system.Kernel());
auto& page_table = process.GetPageTable();
auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()};
if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
result.IsError()) {
return result;
}
const auto result = page_table.MapMemory(dst_addr, src_addr, size);
if (result.raw == ResultInvalidCurrentMemory.raw && process.IsHomebrewInPlaceNextLoad()) {
if (UnlockHomebrewInPlaceDeviceSharedSource(process, src_addr, size, result).IsSuccess()) {
const auto retry_result = page_table.MapMemory(dst_addr, src_addr, size);
if (retry_result.IsError()) {
LOG_WARNING(Kernel_SVC,
"NextLoad in-place: svcMapMemory retry failed after "
"DeviceShared cleanup dst=0x{:016X}, src=0x{:016X}, size=0x{:X}, "
"result={:#X}",
dst_addr, src_addr, size, retry_result.raw);
}
R_RETURN(retry_result);
}
}
R_RETURN(result);
R_RETURN(page_table.MapMemory(dst_addr, src_addr, size));
}
/// Unmaps a region that was previously mapped with svcMapMemory
@@ -350,23 +150,14 @@ Result UnmapMemory(Core::System& system, u64 dst_addr, u64 src_addr, u64 size) {
LOG_TRACE(Kernel_SVC, "called, dst_addr={:#x}, src_addr={:#x}, size={:#x}", dst_addr,
src_addr, size);
auto& process = GetCurrentProcess(system.Kernel());
auto& page_table = process.GetPageTable();
auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()};
if (const Result result{MapUnmapMemorySanityChecks(page_table, dst_addr, src_addr, size)};
result.IsError()) {
return result;
}
const auto result = page_table.UnmapMemory(dst_addr, src_addr, size);
if (result.raw == ResultInvalidCurrentMemory.raw && process.IsHomebrewInPlaceNextLoad()) {
LOG_WARNING(Kernel_SVC,
"NextLoad in-place: svcUnmapMemory failed dst=0x{:016X}, "
"src=0x{:016X}, size=0x{:X}, result={:#X}",
dst_addr, src_addr, size, result.raw);
}
R_RETURN(result);
R_RETURN(page_table.UnmapMemory(dst_addr, src_addr, size));
}
Result SetMemoryPermission64(Core::System& system, uint64_t address, uint64_t size,
+5 -204
View File
@@ -4,216 +4,17 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/input.h"
#include "core/core.h"
#include "core/file_sys/vfs/vfs_types.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/svc.h"
#include "core/hle/service/hid/hid_server.h"
#include "core/hle/service/nvdrv/nvdrv_interface.h"
#include "core/hle/service/sm/sm.h"
#include "core/loader/nro.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/resource_manager.h"
namespace Kernel::Svc {
namespace {
constexpr size_t HomebrewNextLoadPathSize = 0x200;
constexpr size_t HomebrewNextLoadArgvSize = 0x800;
std::string ReadHomebrewString(Core::Memory::Memory& memory, KProcessAddress address,
size_t max_size) {
if (GetInteger(address) == 0) {
return {};
}
return memory.ReadCString(Common::ProcessAddress{GetInteger(address)}, max_size);
}
} // namespace
/// Exits the current process
void ExitProcess(Core::System& system, std::span<uint64_t, 8> args) {
void ExitProcess(Core::System& system) {
auto* current_process = GetCurrentProcessPointer(system.Kernel());
auto* current_thread = GetCurrentThreadPointer(system.Kernel());
LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessId());
const auto next_load_path_addr = current_process->GetHomebrewNextLoadPathAddr();
const auto next_load_argv_addr = current_process->GetHomebrewNextLoadArgvAddr();
if (GetInteger(next_load_path_addr) != 0) {
auto& memory = current_process->GetMemory();
const auto next_load_path =
ReadHomebrewString(memory, next_load_path_addr, HomebrewNextLoadPathSize);
const auto next_load_argv =
ReadHomebrewString(memory, next_load_argv_addr, HomebrewNextLoadArgvSize);
if (!next_load_path.empty()) {
auto guest_path = Common::FS::SanitizePath(next_load_path);
constexpr std::string_view SdmcPrefix{"sdmc:"};
FileSys::VirtualFile file{};
const bool is_sdmc_path = guest_path.rfind(SdmcPrefix, 0) == 0;
const bool is_absolute_guest_path = !guest_path.empty() && guest_path.front() == '/';
if (is_sdmc_path || is_absolute_guest_path) {
auto relative_path =
is_sdmc_path ? guest_path.substr(SdmcPrefix.size()) : guest_path;
while (!relative_path.empty() && relative_path.front() == '/') {
relative_path.erase(relative_path.begin());
}
const auto host_path = Common::FS::GetEdenPath(Common::FS::EdenPath::SDMCDir) /
std::filesystem::path{Common::FS::ToU8String(relative_path)};
const auto host_path_string = Common::FS::PathToUTF8String(host_path);
file = Core::GetGameFileFromPath(system.GetFilesystem(), host_path_string);
if (!file) {
LOG_WARNING(Kernel_SVC,
"NextLoad: failed to open guest_path='{}', host_path='{}'",
next_load_path, host_path_string);
}
} else {
file = Core::GetGameFileFromPath(system.GetFilesystem(), guest_path);
if (!file) {
LOG_WARNING(Kernel_SVC, "NextLoad: failed to open guest_path='{}'",
next_load_path);
}
}
if (file) {
const auto nvdrv =
system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv:s");
if (!nvdrv) {
LOG_WARNING(Kernel_SVC, "NextLoad: NVDRV service unavailable for reset");
} else {
nvdrv->GetModule()->ResetForProcess(current_process);
}
auto& page_table = current_process->GetPageTable();
const u64 heap_start = GetInteger(page_table.GetHeapRegionStart());
const u64 heap_size = page_table.GetHeapRegionSize();
const u64 heap_end = heap_start + heap_size;
if (heap_start != 0 && heap_size != 0 && heap_end > heap_start) {
struct DeviceSharedBlock {
u64 address;
u64 size;
u16 device_use_count;
};
std::vector<DeviceSharedBlock> blocks;
for (u64 cursor = heap_start; cursor < heap_end;) {
KMemoryInfo info;
PageInfo page_info;
const auto query_result =
page_table.QueryInfo(std::addressof(info), std::addressof(page_info),
cursor);
if (query_result.IsError()) {
LOG_WARNING(Kernel_SVC,
"NextLoad: DeviceShared heap cleanup query failed "
"address=0x{:016X}, result={:#X}",
cursor, query_result.raw);
break;
}
const u64 block_end = (std::min<u64>)(info.GetEndAddress(), heap_end);
if (block_end <= cursor) {
LOG_WARNING(Kernel_SVC,
"NextLoad: DeviceShared heap cleanup walk stalled "
"cursor=0x{:016X}, block=0x{:016X}/0x{:X}",
cursor, info.GetAddress(), info.GetSize());
break;
}
const bool is_device_shared =
True(info.GetState() & KMemoryState::FlagCanDeviceMap) &&
info.GetAttribute() == KMemoryAttribute::DeviceShared &&
info.m_device_use_count > 0;
if (is_device_shared) {
blocks.push_back(DeviceSharedBlock{
.address = cursor,
.size = block_end - cursor,
.device_use_count = info.m_device_use_count,
});
}
cursor = block_end;
}
for (const auto& block : blocks) {
for (u16 unlock = 0; unlock < block.device_use_count; unlock++) {
const auto unlock_result =
page_table.UnlockForDeviceAddressSpace(block.address, block.size);
if (unlock_result.IsError()) {
LOG_WARNING(Kernel_SVC,
"NextLoad: DeviceShared heap cleanup unlock failed "
"address=0x{:016X}, size=0x{:X}, remaining={}, "
"result={:#X}",
block.address, block.size,
block.device_use_count - unlock - 1, unlock_result.raw);
break;
}
}
}
}
if (Loader::LoadNroInPlace(system, *current_process, *current_thread, file,
next_load_path, next_load_argv)) {
const auto aruid = current_process->GetProcessId();
if (const auto hid =
system.ServiceManager().GetService<Service::HID::IHidServer>("hid")) {
const auto resource_manager = hid->GetResourceManager();
resource_manager->UnregisterAppletResourceUserId(aruid);
const auto register_result =
resource_manager->RegisterAppletResourceUserId(aruid, true);
if (register_result.IsError()) {
LOG_WARNING(Kernel_SVC,
"NextLoad: failed to register HID applet resource "
"aruid={}, result={:#X}",
aruid, register_result.raw);
}
} else {
LOG_WARNING(Kernel_SVC, "NextLoad: HID service unavailable for reset");
}
auto& hid_core = system.HIDCore();
hid_core.DisableAllControllerConfiguration();
hid_core.SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
hid_core.ReloadInputDevices();
const auto activate_controller = [&](Core::HID::NpadIdType npad_id) {
auto* controller = hid_core.GetEmulatedController(npad_id);
if (controller == nullptr) {
return;
}
(void)controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices,
Common::Input::PollingMode::Active);
};
activate_controller(Core::HID::NpadIdType::Player1);
activate_controller(Core::HID::NpadIdType::Handheld);
system.Kernel().CurrentPhysicalCore().LoadContext(current_thread);
const auto& context = current_thread->GetContext();
for (size_t i = 0; i < args.size(); i++) {
args[i] = context.r[i];
}
return;
}
}
}
}
ASSERT_MSG(current_process->GetState() == KProcess::State::Running,
"Process has already exited");
@@ -331,8 +132,8 @@ Result TerminateProcess(Core::System& system, Handle process_handle) {
R_THROW(ResultNotImplemented);
}
void ExitProcess64(Core::System& system, std::span<uint64_t, 8> args) {
ExitProcess(system, args);
void ExitProcess64(Core::System& system) {
ExitProcess(system);
}
Result GetProcessId64(Core::System& system, uint64_t* out_process_id, Handle process_handle) {
@@ -363,8 +164,8 @@ Result GetProcessInfo64(Core::System& system, int64_t* out_info, Handle process_
R_RETURN(GetProcessInfo(system, out_info, process_handle, info_type));
}
void ExitProcess64From32(Core::System& system, std::span<uint64_t, 8> args) {
ExitProcess(system, args);
void ExitProcess64From32(Core::System& system) {
ExitProcess(system);
}
Result GetProcessId64From32(Core::System& system, uint64_t* out_process_id, Handle process_handle) {
@@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <openssl/evp.h>
#include "common/settings.h"
#include "common/uuid.h"
#include "core/file_sys/control_metadata.h"
@@ -154,20 +156,7 @@ Result IApplicationFunctions::GetDesiredLanguage(Out<u64> out_language_code) {
// Default to 0 (all languages supported)
u32 supported_languages = 0;
const auto res = [this] {
const FileSys::PatchManager pm{m_applet->program_id, system.GetFileSystemController(),
system.GetContentProvider()};
auto metadata = pm.GetControlMetadata();
if (metadata.first != nullptr) {
return metadata;
}
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(m_applet->program_id),
system.GetFileSystemController(),
system.GetContentProvider()};
return pm_update.GetControlMetadata();
}();
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
if (res.first != nullptr) {
supported_languages = res.first->GetSupportedLanguages();
}
@@ -205,20 +194,7 @@ Result IApplicationFunctions::SetTerminateResult(Result terminate_result) {
Result IApplicationFunctions::GetDisplayVersion(Out<DisplayVersion> out_display_version) {
LOG_DEBUG(Service_AM, "called");
const auto res = [this] {
const FileSys::PatchManager pm{m_applet->program_id, system.GetFileSystemController(),
system.GetContentProvider()};
auto metadata = pm.GetControlMetadata();
if (metadata.first != nullptr) {
return metadata;
}
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(m_applet->program_id),
system.GetFileSystemController(),
system.GetContentProvider()};
return pm_update.GetControlMetadata();
}();
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
if (res.first != nullptr) {
const auto& version = res.first->GetVersionString();
std::memcpy(out_display_version->string.data(), version.data(),
@@ -347,8 +323,21 @@ Result IApplicationFunctions::NotifyRunning(Out<bool> out_became_running) {
}
Result IApplicationFunctions::GetPseudoDeviceId(Out<Common::UUID> out_pseudo_device_id) {
LOG_WARNING(Service_AM, "(STUBBED) called");
*out_pseudo_device_id = {};
LOG_WARNING(Service_AM, "(stubbed)");
// This should be hashed with the device specific hash
// for now this will do
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
u8 hash[EVP_MAX_MD_SIZE];
unsigned int hash_len = 0;
auto const seed = res.first->raw.seed_for_pseudo_device_id;
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
auto const algorithm = EVP_sha1();
EVP_DigestInit_ex(ctx, algorithm, nullptr);
EVP_DigestUpdate(ctx, &seed, sizeof(seed));
EVP_DigestFinal_ex(ctx, hash, &hash_len);
EVP_MD_CTX_free(ctx);
*out_pseudo_device_id = Common::UUID::MakeRFC4122V5(std::span<u8, 20>{hash, std::size(hash)});
R_SUCCEED();
}
@@ -252,19 +252,7 @@ Result ILibraryAppletSelfAccessor::GetMainAppletApplicationDesiredLanguage(
// Default to 0 (all languages supported)
u32 supported_languages = 0;
const auto res = [this, identity] {
const FileSys::PatchManager pm{identity.application_id, system.GetFileSystemController(),
system.GetContentProvider()};
auto metadata = pm.GetControlMetadata();
if (metadata.first != nullptr) {
return metadata;
}
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(identity.application_id),
system.GetFileSystemController(),
system.GetContentProvider()};
return pm_update.GetControlMetadata();
}();
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, identity.application_id);
if (res.first != nullptr) {
supported_languages = res.first->GetSupportedLanguages();
+8 -15
View File
@@ -4,7 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
#include <utility>
#include "common/assert.h"
@@ -258,7 +257,7 @@ Result VfsDirectoryServiceWrapper::OpenFile(FileSys::VirtualFile* out_file,
npath.remove_prefix(1);
}
auto file = backing->GetFileRelative(npath, mode);
auto file = backing->GetFileRelative(npath);
if (file == nullptr) {
return FileSys::ResultPathNotFound;
}
@@ -334,16 +333,14 @@ FileSystemController::~FileSystemController() = default;
Result FileSystemController::RegisterProcess(
ProcessId process_id, ProgramId program_id,
std::shared_ptr<FileSys::RomFSFactory>&& romfs_factory, std::string homebrew_initial_cwd) {
std::shared_ptr<FileSys::RomFSFactory>&& romfs_factory) {
std::scoped_lock lk{registration_lock};
registrations.insert_or_assign(process_id,
Registration{
.program_id = program_id,
.romfs_factory = std::move(romfs_factory),
.save_data_factory = CreateSaveDataFactory(program_id),
.homebrew_initial_cwd = std::move(homebrew_initial_cwd),
});
registrations.emplace(process_id, Registration{
.program_id = program_id,
.romfs_factory = std::move(romfs_factory),
.save_data_factory = CreateSaveDataFactory(program_id),
});
LOG_DEBUG(Service_FS, "Registered for process {}", process_id);
return ResultSuccess;
@@ -351,8 +348,7 @@ Result FileSystemController::RegisterProcess(
Result FileSystemController::OpenProcess(
ProgramId* out_program_id, std::shared_ptr<SaveDataController>* out_save_data_controller,
std::shared_ptr<RomFsController>* out_romfs_controller, ProcessId process_id,
std::string* out_homebrew_initial_cwd) {
std::shared_ptr<RomFsController>* out_romfs_controller, ProcessId process_id) {
std::scoped_lock lk{registration_lock};
const auto it = registrations.find(process_id);
@@ -365,9 +361,6 @@ Result FileSystemController::OpenProcess(
std::make_shared<SaveDataController>(system, it->second.save_data_factory);
*out_romfs_controller =
std::make_shared<RomFsController>(it->second.romfs_factory, it->second.program_id);
if (out_homebrew_initial_cwd != nullptr) {
*out_homebrew_initial_cwd = it->second.homebrew_initial_cwd;
}
return ResultSuccess;
}
+4 -7
View File
@@ -8,7 +8,6 @@
#include <memory>
#include <mutex>
#include <string>
#include "common/common_types.h"
#include "core/file_sys/fs_directory.h"
#include "core/file_sys/fs_filesystem.h"
@@ -72,12 +71,11 @@ public:
~FileSystemController();
Result RegisterProcess(ProcessId process_id, ProgramId program_id,
std::shared_ptr<FileSys::RomFSFactory>&& factory,
std::string homebrew_initial_cwd = {});
std::shared_ptr<FileSys::RomFSFactory>&& factory);
Result OpenProcess(ProgramId* out_program_id,
std::shared_ptr<SaveDataController>* out_save_data_controller,
std::shared_ptr<RomFsController>* out_romfs_controller,
ProcessId process_id, std::string* out_homebrew_initial_cwd = nullptr);
std::shared_ptr<SaveDataController>* out_save_data_controller,
std::shared_ptr<RomFsController>* out_romfs_controller,
ProcessId process_id);
void SetPackedUpdate(ProcessId process_id, FileSys::VirtualFile update_raw);
std::shared_ptr<SaveDataController> OpenSaveDataController();
@@ -138,7 +136,6 @@ private:
ProgramId program_id;
std::shared_ptr<FileSys::RomFSFactory> romfs_factory;
std::shared_ptr<FileSys::SaveDataFactory> save_data_factory;
std::string homebrew_initial_cwd;
};
std::mutex registration_lock;
@@ -4,9 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
#include "common/fs/path_util.h"
#include "common/string_util.h"
#include "core/file_sys/fssrv/fssrv_sf_path.h"
#include "core/hle/service/cmif_serialization.h"
@@ -16,29 +13,10 @@
namespace Service::FileSystem {
static std::string ResolveHomebrewCwdRootAlias(std::string path,
std::string_view homebrew_initial_cwd) {
if (homebrew_initial_cwd.empty()) {
return path;
}
const std::string normalized_path = Common::FS::SanitizePath(path);
if (normalized_path.empty() || normalized_path == "/" ||
normalized_path != homebrew_initial_cwd ||
path.size() != normalized_path.size() + 2 ||
path.substr(0, normalized_path.size()) != normalized_path ||
path.substr(normalized_path.size()) != "//") {
return path;
}
return "/";
}
IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_,
std::string homebrew_initial_cwd_)
: ServiceFramework{system_, "IFileSystem"},
backend{std::make_unique<FileSys::Fsa::IFileSystem>(dir_)},
size_getter{std::move(size_getter_)}, homebrew_initial_cwd{std::move(homebrew_initial_cwd_)} {
IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_)
: ServiceFramework{system_, "IFileSystem"}, backend{std::make_unique<FileSys::Fsa::IFileSystem>(
dir_)},
size_getter{std::move(size_getter_)} {
static const FunctionInfo functions[] = {
{0, D<&IFileSystem::CreateFile>, "CreateFile"},
{1, D<&IFileSystem::DeleteFile>, "DeleteFile"},
@@ -65,8 +43,7 @@ Result IFileSystem::CreateFile(const InLargeData<FileSys::Sf::Path, BufferAttr_H
s32 option, s64 size) {
LOG_DEBUG(Service_FS, "called. file={}, option={:#x}, size={:#08x}", path->str, option, size);
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
R_RETURN(backend->CreateFile(FileSys::Path(fs_path.c_str()), size));
R_RETURN(backend->CreateFile(FileSys::Path(path->str), size));
}
Result IFileSystem::DeleteFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path) {
@@ -117,8 +94,7 @@ Result IFileSystem::OpenFile(OutInterface<IFile> out_interface,
LOG_DEBUG(Service_FS, "called. file={}, mode={}", path->str, mode);
FileSys::VirtualFile vfs_file{};
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
R_TRY(backend->OpenFile(&vfs_file, FileSys::Path(fs_path.c_str()),
R_TRY(backend->OpenFile(&vfs_file, FileSys::Path(path->str),
static_cast<FileSys::OpenMode>(mode)));
*out_interface = std::make_shared<IFile>(system, vfs_file);
@@ -131,8 +107,7 @@ Result IFileSystem::OpenDirectory(OutInterface<IDirectory> out_interface,
LOG_DEBUG(Service_FS, "called. directory={}, mode={}", path->str, mode);
FileSys::VirtualDir vfs_dir{};
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
R_TRY(backend->OpenDirectory(&vfs_dir, FileSys::Path(fs_path.c_str()),
R_TRY(backend->OpenDirectory(&vfs_dir, FileSys::Path(path->str),
static_cast<FileSys::OpenDirectoryMode>(mode)));
*out_interface = std::make_shared<IDirectory>(system, vfs_dir,
@@ -145,8 +120,7 @@ Result IFileSystem::GetEntryType(
LOG_DEBUG(Service_FS, "called. file={}", path->str);
FileSys::DirectoryEntryType vfs_entry_type{};
const auto fs_path = ResolveHomebrewCwdRootAlias(path->str, homebrew_initial_cwd);
R_TRY(backend->GetEntryType(&vfs_entry_type, FileSys::Path(fs_path.c_str())));
R_TRY(backend->GetEntryType(&vfs_entry_type, FileSys::Path(path->str)));
*out_type = static_cast<u32>(vfs_entry_type);
R_SUCCEED();
@@ -1,13 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "common/common_funcs.h"
#include "core/file_sys/fs_filesystem.h"
#include "core/file_sys/fsa/fs_i_filesystem.h"
@@ -28,8 +23,7 @@ class IDirectory;
class IFileSystem final : public ServiceFramework<IFileSystem> {
public:
explicit IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_,
std::string homebrew_initial_cwd_ = {});
explicit IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGetter size_getter_);
Result CreateFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path, s32 option,
s64 size);
@@ -61,7 +55,6 @@ public:
private:
std::unique_ptr<FileSys::Fsa::IFileSystem> backend;
SizeGetter size_getter;
std::string homebrew_initial_cwd;
};
} // namespace Service::FileSystem
@@ -192,9 +192,8 @@ Result FSP_SRV::SetCurrentProcess(ClientProcessId pid) {
LOG_DEBUG(Service_FS, "called. current_process_id={:#016x}", current_process_id);
homebrew_initial_cwd.clear();
R_RETURN(fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller,
current_process_id, &homebrew_initial_cwd));
R_RETURN(
fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller, current_process_id));
}
Result FSP_SRV::OpenFileSystemWithPatch(OutInterface<IFileSystem> out_interface,
@@ -225,8 +224,7 @@ Result FSP_SRV::OpenSdCardFileSystem(OutInterface<IFileSystem> out_interface) {
fsc.OpenSDMC(&sdmc_dir);
*out_interface = std::make_shared<IFileSystem>(
system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard),
homebrew_initial_cwd);
system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
R_SUCCEED();
}
@@ -7,7 +7,6 @@
#pragma once
#include <memory>
#include <string>
#include "core/file_sys/fs_save_data_types.h"
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/filesystem/fsp/fsp_types.h"
@@ -124,7 +123,6 @@ private:
u32 access_log_program_index = 0;
AccessLogMode access_log_mode = AccessLogMode::None;
u64 program_id = 0;
std::string homebrew_initial_cwd;
std::shared_ptr<SaveDataController> save_data_controller;
std::shared_ptr<RomFsController> romfs_controller;
};
+1 -91
View File
@@ -1,15 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <atomic>
#include <deque>
#include <mutex>
#include <vector>
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/nvdrv/core/container.h"
@@ -45,16 +40,6 @@ Container::Container(Tegra::Host1x::Host1x& host1x_) {
Container::~Container() = default;
static bool IsSameProcess(Kernel::KProcess* lhs, Kernel::KProcess* rhs) {
if (lhs == rhs) {
return true;
}
if (lhs == nullptr || rhs == nullptr) {
return false;
}
return lhs->GetProcessId() == rhs->GetProcessId();
}
SessionId Container::OpenSession(Kernel::KProcess* process) {
using namespace Common::Literals;
@@ -63,7 +48,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
if (!session.is_active) {
continue;
}
if (IsSameProcess(session.process, process)) {
if (session.process == process) {
session.ref_count++;
return session.id;
}
@@ -131,15 +116,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
void Container::CloseSession(SessionId session_id) {
std::scoped_lock lk(impl->session_guard);
if (session_id.id >= impl->sessions.size()) {
return;
}
auto& session = impl->sessions[session_id.id];
if (!session.is_active || session.ref_count <= 0) {
return;
}
if (--session.ref_count > 0) {
return;
}
@@ -157,73 +134,6 @@ void Container::CloseSession(SessionId session_id) {
impl->id_pool.emplace_front(session_id.id);
}
size_t Container::CloseSessions(std::span<const SessionId> session_ids) {
std::vector<SessionId> valid_session_ids;
valid_session_ids.reserve(session_ids.size());
{
std::scoped_lock lk(impl->session_guard);
for (const auto session_id : session_ids) {
if (session_id.id >= impl->sessions.size()) {
continue;
}
auto& session = impl->sessions[session_id.id];
if (!session.is_active) {
continue;
}
const auto duplicate = std::ranges::any_of(
valid_session_ids, [session_id](const auto candidate) {
return candidate.id == session_id.id;
});
if (duplicate) {
continue;
}
session.ref_count = 1;
valid_session_ids.push_back(session_id);
}
}
for (const auto session_id : valid_session_ids) {
CloseSession(session_id);
}
return valid_session_ids.size();
}
std::vector<SessionId> Container::GetSessionIdsForProcess(Kernel::KProcess* process) {
std::vector<SessionId> session_ids;
std::scoped_lock lk(impl->session_guard);
for (const auto& session : impl->sessions) {
if (!session.is_active || !IsSameProcess(session.process, process)) {
continue;
}
session_ids.push_back(session.id);
}
return session_ids;
}
std::vector<SessionId> Container::GetActiveSessionIds() const {
std::vector<SessionId> session_ids;
std::scoped_lock lk(impl->session_guard);
for (const auto& session : impl->sessions) {
if (session.is_active) {
session_ids.push_back(session.id);
}
}
return session_ids;
}
bool Container::IsSessionActive(SessionId session_id) const {
std::scoped_lock lk(impl->session_guard);
return session_id.id < impl->sessions.size() && impl->sessions[session_id.id].is_active;
}
Session* Container::GetSession(SessionId session_id) {
std::atomic_thread_fence(std::memory_order_acquire);
return &impl->sessions[session_id.id];
@@ -9,10 +9,7 @@
#include <deque>
#include <memory>
#include <span>
#include <cstddef>
#include <ankerl/unordered_dense.h>
#include <vector>
#include "core/device_memory_manager.h"
#include "core/hle/service/nvdrv/nvdata.h"
@@ -62,10 +59,6 @@ public:
SessionId OpenSession(Kernel::KProcess* process);
void CloseSession(SessionId id);
size_t CloseSessions(std::span<const SessionId> session_ids);
std::vector<SessionId> GetSessionIdsForProcess(Kernel::KProcess* process);
std::vector<SessionId> GetActiveSessionIds() const;
bool IsSessionActive(SessionId id) const;
Session* GetSession(SessionId id);
+8 -56
View File
@@ -6,12 +6,10 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include <functional>
#include <vector>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/core/heap_mapper.h"
#include "core/hle/service/nvdrv/core/nvmap.h"
@@ -328,65 +326,19 @@ std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool interna
}
void NvMap::UnmapAllHandles(NvCore::SessionId session_id) {
auto* session = core.GetSession(session_id);
auto* process = session != nullptr ? session->process : nullptr;
auto handle_ids = [&] {
auto handles_copy = [&] {
std::scoped_lock lk{handles_lock};
std::vector<Handle::Id> ids;
ids.reserve(handles.size());
for (const auto& entry : handles) {
ids.push_back(entry.first);
}
return ids;
return handles;
}();
for (const auto id : handle_ids) {
bool unlocked_pages = false;
while (true) {
bool last_user_reference = false;
VAddr address = 0;
size_t size = 0;
{
const auto handle = GetHandle(id);
if (!handle) {
break;
}
std::scoped_lock lk{handle->mutex};
if (handle->session_id.id != session_id.id || handle->dupes <= 0) {
break;
}
last_user_reference = handle->dupes == 1;
address = handle->address;
size = handle->size;
}
const auto free_info = FreeHandle(id, false);
if (!free_info) {
break;
}
if (!unlocked_pages && process != nullptr && address != 0 && size != 0 &&
(free_info->can_unlock || last_user_reference)) {
const auto unlock_result =
process->GetPageTable().UnlockForDeviceAddressSpace(address, size);
if (unlock_result.IsError()) {
LOG_WARNING(Service_NVDRV,
"NextLoad: nvmap session cleanup unlock failed, "
"handle={}, session={}, address=0x{:016X}, size=0x{:X}, "
"result={:#X}",
id, session_id.id, address, size, unlock_result.raw);
}
unlocked_pages = true;
}
if (last_user_reference) {
break;
for (auto& [id, handle] : handles_copy) {
{
std::scoped_lock lk{handle->mutex};
if (handle->session_id.id != session_id.id || handle->dupes <= 0) {
continue;
}
}
FreeHandle(id, false);
}
}
@@ -6,6 +6,7 @@
#include <boost/container/small_vector.hpp>
#include "common/adpf.h"
#include "common/assert.h"
#include "common/logging.h"
#include "core/core.h"
@@ -86,11 +87,16 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers
}
system.GPU().RequestComposite(std::move(output_layers), std::move(output_fences));
Common::ADPF::ReportFrameInterval();
system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs());
system.GetPerfStats().EndSystemFrame();
system.GetPerfStats().BeginSystemFrame();
}
void nvdisp_disp0::WaitForComposite() {
system.GPU().WaitForComposite();
}
Kernel::KEvent* nvdisp_disp0::QueryEvent(u32 event_id) {
LOG_CRITICAL(Service_NVDRV, "Unknown DISP Event {}", event_id);
return nullptr;
@@ -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-License-Identifier: GPL-2.0-or-later
@@ -37,6 +40,8 @@ public:
/// Performs a screen flip, compositing each buffer.
void Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers);
void WaitForComposite();
Kernel::KEvent* QueryEvent(u32 event_id) override;
private:
+1 -104
View File
@@ -1,17 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <utility>
#include <vector>
#include <fmt/ranges.h>
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
@@ -138,9 +133,6 @@ DeviceFD Module::Open(const std::string& device_name, NvCore::SessionId session_
auto device = builder(fd)->second;
device->OnOpen(session_id, fd);
if (container.IsSessionActive(session_id)) {
open_file_sessions.emplace(fd, session_id);
}
return fd;
}
@@ -212,7 +204,6 @@ NvResult Module::Close(DeviceFD fd) {
itr->second->OnClose(fd);
open_files.erase(itr);
open_file_sessions.erase(fd);
return NvResult::Success;
}
@@ -237,98 +228,4 @@ NvResult Module::QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event) {
return NvResult::Success;
}
static bool ContainsSession(std::span<const NvCore::SessionId> session_ids,
NvCore::SessionId session_id) {
return std::ranges::any_of(session_ids, [session_id](const auto candidate) {
return candidate.id == session_id.id;
});
}
static void AppendUniqueSession(std::vector<NvCore::SessionId>& session_ids,
NvCore::SessionId session_id) {
if (!ContainsSession(session_ids, session_id)) {
session_ids.push_back(session_id);
}
}
size_t Module::CloseFilesForSessions(std::span<const NvCore::SessionId> session_ids) {
std::vector<DeviceFD> fds;
fds.reserve(open_file_sessions.size());
for (const auto& [fd, session_id] : open_file_sessions) {
if (ContainsSession(session_ids, session_id)) {
fds.push_back(fd);
}
}
for (const auto fd : fds) {
Close(fd);
}
return fds.size();
}
void Module::CloseSession(NvCore::SessionId session_id) {
container.CloseSession(session_id);
}
void Module::TrackSessionAruid(NvCore::SessionId session_id, u64 aruid) {
const bool active = container.IsSessionActive(session_id);
if (active) {
session_aruids[session_id.id] = aruid;
}
}
std::vector<NvCore::SessionId> Module::GetSessionIdsForAruid(u64 aruid) const {
std::vector<NvCore::SessionId> session_ids;
for (const auto& [session_id, session_aruid] : session_aruids) {
if (session_aruid == aruid) {
session_ids.push_back(NvCore::SessionId{session_id});
}
}
return session_ids;
}
size_t Module::ResetForProcess(Kernel::KProcess* process) {
const auto process_id = process != nullptr ? process->GetProcessId() : 0;
auto session_ids = container.GetSessionIdsForProcess(process);
if (process_id != 0) {
for (const auto session_id : GetSessionIdsForAruid(process_id)) {
AppendUniqueSession(session_ids, session_id);
}
}
const auto active_session_ids = container.GetActiveSessionIds();
const auto active_before = active_session_ids.size();
const bool has_active_candidate =
std::ranges::any_of(session_ids, [this](const auto session_id) {
return container.IsSessionActive(session_id);
});
bool used_active_sessions = false;
if (!has_active_candidate && !active_session_ids.empty()) {
for (const auto session_id : active_session_ids) {
AppendUniqueSession(session_ids, session_id);
}
used_active_sessions = true;
}
const auto closed_files = CloseFilesForSessions(session_ids);
const auto closed_sessions = container.CloseSessions(session_ids);
for (const auto session_id : session_ids) {
if (!container.IsSessionActive(session_id)) {
session_aruids.erase(session_id.id);
}
}
if (used_active_sessions) {
LOG_WARNING(Service_NVDRV,
"NextLoad: NVDRV reset used active sessions because process-owned "
"sessions were not found, process_id={}, sessions={}, files={}, active_before={}",
process_id, closed_sessions, closed_files, active_before);
}
return closed_sessions;
}
} // namespace Service::Nvidia
-10
View File
@@ -12,7 +12,6 @@
#include <memory>
#include <span>
#include <string>
#include <vector>
#include <ankerl/unordered_dense.h>
#include "common/common_types.h"
@@ -27,7 +26,6 @@ class System;
namespace Kernel {
class KEvent;
class KProcess;
}
namespace Service::Nvidia {
@@ -91,9 +89,6 @@ public:
NvResult Close(DeviceFD fd);
NvResult QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event);
void CloseSession(NvCore::SessionId session_id);
void TrackSessionAruid(NvCore::SessionId session_id, u64 aruid);
size_t ResetForProcess(Kernel::KProcess* process);
NvCore::Container& GetContainer() {
return container;
@@ -111,17 +106,12 @@ private:
using FilesContainerType = ankerl::unordered_dense::map<DeviceFD, std::shared_ptr<Devices::nvdevice>>;
/// Mapping of file descriptors to the devices they reference.
FilesContainerType open_files;
ankerl::unordered_dense::map<DeviceFD, NvCore::SessionId> open_file_sessions;
ankerl::unordered_dense::map<size_t, u64> session_aruids;
KernelHelpers::ServiceContext service_context;
EventInterface events_interface;
ankerl::unordered_dense::map<std::string, std::function<FilesContainerType::iterator(DeviceFD)>> builders;
size_t CloseFilesForSessions(std::span<const NvCore::SessionId> session_ids);
std::vector<NvCore::SessionId> GetSessionIdsForAruid(u64 aruid) const;
};
void LoopProcess(Core::System& system);
@@ -212,10 +212,7 @@ void NVDRV::QueryEvent(HLERequestContext& ctx) {
void NVDRV::SetAruid(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
pid = rp.Pop<u64>();
LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid={:#X}", pid);
if (is_initialized) {
nvdrv->TrackSessionAruid(session_id, pid);
}
LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid={:#x}", pid);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@@ -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-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -57,18 +57,8 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
// Set default speed limit to 100%.
*out_speed_scale = 1.0f;
// If no layers are available, skip the logic.
bool any_visible = false;
for (auto& layer : display.stack.layers) {
if (layer->visible) {
any_visible = true;
break;
}
}
if (!any_visible) {
*out_speed_scale = 1.0f;
return 1;
}
nvdisp.WaitForComposite();
this->ReleaseFramebuffersLocked(display);
// Determine the number of vsync periods to wait before composing again.
std::optional<s32> swap_interval{};
@@ -158,55 +148,30 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
nvdisp.Composite(composition_stack);
}
// Batch framebuffer releases, instead of one-into-one.
std::vector<std::pair<Layer*, Framebuffer*>> to_release;
for (auto& [layer_id, framebuffer] : m_framebuffers) {
if (!framebuffer.is_acquired)
continue;
auto layer = display.stack.FindLayer(layer_id);
if (!layer)
continue;
// Overlay layers always release after every compose
// Non-overlay layers release based on their swap interval
if (layer->is_overlay || framebuffer.release_frame_number <= m_frame_number) {
to_release.emplace_back(layer.get(), &framebuffer);
}
}
for (auto& [layer, framebuffer] : to_release) {
layer->buffer_item_consumer->ReleaseBuffer(framebuffer->item, android::Fence::NoFence());
framebuffer->is_acquired = false;
}
// Advance by 1 frame (60 FPS compositing)
m_frame_number += 1;
// Release any necessary framebuffers (non-overlay layers only, as overlays are already released above).
return 1;
}
void HardwareComposer::ReleaseFramebuffersLocked(Display& display) {
for (auto& [layer_id, framebuffer] : m_framebuffers) {
if (!framebuffer.is_acquired) {
// Already released.
continue;
}
if (framebuffer.release_frame_number > m_frame_number) {
const auto layer = display.stack.FindLayer(layer_id);
if (!layer) {
continue;
}
if (const auto layer = display.stack.FindLayer(layer_id); layer != nullptr) {
// Skip overlay layers as they were already released above
if (layer->is_overlay) {
continue;
}
// TODO: support release fence
// This is needed to prevent screen tearing
layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
framebuffer.is_acquired = false;
if (!layer->is_overlay && framebuffer.release_frame_number > m_frame_number) {
continue;
}
layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
framebuffer.is_acquired = false;
}
return 1;
}
void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) {
@@ -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-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -52,6 +52,7 @@ private:
private:
bool TryAcquireFramebufferLocked(Layer& layer, Framebuffer& framebuffer);
CacheStatus CacheFramebufferLocked(Layer& layer, ConsumerId consumer_id);
void ReleaseFramebuffersLocked(Display& display);
};
} // namespace Service::Nvnflinger
+32
View File
@@ -4,7 +4,11 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "common/adpf.h"
#include "common/settings.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/service/vi/conductor.h"
@@ -14,6 +18,8 @@
constexpr auto FrameNs = std::chrono::nanoseconds{1000000000 / 60};
constexpr s64 UNLOCKED_TARGET_DIVISOR = 4;
namespace Service::VI {
Conductor::Conductor(Core::System& system, Container& container, DisplayList& displays)
@@ -68,6 +74,9 @@ void Conductor::UnlinkVsyncEvent(u64 display_id, Event* event) {
}
void Conductor::ProcessVsync() {
Common::PollThreadPolicies();
Common::ADPF::SetTargetWorkDuration(std::chrono::nanoseconds{this->GetFramePeriodNs()});
for (auto& [display_id, manager] : m_vsync_managers) {
m_container.ComposeOnDisplay(&m_swap_interval, &m_compose_speed_scale, display_id);
manager.SignalVsync(m_system.Kernel());
@@ -76,6 +85,8 @@ void Conductor::ProcessVsync() {
void Conductor::VsyncThread(std::stop_token token) {
Common::SetCurrentThreadName("VSyncThread");
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
Common::SetCurrentThreadToPerformanceCores();
while (!token.stop_requested()) {
m_signal.Wait();
@@ -114,4 +125,25 @@ s64 Conductor::GetNextTicks() const {
return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
}
s64 Conductor::GetFramePeriodNs() const {
const auto& settings = Settings::values;
f32 speed_scale = 1.f;
bool unlocked = false;
if (settings.use_multi_core.GetValue()) {
if (settings.use_speed_limit.GetValue()) {
speed_scale = 100.f / Settings::SpeedLimit();
} else {
unlocked = true;
}
}
speed_scale /= m_compose_speed_scale;
const f32 effective_fps = 60.f / static_cast<f32>(m_swap_interval);
s64 period = static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
if (unlocked) {
period /= UNLOCKED_TARGET_DIVISOR;
}
return std::clamp<s64>(period, 1'000'000, 100'000'000);
}
} // namespace Service::VI
+1
View File
@@ -44,6 +44,7 @@ private:
void ProcessVsync();
void VsyncThread(std::stop_token token);
s64 GetNextTicks() const;
s64 GetFramePeriodNs() const;
private:
Core::System& m_system;
+2 -4
View File
@@ -40,7 +40,6 @@ namespace Network {
namespace {
enum class CallType {
Connect,
Send,
Other,
};
@@ -132,7 +131,7 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
case WSAENOTCONN:
return Errno::NOTCONN;
case WSAEWOULDBLOCK:
return call_type == CallType::Connect ? Errno::INPROGRESS : Errno::AGAIN;
return Errno::AGAIN;
case WSAECONNREFUSED:
return Errno::CONNREFUSED;
case WSAECONNABORTED:
@@ -564,7 +563,6 @@ int TranslateTypeToNative(Type type) {
NETWORK_PROTOCOL_TRANSLATE_ELEM(UDPLITE)
#elif defined(_WIN32)
#define NETWORK_PROTOCOL_TRANSLATE_LIST \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IP) \
/*NETWORK_PROTOCOL_TRANSLATE_ELEM(HOPOPTS)*/ \
NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMP) \
NETWORK_PROTOCOL_TRANSLATE_ELEM(IGMP) \
@@ -890,7 +888,7 @@ Errno Socket::Connect(SockAddrIn addr_in) {
return Errno::SUCCESS;
}
return GetAndLogLastError(CallType::Connect);
return GetAndLogLastError();
}
std::pair<SockAddrIn, Errno> Socket::GetPeerName() {
-311
View File
@@ -1,311 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/loader/homebrew_nxlink.h"
#include <array>
#include <cctype>
#include <memory>
#include <mutex>
#include <optional>
#include <stop_token>
#include <utility>
#include <boost/asio.hpp>
#include "common/logging.h"
#include "common/polyfill_thread.h"
#include "common/thread.h"
namespace Loader::HomebrewNxlink {
namespace {
using boost::asio::ip::tcp;
constexpr u16 NxlinkClientPort = 28771;
constexpr size_t ReceiveBufferSize = 1024;
constexpr size_t MaxLogLineSize = 16 * 1024;
struct LogConnection {
explicit LogConnection(boost::asio::io_context& io_context_) : socket{io_context_} {}
tcp::socket socket;
std::array<char, ReceiveBufferSize> buffer{};
std::string line;
};
struct LogServerState {
LogServerState() : acceptor{io_context} {}
boost::asio::io_context io_context;
tcp::acceptor acceptor;
};
std::mutex server_mutex;
std::shared_ptr<LogServerState> server_state;
std::optional<std::jthread> server_thread;
std::optional<std::string> GetLastArgvToken(std::string_view argv_string) {
while (!argv_string.empty() && argv_string.back() == '\0') {
argv_string.remove_suffix(1);
}
std::optional<std::string_view> last_token;
bool in_token = false;
bool quoted = false;
size_t token_begin = 0;
size_t token_size = 0;
for (size_t i = 0; i <= argv_string.size(); i++) {
const char c = i < argv_string.size() ? argv_string[i] : '\0';
if (!in_token) {
if (c == '\0' || std::isspace(static_cast<unsigned char>(c))) {
continue;
}
in_token = true;
token_size = 0;
if (c == '"') {
quoted = true;
token_begin = i + 1;
} else {
quoted = false;
token_begin = i;
token_size = 1;
}
continue;
}
const bool token_end =
quoted ? c == '"' || c == '\0'
: c == '\0' || std::isspace(static_cast<unsigned char>(c));
if (token_end) {
if (token_size != 0) {
last_token = argv_string.substr(token_begin, token_size);
}
in_token = false;
quoted = false;
token_size = 0;
continue;
}
token_size++;
}
if (!last_token) {
return std::nullopt;
}
return std::string{*last_token};
}
void AppendArgvToken(std::string& argv_string, std::string_view token) {
while (!argv_string.empty() && argv_string.back() == '\0') {
argv_string.pop_back();
}
if (!argv_string.empty()) {
argv_string.push_back(' ');
}
argv_string.append(token);
}
void FlushLogLine(std::string& line) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (!line.empty()) {
LOG_INFO(Loader, "{}", line);
line.clear();
}
}
void ConsumeLogBytes(LogConnection& connection, std::string_view bytes) {
for (const char byte : bytes) {
if (byte == '\n') {
FlushLogLine(connection.line);
continue;
}
connection.line.push_back(byte);
if (connection.line.size() >= MaxLogLineSize) {
FlushLogLine(connection.line);
}
}
}
void StartRead(std::shared_ptr<LogConnection> connection) {
connection->socket.async_read_some(
boost::asio::buffer(connection->buffer),
[connection](const boost::system::error_code& error, size_t bytes_read) {
if (error.failed()) {
FlushLogLine(connection->line);
return;
}
ConsumeLogBytes(*connection, {connection->buffer.data(), bytes_read});
StartRead(connection);
});
}
void StartAccept(std::shared_ptr<LogServerState> state) {
auto connection = std::make_shared<LogConnection>(state->io_context);
state->acceptor.async_accept(
connection->socket,
[state, connection](const boost::system::error_code& error) {
if (!error.failed()) {
LOG_INFO(Loader, "Homebrew nxlink log client connected");
StartRead(connection);
}
if (state->acceptor.is_open()) {
StartAccept(state);
}
});
}
void StartLogServer() {
std::scoped_lock lock{server_mutex};
if (server_thread) {
return;
}
auto state = std::make_shared<LogServerState>();
const tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), NxlinkClientPort};
boost::system::error_code error;
state->acceptor.open(endpoint.protocol(), error);
if (!error.failed()) {
state->acceptor.set_option(tcp::acceptor::reuse_address(true), error);
}
if (!error.failed()) {
state->acceptor.bind(endpoint, error);
}
if (!error.failed()) {
state->acceptor.listen(boost::asio::socket_base::max_listen_connections, error);
}
if (error.failed()) {
LOG_WARNING(Loader, "Homebrew nxlink log server could not listen on 127.0.0.1:{}: {}",
NxlinkClientPort, error.message());
return;
}
StartAccept(state);
server_state = state;
server_thread.emplace([state](std::stop_token stop_token) {
Common::SetCurrentThreadName("HomebrewNxlink");
std::stop_callback stop_callback{stop_token, [state] {
boost::system::error_code ignored;
state->acceptor.close(ignored);
state->io_context.stop();
}};
LOG_INFO(Loader, "Homebrew nxlink log server listening on 127.0.0.1:{}",
NxlinkClientPort);
state->io_context.run();
LOG_INFO(Loader, "Homebrew nxlink log server stopped");
});
}
} // namespace
bool IsArgvMarker(std::string_view token) {
if (token.size() != ArgvMarkerSize || token.substr(8) != ArgvMarkerSuffix) {
return false;
}
for (size_t i = 0; i < 8; i++) {
if (!std::isxdigit(static_cast<unsigned char>(token[i]))) {
return false;
}
}
return true;
}
bool IsLoopbackArgvMarker(std::string_view token) {
if (!IsArgvMarker(token)) {
return false;
}
for (size_t i = 0; i < 8; i++) {
if (std::tolower(static_cast<unsigned char>(token[i])) !=
std::tolower(static_cast<unsigned char>(LoopbackArgvMarker[i]))) {
return false;
}
}
return true;
}
std::optional<std::string> GetArgvMarker(std::string_view argv_string) {
auto last_token = GetLastArgvToken(argv_string);
if (!last_token || !IsArgvMarker(*last_token)) {
return std::nullopt;
}
return last_token;
}
std::optional<std::string> PrepareArgv(std::string& argv_string,
std::string_view inherited_marker,
bool append_loopback_marker) {
auto marker = GetArgvMarker(argv_string);
if (!marker && IsArgvMarker(inherited_marker)) {
AppendArgvToken(argv_string, inherited_marker);
marker = std::string{inherited_marker};
}
if (!marker && append_loopback_marker) {
AppendArgvToken(argv_string, LoopbackArgvMarker);
marker = std::string{LoopbackArgvMarker};
}
return marker;
}
void ApplyServerMode(Settings::HomebrewNxlinkServerMode mode,
const std::optional<std::string>& active_marker) {
switch (mode) {
case Settings::HomebrewNxlinkServerMode::Disabled:
StopServer();
return;
case Settings::HomebrewNxlinkServerMode::EdenLog:
if (active_marker && IsLoopbackArgvMarker(*active_marker)) {
StartLogServer();
return;
}
StopServer();
if (active_marker) {
LOG_INFO(Loader,
"Homebrew nxlink server mode overridden by argv marker '{}'; not starting "
"local nxlink log server",
*active_marker);
} else {
LOG_WARNING(Loader,
"Homebrew nxlink log server requested, but no argv marker is active");
}
return;
case Settings::HomebrewNxlinkServerMode::HostStdout:
case Settings::HomebrewNxlinkServerMode::File:
StopServer();
LOG_WARNING(Loader, "Homebrew nxlink server mode {} is not implemented",
static_cast<int>(mode));
return;
}
}
void StopServer() {
std::shared_ptr<LogServerState> state;
std::optional<std::jthread> thread;
{
std::scoped_lock lock{server_mutex};
state = std::move(server_state);
thread = std::move(server_thread);
}
if (state) {
boost::system::error_code ignored;
state->acceptor.close(ignored);
state->io_context.stop();
}
if (thread) {
thread->request_stop();
}
}
} // namespace Loader::HomebrewNxlink
-30
View File
@@ -1,30 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <cstddef>
#include <optional>
#include <string>
#include <string_view>
#include "common/settings_enums.h"
namespace Loader::HomebrewNxlink {
constexpr size_t ArgvMarkerSize = 16;
constexpr std::string_view ArgvMarkerSuffix = "_NXLINK_";
constexpr std::string_view LoopbackArgvMarker = "0100007F_NXLINK_";
bool IsArgvMarker(std::string_view token);
bool IsLoopbackArgvMarker(std::string_view token);
std::optional<std::string> GetArgvMarker(std::string_view argv_string);
std::optional<std::string> PrepareArgv(std::string& argv_string,
std::string_view inherited_marker,
bool append_loopback_marker = false);
void ApplyServerMode(Settings::HomebrewNxlinkServerMode mode,
const std::optional<std::string>& active_marker);
void StopServer();
} // namespace Loader::HomebrewNxlink
+51 -503
View File
@@ -4,20 +4,17 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstring>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "common/alignment.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/fs/path_util.h"
#include "common/logging.h"
#include "common/settings.h"
#include "common/random.h"
@@ -26,14 +23,11 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/romfs_factory.h"
#include "core/file_sys/vfs/vfs_offset.h"
#include "core/hardware_properties.h"
#include "core/hle/api_version.h"
#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/homebrew_nxlink.h"
#include "core/loader/nro.h"
#include "core/memory.h"
@@ -158,432 +152,8 @@ static constexpr u32 PageAlignSize(u32 size) {
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
}
static std::string MakeHomebrewSdmcPath(std::string path) {
std::replace(path.begin(), path.end(), '\\', '/');
while (!path.empty() && path.front() == '/') {
path.erase(path.begin());
}
return "sdmc:/" + path;
}
static std::optional<std::string> TryMakeHomebrewSdmcPathFromHostPath(std::string path) {
std::replace(path.begin(), path.end(), '\\', '/');
std::string sdmc_root =
Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::SDMCDir));
std::replace(sdmc_root.begin(), sdmc_root.end(), '\\', '/');
while (!sdmc_root.empty() && sdmc_root.back() == '/') {
sdmc_root.pop_back();
}
if (!sdmc_root.empty() && path.size() > sdmc_root.size() &&
path.compare(0, sdmc_root.size(), sdmc_root) == 0 && path[sdmc_root.size()] == '/') {
return MakeHomebrewSdmcPath(path.substr(sdmc_root.size() + 1));
}
constexpr std::string_view SdmcMarker{"/sdmc/"};
if (const auto pos = path.rfind(SdmcMarker); pos != std::string::npos) {
return MakeHomebrewSdmcPath(path.substr(pos + SdmcMarker.size()));
}
return std::nullopt;
}
static std::string MakeHomebrewArgv0(std::string nro_path, std::string file_name) {
if (nro_path.empty()) {
nro_path = std::move(file_name);
}
std::replace(nro_path.begin(), nro_path.end(), '\\', '/');
if (nro_path.rfind("sdmc:/", 0) == 0) {
return nro_path;
}
if (auto sdmc_path = TryMakeHomebrewSdmcPathFromHostPath(nro_path)) {
return *sdmc_path;
}
#ifdef __ANDROID__
if (nro_path.find('%') != std::string::npos) {
const auto percent_decode = [](std::string value) {
const auto hex_value = [](char c) -> int {
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
if (c >= 'A' && c <= 'F') {
return c - 'A' + 10;
}
return -1;
};
std::string decoded;
decoded.reserve(value.size());
for (size_t i = 0; i < value.size(); i++) {
if (value[i] == '%' && i + 2 < value.size()) {
const int high = hex_value(value[i + 1]);
const int low = hex_value(value[i + 2]);
if (high >= 0 && low >= 0) {
decoded.push_back(static_cast<char>((high << 4) | low));
i += 2;
continue;
}
}
decoded.push_back(value[i]);
}
return decoded;
};
if (auto sdmc_path = TryMakeHomebrewSdmcPathFromHostPath(percent_decode(nro_path))) {
return *sdmc_path;
}
}
#endif
if (!nro_path.empty() && nro_path.front() == '/') {
return "sdmc:" + nro_path;
}
return nro_path.empty() ? "homebrew" : nro_path;
}
static std::string QuoteHomebrewArgvComponent(const std::string& argument) {
if (argument.find_first_of(" \t\r\n") == std::string::npos) {
return argument;
}
std::string quoted{"\""};
quoted += argument;
quoted.push_back('"');
return quoted;
}
static std::string GetHomebrewInitialCwd(const std::string& argv0) {
constexpr std::string_view SdmcPrefix = "sdmc:";
if (argv0.substr(0, SdmcPrefix.size()) != SdmcPrefix) {
return {};
}
const auto last_slash = argv0.find_last_of('/');
if (last_slash == std::string_view::npos || last_slash < SdmcPrefix.size()) {
return {};
}
std::string cwd = argv0.substr(SdmcPrefix.size(), last_slash - SdmcPrefix.size());
if (cwd.empty()) {
cwd = "/";
}
while (cwd.size() > 1 && cwd.back() == '/') {
cwd.pop_back();
}
return cwd;
}
constexpr size_t HomebrewNextLoadPathSize = 0x200;
constexpr size_t HomebrewNextLoadArgvSize = 0x800;
constexpr u32 HomebrewSvcExitProcessInstruction = 0xD40000E1;
constexpr u32 HomebrewEntryEndOfList = 0;
constexpr u32 HomebrewEntryMainThreadHandle = 1;
constexpr u32 HomebrewEntryNextLoadPath = 2;
constexpr u32 HomebrewEntryOverrideHeap = 3;
constexpr u32 HomebrewEntryArgv = 5;
constexpr u32 HomebrewEntrySyscallAvailableHint = 6;
constexpr u32 HomebrewEntryAppletType = 7;
constexpr u32 HomebrewEntryProcessHandle = 10;
constexpr u32 HomebrewEntryRandomSeed = 14;
constexpr u32 HomebrewEntryHosVersion = 16;
constexpr u32 HomebrewEntrySyscallAvailableHint2 = 17;
constexpr u32 HomebrewAppletTypeApplication = 0;
constexpr u64 HomebrewAllSvcHints = ~u64{0};
constexpr u32 HomebrewHosVersion = (u32{HLE::ApiVersion::HOS_VERSION_MAJOR} << 16) |
(u32{HLE::ApiVersion::HOS_VERSION_MINOR} << 8) |
u32{HLE::ApiVersion::HOS_VERSION_MICRO};
struct HomebrewConfigEntry {
u32_le key;
u32_le flags;
u64_le value[2];
};
static_assert(sizeof(HomebrewConfigEntry) == 0x18);
constexpr size_t HomebrewBaseConfigEntryCount = 10;
constexpr size_t HomebrewInPlaceConfigEntryCount = 11;
constexpr size_t HomebrewBaseConfigTableSize =
HomebrewBaseConfigEntryCount * sizeof(HomebrewConfigEntry);
constexpr size_t HomebrewInPlaceConfigTableSize =
HomebrewInPlaceConfigEntryCount * sizeof(HomebrewConfigEntry);
struct HomebrewNroImage {
Kernel::CodeSet codeset;
size_t image_size{};
size_t args_offset{};
std::optional<size_t> exit_process_offset;
std::optional<std::string> nxlink_argv_marker;
std::string argv_string;
};
static void SetHomebrewConfigPointers(Kernel::KProcess& process, u64 config_addr,
u64 next_load_path_addr, u64 next_load_argv_addr) {
constexpr size_t MainThreadHandleEntryIndex = 0;
constexpr size_t ProcessHandleEntryIndex = 1;
constexpr size_t EntryValueOffset = offsetof(HomebrewConfigEntry, value);
process.SetArgPointer(Kernel::KProcessAddress{config_addr});
process.SetMainThreadHandleAddr(Kernel::KProcessAddress{
config_addr + MainThreadHandleEntryIndex * sizeof(HomebrewConfigEntry) + EntryValueOffset});
process.SetProcessHandleAddr(Kernel::KProcessAddress{
config_addr + ProcessHandleEntryIndex * sizeof(HomebrewConfigEntry) + EntryValueOffset});
process.SetHomebrewNextLoadBufferAddrs(Kernel::KProcessAddress{next_load_path_addr},
Kernel::KProcessAddress{next_load_argv_addr});
}
static std::optional<HomebrewNroImage> BuildHomebrewNroImage(const std::vector<u8>& data,
std::string nro_path,
std::string file_name,
std::string launch_argv,
std::string_view inherited_marker,
bool append_loopback_nxlink_marker) {
if (data.size() < sizeof(NroHeader)) {
return std::nullopt;
}
NroHeader nro_header{};
std::memcpy(&nro_header, data.data(), sizeof(NroHeader));
if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) {
return std::nullopt;
}
if (data.size() < nro_header.file_size ||
nro_header.module_header_offset + sizeof(ModHeader) > PageAlignSize(nro_header.file_size)) {
return std::nullopt;
}
std::vector<u8> program_image(PageAlignSize(nro_header.file_size));
std::memcpy(program_image.data(), data.data(), nro_header.file_size);
Kernel::CodeSet codeset;
for (std::size_t i = 0; i < nro_header.segments.size(); ++i) {
codeset.segments[i].addr = nro_header.segments[i].offset;
codeset.segments[i].offset = nro_header.segments[i].offset;
codeset.segments[i].size = PageAlignSize(nro_header.segments[i].size);
}
u32 bss_size{PageAlignSize(nro_header.bss_size)};
ModHeader mod_header{};
std::memcpy(&mod_header, program_image.data() + nro_header.module_header_offset,
sizeof(ModHeader));
if (mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')) {
bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
}
codeset.DataSegment().size += bss_size;
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
HomebrewNroImage image{.codeset = std::move(codeset)};
const auto argv0 = MakeHomebrewArgv0(std::move(nro_path), std::move(file_name));
if (!launch_argv.empty()) {
image.argv_string = std::move(launch_argv);
} else {
image.argv_string = QuoteHomebrewArgvComponent(argv0);
}
image.nxlink_argv_marker = HomebrewNxlink::PrepareArgv(
image.argv_string, inherited_marker, append_loopback_nxlink_marker);
if (image.argv_string.empty() || image.argv_string.back() != '\0') {
image.argv_string.push_back('\0');
}
const auto& code = image.codeset.CodeSegment();
const size_t code_end = (std::min)(program_image.size(), code.offset + code.size);
for (size_t offset = code.offset; offset + sizeof(u32) <= code_end; offset += sizeof(u32)) {
u32 instruction{};
std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction));
if (instruction == HomebrewSvcExitProcessInstruction) {
image.exit_process_offset = offset;
break;
}
}
const size_t entries_and_buffers =
Common::AlignUp(HomebrewInPlaceConfigTableSize + HomebrewNextLoadPathSize +
HomebrewNextLoadArgvSize + image.argv_string.size(),
Core::Memory::YUZU_PAGESIZE);
image.args_offset = program_image.size();
image.codeset.DataSegment().size += static_cast<u32>(entries_and_buffers);
program_image.resize(image.args_offset + entries_and_buffers);
image.image_size = program_image.size();
image.codeset.memory = std::move(program_image);
return image;
}
bool LoadNroInPlace(Core::System& system, Kernel::KProcess& process, Kernel::KThread& thread,
const FileSys::VirtualFile& nro_file, const std::string& nro_path,
const std::string& launch_argv) {
if (!nro_file) {
return false;
}
#ifdef HAS_NCE
if (Settings::IsNceEnabled()) {
LOG_WARNING(Loader,
"Homebrew next-load: in-place handoff unavailable because NCE is enabled");
return false;
}
#endif
size_t live_threads = 0;
for (auto& candidate : process.GetThreadList()) {
if (candidate.GetState() != Kernel::ThreadState::Terminated) {
live_threads++;
}
}
if (live_threads != 1) {
LOG_WARNING(Loader, "NextLoad: in-place handoff failed because live_threads={}",
live_threads);
return false;
}
const auto stack_top = process.GetMainThreadStackTop();
if (GetInteger(stack_top) == 0) {
LOG_WARNING(Loader, "NextLoad: in-place handoff failed because stack_top=0");
return false;
}
const auto nxlink_server_mode = Settings::values.homebrew_nxlink_server_mode.GetValue();
const bool append_loopback_nxlink_marker =
nxlink_server_mode != Settings::HomebrewNxlinkServerMode::Disabled;
auto image = BuildHomebrewNroImage(
nro_file->ReadAllBytes(), nro_path, nro_file->GetName(), launch_argv,
process.GetHomebrewNxlinkArgvMarker(), append_loopback_nxlink_marker);
if (!image) {
LOG_WARNING(Loader, "NextLoad: in-place handoff failed because '{}' is invalid",
nro_path);
return false;
}
const auto capacity = process.GetCodeSize();
if (image->image_size > capacity) {
LOG_WARNING(Loader,
"NextLoad: in-place handoff failed because image_size=0x{:X} exceeds "
"capacity=0x{:X}",
image->image_size, capacity);
return false;
}
const u64 base = GetInteger(process.GetEntryPoint());
const u64 heap_addr = GetInteger(process.GetPageTable().GetHeapRegionStart());
const size_t heap_size = process.GetPageTable().GetBasePageTable().GetCurrentHeapSize();
if (heap_addr == 0 || heap_size == 0) {
LOG_WARNING(Loader,
"NextLoad: in-place handoff failed because heap override is "
"unavailable (addr=0x{:016X}, size=0x{:X})",
heap_addr, heap_size);
return false;
}
const u64 config_addr = base + image->args_offset;
const u64 next_load_path_addr = config_addr + HomebrewInPlaceConfigTableSize;
const u64 next_load_argv_addr = next_load_path_addr + HomebrewNextLoadPathSize;
const u64 argv_addr = next_load_argv_addr + HomebrewNextLoadArgvSize;
const u64 argv_entry_addr = image->argv_string.empty() ? 0 : argv_addr;
const std::string argv0 = MakeHomebrewArgv0(nro_path, nro_file->GetName());
const std::string homebrew_initial_cwd = GetHomebrewInitialCwd(argv0);
u64 program_id{};
AppLoader_NRO loader{nro_file};
if (loader.ReadProgramId(program_id) != Loader::ResultStatus::Success) {
LOG_WARNING(Loader, "NextLoad: in-place handoff could not read NRO program id");
}
Kernel::Handle main_thread_handle{};
if (process.GetHandleTable().Add(system.Kernel(), std::addressof(main_thread_handle), &thread)
.IsError()) {
LOG_WARNING(Loader,
"NextLoad: in-place handoff failed because main thread handle failed");
return false;
}
Kernel::Handle process_handle{};
if (process.GetHandleTable().Add(system.Kernel(), std::addressof(process_handle), &process)
.IsError()) {
LOG_WARNING(Loader,
"NextLoad: in-place handoff failed because process handle failed");
return false;
}
if (!process.GetMemory().ZeroBlock(Common::ProcessAddress{heap_addr}, heap_size)) {
LOG_WARNING(Loader,
"NextLoad: in-place handoff failed because heap clear failed "
"(addr=0x{:016X}, size=0x{:X})",
heap_addr, heap_size);
return false;
}
process.LoadModule(system.Kernel(), std::move(image->codeset), process.GetEntryPoint());
const HomebrewConfigEntry entries[HomebrewInPlaceConfigEntryCount] = {
{HomebrewEntryMainThreadHandle, 0, {main_thread_handle, 0}},
{HomebrewEntryProcessHandle, 0, {process_handle, 0}},
{HomebrewEntryNextLoadPath, 0, {next_load_path_addr, next_load_argv_addr}},
{HomebrewEntryOverrideHeap, 0, {heap_addr, heap_size}},
{HomebrewEntryAppletType, 0, {HomebrewAppletTypeApplication, 0}},
{HomebrewEntryArgv, 0, {0, argv_entry_addr}},
{HomebrewEntrySyscallAvailableHint, 0, {HomebrewAllSvcHints, HomebrewAllSvcHints}},
{HomebrewEntryRandomSeed, 0,
{process.GetRandomEntropy(0), process.GetRandomEntropy(1)}},
{HomebrewEntryHosVersion, 0, {HomebrewHosVersion, 0}},
{HomebrewEntrySyscallAvailableHint2, 0, {HomebrewAllSvcHints, 0}},
{HomebrewEntryEndOfList, 0, {0, 0}},
};
process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries, sizeof(entries));
process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, image->argv_string.data(),
image->argv_string.size());
process.GetMemory().Write32(Common::ProcessAddress{GetInteger(thread.GetTlsAddress()) + 0x110},
main_thread_handle);
process.SetArgReturnAddress(Kernel::KProcessAddress{
image->exit_process_offset ? base + *image->exit_process_offset : 0});
SetHomebrewConfigPointers(process, config_addr, next_load_path_addr, next_load_argv_addr);
if (image->nxlink_argv_marker) {
process.SetHomebrewNxlinkArgvMarker(*image->nxlink_argv_marker);
} else {
process.ClearHomebrewNxlinkArgvMarker();
}
HomebrewNxlink::ApplyServerMode(nxlink_server_mode, image->nxlink_argv_marker);
system.GetFileSystemController().RegisterProcess(
process.GetProcessId(), program_id,
std::make_unique<FileSys::RomFSFactory>(loader, system.GetContentProvider(),
system.GetFileSystemController()),
homebrew_initial_cwd);
process.SetHomebrewInPlaceNextLoad(true);
auto& context = thread.GetContext();
context = {};
context.r[0] = config_addr;
context.r[1] = UINT64_MAX;
context.r[18] = Common::Random::Random64(0) | 1;
context.lr = image->exit_process_offset ? base + *image->exit_process_offset : 0;
context.pc = base;
context.sp = GetInteger(stack_top);
context.fpcr = 0;
context.fpsr = 0;
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
if (auto* arm = process.GetArmInterface(core); arm != nullptr) {
arm->ClearInstructionCache();
}
}
return true;
}
static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
const std::vector<u8>& data, std::string nro_path,
std::string file_name) {
const std::vector<u8>& data) {
if (data.size() < sizeof(NroHeader)) {
return {};
}
@@ -625,49 +195,47 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
codeset.DataSegment().size += bss_size;
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
struct ConfigEntry {
u32_le key;
u32_le flags;
u64_le value[2];
};
static_assert(sizeof(ConfigEntry) == 0x18);
// AArch64 encoding for svc #0x7 (ExitProcess).
constexpr u32 kSvcExitProcessInstruction = 0xD40000E1;
constexpr size_t kNumEntries = 4; // MainThreadHandle, AppletType, Argv, EndOfList
constexpr size_t kConfigTableSize = kNumEntries * sizeof(ConfigEntry);
std::string argv_string;
size_t args_offset_in_image = 0;
std::optional<size_t> exit_process_offset_in_image;
const auto& program_args = Settings::values.program_args.GetValue();
const std::string argv0 = MakeHomebrewArgv0(std::move(nro_path), std::move(file_name));
argv_string = QuoteHomebrewArgvComponent(argv0);
if (!program_args.empty()) {
argv_string.push_back(' ');
argv_string = "homebrew ";
argv_string += program_args;
}
const auto nxlink_server_mode = Settings::values.homebrew_nxlink_server_mode.GetValue();
const bool append_loopback_nxlink_marker =
nxlink_server_mode != Settings::HomebrewNxlinkServerMode::Disabled;
const auto nxlink_argv_marker =
HomebrewNxlink::PrepareArgv(argv_string, {}, append_loopback_nxlink_marker);
if (argv_string.empty() || argv_string.back() != '\0') {
argv_string.push_back('\0');
}
const auto& code_segment = codeset.CodeSegment();
const size_t code_end =
(std::min)(program_image.size(), code_segment.offset + code_segment.size);
for (size_t offset = code_segment.offset; offset + sizeof(u32) <= code_end;
offset += sizeof(u32)) {
u32 instruction{};
std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction));
if (instruction == HomebrewSvcExitProcessInstruction) {
exit_process_offset_in_image = offset;
break;
const auto& code = codeset.CodeSegment();
const size_t code_end = (std::min)(program_image.size(), code.offset + code.size);
for (size_t offset = code.offset; offset + sizeof(u32) <= code_end; offset += sizeof(u32)) {
u32 instruction{};
std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction));
if (instruction == kSvcExitProcessInstruction) {
exit_process_offset_in_image = offset;
break;
}
}
if (!exit_process_offset_in_image) {
LOG_WARNING(Loader,
"Unable to find svcExitProcess in NRO; returning from main may fault");
}
}
if (!exit_process_offset_in_image) {
LOG_WARNING(Loader, "Unable to find svcExitProcess in NRO; returning from main may fault");
}
const size_t entries_and_buffers =
Common::AlignUp(HomebrewBaseConfigTableSize + HomebrewNextLoadPathSize +
HomebrewNextLoadArgvSize + argv_string.size(),
Core::Memory::YUZU_PAGESIZE);
const size_t entries_and_argv =
Common::AlignUp(kConfigTableSize + argv_string.size(), Core::Memory::YUZU_PAGESIZE);
args_offset_in_image = program_image.size();
codeset.DataSegment().size += static_cast<u32>(entries_and_buffers);
program_image.resize(args_offset_in_image + entries_and_buffers);
args_offset_in_image = program_image.size();
codeset.DataSegment().size += static_cast<u32>(entries_and_argv);
program_image.resize(args_offset_in_image + entries_and_argv);
}
size_t image_size = program_image.size();
#ifdef HAS_NCE
@@ -695,9 +263,6 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
image_size += patch_segment.size;
}
#endif
// In-place NextLoad reuses the original code mapping; leave room for larger homebrew cores.
constexpr size_t HomebrewCodeArenaSize = 192 * 1024 * 1024;
image_size = (std::max)(image_size, HomebrewCodeArenaSize);
// Enable direct memory mapping in case of NCE.
const u64 fastmem_base = [&]() -> size_t {
@@ -732,44 +297,31 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
// Load codeset for current process
codeset.memory = std::move(program_image);
process.LoadModule(system.Kernel(), std::move(codeset), process.GetEntryPoint());
process.SetHomebrewInPlaceNextLoad(false);
if (nxlink_argv_marker) {
process.SetHomebrewNxlinkArgvMarker(*nxlink_argv_marker);
} else {
process.ClearHomebrewNxlinkArgvMarker();
}
HomebrewNxlink::ApplyServerMode(nxlink_server_mode, nxlink_argv_marker);
{
if (!argv_string.empty()) {
constexpr u32 kEntryEndOfList = 0;
constexpr u32 kEntryMainThreadHandle = 1;
constexpr u32 kEntryArgv = 5;
constexpr u32 kEntryAppletType = 7;
constexpr u32 kAppletTypeApplication = 0;
const u64 base = GetInteger(process.GetEntryPoint());
const u64 config_addr = base + args_offset_in_image;
const u64 next_load_path_addr = config_addr + HomebrewBaseConfigTableSize;
const u64 next_load_argv_addr = next_load_path_addr + HomebrewNextLoadPathSize;
const u64 argv_addr = next_load_argv_addr + HomebrewNextLoadArgvSize;
const u64 argv_entry_addr = argv_string.empty() ? 0 : argv_addr;
const u64 argv_addr = config_addr + kConfigTableSize;
const HomebrewConfigEntry entries[HomebrewBaseConfigEntryCount] = {
{HomebrewEntryMainThreadHandle, 0, {0, 0}}, // Value[0] patched in Run()
{HomebrewEntryProcessHandle, 0, {0, 0}}, // Value[0] patched in Run()
{HomebrewEntryNextLoadPath, 0, {next_load_path_addr, next_load_argv_addr}},
{HomebrewEntryAppletType, 0, {HomebrewAppletTypeApplication, 0}},
{HomebrewEntryArgv, 0, {0, argv_entry_addr}},
{HomebrewEntrySyscallAvailableHint, 0, {HomebrewAllSvcHints, HomebrewAllSvcHints}},
{HomebrewEntryRandomSeed, 0,
{process.GetRandomEntropy(0), process.GetRandomEntropy(1)}},
{HomebrewEntryHosVersion, 0, {HomebrewHosVersion, 0}},
{HomebrewEntrySyscallAvailableHint2, 0, {HomebrewAllSvcHints, 0}},
{HomebrewEntryEndOfList, 0, {0, 0}},
const ConfigEntry entries[kNumEntries] = {
{kEntryMainThreadHandle, 0, {0, 0}}, // Value[0] patched in Run()
{kEntryAppletType, 0, {kAppletTypeApplication, 0}},
{kEntryArgv, 0, {0, argv_addr}},
{kEntryEndOfList, 0, {0, 0}},
};
process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries,
sizeof(entries));
if (!argv_string.empty()) {
process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, argv_string.data(),
argv_string.size());
}
process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries, sizeof(entries));
process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, argv_string.data(), argv_string.size());
constexpr size_t kMainThreadHandleValueOffset = offsetof(ConfigEntry, value);
process.SetArgPointer(Kernel::KProcessAddress{config_addr});
if (exit_process_offset_in_image) {
process.SetArgReturnAddress(Kernel::KProcessAddress{base + *exit_process_offset_in_image});
}
SetHomebrewConfigPointers(process, config_addr, next_load_path_addr, next_load_argv_addr);
process.SetMainThreadHandleAddr(Kernel::KProcessAddress{config_addr + kMainThreadHandleValueOffset});
}
return true;
@@ -777,8 +329,7 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
bool AppLoader_NRO::LoadNro(Core::System& system, Kernel::KProcess& process,
const FileSys::VfsFile& nro_file) {
return LoadNroImpl(system, process, nro_file.ReadAllBytes(), nro_file.GetFullPath(),
nro_file.GetName());
return LoadNroImpl(system, process, nro_file.ReadAllBytes());
}
AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::System& system) {
@@ -792,13 +343,10 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::S
u64 program_id{};
ReadProgramId(program_id);
const std::string argv0 = MakeHomebrewArgv0(file->GetFullPath(), file->GetName());
const std::string homebrew_initial_cwd = GetHomebrewInitialCwd(argv0);
system.GetFileSystemController().RegisterProcess(
process.GetProcessId(), program_id,
std::make_unique<FileSys::RomFSFactory>(*this, system.GetContentProvider(),
system.GetFileSystemController()),
homebrew_initial_cwd);
system.GetFileSystemController()));
is_loaded = true;
return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority,
-8
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -22,15 +19,10 @@ class NACP;
namespace Kernel {
class KProcess;
class KThread;
}
namespace Loader {
[[nodiscard]] bool LoadNroInPlace(Core::System& system, Kernel::KProcess& process,
Kernel::KThread& thread, const FileSys::VirtualFile& nro_file,
const std::string& nro_path, const std::string& launch_argv);
/// Loads an NRO file
class AppLoader_NRO final : public AppLoader {
public:
+5 -2
View File
@@ -504,9 +504,12 @@ void NPad::OnUpdate(Kernel::KernelCore& kernel, const Core::Timing::CoreTiming&
for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) {
auto& controller = controller_data[aruid_index][i];
controller.shared_memory =
&data->shared_memory_format->npad.npad_entry[i].internal_state;
controller.shared_memory = &data->shared_memory_format->npad.npad_entry[i].internal_state;
auto* npad = controller.shared_memory;
if (!npad || !controller.device) {
LOG_WARNING(Service_HID, "No device for {}", i);
continue;
}
const auto& controller_type = controller.device->GetNpadStyleIndex();
-7
View File
@@ -648,13 +648,6 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
// Disable raw input. When enabled this setting causes SDL to die when a web applet opens
SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, Settings::values.enable_raw_input ? "1" : "0");
#ifdef _WIN32
if (Settings::values.disable_wgi_xinput) {
SDL_SetHintWithPriority(SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT, "0", SDL_HINT_OVERRIDE);
SDL_SetHintWithPriority(SDL_HINT_JOYSTICK_WGI, "0", SDL_HINT_OVERRIDE);
}
#endif
// SDL3 defaults Steam Controller Bluetooth HIDAPI support to off, which can disable gyro.
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1");
SDL_SetHint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION, "1");
+15 -18
View File
@@ -92,12 +92,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
tr("Change the accuracy of the emulated CPU (for debugging only)."));
INSERT(Settings, cpu_backend, tr("Backend:"), QString());
INSERT(Settings, fast_cpu_time, tr("CPU Overclock"),
tr("Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see "
"reduced performance, "
"and certain games may behave improperly.\nUse Boost (1700MHz) to run at the "
"Switch's highest native "
"clock, or Fast (2000MHz) to run at 2x clock."));
INSERT(Settings, cpu_clock, tr("CPU Clocks"),
tr("Raises the clock the emulated CPU reports, which removes some FPS limiters.\n"
"Weaker CPUs may see reduced performance, and certain games may behave improperly."));
INSERT(Settings, use_custom_cpu_ticks, QString(), QString());
INSERT(Settings, cpu_ticks, tr("Custom CPU Ticks"),
@@ -230,9 +227,11 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly."));
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
tr("May reduce shader stutter."));
INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"),
tr("Overclocks the emulated GPU to increase dynamic resolution and render "
"distance.\nUse 256 for maximal performance and 512 for maximal graphics fidelity."));
INSERT(Settings, use_unified_memory, tr("Enable unified memory access"),
tr("Lets the GPU write buffer readbacks directly into guest memory."));
INSERT(Settings, gpu_clock, tr("GPU Clocks"),
tr("Makes the game believe GPU work finishes faster than it does, so it stops lowering "
"resolution and render distance to fit the Switch's clocks."));
INSERT(Settings, gpu_unswizzle_enabled, tr("GPU Unswizzle"),
tr("Accelerates BCn 3D texture decoding using GPU compute.\n"
"Disable if experiencing crashes or graphical glitches."));
@@ -302,8 +301,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
INSERT(Settings, device_name, tr("Device Name"), tr("The name of the console."));
INSERT(Settings, program_args, tr("Homebrew Args"),
tr("Command-line arguments passed to homebrew at launch (e.g. -noglsl)."));
INSERT(Settings, homebrew_nxlink_server_mode, tr("nxlink Server"),
tr("Starts a local nxlink server for homebrew stdout/stderr streams."));
INSERT(Settings, custom_rtc, tr("Custom RTC Date:"),
tr("This option allows to change the clock of the console.\n"
"Can be used to manipulate time in games."));
@@ -641,9 +638,9 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
}});
translations->insert({Settings::EnumMetadata<Settings::CpuClock>::Index(),
{
PAIR(CpuClock, Off, tr("Off")),
PAIR(CpuClock, Boost, tr("Boost (1700MHz)")),
PAIR(CpuClock, Fast, tr("Fast (2000MHz)")),
PAIR(CpuClock, Normal, tr("Normal")),
PAIR(CpuClock, Boost, tr("Boost")),
PAIR(CpuClock, Overclock, tr("Overclock")),
}});
translations->insert(
{Settings::EnumMetadata<Settings::ConfirmStop>::Index(),
@@ -652,11 +649,11 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
PAIR(ConfirmStop, Ask_Based_On_Game, tr("Only if game specifies not to stop")),
PAIR(ConfirmStop, Ask_Never, tr("Never ask")),
}});
translations->insert({Settings::EnumMetadata<Settings::GpuOverclock>::Index(),
translations->insert({Settings::EnumMetadata<Settings::GpuClock>::Index(),
{
PAIR(GpuOverclock, Normal, tr("Off")),
PAIR(GpuOverclock, Medium, tr("Medium (256)")),
PAIR(GpuOverclock, High, tr("High (512)")),
PAIR(GpuClock, Normal, tr("Normal")),
PAIR(GpuClock, Boost, tr("Boost")),
PAIR(GpuClock, Overclock, tr("Overclock")),
}});
translations->insert({Settings::EnumMetadata<Settings::GpuUnswizzleSize>::Index(),
{
+1 -3
View File
@@ -159,9 +159,7 @@ void GameListModel::RemoveFavorite(u64 program_id) {
void GameListModel::Repopulate() {
current_worker.reset();
if (!QtCommon::system->IsPoweredOn()) {
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
}
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
PopulateAsync(UISettings::values.game_dirs);
}
+2
View File
@@ -158,6 +158,8 @@ add_library(video_core STATIC
renderer_vulkan/vk_compute_pass.h
renderer_vulkan/vk_compute_pipeline.cpp
renderer_vulkan/vk_compute_pipeline.h
renderer_vulkan/vk_descriptor_buffer.cpp
renderer_vulkan/vk_descriptor_buffer.h
renderer_vulkan/vk_descriptor_pool.cpp
renderer_vulkan/vk_descriptor_pool.h
renderer_vulkan/vk_fence_manager.cpp
+212 -36
View File
@@ -571,7 +571,11 @@ void BufferCache<P>::AccumulateFlushes() {
template <class P>
bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept {
return (!async_buffers.empty() && async_buffers.front().has_value());
if (async_buffers.empty()) {
return false;
}
return async_buffers.front().has_value() ||
!pending_downloads.front().unified_copies.empty();
}
template <class P>
@@ -579,6 +583,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
AccumulateFlushes();
if (committed_gpu_modified_ranges.empty()) {
pending_downloads.emplace_back();
async_buffers.emplace_back(std::optional<Async_Buffer>{});
return;
}
@@ -638,27 +643,83 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
}
committed_gpu_modified_ranges.clear();
if (downloads.empty()) {
pending_downloads.emplace_back();
async_buffers.emplace_back(std::optional<Async_Buffer>{});
return;
}
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes, true);
boost::container::small_vector<BufferCopy, 4> normalized_copies;
runtime.PreCopyBarrier();
struct QueuedUnifiedCopy {
u64 window;
BufferId buffer_id;
boost::container::small_vector<BufferCopy, 16> copies;
};
AsyncDownloadBatch batch;
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> staging_downloads;
boost::container::small_vector<QueuedUnifiedCopy, 4> unified_copy_queue;
boost::container::small_vector<u64, 4> window_ids;
UnifiedWindowGroups groups;
u64 staging_size_bytes = 0;
for (auto& [copy, buffer_id] : downloads) {
copy.dst_offset += download_staging.offset;
const std::array copies{copy};
BufferCopy second_copy{copy};
Buffer& buffer = slot_buffers[buffer_id];
second_copy.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
const DAddr orig_device_addr = static_cast<DAddr>(second_copy.src_offset);
const DAddr orig_device_addr = buffer.CpuAddr() + copy.src_offset;
bool unified = false;
if constexpr (USE_UNIFIED_MEMORY) {
if (runtime.HasUnifiedMemory()) {
window_ids.clear();
groups.clear();
unified = ResolveUnifiedWindows(orig_device_addr, copy.src_offset, copy.size,
window_ids, groups);
}
}
BufferCopy record{copy};
record.src_offset = static_cast<size_t>(orig_device_addr);
if (unified) {
async_downloads.Add(orig_device_addr, copy.size);
buffer.MarkUsage(copy.src_offset, copy.size);
for (size_t i = 0; i < window_ids.size(); ++i) {
unified_copy_queue.push_back(
QueuedUnifiedCopy{window_ids[i], buffer_id, std::move(groups[i])});
}
batch.unified_copies.push_back(record);
continue;
}
copy.dst_offset = staging_size_bytes;
constexpr u64 align = 64ULL;
staging_size_bytes += (copy.size + align - 1) & ~(align - 1ULL);
staging_downloads.push_back({copy, buffer_id});
}
std::optional<Async_Buffer> download_staging;
if (!staging_downloads.empty()) {
download_staging = runtime.DownloadStagingBuffer(staging_size_bytes, true);
}
runtime.PreCopyBarrier();
for (auto& [copy, buffer_id] : staging_downloads) {
copy.dst_offset += download_staging->offset;
const std::array copies{copy};
Buffer& buffer = slot_buffers[buffer_id];
BufferCopy record{copy};
record.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
const DAddr orig_device_addr = static_cast<DAddr>(record.src_offset);
async_downloads.Add(orig_device_addr, copy.size);
buffer.MarkUsage(copy.src_offset, copy.size);
runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
normalized_copies.push_back(second_copy);
runtime.CopyBuffer(download_staging->buffer, buffer, copies, false);
batch.staging_copies.push_back(record);
}
if constexpr (USE_UNIFIED_MEMORY) {
for (const auto& queued : unified_copy_queue) {
const std::span<const BufferCopy> group_span(queued.copies.data(),
queued.copies.size());
runtime.CopyToUnifiedMemory(queued.window, slot_buffers[queued.buffer_id], group_span);
}
if (!unified_copy_queue.empty()) {
runtime.UnifiedMemoryHostBarrier();
}
}
runtime.PostCopyBarrier();
pending_downloads.emplace_back(std::move(normalized_copies));
async_buffers.emplace_back(download_staging);
pending_downloads.emplace_back(std::move(batch));
async_buffers.emplace_back(std::move(download_staging));
}
template <class P>
@@ -673,32 +734,49 @@ void BufferCache<P>::PopAsyncFlushes() {
template <class P>
void BufferCache<P>::PopAsyncBuffers() {
if (async_buffers.empty()) {
return;
}
if (!async_buffers.front().has_value()) {
struct Writeback {
DAddr addr;
const u8* src;
u64 size;
};
boost::container::small_vector<Writeback, 8> writebacks;
{
std::scoped_lock lock{mutex};
if (async_buffers.empty()) {
return;
}
auto& batch = pending_downloads.front();
auto& async_buffer = async_buffers.front();
if (async_buffer.has_value()) {
const u8* base = async_buffer->mapped_span.data();
const size_t base_offset = async_buffer->offset;
for (const auto& copy : batch.staging_copies) {
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
const u64 dst_offset = copy.dst_offset - base_offset;
const u8* read_mapped_memory = base + dst_offset;
async_downloads.ForEachInRange(
device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
writebacks.push_back(
{start, &read_mapped_memory[start - device_addr], end - start});
});
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
gpu_modified_ranges.Subtract(start, end - start);
});
}
async_buffers_death_ring.emplace_back(*async_buffer);
}
for (const auto& copy : batch.unified_copies) {
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
gpu_modified_ranges.Subtract(start, end - start);
});
}
async_buffers.pop_front();
return;
pending_downloads.pop_front();
}
auto& downloads = pending_downloads.front();
auto& async_buffer = async_buffers.front();
u8* base = async_buffer->mapped_span.data();
const size_t base_offset = async_buffer->offset;
for (const auto& copy : downloads) {
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
const u64 dst_offset = copy.dst_offset - base_offset;
const u8* read_mapped_memory = base + dst_offset;
async_downloads.ForEachInRange(device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
device_memory.WriteBlockUnsafe(start, &read_mapped_memory[start - device_addr],
end - start);
});
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
gpu_modified_ranges.Subtract(start, end - start);
});
for (const auto& wb : writebacks) {
device_memory.WriteBlockUnsafe(wb.addr, wb.src, wb.size);
}
async_buffers_death_ring.emplace_back(*async_buffer);
async_buffers.pop_front();
pending_downloads.pop_front();
}
template <class P>
@@ -1699,6 +1777,98 @@ void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
}
}
template <class P>
bool BufferCache<P>::ResolveUnifiedWindows(
[[maybe_unused]] DAddr device_addr, [[maybe_unused]] u64 buffer_offset,
[[maybe_unused]] u64 size, [[maybe_unused]] boost::container::small_vector<u64, 4>& window_ids,
[[maybe_unused]] UnifiedWindowGroups& groups) {
if constexpr (USE_UNIFIED_MEMORY) {
const u8* const physical_base = device_memory.GetPhysicalBase();
const u64 unified_base = runtime.UnifiedMemoryBase();
const u64 unified_size = runtime.UnifiedMemorySize();
const u64 window_size = runtime.UnifiedMemoryWindowSize();
if (window_size == 0) {
return false;
}
const auto group_for = [&](u64 window) -> boost::container::small_vector<BufferCopy, 16>& {
for (size_t i = 0; i < window_ids.size(); ++i) {
if (window_ids[i] == window) {
return groups[i];
}
}
window_ids.push_back(window);
groups.emplace_back();
return groups.back();
};
u64 downloaded = 0;
while (downloaded < size) {
const DAddr page_addr = device_addr + downloaded;
const u8* const ptr = device_memory.GetPointer<u8>(page_addr);
if (ptr == nullptr) {
return false;
}
const u64 page_offset = page_addr & Core::DEVICE_PAGEMASK;
u64 chunk = (std::min)(size - downloaded,
static_cast<u64>(Core::DEVICE_PAGESIZE) - page_offset);
const u64 phys_offset = static_cast<u64>(ptr - physical_base);
if (phys_offset < unified_base || phys_offset - unified_base + chunk > unified_size) {
return false;
}
const u64 relative = phys_offset - unified_base;
const u64 window = relative / window_size;
const u64 local_offset = relative % window_size;
chunk = (std::min)(chunk, window_size - local_offset);
auto& group = group_for(window);
if (!group.empty()) {
BufferCopy& last = group.back();
if (last.src_offset + last.size == buffer_offset + downloaded &&
last.dst_offset + last.size == local_offset) {
last.size += chunk;
downloaded += chunk;
continue;
}
}
group.push_back(BufferCopy{
.src_offset = buffer_offset + downloaded,
.dst_offset = local_offset,
.size = chunk,
});
downloaded += chunk;
}
return true;
} else {
return false;
}
}
template <class P>
bool BufferCache<P>::TryUnifiedDownloadMemory([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] std::span<BufferCopy> copies) {
if constexpr (USE_UNIFIED_MEMORY) {
boost::container::small_vector<u64, 4> window_ids;
UnifiedWindowGroups groups;
for (const BufferCopy& copy : copies) {
if (!ResolveUnifiedWindows(buffer.CpuAddr() + copy.src_offset, copy.src_offset,
copy.size, window_ids, groups)) {
return false;
}
}
for (const BufferCopy& copy : copies) {
buffer.MarkUsage(copy.src_offset, copy.size);
}
runtime.PreCopyBarrier();
for (size_t i = 0; i < window_ids.size(); ++i) {
const std::span<const BufferCopy> group_span(groups[i].data(), groups[i].size());
runtime.CopyToUnifiedMemory(window_ids[i], buffer, group_span);
}
runtime.UnifiedMemoryHostBarrier();
runtime.Finish();
return true;
} else {
return false;
}
}
template <class P>
void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] u64 total_size_bytes,
@@ -1802,6 +1972,12 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
}
if constexpr (USE_MEMORY_MAPS) {
if constexpr (USE_UNIFIED_MEMORY) {
if (runtime.HasUnifiedMemory() &&
TryUnifiedDownloadMemory(buffer, std::span(copies.data(), copies.size()))) {
return;
}
}
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
const u8* const mapped_memory = download_staging.mapped_span.data();
const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());
@@ -180,6 +180,7 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<BufferCacheChannelInf
static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = P::USE_MEMORY_MAPS_FOR_UPLOADS;
static constexpr bool USE_UNIFIED_MEMORY = P::USE_UNIFIED_MEMORY;
#ifdef YUZU_LEGACY
static constexpr s64 TARGET_THRESHOLD = 3_GiB;
@@ -443,6 +444,15 @@ private:
void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
bool TryUnifiedDownloadMemory(Buffer& buffer, std::span<BufferCopy> copies);
using UnifiedWindowGroups =
boost::container::small_vector<boost::container::small_vector<BufferCopy, 16>, 4>;
bool ResolveUnifiedWindows(DAddr device_addr, u64 buffer_offset, u64 size,
boost::container::small_vector<u64, 4>& window_ids,
UnifiedWindowGroups& groups);
void DownloadBufferMemory(Buffer& buffer_id);
void DownloadBufferMemory(Buffer& buffer_id, DAddr device_addr, u64 size);
@@ -498,9 +508,14 @@ private:
std::deque<Common::RangeSet<DAddr>> committed_gpu_modified_ranges;
// Async Buffers
struct AsyncDownloadBatch {
boost::container::small_vector<BufferCopy, 4> staging_copies;
boost::container::small_vector<BufferCopy, 4> unified_copies;
};
Common::OverlapRangeSet<DAddr> async_downloads;
std::deque<std::optional<Async_Buffer>> async_buffers;
std::deque<boost::container::small_vector<BufferCopy, 4>> pending_downloads;
std::deque<AsyncDownloadBatch> pending_downloads;
std::optional<Async_Buffer> current_buffer;
std::deque<Async_Buffer> async_buffers_death_ring;
+1
View File
@@ -195,6 +195,7 @@ private:
void ReleaseThreadFunc(std::stop_token stop_token) {
Common::SetCurrentThreadName("GPUFencingThread");
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
Common::SetCurrentThreadToPerformanceCores();
TFence current_fence;
std::deque<std::function<void()>> current_operations;
+58 -30
View File
@@ -10,6 +10,7 @@
#include <condition_variable>
#include <list>
#include <memory>
#include <utility>
#include "common/assert.h"
#include "common/settings.h"
@@ -39,6 +40,19 @@
namespace Tegra {
namespace {
constexpr u64 GpuClockMultiplier(Settings::GpuClock clock) {
switch (clock) {
case Settings::GpuClock::Boost:
return 256;
case Settings::GpuClock::Overclock:
return 512;
default:
return 1;
}
}
} // Anonymous namespace
struct GPU::Impl {
explicit Impl(Core::System& system_, bool is_async_, bool use_nvdec_)
: system{system_}
@@ -116,7 +130,7 @@ struct GPU::Impl {
[[nodiscard]] u64 RequestSyncOperation(Func&& action) {
std::unique_lock lck{sync_request_mutex};
const u64 fence = ++last_sync_fence;
sync_requests.emplace_back(action);
sync_requests.emplace_back(std::forward<Func>(action));
return fence;
}
@@ -145,14 +159,8 @@ struct GPU::Impl {
}
[[nodiscard]] u64 GetTicks() const {
u64 gpu_tick = system.CoreTiming().GetGPUTicks();
Settings::GpuOverclock overclock = Settings::values.fast_gpu_time.GetValue();
if (overclock != Settings::GpuOverclock::Normal) {
gpu_tick /= 256 * u64(overclock);
}
return gpu_tick;
const u64 gpu_tick = system.CoreTiming().GetGPUTicks();
return gpu_tick / GpuClockMultiplier(Settings::values.gpu_clock.GetValue());
}
void RendererFrameEndNotify() {
@@ -225,9 +233,9 @@ struct GPU::Impl {
}
void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers, std::vector<Service::Nvidia::NvFence>&& fences) {
size_t num_fences{fences.size()};
const size_t num_fences{fences.size()};
size_t current_request_counter{};
{
if (num_fences != 0) {
std::unique_lock<std::mutex> lk(request_swap_mutex);
if (free_swap_counters.empty()) {
current_request_counter = request_swap_counters.size();
@@ -238,27 +246,42 @@ struct GPU::Impl {
free_swap_counters.pop_front();
}
}
const auto wait_fence = RequestSyncOperation([this, current_request_counter, &layers, &fences, num_fences] {
auto& syncpoint_manager = system.Host1x().GetSyncpointManager();
if (num_fences == 0) {
renderer->Composite(layers);
}
const auto executer = [this, current_request_counter, layers_copy = layers]() {
{
std::unique_lock<std::mutex> lk(request_swap_mutex);
if (--request_swap_counters[current_request_counter] != 0) {
return;
}
free_swap_counters.push_back(current_request_counter);
pending_composite_fence = RequestSyncOperation(
[this, current_request_counter, num_fences, composite_layers = std::move(layers),
composite_fences = std::move(fences)] {
if (num_fences == 0) {
renderer->Composite(composite_layers);
return;
}
renderer->Composite(layers_copy);
};
for (size_t i = 0; i < num_fences; i++) {
syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer);
}
});
auto& syncpoint_manager = system.Host1x().GetSyncpointManager();
const auto executer = [this, current_request_counter, composite_layers]() {
{
std::unique_lock<std::mutex> lk(request_swap_mutex);
if (--request_swap_counters[current_request_counter] != 0) {
return;
}
free_swap_counters.push_back(current_request_counter);
}
renderer->Composite(composite_layers);
};
for (size_t i = 0; i < num_fences; i++) {
syncpoint_manager.RegisterGuestAction(composite_fences[i].id,
composite_fences[i].value, executer);
}
});
gpu_thread.TickGPU(is_async);
WaitForSyncOperation(wait_fence);
}
void WaitForComposite() {
const u64 fence = pending_composite_fence;
if (fence == 0) {
return;
}
pending_composite_fence = 0;
if (shutting_down.load(std::memory_order_relaxed)) {
return;
}
WaitForSyncOperation(fence);
}
std::vector<u8> GetAppletCaptureBuffer() {
@@ -311,6 +334,7 @@ struct GPU::Impl {
std::deque<size_t> free_swap_counters;
std::deque<size_t> request_swap_counters;
std::mutex request_swap_mutex;
u64 pending_composite_fence{};
};
GPU::GPU(Core::System& system, bool is_async, bool use_nvdec)
@@ -428,6 +452,10 @@ void GPU::RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
impl->RequestComposite(std::move(layers), std::move(fences));
}
void GPU::WaitForComposite() {
impl->WaitForComposite();
}
std::vector<u8> GPU::GetAppletCaptureBuffer() {
return impl->GetAppletCaptureBuffer();
}
+3 -1
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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -218,6 +218,8 @@ public:
void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
std::vector<Service::Nvidia::NvFence>&& fences);
void WaitForComposite();
std::vector<u8> GetAppletCaptureBuffer();
/// Performs any additional setup necessary in order to begin GPU emulation.
+1
View File
@@ -30,6 +30,7 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten
thread = std::jthread([&](std::stop_token stop_token) {
Common::SetCurrentThreadName("GPU");
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
Common::SetCurrentThreadToPerformanceCores();
system.RegisterHostThread();
auto current_context = context.Acquire();

Some files were not shown because too many files have changed in this diff Show More