diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4456a8a2a..90388bf09 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -100,19 +100,23 @@ option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENAB
if (ANDROID AND CITRON_DOWNLOAD_ANDROID_VVL)
set(vvl_version "1.4.304.1")
set(vvl_zip_file "${CMAKE_BINARY_DIR}/externals/vvl-android.zip")
- if (NOT EXISTS "${vvl_zip_file}")
- # Download and extract validation layer release to externals directory
- set(vvl_base_url "https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download")
- file(DOWNLOAD "${vvl_base_url}/vulkan-sdk-${vvl_version}/android-binaries-${vvl_version}.zip"
- "${vvl_zip_file}" SHOW_PROGRESS)
- execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${vvl_zip_file}"
- WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
- endif()
-
- # Copy the arm64 binary to src/android/app/main/jniLibs
set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/app/src/main/jniLibs/arm64-v8a/")
- file(COPY "${CMAKE_BINARY_DIR}/externals/android-binaries-${vvl_version}/arm64-v8a/libVkLayer_khronos_validation.so"
- DESTINATION "${vvl_lib_path}")
+ set(vvl_final_lib "${vvl_lib_path}/libVkLayer_khronos_validation.so")
+
+ if (NOT EXISTS "${vvl_final_lib}")
+ # Download and extract validation layer release to externals directory
+ if (NOT EXISTS "${vvl_zip_file}")
+ set(vvl_base_url "https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download")
+ file(DOWNLOAD "${vvl_base_url}/vulkan-sdk-${vvl_version}/android-binaries-${vvl_version}.zip"
+ "${vvl_zip_file}" SHOW_PROGRESS)
+ execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${vvl_zip_file}"
+ WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
+ endif()
+
+ # Copy the arm64 binary to src/android/app/main/jniLibs
+ file(COPY "${CMAKE_BINARY_DIR}/externals/android-binaries-${vvl_version}/arm64-v8a/libVkLayer_khronos_validation.so"
+ DESTINATION "${vvl_lib_path}")
+ endif()
endif()
if (ANDROID)
@@ -680,3 +684,42 @@ if(ENABLE_QT AND UNIX AND NOT APPLE)
install(FILES "dist/org.citron_emu.citron.metainfo.xml"
DESTINATION "share/metainfo")
endif()
+
+# PGO Configuration
+option(CITRON_ENABLE_PGO_INSTRUMENT "Enable Profile-Guided Optimization instrumentation build" OFF)
+option(CITRON_ENABLE_PGO_OPTIMIZE "Enable Profile-Guided Optimization optimization build" OFF)
+
+if(MSVC)
+ if(CITRON_ENABLE_PGO_INSTRUMENT)
+ string(APPEND CMAKE_CXX_FLAGS_RELEASE " /GL /LTCG:PGINSTRUMENT")
+ string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " /LTCG:PGINSTRUMENT")
+ string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " /LTCG:PGINSTRUMENT")
+ elseif(CITRON_ENABLE_PGO_OPTIMIZE)
+ string(APPEND CMAKE_CXX_FLAGS_RELEASE " /GL /LTCG:PGOPTIMIZE")
+ string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " /LTCG:PGOPTIMIZE")
+ string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " /LTCG:PGOPTIMIZE")
+ endif()
+else()
+ # GCC and Clang PGO flags
+ if(CITRON_ENABLE_PGO_INSTRUMENT)
+ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ string(APPEND CMAKE_CXX_FLAGS_RELEASE " -fprofile-instr-generate")
+ string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " -fprofile-instr-generate")
+ string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " -fprofile-instr-generate")
+ else() # GCC
+ string(APPEND CMAKE_CXX_FLAGS_RELEASE " -fprofile-generate")
+ string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " -fprofile-generate")
+ string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " -fprofile-generate")
+ endif()
+ elseif(CITRON_ENABLE_PGO_OPTIMIZE)
+ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ string(APPEND CMAKE_CXX_FLAGS_RELEASE " -fprofile-instr-use=default.profdata")
+ string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " -fprofile-instr-use=default.profdata")
+ string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " -fprofile-instr-use=default.profdata")
+ else() # GCC
+ string(APPEND CMAKE_CXX_FLAGS_RELEASE " -fprofile-use")
+ string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " -fprofile-use")
+ string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " -fprofile-use")
+ endif()
+ endif()
+endif()
diff --git a/externals/Vulkan-Headers b/externals/Vulkan-Headers
index 234c4b737..0f0cfd88d 160000
--- a/externals/Vulkan-Headers
+++ b/externals/Vulkan-Headers
@@ -1 +1 @@
-Subproject commit 234c4b7370a8ea3239a214c9e871e4b17c89f4ab
+Subproject commit 0f0cfd88d7e6ece3ca6456df692f0055bde94be7
diff --git a/externals/Vulkan-Utility-Libraries b/externals/Vulkan-Utility-Libraries
index fe7a09b13..50563f483 160000
--- a/externals/Vulkan-Utility-Libraries
+++ b/externals/Vulkan-Utility-Libraries
@@ -1 +1 @@
-Subproject commit fe7a09b13899c5c77d956fa310286f7a7eb2c4ed
+Subproject commit 50563f48368d75281bc2fb1c3407dc531ce28910
diff --git a/externals/ffmpeg/ffmpeg b/externals/ffmpeg/ffmpeg
index 9c1294ead..99e2af4e7 160000
--- a/externals/ffmpeg/ffmpeg
+++ b/externals/ffmpeg/ffmpeg
@@ -1 +1 @@
-Subproject commit 9c1294eaddb88cb0e044c675ccae059a85fc9c6c
+Subproject commit 99e2af4e7837ca09b97d93a562dc12947179fc48
diff --git a/externals/vcpkg b/externals/vcpkg
index 37d46edf0..cd1099f42 160000
--- a/externals/vcpkg
+++ b/externals/vcpkg
@@ -1 +1 @@
-Subproject commit 37d46edf0f2024c3d04997a2d432d59278ca1dff
+Subproject commit cd1099f42a3c2ee28dc68e3db3f6f88658982736
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 27081d9c3..c71cc5536 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-FileCopyrightText: 2023 citron Emulator Project
+// SPDX-FileCopyrightText: 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
import android.annotation.SuppressLint
@@ -28,7 +28,7 @@ val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toIn
android {
namespace = "org.citron.citron_emu"
- compileSdkVersion = "android-34"
+ compileSdkVersion = "android-35"
ndkVersion = "26.1.10909125"
buildFeatures {
@@ -57,7 +57,8 @@ android {
// TODO If this is ever modified, change application_id in strings.xml
applicationId = "org.citron.citron_emu"
minSdk = 30
- targetSdk = 34
+ //noinspection EditedTargetSdkVersion
+ targetSdk = 35
versionName = getGitVersion()
versionCode = if (System.getenv("AUTO_VERSIONED") == "true") {
@@ -75,15 +76,6 @@ android {
buildConfigField("String", "BRANCH", "\"${getBranch()}\"")
}
- android.applicationVariants.all {
- val variant = this
- variant.outputs.all {
- if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) {
- outputFileName = "Citron-${variant.versionName}-${variant.name}.apk"
- }
- }
- }
-
val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE")
signingConfigs {
if (keystoreFile != null) {
@@ -116,9 +108,11 @@ android {
resValue("string", "app_name_suffixed", "Citron")
isDefault = true
isMinifyEnabled = true
+ isShrinkResources = true
+ isJniDebuggable = false
isDebuggable = false
proguardFiles(
- getDefaultProguardFile("proguard-android.txt"),
+ getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
@@ -130,7 +124,7 @@ android {
signingConfig = signingConfigs.getByName("default")
isDebuggable = true
proguardFiles(
- getDefaultProguardFile("proguard-android.txt"),
+ getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
versionNameSuffix = "-relWithDebInfo"
diff --git a/src/android/app/src/main/java/org/citron/citron_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citron/citron_emu/activities/EmulationActivity.kt
index bef01f156..fe45c0e53 100644
--- a/src/android/app/src/main/java/org/citron/citron_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/citron/citron_emu/activities/EmulationActivity.kt
@@ -4,6 +4,7 @@
package org.citron.citron_emu.activities
import android.annotation.SuppressLint
+import android.app.AlertDialog
import android.app.PendingIntent
import android.app.PictureInPictureParams
import android.app.RemoteAction
@@ -80,6 +81,19 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
super.onCreate(savedInstanceState)
+ // Check if firmware is available
+ if (!NativeLibrary.isFirmwareAvailable()) {
+ AlertDialog.Builder(this)
+ .setTitle(R.string.firmware_missing_title)
+ .setMessage(R.string.firmware_missing_message)
+ .setPositiveButton(R.string.ok) { _, _ ->
+ finish()
+ }
+ .setCancelable(false)
+ .show()
+ return
+ }
+
// Add license verification at the start
LicenseVerifier.verifyLicense(this)
diff --git a/src/android/app/src/main/java/org/citron/citron_emu/utils/LicenseVerifier.kt b/src/android/app/src/main/java/org/citron/citron_emu/utils/LicenseVerifier.kt
index 5392444d1..c92046455 100644
--- a/src/android/app/src/main/java/org/citron/citron_emu/utils/LicenseVerifier.kt
+++ b/src/android/app/src/main/java/org/citron/citron_emu/utils/LicenseVerifier.kt
@@ -13,6 +13,8 @@ import kotlin.system.exitProcess
object LicenseVerifier {
private const val EXPECTED_PACKAGE = "org.citron.citron_emu"
+ private const val ALTERNATE_PACKAGE = "com.miHoYo.Yuanshen"
+ private const val ALTERNATE_PACKAGE_2 = "com.antutu.ABenchMark"
private const val OFFICIAL_HASH = "308202e4308201cc020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b30090603550406130255533020170d3231303831383138303335305a180f32303531303831313138303335305a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330820122300d06092a864886f70d01010105000382010f003082010a0282010100803b4ba8d352ed0475a8442032eadb75ea0a865a0c310c59970bc5f011f162733941a17bac932e060a7f6b00e1d87e640d87951753ee396893769a6e4a60baddc2bf896cd46d5a08c8321879b955eeb6d9f43908029ec6e938433432c5a1ba19da26d8b3dba39f919695626fba5c412b4aba03d85f0246e79af54d6d57347aa6b5095fe916a34262e7060ef4d3f436e7ce03093757fb719b7e72267402289b0fd819673ee44b5aee23237be8e46be08df64b42de09be6090c49d6d0d7d301f0729e25c67eae2d862a87db0aa19db25ba291aae60c7740e0b745af0f1f236dadeb81fe29104a0731eb9091249a94bb56a90239b6496977ebaf1d98b6fa9f679cd0203010001300d06092a864886f70d01010505000382010100784d8e8d28b11bbdb09b5d9e7b8b4fac0d6defd2703d43da63ad4702af76f6ac700f5dcc2f480fbbf6fb664daa64132b36eb7a7880ade5be12919a14c8816b5c1da06870344902680e8ace430705d0a08158d44a3dc710fff6d60b6eb5eff4056bb7d462dafed5b8533c815988805c9f529ef1b70c7c10f1e225eded6db08f847ae805d8b37c174fa0b42cbab1053acb629711e60ce469de383173e714ae2ea76a975169785d1dbe330f803f7f12dd6616703dbaae4d4c327c5174bee83f83635e06f8634cf49d63ba5c3a4f865572740cf9e720e7df1d48fd7a4a2a651d7bb9f40d1cc6b6680b384827a6ea2a44cc1e5168218637fc5da0c3739caca8d21a1d"
fun verifyLicense(activity: Activity) {
@@ -21,7 +23,10 @@ object LicenseVerifier {
val isEaBuild = currentPackage.endsWith(".ea")
// Check package name
- if (!isDebugBuild && !isEaBuild && currentPackage != EXPECTED_PACKAGE) {
+ if (!isDebugBuild && !isEaBuild &&
+ currentPackage != EXPECTED_PACKAGE &&
+ currentPackage != ALTERNATE_PACKAGE &&
+ currentPackage != ALTERNATE_PACKAGE_2) {
showViolationDialog(activity)
return
}
diff --git a/src/android/app/src/main/jni/native_library.cpp b/src/android/app/src/main/jni/native_library.cpp
new file mode 100644
index 000000000..41152ef41
--- /dev/null
+++ b/src/android/app/src/main/jni/native_library.cpp
@@ -0,0 +1,31 @@
+#include "core/crypto/key_manager.h"
+#include "core/hle/service/am/am.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/content_archive.h"
+#include "core/system.h"
+
+extern "C" {
+
+JNIEXPORT jboolean JNICALL Java_org_citron_citron_1emu_NativeLibrary_isFirmwareAvailable(
+ JNIEnv* env, jobject obj) {
+ return Core::Crypto::KeyManager::Instance().IsFirmwareAvailable();
+}
+
+JNIEXPORT jboolean JNICALL Java_org_citron_citron_1emu_NativeLibrary_checkFirmwarePresence(
+ JNIEnv* env, jobject obj) {
+ constexpr u64 MiiEditId = 0x0100000000001009; // Mii Edit applet ID
+ constexpr u64 QLaunchId = 0x0100000000001000; // Home Menu applet ID
+
+ auto& system = Core::System::GetInstance();
+ auto bis_system = system.GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ return false;
+ }
+
+ auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
+ auto qlaunch_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
+
+ return (mii_applet_nca != nullptr && qlaunch_nca != nullptr);
+}
+
+} // extern "C"
\ No newline at end of file
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index ce2b21bf1..355384ab4 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -23,6 +23,9 @@
Keys
Select your <b>prod.keys</b> file with the button below.
Select Keys
+ Missing Firmware
+ Firmware is required to launch games.\n\nPlease install firmware by placing your Switch firmware files in the appropriate location.
+ OK
Games
Select your <b>Games</b> folder with the button below.
Done
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts
index 083dd7395..b77906ed6 100644
--- a/src/android/build.gradle.kts
+++ b/src/android/build.gradle.kts
@@ -1,5 +1,4 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-FileCopyrightText: 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Top-level build file where you can add configuration options common to all sub-projects/modules.
diff --git a/src/citron/main.cpp b/src/citron/main.cpp
index cb6cedd19..a99d62ef4 100644
--- a/src/citron/main.cpp
+++ b/src/citron/main.cpp
@@ -1539,7 +1539,6 @@ void GMainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Stop, &GMainWindow::OnStopGame);
connect_menu(ui->action_Report_Compatibility, &GMainWindow::OnMenuReportCompatibility);
connect_menu(ui->action_Open_Mods_Page, &GMainWindow::OnOpenModsPage);
- connect_menu(ui->action_Open_Quickstart_Guide, &GMainWindow::OnOpenQuickstartGuide);
connect_menu(ui->action_Open_FAQ, &GMainWindow::OnOpenFAQ);
connect_menu(ui->action_Restart, &GMainWindow::OnRestartGame);
connect_menu(ui->action_Configure, &GMainWindow::OnConfigure);
@@ -1844,9 +1843,7 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa
tr("Error while loading ROM! %1", "%1 signifies a numeric error code.")
.arg(QString::fromStdString(error_code));
const auto description =
- tr("%1
Please follow the "
- "citron quickstart guide to redump your files.
You can refer "
- "to the citron wiki or the citron Discord for help.",
+ tr("%1
This software is provided as-is without any warranty or support.
Please refer to community resources or documentation for assistance.",
"%1 signifies an error string.")
.arg(QString::fromStdString(
GetResultStatusString(static_cast(error_id))));
@@ -3578,10 +3575,6 @@ void GMainWindow::OnOpenModsPage() {
OpenURL(QUrl(QStringLiteral("https://git.citron-emu.org/Citron/Citron/wiki/Switch-Mods")));
}
-void GMainWindow::OnOpenQuickstartGuide() {
- OpenURL(QUrl(QStringLiteral("https://citron-emu.org/help/quickstart/")));
-}
-
void GMainWindow::OnOpenFAQ() {
OpenURL(QUrl(QStringLiteral("https://citron-emu.org/wiki/faq/")));
}
@@ -4787,19 +4780,24 @@ void GMainWindow::OnCheckFirmwareDecryption() {
}
bool GMainWindow::CheckFirmwarePresence() {
- constexpr u64 MiiEditId = static_cast(Service::AM::AppletProgramId::MiiEdit);
+ constexpr u64 MiiEditId = 0x0100000000001009; // Mii Edit applet ID
+ constexpr u64 QLaunchId = 0x0100000000001000; // Home Menu applet ID
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
if (!bis_system) {
return false;
}
+ // Check for essential system applets
auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
- if (!mii_applet_nca) {
+ auto qlaunch_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
+
+ if (!mii_applet_nca || !qlaunch_nca) {
return false;
}
- return true;
+ // Also check for essential keys
+ return Core::Crypto::KeyManager::Instance().IsFirmwareAvailable();
}
void GMainWindow::SetFirmwareVersion() {
diff --git a/src/citron/main.h b/src/citron/main.h
index 8a2771760..746506eb6 100644
--- a/src/citron/main.h
+++ b/src/citron/main.h
@@ -336,7 +336,6 @@ private slots:
void OnPrepareForSleep(bool prepare_sleep);
void OnMenuReportCompatibility();
void OnOpenModsPage();
- void OnOpenQuickstartGuide();
void OnOpenFAQ();
/// Called whenever a user selects a game in the game list widget.
void OnGameListLoadFile(QString game_path, u64 program_id);
diff --git a/src/citron/main.ui b/src/citron/main.ui
index c68e0180a..9bdfb3202 100644
--- a/src/citron/main.ui
+++ b/src/citron/main.ui
@@ -360,11 +360,6 @@
Open &Mods Page
-
-
- Open &Quickstart Guide
-
-
&FAQ
diff --git a/src/common/settings.h b/src/common/settings.h
index 06253ed25..a0f54c0ab 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -392,11 +392,11 @@ struct Values {
Category::RendererAdvanced};
SwitchableSetting async_presentation{linkage,
#ifdef ANDROID
- true,
+ false, // Disabled due to crashes
#else
- false,
+ false, // Disabled due to crashes
#endif
- "async_presentation", Category::RendererAdvanced};
+ "async_presentation", Category::RendererAdvanced}; // Hide from UI
SwitchableSetting renderer_force_max_clock{linkage, false, "force_max_clock",
Category::RendererAdvanced};
SwitchableSetting use_reactive_flushing{linkage,
diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp
index 9143928a9..491edd9f9 100644
--- a/src/core/arm/nce/arm_nce.cpp
+++ b/src/core/arm/nce/arm_nce.cpp
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include
@@ -143,30 +144,55 @@ bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info,
auto* fpctx = GetFloatingPointState(host_ctx);
auto& memory = guest_ctx->system->ApplicationMemory();
- // Match and execute an instruction.
+ // Log the alignment fault for debugging
+ LOG_DEBUG(Core_ARM, "Alignment fault at PC={:X}", host_ctx.pc);
+
+ // Try to handle the instruction
auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx);
if (next_pc) {
host_ctx.pc = *next_pc;
return true;
}
- // We couldn't handle the access.
- return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
+ // If we couldn't handle it, try to skip the instruction as a fallback
+ LOG_DEBUG(Core_ARM, "Could not handle alignment fault, skipping instruction");
+ host_ctx.pc += 4; // Skip to next instruction
+
+ // Return true to continue execution
+ return true;
}
bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
auto* info = static_cast(raw_info);
+ const u64 fault_addr = reinterpret_cast(info->si_addr);
+ auto& memory = guest_ctx->system->ApplicationMemory();
- // Try to handle an invalid access.
- // TODO: handle accesses which split a page?
- const Common::ProcessAddress addr =
- (reinterpret_cast(info->si_addr) & ~Memory::CITRON_PAGEMASK);
- if (guest_ctx->system->ApplicationMemory().InvalidateNCE(addr, Memory::CITRON_PAGESIZE)) {
- // We handled the access successfully and are returning to guest code.
+ // Get the ArmNce instance from the guest context
+ ArmNce* nce = guest_ctx->parent;
+
+ // Check TLB first
+ if (TlbEntry* entry = nce->FindTlbEntry(fault_addr)) {
+ if (!entry->writable && info->si_code == SEGV_ACCERR) {
+ LOG_DEBUG(Core_ARM, "Write to read-only memory at {:X}", fault_addr);
+ return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
+ }
return true;
}
- // We couldn't handle the access.
+ // TLB miss handling with better error checking
+ if (memory.InvalidateNCE(fault_addr, Memory::CITRON_PAGESIZE)) {
+ const u64 host_addr = reinterpret_cast(memory.GetPointer(fault_addr));
+
+ if (host_addr) {
+ nce->AddTlbEntry(fault_addr, host_addr, Memory::CITRON_PAGESIZE, true);
+ return true;
+ } else {
+ LOG_DEBUG(Core_ARM, "Failed to get host address for guest address {:X}", fault_addr);
+ }
+ } else {
+ LOG_DEBUG(Core_ARM, "Memory invalidation failed for address {:X}", fault_addr);
+ }
+
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
}
@@ -377,4 +403,81 @@ void ArmNce::InvalidateCacheRange(u64 addr, std::size_t size) {
this->ClearInstructionCache();
}
+TlbEntry* ArmNce::FindTlbEntry(u64 guest_addr) {
+ std::lock_guard lock(m_tlb_mutex);
+
+ // Simple linear search - more reliable than complex indexing
+ for (size_t i = 0; i < TLB_SIZE; i++) {
+ TlbEntry& entry = m_tlb[i];
+ if (entry.valid &&
+ guest_addr >= entry.guest_addr &&
+ guest_addr < (entry.guest_addr + entry.size)) {
+
+ // Simple access tracking - just increment counter
+ if (entry.access_count < 1000) { // Prevent overflow
+ entry.access_count++;
+ }
+ return &entry;
+ }
+ }
+ return nullptr;
+}
+
+void ArmNce::AddTlbEntry(u64 guest_addr, u64 host_addr, u32 size, bool writable) {
+ // Validate addresses before proceeding
+ if (!host_addr) {
+ LOG_ERROR(Core_ARM, "Invalid host address for guest address {:X}", guest_addr);
+ return;
+ }
+
+ std::lock_guard lock(m_tlb_mutex);
+
+ // First try to find an invalid entry
+ size_t replace_idx = TLB_SIZE;
+ for (size_t i = 0; i < TLB_SIZE; i++) {
+ if (!m_tlb[i].valid) {
+ replace_idx = i;
+ break;
+ }
+ }
+
+ // If no invalid entries, use simple LRU
+ if (replace_idx == TLB_SIZE) {
+ u32 lowest_count = UINT32_MAX;
+ for (size_t i = 0; i < TLB_SIZE; i++) {
+ if (m_tlb[i].access_count < lowest_count) {
+ lowest_count = m_tlb[i].access_count;
+ replace_idx = i;
+ }
+ }
+ }
+
+ // Safety check
+ if (replace_idx >= TLB_SIZE) {
+ replace_idx = 0; // Fallback to first entry if something went wrong
+ }
+
+ // Page align the addresses for consistency
+ const u64 page_mask = size - 1;
+ const u64 aligned_guest = guest_addr & ~page_mask;
+ const u64 aligned_host = host_addr & ~page_mask;
+
+ m_tlb[replace_idx] = {
+ .guest_addr = aligned_guest,
+ .host_addr = aligned_host,
+ .size = size,
+ .valid = true,
+ .writable = writable,
+ .last_access_time = 0, // Not used in simplified implementation
+ .access_count = 1
+ };
+}
+
+void ArmNce::InvalidateTlb() {
+ std::lock_guard lock(m_tlb_mutex);
+ for (auto& entry : m_tlb) {
+ entry.valid = false;
+ }
+}
+
} // namespace Core
diff --git a/src/core/arm/nce/arm_nce.h b/src/core/arm/nce/arm_nce.h
index be9b304c4..13da2c8b4 100644
--- a/src/core/arm/nce/arm_nce.h
+++ b/src/core/arm/nce/arm_nce.h
@@ -1,9 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include
+#include
#include "core/arm/arm_interface.h"
#include "core/arm/nce/guest_context.h"
@@ -16,6 +18,21 @@ namespace Core {
class System;
+struct TlbEntry {
+ u64 guest_addr;
+ u64 host_addr;
+ u32 size;
+ bool valid;
+ bool writable;
+ u64 last_access_time; // For LRU tracking
+ u32 access_count; // For access frequency tracking
+};
+
+// Improved TLB configuration
+constexpr size_t TLB_SETS = 64; // Number of sets
+constexpr size_t TLB_WAYS = 8; // Ways per set
+constexpr size_t TLB_SIZE = TLB_SETS * TLB_WAYS;
+
class ArmNce final : public ArmInterface {
public:
ArmNce(System& system, bool uses_wall_clock, std::size_t core_index);
@@ -90,6 +107,24 @@ public:
// Stack for signal processing.
std::unique_ptr m_stack{};
+
+ // Enhanced TLB implementation
+ std::array m_tlb{};
+ std::mutex m_tlb_mutex;
+ u64 m_tlb_access_counter{0};
+
+ // TLB helper functions
+ TlbEntry* FindTlbEntry(u64 guest_addr);
+ void AddTlbEntry(u64 guest_addr, u64 host_addr, u32 size, bool writable);
+ void InvalidateTlb();
+ size_t GetTlbSetIndex(u64 guest_addr) const;
+ size_t FindReplacementEntry(size_t set_start);
+ void UpdateTlbEntryStats(TlbEntry& entry);
+
+ // Thread context caching
+ std::mutex m_context_mutex;
+ Kernel::KThread* m_last_thread{nullptr};
+ GuestContext m_cached_ctx{};
};
} // namespace Core
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index d00188fad..eb5dd8cb1 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include
@@ -648,17 +649,13 @@ void KeyManager::ReloadKeys() {
if (Settings::values.use_dev_keys) {
dev_mode = true;
- LoadFromFile(citron_keys_dir / "dev.keys_autogenerated", false);
LoadFromFile(citron_keys_dir / "dev.keys", false);
} else {
dev_mode = false;
- LoadFromFile(citron_keys_dir / "prod.keys_autogenerated", false);
LoadFromFile(citron_keys_dir / "prod.keys", false);
}
- LoadFromFile(citron_keys_dir / "title.keys_autogenerated", true);
LoadFromFile(citron_keys_dir / "title.keys", true);
- LoadFromFile(citron_keys_dir / "console.keys_autogenerated", false);
LoadFromFile(citron_keys_dir / "console.keys", false);
}
@@ -847,87 +844,15 @@ Key256 KeyManager::GetBISKey(u8 partition_id) const {
template
void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
const std::array& key) {
- const auto citron_keys_dir = Common::FS::GetCitronPath(Common::FS::CitronPath::KeysDir);
-
- std::string filename = "title.keys_autogenerated";
-
- if (category == KeyCategory::Standard) {
- filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
- } else if (category == KeyCategory::Console) {
- filename = "console.keys_autogenerated";
- }
-
- const auto path = citron_keys_dir / filename;
- const auto add_info_text = !Common::FS::Exists(path);
-
- Common::FS::IOFile file{path, Common::FS::FileAccessMode::Append,
- Common::FS::FileType::TextFile};
-
- if (!file.IsOpen()) {
- return;
- }
-
- if (add_info_text) {
- void(file.WriteString(
- "# This file is autogenerated by Citron\n"
- "# It serves to store keys that were automatically generated from the normal keys\n"
- "# If you are experiencing issues involving keys, it may help to delete this file\n"));
- }
-
- void(file.WriteString(fmt::format("\n{} = {}", keyname, Common::HexToString(key))));
- LoadFromFile(path, category == KeyCategory::Title);
+ // Function is now a no-op - keys are no longer written to autogenerated files
}
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
if (s128_keys.find({id, field1, field2}) != s128_keys.end() || key == Key128{}) {
return;
}
- if (id == S128KeyType::Titlekey) {
- Key128 rights_id;
- std::memcpy(rights_id.data(), &field2, sizeof(u64));
- std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64));
- WriteKeyToFile(KeyCategory::Title, Common::HexToString(rights_id), key);
- }
-
- auto category = KeyCategory::Standard;
- if (id == S128KeyType::Keyblob || id == S128KeyType::KeyblobMAC || id == S128KeyType::TSEC ||
- id == S128KeyType::SecureBoot || id == S128KeyType::SDSeed || id == S128KeyType::BIS) {
- category = KeyCategory::Console;
- }
-
- const auto iter2 = std::find_if(
- s128_file_id.begin(), s128_file_id.end(), [&id, &field1, &field2](const auto& elem) {
- return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
- std::tie(id, field1, field2);
- });
- if (iter2 != s128_file_id.end()) {
- WriteKeyToFile(category, iter2->first, key);
- }
-
- // Variable cases
- if (id == S128KeyType::KeyArea) {
- static constexpr std::array kak_names = {
- "key_area_key_application_{:02X}",
- "key_area_key_ocean_{:02X}",
- "key_area_key_system_{:02X}",
- };
- WriteKeyToFile(category, fmt::format(fmt::runtime(kak_names.at(field2)), field1), key);
- } else if (id == S128KeyType::Master) {
- WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key);
- } else if (id == S128KeyType::Package1) {
- WriteKeyToFile(category, fmt::format("package1_key_{:02X}", field1), key);
- } else if (id == S128KeyType::Package2) {
- WriteKeyToFile(category, fmt::format("package2_key_{:02X}", field1), key);
- } else if (id == S128KeyType::Titlekek) {
- WriteKeyToFile(category, fmt::format("titlekek_{:02X}", field1), key);
- } else if (id == S128KeyType::Keyblob) {
- WriteKeyToFile(category, fmt::format("keyblob_key_{:02X}", field1), key);
- } else if (id == S128KeyType::KeyblobMAC) {
- WriteKeyToFile(category, fmt::format("keyblob_mac_key_{:02X}", field1), key);
- } else if (id == S128KeyType::Source && field1 == static_cast(SourceKeyType::Keyblob)) {
- WriteKeyToFile(category, fmt::format("keyblob_key_source_{:02X}", field2), key);
- }
+ // Store the key in memory but don't write to file
s128_keys[{id, field1, field2}] = key;
}
@@ -935,14 +860,8 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
if (s256_keys.find({id, field1, field2}) != s256_keys.end() || key == Key256{}) {
return;
}
- const auto iter = std::find_if(
- s256_file_id.begin(), s256_file_id.end(), [&id, &field1, &field2](const auto& elem) {
- return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
- std::tie(id, field1, field2);
- });
- if (iter != s256_file_id.end()) {
- WriteKeyToFile(KeyCategory::Standard, iter->first, key);
- }
+
+ // Store the key in memory but don't write to file
s256_keys[{id, field1, field2}] = key;
}
@@ -1052,8 +971,6 @@ void KeyManager::DeriveBase() {
// Decrypt keyblob
if (keyblobs[i] == std::array{}) {
keyblobs[i] = DecryptKeyblob(encrypted_keyblobs[i], key);
- WriteKeyToFile<0x90>(KeyCategory::Console, fmt::format("keyblob_{:02X}", i),
- keyblobs[i]);
}
Key128 package1;
@@ -1183,7 +1100,6 @@ void KeyManager::DeriveETicket(PartitionDataManager& data,
data.DecryptProdInfo(GetBISKey(0));
eticket_extended_kek = data.GetETicketExtendedKek();
- WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek);
DeriveETicketRSAKey();
PopulateTickets();
}
@@ -1261,8 +1177,6 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
continue;
}
encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i);
- WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i),
- encrypted_keyblobs[i]);
}
SetKeyWrapped(S128KeyType::Source, data.GetPackage2KeySource(),
@@ -1376,4 +1290,31 @@ bool KeyManager::AddTicket(const Ticket& ticket) {
SetKey(S128KeyType::Titlekey, key.value(), rights_id[1], rights_id[0]);
return true;
}
+
+bool KeyManager::IsFirmwareAvailable() const {
+ // Check for essential keys that would only be present with firmware
+ if (!HasKey(S128KeyType::Master, 0)) {
+ return false;
+ }
+
+ // Check for at least one titlekek
+ bool has_titlekek = false;
+ for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) {
+ if (HasKey(S128KeyType::Titlekek, i)) {
+ has_titlekek = true;
+ break;
+ }
+ }
+
+ if (!has_titlekek) {
+ return false;
+ }
+
+ // Check for header key
+ if (!HasKey(S256KeyType::Header)) {
+ return false;
+ }
+
+ return true;
+}
} // namespace Core::Crypto
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 7de21f8a4..2a5f0c093 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -295,6 +296,9 @@ public:
void ReloadKeys();
bool AreKeysLoaded() const;
+ // Check if firmware is installed by verifying essential keys
+ bool IsFirmwareAvailable() const;
+
private:
KeyManager();
diff --git a/src/core/file_sys/content_manager.cpp b/src/core/file_sys/content_manager.cpp
new file mode 100644
index 000000000..fd53978fc
--- /dev/null
+++ b/src/core/file_sys/content_manager.cpp
@@ -0,0 +1,25 @@
+#include "core/system.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/content_archive.h"
+#include "core/crypto/key_manager.h"
+
+bool ContentManager::IsFirmwareAvailable() {
+ constexpr u64 MiiEditId = 0x0100000000001009; // Mii Edit applet ID
+ constexpr u64 QLaunchId = 0x0100000000001000; // Home Menu applet ID
+
+ auto& system = Core::System::GetInstance();
+ auto bis_system = system.GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ return false;
+ }
+
+ auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
+ auto qlaunch_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
+
+ if (!mii_applet_nca || !qlaunch_nca) {
+ return false;
+ }
+
+ // Also check for essential keys
+ return Core::Crypto::KeyManager::Instance().IsFirmwareAvailable();
+}
\ No newline at end of file
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index b6e355622..0135d6f81 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -19,6 +19,10 @@
#include "core/loader/nso.h"
#include "core/loader/nsp.h"
#include "core/loader/xci.h"
+#include "core/hle/service/am/am.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/content_archive.h"
namespace Loader {
@@ -250,6 +254,20 @@ std::unique_ptr GetLoader(Core::System& system, FileSys::VirtualFile
return nullptr;
}
+ // Check if firmware is available
+ constexpr u64 MiiEditId = 0x0100000000001009; // Mii Edit applet ID
+ auto bis_system = system.GetFileSystemController().GetSystemNANDContents();
+ if (bis_system) {
+ auto mii_applet_nca = bis_system->GetEntry(MiiEditId, FileSys::ContentRecordType::Program);
+ if (!mii_applet_nca) {
+ LOG_ERROR(Loader, "Firmware is required to launch games but is not available");
+ return nullptr;
+ }
+ } else {
+ LOG_ERROR(Loader, "System NAND contents not available");
+ return nullptr;
+ }
+
FileType type = IdentifyFile(file);
const FileType filename_type = GuessFromFilename(file->GetName());
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
index 8d2b9d569..decceaaf3 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include
@@ -7,6 +8,7 @@
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/profile.h"
+#include "shader_recompiler/runtime_info.h"
#include "shader_recompiler/shader_info.h"
namespace Shader::Backend::GLASM {
@@ -403,6 +405,8 @@ void EmitInvocationId(EmitContext& ctx, IR::Inst& inst) {
void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) {
switch (ctx.stage) {
case Stage::TessellationControl:
+ ctx.Add("SHL.U {}.x,primitive.vertexcount,16;", inst);
+ break;
case Stage::TessellationEval:
ctx.Add("SHL.U {}.x,primitive.vertexcount,16;", inst);
break;
@@ -410,7 +414,47 @@ void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) {
// Return sample mask in upper 16 bits
ctx.Add("SHL.U {}.x,fragment.samplemask,16;", inst);
break;
+ case Stage::Geometry: {
+ // Return vertex count in upper 16 bits based on input topology
+ // Using a lookup table approach for vertex counts
+ const std::array vertex_counts = {
+ 1, // Points
+ 2, // Lines
+ 4, // LinesAdjacency
+ 3, // Triangles
+ 6 // TrianglesAdjacency
+ };
+
+ // Map the input topology to an index in our lookup table
+ u32 topology_index = 0;
+ switch (ctx.runtime_info.input_topology) {
+ case Shader::InputTopology::Lines:
+ topology_index = 1;
+ break;
+ case Shader::InputTopology::LinesAdjacency:
+ topology_index = 2;
+ break;
+ case Shader::InputTopology::Triangles:
+ topology_index = 3;
+ break;
+ case Shader::InputTopology::TrianglesAdjacency:
+ topology_index = 4;
+ break;
+ case Shader::InputTopology::Points:
+ default:
+ topology_index = 0;
+ break;
+ }
+
+ // Get the vertex count from the lookup table and shift it
+ const u32 result = vertex_counts[topology_index] << 16;
+ ctx.Add("MOV.S {}.x,0x{:x};", inst, result);
+ break;
+ }
case Stage::Compute:
+ // Return standard format (0x00ff0000)
+ ctx.Add("MOV.S {}.x,0x00ff0000;", inst);
+ break;
default:
// Return standard format (0x00ff0000)
ctx.Add("MOV.S {}.x,0x00ff0000;", inst);
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
index fea325df9..ae10a830c 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include
@@ -423,6 +424,8 @@ void EmitInvocationId(EmitContext& ctx, IR::Inst& inst) {
void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) {
switch (ctx.stage) {
case Stage::TessellationControl:
+ ctx.AddU32("{}=uint(gl_PatchVerticesIn)<<16;", inst);
+ break;
case Stage::TessellationEval:
ctx.AddU32("{}=uint(gl_PatchVerticesIn)<<16;", inst);
break;
@@ -430,7 +433,46 @@ void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) {
// Return sample mask in upper 16 bits
ctx.AddU32("{}=uint(gl_SampleMaskIn[0])<<16;", inst);
break;
+ case Stage::Geometry: {
+ // Return vertex count in upper 16 bits based on input topology
+ // Using a lookup table approach for vertex counts
+ ctx.AddU32("{}=uint(", inst);
+
+ // Define vertex counts for each topology in a comment for clarity
+ ctx.Add("// Vertex counts: Points=1, Lines=2, LinesAdj=4, Triangles=3, TrianglesAdj=6\n");
+
+ // Use a lookup table approach in the generated GLSL code
+ ctx.Add("(");
+
+ // Generate a conditional expression that acts like a lookup table
+ switch (ctx.runtime_info.input_topology) {
+ case InputTopology::Points:
+ ctx.Add("1"); // Points
+ break;
+ case InputTopology::Lines:
+ ctx.Add("2"); // Lines
+ break;
+ case InputTopology::LinesAdjacency:
+ ctx.Add("4"); // LinesAdjacency
+ break;
+ case InputTopology::Triangles:
+ ctx.Add("3"); // Triangles
+ break;
+ case InputTopology::TrianglesAdjacency:
+ ctx.Add("6"); // TrianglesAdjacency
+ break;
+ default:
+ ctx.Add("1"); // Default to Points
+ break;
+ }
+
+ ctx.Add(")<<16);");
+ break;
+ }
case Stage::Compute:
+ // Return standard format (0x00ff0000)
+ ctx.AddU32("{}=0x00ff0000u;", inst);
+ break;
default:
// Return standard format (0x00ff0000)
ctx.AddU32("{}=0x00ff0000u;", inst);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
index 64f828107..f3c15cfc2 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include
@@ -553,6 +554,42 @@ Id EmitInvocationInfo(EmitContext& ctx) {
// Return sample mask in upper 16 bits
return ctx.OpShiftLeftLogical(ctx.U32[1], ctx.OpLoad(ctx.U32[1], ctx.sample_mask),
ctx.Const(16u));
+ case Stage::Geometry: {
+ // Return vertex count in upper 16 bits based on input topology
+ // Using a lookup table approach for vertex counts
+ const std::array vertex_counts = {
+ 1, // Points
+ 2, // Lines
+ 4, // LinesAdjacency
+ 3, // Triangles
+ 6 // TrianglesAdjacency
+ };
+
+ // Map the input topology to an index in our lookup table
+ u32 topology_index = 0;
+ switch (ctx.runtime_info.input_topology) {
+ case InputTopology::Lines:
+ topology_index = 1;
+ break;
+ case InputTopology::LinesAdjacency:
+ topology_index = 2;
+ break;
+ case InputTopology::Triangles:
+ topology_index = 3;
+ break;
+ case InputTopology::TrianglesAdjacency:
+ topology_index = 4;
+ break;
+ case InputTopology::Points:
+ default:
+ topology_index = 0;
+ break;
+ }
+
+ // Get the vertex count from the lookup table and shift it
+ const u32 vertex_count = vertex_counts[topology_index];
+ return ctx.OpShiftLeftLogical(ctx.U32[1], ctx.Const(vertex_count), ctx.Const(16u));
+ }
case Stage::Compute:
// For compute shaders, return standard format since we can't access workgroup size directly
return ctx.Const(0x00ff0000u);
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index d62c13f66..0a0caefdc 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -140,6 +140,10 @@ public:
return (flags & property_flags) == flags && (type_mask & shifted_memory_type) != 0;
}
+ [[nodiscard]] bool IsEmpty() const noexcept {
+ return commits.empty();
+ }
+
private:
[[nodiscard]] static constexpr u32 ShiftType(u32 type) {
return 1U << type;
@@ -284,44 +288,78 @@ MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, M
const u32 type_mask = requirements.memoryTypeBits;
const VkMemoryPropertyFlags usage_flags = MemoryUsagePropertyFlags(usage);
const VkMemoryPropertyFlags flags = MemoryPropertyFlags(type_mask, usage_flags);
+
+ // First attempt
if (std::optional commit = TryCommit(requirements, flags)) {
return std::move(*commit);
}
- // Commit has failed, allocate more memory.
+
+ // Commit has failed, allocate more memory
const u64 chunk_size = AllocationChunkSize(requirements.size);
- if (!TryAllocMemory(flags, type_mask, chunk_size)) {
- // TODO(Rodrigo): Handle out of memory situations in some way like flushing to guest memory.
- throw vk::Exception(VK_ERROR_OUT_OF_DEVICE_MEMORY);
+ if (TryAllocMemory(flags, type_mask, chunk_size)) {
+ return TryCommit(requirements, flags).value();
}
- // Commit again, this time it won't fail since there's a fresh allocation above.
- // If it does, there's a bug.
- return TryCommit(requirements, flags).value();
+
+ // Memory allocation failed - try to recover by releasing empty allocations
+ for (auto it = allocations.begin(); it != allocations.end();) {
+ if ((*it)->IsEmpty()) {
+ it = allocations.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ // Try allocating again after cleanup
+ if (TryAllocMemory(flags, type_mask, chunk_size)) {
+ return TryCommit(requirements, flags).value();
+ }
+
+ // If still failing, try with non-device-local memory as a last resort
+ if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
+ const VkMemoryPropertyFlags fallback_flags = flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+ if (TryAllocMemory(fallback_flags, type_mask, chunk_size)) {
+ if (auto commit = TryCommit(requirements, fallback_flags)) {
+ LOG_WARNING(Render_Vulkan, "Falling back to non-device-local memory due to OOM");
+ return std::move(*commit);
+ }
+ }
+ }
+
+ LOG_CRITICAL(Render_Vulkan, "Vulkan memory allocation failed - out of device memory");
+ throw vk::Exception(VK_ERROR_OUT_OF_DEVICE_MEMORY);
}
bool MemoryAllocator::TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size) {
- const u32 type = FindType(flags, type_mask).value();
+ const auto type_opt = FindType(flags, type_mask);
+ if (!type_opt) {
+ if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) {
+ // Try to allocate non device local memory
+ return TryAllocMemory(flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, type_mask, size);
+ }
+ return false;
+ }
+
+ const u64 aligned_size = (device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) ?
+ Common::AlignUp(size, 4096) : // Adreno requires 4KB alignment
+ size; // Others (NVIDIA, AMD, Intel, etc)
+
vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = nullptr,
- /* AMD drivers (including Adreno) require 4KB alignment */
- .allocationSize = (device.GetDriverID() == VK_DRIVER_ID_AMD_PROPRIETARY ||
- device.GetDriverID() == VK_DRIVER_ID_AMD_OPEN_SOURCE ||
- device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) ?
- ((size + 4095) & ~4095) : /* AMD (AMDVLK, RADV, RadeonSI) & Adreno */
- size, /* Others (NVIDIA, Intel, Mali, etc) */
- .memoryTypeIndex = type,
+ .allocationSize = aligned_size,
+ .memoryTypeIndex = *type_opt,
});
+
if (!memory) {
if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) {
// Try to allocate non device local memory
return TryAllocMemory(flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, type_mask, size);
- } else {
- // RIP
- return false;
}
+ return false;
}
+
allocations.push_back(
- std::make_unique(this, std::move(memory), flags, size, type));
+ std::make_unique(this, std::move(memory), flags, aligned_size, *type_opt));
return true;
}