From feaedd02f7ba1fff6597f8fefdca862367e5fb97 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 19 Feb 2026 03:42:19 +0000 Subject: [PATCH 01/74] sudachi ios stuff --- src/ios/Eden/AppUI-Bridging-Header.h | 11 + src/ios/Eden/AppUI.swift | 108 ++++ .../AppUIGameInformation.h | 26 + .../AppUIGameInformation.mm | 434 +++++++++++++ src/ios/Eden/Wrapper/AppUIObjC.h | 92 +++ src/ios/Eden/Wrapper/AppUIObjC.mm | 258 ++++++++ src/ios/Eden/Wrapper/Config/Config.h | 568 ++++++++++++++++++ src/ios/Eden/Wrapper/Config/Config.mm | 330 ++++++++++ .../DirectoryManager/DirectoryManager.h | 10 + .../DirectoryManager/DirectoryManager.mm | 16 + .../EmulationSession/EmulationSession.h | 98 +++ .../EmulationSession/EmulationSession.mm | 528 ++++++++++++++++ .../Wrapper/EmulationWindow/EmulationWindow.h | 80 +++ .../EmulationWindow/EmulationWindow.mm | 85 +++ 14 files changed, 2644 insertions(+) create mode 100644 src/ios/Eden/AppUI-Bridging-Header.h create mode 100644 src/ios/Eden/AppUI.swift create mode 100644 src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h create mode 100644 src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm create mode 100644 src/ios/Eden/Wrapper/AppUIObjC.h create mode 100644 src/ios/Eden/Wrapper/AppUIObjC.mm create mode 100644 src/ios/Eden/Wrapper/Config/Config.h create mode 100644 src/ios/Eden/Wrapper/Config/Config.mm create mode 100644 src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h create mode 100644 src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm create mode 100644 src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h create mode 100644 src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm create mode 100644 src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h create mode 100644 src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm diff --git a/src/ios/Eden/AppUI-Bridging-Header.h b/src/ios/Eden/AppUI-Bridging-Header.h new file mode 100644 index 0000000000..a75f43550d --- /dev/null +++ b/src/ios/Eden/AppUI-Bridging-Header.h @@ -0,0 +1,11 @@ +// +// AppUI-Bridging-Header.h - Sudachi +// Created by Jarrod Norwell on 4/3/2024. +// + +#ifndef AppUI_Bridging_Header_h +#define AppUI_Bridging_Header_h + +#import "Wrapper/AppUIObjC.h" + +#endif /* AppUI_Bridging_Header_h */ diff --git a/src/ios/Eden/AppUI.swift b/src/ios/Eden/AppUI.swift new file mode 100644 index 0000000000..b8f79528b7 --- /dev/null +++ b/src/ios/Eden/AppUI.swift @@ -0,0 +1,108 @@ +// +// AppUI.swift - Sudachi +// Created by Jarrod Norwell on 4/3/2024. +// + +import Foundation +import QuartzCore.CAMetalLayer + +public struct AppUI { + + public static let shared = AppUI() + + fileprivate let appUIObjC = AppUIObjC.shared() + + public func configure(layer: CAMetalLayer, with size: CGSize) { + appUIObjC.configure(layer: layer, with: size) + } + + public func information(for url: URL) -> AppUIInformation { + appUIObjC.gameInformation.information(for: url) + } + + public func insert(game url: URL) { + appUIObjC.insert(game: url) + } + + public func insert(games urls: [URL]) { + appUIObjC.insert(games: urls) + } + + public func bootOS() { + appUIObjC.bootOS() + } + + public func pause() { + appUIObjC.pause() + } + + public func play() { + appUIObjC.play() + } + + public func ispaused() -> Bool { + return appUIObjC.ispaused() + } + + public func FirstFrameShowed() -> Bool { + return appUIObjC.hasfirstfame() + } + + public func canGetFullPath() -> Bool { + return appUIObjC.canGetFullPath() + } + + + public func exit() { + appUIObjC.quit() + } + + public func step() { + appUIObjC.step() + } + + public func orientationChanged(orientation: UIInterfaceOrientation, with layer: CAMetalLayer, size: CGSize) { + appUIObjC.orientationChanged(orientation: orientation, with: layer, size: size) + } + + public func touchBegan(at point: CGPoint, for index: UInt) { + appUIObjC.touchBegan(at: point, for: index) + } + + public func touchEnded(for index: UInt) { + appUIObjC.touchEnded(for: index) + } + + public func touchMoved(at point: CGPoint, for index: UInt) { + appUIObjC.touchMoved(at: point, for: index) + } + + public func gyroMoved(x: Float, y: Float, z: Float, accelX: Float, accelY: Float, accelZ: Float, controllerId: Int32, deltaTimestamp: Int32) { + // Calling the Objective-C function with both gyroscope and accelerometer data + appUIObjC.virtualControllerGyro(controllerId, + deltaTimestamp: deltaTimestamp, + gyroX: x, + gyroY: y, + gyroZ: z, + accelX: accelX, + accelY: accelY, + accelZ: accelZ) + } + + + public func thumbstickMoved(analog: VirtualControllerAnalogType, x: Float, y: Float, controllerid: Int) { + appUIObjC.thumbstickMoved(analog, x: CGFloat(x), y: CGFloat(y), controllerId: Int32(controllerid)) + } + + public func virtualControllerButtonDown(button: VirtualControllerButtonType, controllerid: Int) { + appUIObjC.virtualControllerButtonDown(button, controllerId: Int32(controllerid)) + } + + public func virtualControllerButtonUp(button: VirtualControllerButtonType, controllerid: Int) { + appUIObjC.virtualControllerButtonUp(button, controllerId: Int32(controllerid)) + } + + public func settingsSaved() { + appUIObjC.settingsChanged() + } +} diff --git a/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h new file mode 100644 index 0000000000..5040775ad0 --- /dev/null +++ b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h @@ -0,0 +1,26 @@ +// +// AppUIGameInformation.h - Sudachi +// Created by Jarrod Norwell on 1/20/24. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AppUIInformation : NSObject +@property (nonatomic, strong) NSString *developer; +@property (nonatomic, strong) NSData *iconData; +@property (nonatomic) BOOL isHomebrew; +@property (nonatomic) uint64_t programID; +@property (nonatomic, strong) NSString *title, *version; + +-(AppUIInformation *) initWithDeveloper:(NSString *)developer iconData:(NSData *)iconData isHomebrew:(BOOL)isHomebrew programID:(uint64_t)programID title:(NSString *)title version:(NSString *)version; +@end + +@interface AppUIGameInformation : NSObject ++(AppUIGameInformation *) sharedInstance NS_SWIFT_NAME(shared()); + +-(AppUIInformation *) informationForGame:(NSURL *)url NS_SWIFT_NAME(information(for:)); +@end + +NS_ASSUME_NONNULL_END diff --git a/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm new file mode 100644 index 0000000000..00c894d078 --- /dev/null +++ b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm @@ -0,0 +1,434 @@ +// +// AppUIGameInformation.mm - Sudachi +// Created by Jarrod Norwell on 1/20/24. +// + +#import "AppUIGameInformation.h" +#import "../DirectoryManager/DirectoryManager.h" +#import "../EmulationSession/EmulationSession.h" + +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "core/core.h" +#include "core/file_sys/fs_filesystem.h" +#include "core/file_sys/patch_manager.h" +#include "core/loader/loader.h" +#include "core/loader/nro.h" +#include "frontend_common/yuzu_config.h" + +struct GameMetadata { + std::string title; + u64 programId; + std::string developer; + std::string version; + std::vector icon; + bool isHomebrew; +}; + + +class SdlConfig final : public YuzuConfig { +public: + explicit SdlConfig(std::optional config_path); + ~SdlConfig() override; + + void ReloadAllValues() override; + void SaveAllValues() override; + +protected: + void ReadSdlValues(); + void ReadSdlPlayerValues(std::size_t player_index); + void ReadSdlControlValues(); + void ReadHidbusValues() override; + void ReadDebugControlValues() override; + void ReadPathValues() override {} + void ReadShortcutValues() override {} + void ReadUIValues() override {} + void ReadUIGamelistValues() override {} + void ReadUILayoutValues() override {} + void ReadMultiplayerValues() override {} + + void SaveSdlValues(); + void SaveSdlPlayerValues(std::size_t player_index); + void SaveSdlControlValues(); + void SaveHidbusValues() override; + void SaveDebugControlValues() override; + void SavePathValues() override {} + void SaveShortcutValues() override {} + void SaveUIValues() override {} + void SaveUIGamelistValues() override {} + void SaveUILayoutValues() override {} + void SaveMultiplayerValues() override {} + + std::vector& FindRelevantList(YuzuSettings::Category category) override; + +public: + static const std::array default_buttons; + static const std::array default_motions; + static const std::array, YuzuSettings::NativeAnalog::NumAnalogs> default_analogs; + static const std::array default_stick_mod; + static const std::array default_ringcon_analogs; +}; + + +#define SDL_MAIN_HANDLED +#include + +#include "common/logging/log.h" +#include "input_common/main.h" + +const std::array SdlConfig::default_buttons = { + SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T, + SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W, + SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B, +}; + +const std::array SdlConfig::default_motions = { + SDL_SCANCODE_7, + SDL_SCANCODE_8, +}; + +const std::array, YuzuSettings::NativeAnalog::NumAnalogs> SdlConfig::default_analogs{ + { + { + SDL_SCANCODE_UP, + SDL_SCANCODE_DOWN, + SDL_SCANCODE_LEFT, + SDL_SCANCODE_RIGHT, + }, + { + SDL_SCANCODE_I, + SDL_SCANCODE_K, + SDL_SCANCODE_J, + SDL_SCANCODE_L, + }, + }}; + +const std::array SdlConfig::default_stick_mod = { + SDL_SCANCODE_D, + 0, +}; + +const std::array SdlConfig::default_ringcon_analogs{{ + 0, + 0, +}}; + +SdlConfig::SdlConfig(const std::optional config_path) { + Initialize(config_path); + ReadSdlValues(); + SaveSdlValues(); +} + +SdlConfig::~SdlConfig() { + if (global) { + SdlConfig::SaveAllValues(); + } +} + +void SdlConfig::ReloadAllValues() { + Reload(); + ReadSdlValues(); + SaveSdlValues(); +} + +void SdlConfig::SaveAllValues() { + SaveValues(); + SaveSdlValues(); +} + +void SdlConfig::ReadSdlValues() { + ReadSdlControlValues(); +} + +void SdlConfig::ReadSdlControlValues() { + BeginGroup(YuzuSettings::TranslateCategory(YuzuSettings::Category::Controls)); + + YuzuSettings::values.players.SetGlobal(!IsCustomConfig()); + for (std::size_t p = 0; p < YuzuSettings::values.players.GetValue().size(); ++p) { + ReadSdlPlayerValues(p); + } + if (IsCustomConfig()) { + EndGroup(); + return; + } + ReadDebugControlValues(); + ReadHidbusValues(); + + EndGroup(); +} + +void SdlConfig::ReadSdlPlayerValues(const std::size_t player_index) { + std::string player_prefix; + if (type != ConfigType::InputProfile) { + player_prefix.append("player_").append(ToString(player_index)).append("_"); + } + + auto& player = YuzuSettings::values.players.GetValue()[player_index]; + if (IsCustomConfig()) { + const auto profile_name = + ReadStringSetting(std::string(player_prefix).append("profile_name")); + if (profile_name.empty()) { + // Use the global input config + player = YuzuSettings::values.players.GetValue(true)[player_index]; + player.profile_name = ""; + return; + } + } + + for (int i = 0; i < YuzuSettings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + auto& player_buttons = player.buttons[i]; + + player_buttons = ReadStringSetting( + std::string(player_prefix).append(YuzuSettings::NativeButton::mapping[i]), default_param); + if (player_buttons.empty()) { + player_buttons = default_param; + } + } + + for (int i = 0; i < YuzuSettings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + auto& player_analogs = player.analogs[i]; + + player_analogs = ReadStringSetting( + std::string(player_prefix).append(YuzuSettings::NativeAnalog::mapping[i]), default_param); + if (player_analogs.empty()) { + player_analogs = default_param; + } + } + + for (int i = 0; i < YuzuSettings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + auto& player_motions = player.motions[i]; + + player_motions = ReadStringSetting( + std::string(player_prefix).append(YuzuSettings::NativeMotion::mapping[i]), default_param); + if (player_motions.empty()) { + player_motions = default_param; + } + } +} + +void SdlConfig::ReadDebugControlValues() { + for (int i = 0; i < YuzuSettings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + auto& debug_pad_buttons = YuzuSettings::values.debug_pad_buttons[i]; + debug_pad_buttons = ReadStringSetting( + std::string("debug_pad_").append(YuzuSettings::NativeButton::mapping[i]), default_param); + if (debug_pad_buttons.empty()) { + debug_pad_buttons = default_param; + } + } + for (int i = 0; i < YuzuSettings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + auto& debug_pad_analogs = YuzuSettings::values.debug_pad_analogs[i]; + debug_pad_analogs = ReadStringSetting( + std::string("debug_pad_").append(YuzuSettings::NativeAnalog::mapping[i]), default_param); + if (debug_pad_analogs.empty()) { + debug_pad_analogs = default_param; + } + } +} + +void SdlConfig::ReadHidbusValues() { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); + auto& ringcon_analogs = YuzuSettings::values.ringcon_analogs; + + ringcon_analogs = ReadStringSetting(std::string("ring_controller"), default_param); + if (ringcon_analogs.empty()) { + ringcon_analogs = default_param; + } +} + +void SdlConfig::SaveSdlValues() { + LOG_DEBUG(Config, "Saving SDL configuration values"); + SaveSdlControlValues(); + + WriteToIni(); +} + +void SdlConfig::SaveSdlControlValues() { + BeginGroup(YuzuSettings::TranslateCategory(YuzuSettings::Category::Controls)); + + YuzuSettings::values.players.SetGlobal(!IsCustomConfig()); + for (std::size_t p = 0; p < YuzuSettings::values.players.GetValue().size(); ++p) { + SaveSdlPlayerValues(p); + } + if (IsCustomConfig()) { + EndGroup(); + return; + } + SaveDebugControlValues(); + SaveHidbusValues(); + + EndGroup(); +} + +void SdlConfig::SaveSdlPlayerValues(const std::size_t player_index) { + std::string player_prefix; + if (type != ConfigType::InputProfile) { + player_prefix = std::string("player_").append(ToString(player_index)).append("_"); + } + + const auto& player = YuzuSettings::values.players.GetValue()[player_index]; + if (IsCustomConfig() && player.profile_name.empty()) { + // No custom profile selected + return; + } + + for (int i = 0; i < YuzuSettings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + WriteStringSetting(std::string(player_prefix).append(YuzuSettings::NativeButton::mapping[i]), + player.buttons[i], std::make_optional(default_param)); + } + for (int i = 0; i < YuzuSettings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + WriteStringSetting(std::string(player_prefix).append(YuzuSettings::NativeAnalog::mapping[i]), + player.analogs[i], std::make_optional(default_param)); + } + for (int i = 0; i < YuzuSettings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + WriteStringSetting(std::string(player_prefix).append(YuzuSettings::NativeMotion::mapping[i]), + player.motions[i], std::make_optional(default_param)); + } +} + +void SdlConfig::SaveDebugControlValues() { + for (int i = 0; i < YuzuSettings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + WriteStringSetting(std::string("debug_pad_").append(YuzuSettings::NativeButton::mapping[i]), + YuzuSettings::values.debug_pad_buttons[i], + std::make_optional(default_param)); + } + for (int i = 0; i < YuzuSettings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + WriteStringSetting(std::string("debug_pad_").append(YuzuSettings::NativeAnalog::mapping[i]), + YuzuSettings::values.debug_pad_analogs[i], + std::make_optional(default_param)); + } +} + +void SdlConfig::SaveHidbusValues() { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); + WriteStringSetting(std::string("ring_controller"), YuzuSettings::values.ringcon_analogs, + std::make_optional(default_param)); +} + +std::vector& SdlConfig::FindRelevantList(YuzuSettings::Category category) { + return YuzuSettings::values.linkage.by_category[category]; +} + + + + + + + +std::unordered_map m_game_metadata_cache; + +GameMetadata CacheGameMetadata(const std::string& path) { + const auto file = + Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path); + auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); + + GameMetadata 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(); + } else { + entry.developer = ""; + } + + entry.version = "1.0.0"; + } + + if (loader->GetFileType() == Loader::FileType::NRO) { + auto loader_nro = reinterpret_cast(loader.get()); + entry.isHomebrew = loader_nro->IsHomebrew(); + } else { + entry.isHomebrew = false; + } + + m_game_metadata_cache[path] = entry; + + return entry; +} + +GameMetadata GameMetadata(const std::string& path, bool reload = false) { + if (!EmulationSession::GetInstance().IsInitialized()) { + Common::FS::SetAppDirectory(DirectoryManager::AppUIDirectory()); + + EmulationSession::GetInstance().System().Initialize(); + EmulationSession::GetInstance().InitializeSystem(false); + } + + if (reload) { + return CacheGameMetadata(path); + } + + if (auto search = m_game_metadata_cache.find(path); search != m_game_metadata_cache.end()) { + return search->second; + } + + return CacheGameMetadata(path); +} + + +@implementation AppUIInformation +-(AppUIInformation *) initWithDeveloper:(NSString *)developer iconData:(NSData *)iconData isHomebrew:(BOOL)isHomebrew programID:(uint64_t)programID + title:(NSString *)title version:(NSString *)version { + if (self = [super init]) { + self.developer = developer; + self.iconData = iconData; + self.isHomebrew = isHomebrew; + self.programID = programID; + self.title = title; + self.version = version; + } return self; +} +@end + +@implementation AppUIGameInformation ++(AppUIGameInformation *) sharedInstance { + static AppUIGameInformation *sharedInstance = NULL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; +} + + +-(AppUIInformation *) informationForGame:(NSURL *)url { + auto gameMetadata = GameMetadata([url.path UTF8String]); + + return [[AppUIInformation alloc] initWithDeveloper:[NSString stringWithCString:gameMetadata.developer.c_str() encoding:NSUTF8StringEncoding] + iconData:[NSData dataWithBytes:gameMetadata.icon.data() length:gameMetadata.icon.size()] + isHomebrew:gameMetadata.isHomebrew programID:gameMetadata.programId + title:[NSString stringWithCString:gameMetadata.title.c_str() encoding:NSUTF8StringEncoding] + version:[NSString stringWithCString:gameMetadata.version.c_str() encoding:NSUTF8StringEncoding]]; +} +@end diff --git a/src/ios/Eden/Wrapper/AppUIObjC.h b/src/ios/Eden/Wrapper/AppUIObjC.h new file mode 100644 index 0000000000..d3594d74d1 --- /dev/null +++ b/src/ios/Eden/Wrapper/AppUIObjC.h @@ -0,0 +1,92 @@ +// +// AppUIObjC.h - Sudachi +// Created by Jarrod Norwell on 1/8/24. +// + +#import +#import + +#import "AppUIGameInformation/AppUIGameInformation.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, VirtualControllerAnalogType) { + VirtualControllerAnalogTypeLeft = 0, + VirtualControllerAnalogTypeRight = 1 +}; + +typedef NS_ENUM(NSUInteger, VirtualControllerButtonType) { + VirtualControllerButtonTypeA = 0, + VirtualControllerButtonTypeB = 1, + VirtualControllerButtonTypeX = 2, + VirtualControllerButtonTypeY = 3, + VirtualControllerButtonTypeL = 4, + VirtualControllerButtonTypeR = 5, + VirtualControllerButtonTypeTriggerL = 6, + VirtualControllerButtonTypeTriggerR = 7, + VirtualControllerButtonTypeTriggerZL = 8, + VirtualControllerButtonTypeTriggerZR = 9, + VirtualControllerButtonTypePlus = 10, + VirtualControllerButtonTypeMinus = 11, + VirtualControllerButtonTypeDirectionalPadLeft = 12, + VirtualControllerButtonTypeDirectionalPadUp = 13, + VirtualControllerButtonTypeDirectionalPadRight = 14, + VirtualControllerButtonTypeDirectionalPadDown = 15, + VirtualControllerButtonTypeSL = 16, + VirtualControllerButtonTypeSR = 17, + VirtualControllerButtonTypeHome = 18, + VirtualControllerButtonTypeCapture = 19 +}; + +@interface AppUIObjC : NSObject { + CAMetalLayer *_layer; + CGSize _size; +} + +@property (nonatomic, strong) AppUIGameInformation *gameInformation; + ++(AppUIObjC *) sharedInstance NS_SWIFT_NAME(shared()); +-(void) configureLayer:(CAMetalLayer *)layer withSize:(CGSize)size NS_SWIFT_NAME(configure(layer:with:)); +-(void) bootOS; +-(void) pause; +-(void) play; +-(BOOL) ispaused; +-(BOOL) canGetFullPath; +-(void) quit; +-(void) insertGame:(NSURL *)url NS_SWIFT_NAME(insert(game:)); +-(void) insertGames:(NSArray *)games NS_SWIFT_NAME(insert(games:)); +-(void) step; +-(BOOL) hasfirstfame; + +-(void) touchBeganAtPoint:(CGPoint)point index:(NSUInteger)index NS_SWIFT_NAME(touchBegan(at:for:)); +-(void) touchEndedForIndex:(NSUInteger)index; +-(void) touchMovedAtPoint:(CGPoint)point index:(NSUInteger)index NS_SWIFT_NAME(touchMoved(at:for:)); + +-(void) thumbstickMoved:(VirtualControllerAnalogType)analog + x:(CGFloat)x + y:(CGFloat)y + controllerId:(int)controllerId; + +-(void) virtualControllerGyro:(int)controllerId + deltaTimestamp:(int)delta_timestamp + gyroX:(float)gyro_x + gyroY:(float)gyro_y + gyroZ:(float)gyro_z + accelX:(float)accel_x + accelY:(float)accel_y + accelZ:(float)accel_z; + +-(void) virtualControllerButtonDown:(VirtualControllerButtonType)button + controllerId:(int)controllerId; + +-(void) virtualControllerButtonUp:(VirtualControllerButtonType)button + controllerId:(int)controllerId; + + +-(void) orientationChanged:(UIInterfaceOrientation)orientation with:(CAMetalLayer *)layer size:(CGSize)size NS_SWIFT_NAME(orientationChanged(orientation:with:size:)); + +-(void) settingsChanged; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/ios/Eden/Wrapper/AppUIObjC.mm b/src/ios/Eden/Wrapper/AppUIObjC.mm new file mode 100644 index 0000000000..065ce6d046 --- /dev/null +++ b/src/ios/Eden/Wrapper/AppUIObjC.mm @@ -0,0 +1,258 @@ +// +// AppUIObjC.mm - Sudachi +// Created by Jarrod Norwell on 1/8/24. +// + +#import "AppUIObjC.h" + +#import "Config/Config.h" +#import "EmulationSession/EmulationSession.h" +#import "DirectoryManager/DirectoryManager.h" + +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/settings.h" +#include "common/fs/fs.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/savedata_factory.h" +#include "core/loader/nro.h" +#include "frontend_common/content_manager.h" +#include "common/settings_enums.h" +#include "network/announce_multiplayer_session.h" +#include "common/announce_multiplayer_room.h" +#include "network/network.h" + +#include "common/detached_tasks.h" +#include "common/dynamic_library.h" +#include "common/fs/path_util.h" +#include "common/logging/backend.h" +#include "common/logging/log.h" +#include "common/microprofile.h" +#include "common/scm_rev.h" +#include "common/scope_exit.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/cpu_manager.h" +#include "core/crypto/key_manager.h" +#include "core/file_sys/card_image.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/fs_filesystem.h" +#include "core/file_sys/submission_package.h" +#include "core/file_sys/vfs/vfs.h" +#include "core/file_sys/vfs/vfs_real.h" +#include "core/frontend/applets/cabinet.h" +#include "core/frontend/applets/controller.h" +#include "core/frontend/applets/error.h" +#include "core/frontend/applets/general.h" +#include "core/frontend/applets/mii_edit.h" +#include "core/frontend/applets/profile_select.h" +#include "core/frontend/applets/software_keyboard.h" +#include "core/frontend/applets/web_browser.h" +#include "core/hle/service/am/applet_manager.h" +#include "core/hle/service/am/frontend/applets.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/loader.h" +#include "frontend_common/yuzu_config.h" +#include "hid_core/frontend/emulated_controller.h" +#include "hid_core/hid_core.h" +#include "hid_core/hid_types.h" +#include "video_core/renderer_base.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" +#include "video_core/vulkan_common/vulkan_instance.h" +#include "video_core/vulkan_common/vulkan_surface.h" + + +#import + +@implementation AppUIObjC +-(AppUIObjC *) init { + if (self = [super init]) { + _gameInformation = [AppUIGameInformation sharedInstance]; + + + Common::FS::SetAppDirectory(DirectoryManager::AppUIDirectory()); + Config{"config", Config::ConfigType::GlobalConfig}; + + EmulationSession::GetInstance().System().Initialize(); + EmulationSession::GetInstance().InitializeSystem(false); + EmulationSession::GetInstance().InitializeGpuDriver(); + + + YuzuSettings::values.dump_shaders.SetValue(true); + YuzuSettings::values.use_asynchronous_shaders.SetValue(true); + // YuzuSettings::values.astc_recompression.SetValue(YuzuSettings::AstcRecompression::Bc3); + YuzuSettings::values.shader_backend.SetValue(YuzuSettings::ShaderBackend::SpirV); + // YuzuSettings::values.resolution_setup.SetValue(YuzuSettings::ResolutionSetup::Res1X); + // YuzuSettings::values.scaling_filter.SetValue(YuzuSettings::ScalingFilter::Bilinear); + } return self; +} + + ++(AppUIObjC *) sharedInstance { + static AppUIObjC *sharedInstance = NULL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; +} + +- (BOOL)ispaused { + return EmulationSession::GetInstance().IsPaused(); +} + +-(void) pause { + EmulationSession::GetInstance().System().Pause(); + EmulationSession::GetInstance().HaltEmulation(); + EmulationSession::GetInstance().PauseEmulation(); +} + +-(void) play { + + EmulationSession::GetInstance().System().Run(); + EmulationSession::GetInstance().RunEmulation(); + EmulationSession::GetInstance().UnPauseEmulation(); +} + +-(BOOL)hasfirstfame { + @try { + auto* window = &EmulationSession::GetInstance().Window(); + if (window && window->HasFirstFrame()) { + return YES; + } + } + @catch (NSException *exception) { + NSLog(@"Exception occurred: %@", exception); + // Handle the exception, maybe return a default value + return NO; + } + return NO; +} + +- (BOOL)canGetFullPath { + @try { + Core::System& system = EmulationSession::GetInstance().System(); + auto bis_system = system.GetFileSystemController().GetSystemNANDContents(); + + if (bis_system == nullptr) { + return NO; + } + + constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); + auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); + + if (qlaunch_applet_nca == nullptr) { + return NO; + } + + const auto filename = qlaunch_applet_nca->GetFullPath(); + + // If GetFullPath() is successful + return YES; + } @catch (NSException *exception) { + // Handle the exception if needed + return NO; + } +} + +-(void) quit { + EmulationSession::GetInstance().ShutdownEmulation(); +} + +-(void) configureLayer:(CAMetalLayer *)layer withSize:(CGSize)size { + _layer = layer; + _size = size; + EmulationSession::GetInstance().SetNativeWindow((__bridge CA::MetalLayer*)layer, size); +} + +-(void) bootOS { + EmulationSession::GetInstance().BootOS(); +} + +-(void) insertGame:(NSURL *)url { + EmulationSession::GetInstance().InitializeEmulation([url.path UTF8String], [_gameInformation informationForGame:url].programID, true); +} + +-(void) insertGames:(NSArray *)games { + for (NSURL *url in games) { + EmulationSession::GetInstance().ConfigureFilesystemProvider([url.path UTF8String]); + } +} + +-(void) step { + void(EmulationSession::GetInstance().System().Run()); +} + +-(void) touchBeganAtPoint:(CGPoint)point index:(NSUInteger)index { + float h_ratio, w_ratio; + h_ratio = EmulationSession::GetInstance().Window().GetFramebufferLayout().height / (_size.height * [[UIScreen mainScreen] nativeScale]); + w_ratio = EmulationSession::GetInstance().Window().GetFramebufferLayout().width / (_size.width * [[UIScreen mainScreen] nativeScale]); + + EmulationSession::GetInstance().Window().OnTouchPressed([[NSNumber numberWithUnsignedInteger:index] intValue], + (point.x) * [[UIScreen mainScreen] nativeScale] * w_ratio, + ((point.y) * [[UIScreen mainScreen] nativeScale] * h_ratio)); +} + +-(void) touchEndedForIndex:(NSUInteger)index { + EmulationSession::GetInstance().Window().OnTouchReleased([[NSNumber numberWithUnsignedInteger:index] intValue]); +} + +-(void) touchMovedAtPoint:(CGPoint)point index:(NSUInteger)index { + float h_ratio, w_ratio; + h_ratio = EmulationSession::GetInstance().Window().GetFramebufferLayout().height / (_size.height * [[UIScreen mainScreen] nativeScale]); + w_ratio = EmulationSession::GetInstance().Window().GetFramebufferLayout().width / (_size.width * [[UIScreen mainScreen] nativeScale]); + + EmulationSession::GetInstance().Window().OnTouchMoved([[NSNumber numberWithUnsignedInteger:index] intValue], + (point.x) * [[UIScreen mainScreen] nativeScale] * w_ratio, + ((point.y) * [[UIScreen mainScreen] nativeScale] * h_ratio)); +} + +-(void) thumbstickMoved:(VirtualControllerAnalogType)analog + x:(CGFloat)x + y:(CGFloat)y + controllerId:(int)controllerId { + EmulationSession::GetInstance().OnGamepadConnectEvent(controllerId); + EmulationSession::GetInstance().Window().OnGamepadJoystickEvent(controllerId, [[NSNumber numberWithUnsignedInteger:analog] intValue], CGFloat(x), CGFloat(y)); +} + +-(void) virtualControllerButtonDown:(VirtualControllerButtonType)button + controllerId:(int)controllerId { + EmulationSession::GetInstance().OnGamepadConnectEvent(controllerId); + EmulationSession::GetInstance().Window().OnGamepadButtonEvent(controllerId, [[NSNumber numberWithUnsignedInteger:button] intValue], true); +} + +-(void) virtualControllerGyro:(int)controllerId + deltaTimestamp:(int)delta_timestamp + gyroX:(float)gyro_x + gyroY:(float)gyro_y + gyroZ:(float)gyro_z + accelX:(float)accel_x + accelY:(float)accel_y + accelZ:(float)accel_z +{ + EmulationSession::GetInstance().OnGamepadConnectEvent(controllerId); + EmulationSession::GetInstance().Window().OnGamepadMotionEvent(controllerId, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z); +} + + +-(void) virtualControllerButtonUp:(VirtualControllerButtonType)button + controllerId:(int)controllerId { + EmulationSession::GetInstance().OnGamepadConnectEvent(controllerId); + EmulationSession::GetInstance().Window().OnGamepadButtonEvent(controllerId, [[NSNumber numberWithUnsignedInteger:button] intValue], false); +} + + +-(void) orientationChanged:(UIInterfaceOrientation)orientation with:(CAMetalLayer *)layer size:(CGSize)size { + _layer = layer; + _size = size; + EmulationSession::GetInstance().Window().OnSurfaceChanged((__bridge CA::MetalLayer*)layer, size); +} + +-(void) settingsChanged { + Config{"config", Config::ConfigType::GlobalConfig}; +} + + + +@end diff --git a/src/ios/Eden/Wrapper/Config/Config.h b/src/ios/Eden/Wrapper/Config/Config.h new file mode 100644 index 0000000000..5e29db42cf --- /dev/null +++ b/src/ios/Eden/Wrapper/Config/Config.h @@ -0,0 +1,568 @@ +// +// Config.h +// AppUI +// +// Created by Jarrod Norwell on 13/3/2024. +// + +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace DefaultINI { + +const char* android_config_file = R"( + +[ControlsP0] +# The input devices and parameters for each Switch native input +# The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ... +# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..." +# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values + +# Indicates if this player should be connected at boot +connected= + +# for button input, the following devices are available: +# - "keyboard" (default) for keyboard input. Required parameters: +# - "code": the code of the key to bind +# - "sdl" for joystick input using SDL. Required parameters: +# - "guid": SDL identification GUID of the joystick +# - "port": the index of the joystick to bind +# - "button"(optional): the index of the button to bind +# - "hat"(optional): the index of the hat to bind as direction buttons +# - "axis"(optional): the index of the axis to bind +# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right" +# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is +# triggered if the axis value crosses +# - "direction"(only used for axis): "+" means the button is triggered when the axis value +# is greater than the threshold; "-" means the button is triggered when the axis value +# is smaller than the threshold +button_a= +button_b= +button_x= +button_y= +button_lstick= +button_rstick= +button_l= +button_r= +button_zl= +button_zr= +button_plus= +button_minus= +button_dleft= +button_dup= +button_dright= +button_ddown= +button_lstick_left= +button_lstick_up= +button_lstick_right= +button_lstick_down= +button_sl= +button_sr= +button_home= +button_screenshot= + +# for analog input, the following devices are available: +# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters: +# - "up", "down", "left", "right": sub-devices for each direction. +# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00" +# - "modifier": sub-devices as a modifier. +# - "modifier_scale": a float number representing the applied modifier scale to the analog input. +# Must be in range of 0.0-1.0. Defaults to 0.5 +# - "sdl" for joystick input using SDL. Required parameters: +# - "guid": SDL identification GUID of the joystick +# - "port": the index of the joystick to bind +# - "axis_x": the index of the axis to bind as x-axis (default to 0) +# - "axis_y": the index of the axis to bind as y-axis (default to 1) +lstick= +rstick= + +# for motion input, the following devices are available: +# - "keyboard" (default) for emulating random motion input from buttons. Required parameters: +# - "code": the code of the key to bind +# - "sdl" for motion input using SDL. Required parameters: +# - "guid": SDL identification GUID of the joystick +# - "port": the index of the joystick to bind +# - "motion": the index of the motion sensor to bind +# - "cemuhookudp" for motion input using Cemu Hook protocol. Required parameters: +# - "guid": the IP address of the cemu hook server encoded to a hex string. for example 192.168.0.1 = "c0a80001" +# - "port": the port of the cemu hook server +# - "pad": the index of the joystick +# - "motion": the index of the motion sensor of the joystick to bind +motionleft= +motionright= + +[ControlsGeneral] +# To use the debug_pad, prepend debug_pad_ before each button setting above. +# i.e. debug_pad_button_a= + +# Enable debug pad inputs to the guest +# 0 (default): Disabled, 1: Enabled +debug_pad_enabled = + +# Whether to enable or disable vibration +# 0: Disabled, 1 (default): Enabled +vibration_enabled = + +# Whether to enable or disable accurate vibrations +# 0 (default): Disabled, 1: Enabled +enable_accurate_vibrations = + +# Enables controller motion inputs +# 0: Disabled, 1 (default): Enabled +motion_enabled = + +# Defines the udp device's touch screen coordinate system for cemuhookudp devices +# - "min_x", "min_y", "max_x", "max_y" +touch_device= + +# for mapping buttons to touch inputs. +#touch_from_button_map=1 +#touch_from_button_maps_0_name=default +#touch_from_button_maps_0_count=2 +#touch_from_button_maps_0_bind_0=foo +#touch_from_button_maps_0_bind_1=bar +# etc. + +# List of Cemuhook UDP servers, delimited by ','. +# Default: 127.0.0.1:26760 +# Example: 127.0.0.1:26760,123.4.5.67:26761 +udp_input_servers = + +# Enable controlling an axis via a mouse input. +# 0 (default): Off, 1: On +mouse_panning = + +# Set mouse sensitivity. +# Default: 1.0 +mouse_panning_sensitivity = + +# Emulate an analog control stick from keyboard inputs. +# 0 (default): Disabled, 1: Enabled +emulate_analog_keyboard = + +# Enable mouse inputs to the guest +# 0 (default): Disabled, 1: Enabled +mouse_enabled = + +# Enable keyboard inputs to the guest +# 0 (default): Disabled, 1: Enabled +keyboard_enabled = + +[Core] +# Whether to use multi-core for CPU emulation +# 0: Disabled, 1 (default): Enabled +use_multi_core = + +# Enable unsafe extended guest system memory layout (8GB DRAM) +# 0 (default): Disabled, 1: Enabled +use_unsafe_extended_memory_layout = + +[Cpu] +# Adjusts various optimizations. +# Auto-select mode enables choice unsafe optimizations. +# Accurate enables only safe optimizations. +# Unsafe allows any unsafe optimizations. +# 0 (default): Auto-select, 1: Accurate, 2: Enable unsafe optimizations +cpu_accuracy = + +# Allow disabling safe optimizations. +# 0 (default): Disabled, 1: Enabled +cpu_debug_mode = + +# Enable inline page tables optimization (faster guest memory access) +# 0: Disabled, 1 (default): Enabled +cpuopt_page_tables = + +# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps) +# 0: Disabled, 1 (default): Enabled +cpuopt_block_linking = + +# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns) +# 0: Disabled, 1 (default): Enabled +cpuopt_return_stack_buffer = + +# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture) +# 0: Disabled, 1 (default): Enabled +cpuopt_fast_dispatcher = + +# Enable context elimination CPU Optimization (reduce host memory use for guest context) +# 0: Disabled, 1 (default): Enabled +cpuopt_context_elimination = + +# Enable constant propagation CPU optimization (basic IR optimization) +# 0: Disabled, 1 (default): Enabled +cpuopt_const_prop = + +# Enable miscellaneous CPU optimizations (basic IR optimization) +# 0: Disabled, 1 (default): Enabled +cpuopt_misc_ir = + +# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access) +# 0: Disabled, 1 (default): Enabled +cpuopt_reduce_misalign_checks = + +# Enable Host MMU Emulation (faster guest memory access) +# 0: Disabled, 1 (default): Enabled +cpuopt_fastmem = + +# Enable Host MMU Emulation for exclusive memory instructions (faster guest memory access) +# 0: Disabled, 1 (default): Enabled +cpuopt_fastmem_exclusives = + +# Enable fallback on failure of fastmem of exclusive memory instructions (faster guest memory access) +# 0: Disabled, 1 (default): Enabled +cpuopt_recompile_exclusives = + +# Enable optimization to ignore invalid memory accesses (faster guest memory access) +# 0: Disabled, 1 (default): Enabled +cpuopt_ignore_memory_aborts = + +# Enable unfuse FMA (improve performance on CPUs without FMA) +# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. +# 0: Disabled, 1 (default): Enabled +cpuopt_unsafe_unfuse_fma = + +# Enable faster FRSQRTE and FRECPE +# Only enabled if cpu_accuracy is set to Unsafe. +# 0: Disabled, 1 (default): Enabled +cpuopt_unsafe_reduce_fp_error = + +# Enable faster ASIMD instructions (32 bits only) +# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. +# 0: Disabled, 1 (default): Enabled +cpuopt_unsafe_ignore_standard_fpcr = + +# Enable inaccurate NaN handling +# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. +# 0: Disabled, 1 (default): Enabled +cpuopt_unsafe_inaccurate_nan = + +# Disable address space checks (64 bits only) +# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. +# 0: Disabled, 1 (default): Enabled +cpuopt_unsafe_fastmem_check = + +# Enable faster exclusive instructions +# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. +# 0: Disabled, 1 (default): Enabled +cpuopt_unsafe_ignore_global_monitor = + +[Renderer] +# Which backend API to use. +# 0: OpenGL (unsupported), 1 (default): Vulkan, 2: Null +backend = + +# Whether to enable asynchronous presentation (Vulkan only) +# 0: Off, 1 (default): On +async_presentation = + +# Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied). +# 0 (default): Disabled, 1: Enabled +force_max_clock = + +# Enable graphics API debugging mode. +# 0 (default): Disabled, 1: Enabled +debug = + +# Enable shader feedback. +# 0 (default): Disabled, 1: Enabled +renderer_shader_feedback = + +# Enable Nsight Aftermath crash dumps +# 0 (default): Disabled, 1: Enabled +nsight_aftermath = + +# Disable shader loop safety checks, executing the shader without loop logic changes +# 0 (default): Disabled, 1: Enabled +disable_shader_loop_safety_checks = + +# Which Vulkan physical device to use (defaults to 0) +vulkan_device = + +# 0: 0.5x (360p/540p) [EXPERIMENTAL] +# 1: 0.75x (540p/810p) [EXPERIMENTAL] +# 2 (default): 1x (720p/1080p) +# 3: 2x (1440p/2160p) +# 4: 3x (2160p/3240p) +# 5: 4x (2880p/4320p) +# 6: 5x (3600p/5400p) +# 7: 6x (4320p/6480p) +resolution_setup = + +# Pixel filter to use when up- or down-sampling rendered frames. +# 0: Nearest Neighbor +# 1 (default): Bilinear +# 2: Bicubic +# 3: Gaussian +# 4: ScaleForce +# 5: AMD FidelityFX™️ Super Resolution [Vulkan Only] +scaling_filter = + +# Anti-Aliasing (AA) +# 0 (default): None, 1: FXAA +anti_aliasing = + +# Whether to use fullscreen or borderless window mode +# 0 (Windows default): Borderless window, 1 (All other default): Exclusive fullscreen +fullscreen_mode = + +# Aspect ratio +# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Force 16:10, 4: Stretch to Window +aspect_ratio = + +# Anisotropic filtering +# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x +max_anisotropy = + +# Whether to enable VSync or not. +# OpenGL: Values other than 0 enable VSync +# Vulkan: FIFO is selected if the requested mode is not supported by the driver. +# FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. +# FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down. +# Mailbox can have lower latency than FIFO and does not tear but may drop frames. +# Immediate (no synchronization) just presents whatever is available and can exhibit tearing. +# 0: Immediate (Off), 1 (Default): Mailbox (On), 2: FIFO, 3: FIFO Relaxed +use_vsync = + +# Selects the OpenGL shader backend. NV_gpu_program5 is required for GLASM. If NV_gpu_program5 is +# not available and GLASM is selected, GLSL will be used. +# 0: GLSL, 1 (default): GLASM, 2: SPIR-V +shader_backend = + +# Whether to allow asynchronous shader building. +# 0 (default): Off, 1: On +use_asynchronous_shaders = + +# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory. +# 0 (default): Off, 1: On +use_reactive_flushing = + +# NVDEC emulation. +# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding +nvdec_emulation = + +# Accelerate ASTC texture decoding. +# 0 (default): Off, 1: On +accelerate_astc = + +# Turns on the speed limiter, which will limit the emulation speed to the desired speed limit value +# 0: Off, 1: On (default) +use_speed_limit = + +# Limits the speed of the game to run no faster than this value as a percentage of target speed +# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default) +speed_limit = + +# Whether to use disk based shader cache +# 0: Off, 1 (default): On +use_disk_shader_cache = + +# Which gpu accuracy level to use +# 0 (default): Normal, 1: High, 2: Extreme (Very slow) +gpu_accuracy = + +# Whether to use asynchronous GPU emulation +# 0 : Off (slow), 1 (default): On (fast) +use_asynchronous_gpu_emulation = + +# Inform the guest that GPU operations completed more quickly than they did. +# 0: Off, 1 (default): On +use_fast_gpu_time = + +# Force unmodified buffers to be flushed, which can cost performance. +# 0: Off (default), 1: On +use_pessimistic_flushes = + +# Whether to use garbage collection or not for GPU caches. +# 0 (default): Off, 1: On +use_caches_gc = + +# The clear color for the renderer. What shows up on the sides of the bottom screen. +# Must be in range of 0-255. Defaults to 0 for all. +bg_red = +bg_blue = +bg_green = + +[Audio] +# Which audio output engine to use. +# auto (default): Auto-select +# cubeb: Cubeb audio engine (if available) +# sdl2: SDL2 audio engine (if available) +# null: No audio output +output_engine = + +# Which audio device to use. +# auto (default): Auto-select +output_device = + +# Output volume. +# 100 (default): 100%, 0; mute +volume = + +[Data Storage] +# Whether to create a virtual SD card. +# 1: Yes, 0 (default): No +use_virtual_sd = + +# Whether or not to enable gamecard emulation +# 1: Yes, 0 (default): No +gamecard_inserted = + +# Whether or not the gamecard should be emulated as the current game +# If 'gamecard_inserted' is 0 this setting is irrelevant +# 1: Yes, 0 (default): No +gamecard_current_game = + +# Path to an XCI file to use as the gamecard +# If 'gamecard_inserted' is 0 this setting is irrelevant +# If 'gamecard_current_game' is 1 this setting is irrelevant +gamecard_path = + +[System] +# Whether the system is docked +# 1: Yes, 0 (default): No +use_docked_mode = + +# Sets the seed for the RNG generator built into the switch +# rng_seed will be ignored and randomly generated if rng_seed_enabled is false +rng_seed_enabled = +rng_seed = + +# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service +# This will auto-increment, with the time set being the time the game is started +# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used +custom_rtc_enabled = +custom_rtc = + +# Sets the systems language index +# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese, +# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French, +# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese, 17: Brazilian Portuguese +language_index = + +# The system region that yuzu will use during emulation +# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan +region_index = + +# The system time zone that yuzu will use during emulation +# 0: Auto-select (default), 1: Default (system archive value), Others: Index for specified time zone +time_zone_index = + +# Sets the sound output mode. +# 0: Mono, 1 (default): Stereo, 2: Surround +sound_index = + +[Miscellaneous] +# A filter which removes logs below a certain logging level. +# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical +log_filter = *:Trace + +# Use developer keys +# 0 (default): Disabled, 1: Enabled +use_dev_keys = + +[Debugging] +# Record frame time data, can be found in the log directory. Boolean value +record_frame_times = +# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them +dump_exefs=false +# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them +dump_nso=false +# Determines whether or not yuzu will save the filesystem access log. +enable_fs_access_log=false +# Enables verbose reporting services +reporting_services = +# Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode +# false: Retail/Normal Mode (default), true: Kiosk Mode +quest_flag = +# Determines whether debug asserts should be enabled, which will throw an exception on asserts. +# false: Disabled (default), true: Enabled +use_debug_asserts = +# Determines whether unimplemented HLE service calls should be automatically stubbed. +# false: Disabled (default), true: Enabled +use_auto_stub = +# Enables/Disables the macro JIT compiler +disable_macro_jit=false +# Determines whether to enable the GDB stub and wait for the debugger to attach before running. +# false: Disabled (default), true: Enabled +use_gdbstub=false +# The port to use for the GDB server, if it is enabled. +gdbstub_port=6543 + +[WebService] +# Whether or not to enable telemetry +# 0: No, 1 (default): Yes +enable_telemetry = +# URL for Web API +web_api_url = +# Username and token for yuzu Web Service +# See https://profile.yuzu-emu.org/ for more info +yuzu_username = +yuzu_token = + +[Network] +# Name of the network interface device to use with yuzu LAN play. +# e.g. On *nix: 'enp7s0', 'wlp6s0u1u3u3', 'lo' +# e.g. On Windows: 'Ethernet', 'Wi-Fi' +network_interface = + +[AddOns] +# Used to disable add-ons +# List of title IDs of games that will have add-ons disabled (separated by '|'): +title_ids = +# For each title ID, have a key/value pair called `disabled_` equal to the names of the add-ons to disable (sep. by '|') +# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and + on Super Mario Odyssey +)"; +} // namespace DefaultINI + + +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "common/settings.h" + +class INIReader; + +class Config { + bool LoadINI(const std::string& default_contents = "", bool retry = true); + +public: + enum class ConfigType { + GlobalConfig, + PerGameConfig, + InputProfile, + }; + + explicit Config(const std::string& config_name = "config", + ConfigType config_type = ConfigType::GlobalConfig); + ~Config(); + + void Initialize(const std::string& config_name); + +private: + /** + * Applies a value read from the config to a Setting. + * + * @param group The name of the INI group + * @param setting The yuzu setting to modify + */ + template + void ReadSetting(const std::string& group, YuzuSettings::Setting& setting); + + void ReadValues(); + + const ConfigType type; + std::unique_ptr config; + std::string config_loc; + const bool global; +}; diff --git a/src/ios/Eden/Wrapper/Config/Config.mm b/src/ios/Eden/Wrapper/Config/Config.mm new file mode 100644 index 0000000000..3460ee08de --- /dev/null +++ b/src/ios/Eden/Wrapper/Config/Config.mm @@ -0,0 +1,330 @@ +// +// Config.m - Sudachi +// Created by Jarrod Norwell on 13/3/2024. +// + +#import "Config.h" + +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/settings_enums.h" +#include "core/hle/service/acc/profile_manager.h" +#include "input_common/main.h" + +namespace FS = Common::FS; + +Config::Config(const std::string& config_name, ConfigType config_type) + : type(config_type), global{config_type == ConfigType::GlobalConfig} { + Initialize(config_name); +} + +Config::~Config() = default; + +bool Config::LoadINI(const std::string& default_contents, bool retry) { + void(FS::CreateParentDir(config_loc)); + config = std::make_unique(FS::PathToUTF8String(config_loc)); + const auto config_loc_str = FS::PathToUTF8String(config_loc); + if (config->ParseError() < 0) { + if (retry) { + LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", + config_loc_str); + + void(FS::CreateParentDir(config_loc)); + void(FS::WriteStringToFile(config_loc, FS::FileType::TextFile, default_contents)); + + config = std::make_unique(config_loc_str); + + return LoadINI(default_contents, false); + } + LOG_ERROR(Config, "Failed."); + return false; + } + LOG_INFO(Config, "Successfully loaded {}", config_loc_str); + return true; +} + +template <> +void Config::ReadSetting(const std::string& group, YuzuSettings::Setting& setting) { + std::string setting_value = config->Get(group, setting.GetLabel(), setting.GetDefault()); + if (setting_value.empty()) { + setting_value = setting.GetDefault(); + } + setting = std::move(setting_value); +} + +template <> +void Config::ReadSetting(const std::string& group, YuzuSettings::Setting& setting) { + setting = config->GetBoolean(group, setting.GetLabel(), setting.GetDefault()); +} + +template +void Config::ReadSetting(const std::string& group, YuzuSettings::Setting& setting) { + setting = static_cast( + config->GetInteger(group, setting.GetLabel(), static_cast(setting.GetDefault()))); +} + +void Config::ReadValues() { + ReadSetting("ControlsGeneral", YuzuSettings::values.mouse_enabled); + ReadSetting("ControlsGeneral", YuzuSettings::values.touch_device); + ReadSetting("ControlsGeneral", YuzuSettings::values.keyboard_enabled); + ReadSetting("ControlsGeneral", YuzuSettings::values.debug_pad_enabled); + ReadSetting("ControlsGeneral", YuzuSettings::values.vibration_enabled); + ReadSetting("ControlsGeneral", YuzuSettings::values.enable_accurate_vibrations); + ReadSetting("ControlsGeneral", YuzuSettings::values.motion_enabled); + YuzuSettings::values.touchscreen.enabled = + config->GetBoolean("ControlsGeneral", "touch_enabled", true); + YuzuSettings::values.touchscreen.rotation_angle = + config->GetInteger("ControlsGeneral", "touch_angle", 0); + YuzuSettings::values.touchscreen.diameter_x = + config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); + YuzuSettings::values.touchscreen.diameter_y = + config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); + + int num_touch_from_button_maps = + config->GetInteger("ControlsGeneral", "touch_from_button_map", 0); + if (num_touch_from_button_maps > 0) { + for (int i = 0; i < num_touch_from_button_maps; ++i) { + YuzuSettings::TouchFromButtonMap map; + map.name = config->Get("ControlsGeneral", + std::string("touch_from_button_maps_") + std::to_string(i) + + std::string("_name"), + "default"); + const int num_touch_maps = config->GetInteger( + "ControlsGeneral", + std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"), + 0); + map.buttons.reserve(num_touch_maps); + + for (int j = 0; j < num_touch_maps; ++j) { + std::string touch_mapping = + config->Get("ControlsGeneral", + std::string("touch_from_button_maps_") + std::to_string(i) + + std::string("_bind_") + std::to_string(j), + ""); + map.buttons.emplace_back(std::move(touch_mapping)); + } + + YuzuSettings::values.touch_from_button_maps.emplace_back(std::move(map)); + } + } else { + YuzuSettings::values.touch_from_button_maps.emplace_back( + YuzuSettings::TouchFromButtonMap{"default", {}}); + num_touch_from_button_maps = 1; + } + YuzuSettings::values.touch_from_button_map_index = std::clamp( + YuzuSettings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1); + + ReadSetting("ControlsGeneral", YuzuSettings::values.udp_input_servers); + + // Data Storage + ReadSetting("Data Storage", YuzuSettings::values.use_virtual_sd); + FS::SetYuzuPath(FS::YuzuPath::NANDDir, + config->Get("Data Storage", "nand_directory", + FS::GetYuzuPathString(FS::YuzuPath::NANDDir))); + FS::SetYuzuPath(FS::YuzuPath::SDMCDir, + config->Get("Data Storage", "sdmc_directory", + FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))); + FS::SetYuzuPath(FS::YuzuPath::LoadDir, + config->Get("Data Storage", "load_directory", + FS::GetYuzuPathString(FS::YuzuPath::LoadDir))); + FS::SetYuzuPath(FS::YuzuPath::DumpDir, + config->Get("Data Storage", "dump_directory", + FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); + ReadSetting("Data Storage", YuzuSettings::values.gamecard_inserted); + ReadSetting("Data Storage", YuzuSettings::values.gamecard_current_game); + ReadSetting("Data Storage", YuzuSettings::values.gamecard_path); + + // System + ReadSetting("System", YuzuSettings::values.current_user); + YuzuSettings::values.current_user = std::clamp(YuzuSettings::values.current_user.GetValue(), 0, + Service::Account::MAX_USERS - 1); + + // Enable docked mode by default on iOS + YuzuSettings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false) + ? YuzuSettings::ConsoleMode::Docked + : YuzuSettings::ConsoleMode::Handheld); + + const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false); + if (rng_seed_enabled) { + YuzuSettings::values.rng_seed.SetValue(config->GetInteger("System", "rng_seed", 0)); + } else { + YuzuSettings::values.rng_seed.SetValue(0); + } + YuzuSettings::values.rng_seed_enabled.SetValue(rng_seed_enabled); + + const auto custom_rtc_enabled = config->GetBoolean("System", "custom_rtc_enabled", false); + if (custom_rtc_enabled) { + YuzuSettings::values.custom_rtc = config->GetInteger("System", "custom_rtc", 0); + } else { + YuzuSettings::values.custom_rtc = 0; + } + YuzuSettings::values.custom_rtc_enabled = custom_rtc_enabled; + + ReadSetting("System", YuzuSettings::values.language_index); + ReadSetting("System", YuzuSettings::values.region_index); + ReadSetting("System", YuzuSettings::values.time_zone_index); + ReadSetting("System", YuzuSettings::values.sound_index); + + // Core + ReadSetting("Core", YuzuSettings::values.use_multi_core); + ReadSetting("Core", YuzuSettings::values.memory_layout_mode); + + // Cpu + ReadSetting("Cpu", YuzuSettings::values.cpu_accuracy); + ReadSetting("Cpu", YuzuSettings::values.cpu_debug_mode); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_page_tables); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_block_linking); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_return_stack_buffer); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_fast_dispatcher); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_context_elimination); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_const_prop); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_misc_ir); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_reduce_misalign_checks); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_fastmem); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_fastmem_exclusives); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_recompile_exclusives); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_ignore_memory_aborts); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_unfuse_fma); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_reduce_fp_error); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_ignore_standard_fpcr); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_inaccurate_nan); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_fastmem_check); + ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_ignore_global_monitor); + + // Renderer + ReadSetting("Renderer", YuzuSettings::values.renderer_backend); + ReadSetting("Renderer", YuzuSettings::values.renderer_debug); + ReadSetting("Renderer", YuzuSettings::values.renderer_shader_feedback); + ReadSetting("Renderer", YuzuSettings::values.enable_nsight_aftermath); + ReadSetting("Renderer", YuzuSettings::values.disable_shader_loop_safety_checks); + ReadSetting("Renderer", YuzuSettings::values.vulkan_device); + + ReadSetting("Renderer", YuzuSettings::values.resolution_setup); + ReadSetting("Renderer", YuzuSettings::values.scaling_filter); + ReadSetting("Renderer", YuzuSettings::values.fsr_sharpening_slider); + ReadSetting("Renderer", YuzuSettings::values.anti_aliasing); + ReadSetting("Renderer", YuzuSettings::values.fullscreen_mode); + ReadSetting("Renderer", YuzuSettings::values.aspect_ratio); + ReadSetting("Renderer", YuzuSettings::values.max_anisotropy); + ReadSetting("Renderer", YuzuSettings::values.use_speed_limit); + ReadSetting("Renderer", YuzuSettings::values.speed_limit); + ReadSetting("Renderer", YuzuSettings::values.use_disk_shader_cache); + ReadSetting("Renderer", YuzuSettings::values.use_asynchronous_gpu_emulation); + ReadSetting("Renderer", YuzuSettings::values.vsync_mode); + ReadSetting("Renderer", YuzuSettings::values.shader_backend); + ReadSetting("Renderer", YuzuSettings::values.use_asynchronous_shaders); + ReadSetting("Renderer", YuzuSettings::values.nvdec_emulation); + ReadSetting("Renderer", YuzuSettings::values.use_fast_gpu_time); + ReadSetting("Renderer", YuzuSettings::values.use_vulkan_driver_pipeline_cache); + + ReadSetting("Renderer", YuzuSettings::values.bg_red); + ReadSetting("Renderer", YuzuSettings::values.bg_green); + ReadSetting("Renderer", YuzuSettings::values.bg_blue); + + // Use GPU accuracy normal by default on Android + YuzuSettings::values.gpu_accuracy = static_cast(config->GetInteger( + "Renderer", "gpu_accuracy", static_cast(YuzuSettings::GpuAccuracy::Normal))); + + // Use GPU default anisotropic filtering on Android + YuzuSettings::values.max_anisotropy = + static_cast(config->GetInteger("Renderer", "max_anisotropy", 1)); + + // Disable ASTC compute by default on iOS + YuzuSettings::values.accelerate_astc.SetValue( + config->GetBoolean("Renderer", "accelerate_astc", false) ? YuzuSettings::AstcDecodeMode::Gpu + : YuzuSettings::AstcDecodeMode::Cpu); + + // Enable asynchronous presentation by default on Android + YuzuSettings::values.async_presentation = + config->GetBoolean("Renderer", "async_presentation", true); + + // Disable force_max_clock by default on Android + YuzuSettings::values.renderer_force_max_clock = + config->GetBoolean("Renderer", "force_max_clock", false); + + // Disable use_reactive_flushing by default on Android + YuzuSettings::values.use_reactive_flushing = + config->GetBoolean("Renderer", "use_reactive_flushing", false); + + // Audio + ReadSetting("Audio", YuzuSettings::values.sink_id); + ReadSetting("Audio", YuzuSettings::values.audio_output_device_id); + ReadSetting("Audio", YuzuSettings::values.volume); + + // Miscellaneous + // log_filter has a different default here than from common + YuzuSettings::values.log_filter = "*:Info"; + ReadSetting("Miscellaneous", YuzuSettings::values.use_dev_keys); + + // Debugging + YuzuSettings::values.record_frame_times = + config->GetBoolean("Debugging", "record_frame_times", false); + ReadSetting("Debugging", YuzuSettings::values.dump_exefs); + ReadSetting("Debugging", YuzuSettings::values.dump_nso); + ReadSetting("Debugging", YuzuSettings::values.enable_fs_access_log); + ReadSetting("Debugging", YuzuSettings::values.reporting_services); + ReadSetting("Debugging", YuzuSettings::values.quest_flag); + ReadSetting("Debugging", YuzuSettings::values.use_debug_asserts); + ReadSetting("Debugging", YuzuSettings::values.use_auto_stub); + ReadSetting("Debugging", YuzuSettings::values.disable_macro_jit); + ReadSetting("Debugging", YuzuSettings::values.disable_macro_hle); + ReadSetting("Debugging", YuzuSettings::values.use_gdbstub); + ReadSetting("Debugging", YuzuSettings::values.gdbstub_port); + + const auto title_list = config->Get("AddOns", "title_ids", ""); + std::stringstream ss(title_list); + std::string line; + while (std::getline(ss, line, '|')) { + const auto title_id = std::strtoul(line.c_str(), nullptr, 16); + const auto disabled_list = config->Get("AddOns", "disabled_" + line, ""); + + std::stringstream inner_ss(disabled_list); + std::string inner_line; + std::vector out; + while (std::getline(inner_ss, inner_line, '|')) { + out.push_back(inner_line); + } + + YuzuSettings::values.disabled_addons.insert_or_assign(title_id, out); + } + + // Web Service + ReadSetting("WebService", YuzuSettings::values.enable_telemetry); + ReadSetting("WebService", YuzuSettings::values.web_api_url); + ReadSetting("WebService", YuzuSettings::values.yuzu_username); + ReadSetting("WebService", YuzuSettings::values.yuzu_token); + + // Network + ReadSetting("Network", YuzuSettings::values.network_interface); +} + +void Config::Initialize(const std::string& config_name) { + const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir); + const auto config_file = fmt::format("{}.ini", config_name); + + switch (type) { + case ConfigType::GlobalConfig: + config_loc = FS::PathToUTF8String(fs_config_loc / config_file); + break; + case ConfigType::PerGameConfig: + config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file)); + break; + case ConfigType::InputProfile: + config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file); + LoadINI(DefaultINI::android_config_file); + return; + } + LoadINI(DefaultINI::android_config_file); + ReadValues(); +} diff --git a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h new file mode 100644 index 0000000000..3965b6c2bb --- /dev/null +++ b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h @@ -0,0 +1,10 @@ +// +// DirectoryManager.h - Sudachi +// Created by Jarrod Norwell on 1/18/24. +// + +#pragma once + +namespace DirectoryManager { +const char* AppUIDirectory(void); +} diff --git a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm new file mode 100644 index 0000000000..428244fea1 --- /dev/null +++ b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm @@ -0,0 +1,16 @@ +// +// DirectoryManager.mm - Sudachi +// Created by Jarrod Norwell on 1/18/24. +// + +#import + +#import "DirectoryManager.h" + +NSURL *DocumentsDirectory() { + return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject]; +} + +const char* DirectoryManager::AppUIDirectory(void) { + return [[DocumentsDirectory() path] UTF8String]; +} diff --git a/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h b/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h new file mode 100644 index 0000000000..291d414b37 --- /dev/null +++ b/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h @@ -0,0 +1,98 @@ +// +// EmulationSession.h - Sudachi +// Created by Jarrod Norwell on 1/20/24. +// + +#pragma once + +#import + +#import +#import "../EmulationWindow/EmulationWindow.h" + +#include "common/detached_tasks.h" +#include "core/core.h" +#include "core/file_sys/registered_cache.h" +#include "core/hle/service/acc/profile_manager.h" +#include "core/perf_stats.h" +#include "frontend_common/content_manager.h" +#include "video_core/rasterizer_interface.h" + +class EmulationSession final { +public: + explicit EmulationSession(); + ~EmulationSession() = default; + + static EmulationSession& GetInstance(); + const Core::System& System() const; + Core::System& System(); + FileSys::ManualContentProvider* GetContentProvider(); + InputCommon::InputSubsystem& GetInputSubsystem(); + + const EmulationWindow& Window() const; + EmulationWindow& Window(); + CA::MetalLayer* NativeWindow() const; + void SetNativeWindow(CA::MetalLayer* native_window, CGSize size); + void SurfaceChanged(); + + void InitializeGpuDriver(); + + bool IsRunning() const; + bool IsPaused() const; + void PauseEmulation(); + void UnPauseEmulation(); + void HaltEmulation(); + void RunEmulation(); + void ShutdownEmulation(); + + const Core::PerfStatsResults& PerfStats(); + void ConfigureFilesystemProvider(const std::string& filepath); + void InitializeSystem(bool reload); + void SetAppletId(int applet_id); + Core::SystemResultStatus InitializeEmulation(const std::string& filepath, + const std::size_t program_index, + const bool frontend_initiated); + Core::SystemResultStatus BootOS(); + + static void OnEmulationStarted(); + static u64 GetProgramId(std::string programId); + bool IsInitialized() { return is_initialized; }; + + bool IsHandheldOnly(); + void SetDeviceType([[maybe_unused]] int index, int type); + void OnGamepadConnectEvent([[maybe_unused]] int index); + void OnGamepadDisconnectEvent([[maybe_unused]] int index); +private: + static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max); + static void OnEmulationStopped(Core::SystemResultStatus result); + static void ChangeProgram(std::size_t program_index); + +private: + // Window management + std::unique_ptr m_window; + CA::MetalLayer* m_native_window{}; + + // Core emulation + Core::System m_system; + InputCommon::InputSubsystem m_input_subsystem; + Common::DetachedTasks m_detached_tasks; + Core::PerfStatsResults m_perf_stats{}; + std::shared_ptr m_vfs; + Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; + std::atomic m_is_running = false; + std::atomic m_is_paused = false; + std::unique_ptr m_manual_provider; + int m_applet_id{1}; + + // GPU driver parameters + std::shared_ptr m_vulkan_library; + + // Synchronization + std::condition_variable_any m_cv; + mutable std::mutex m_mutex; + bool is_initialized = false; + CGSize m_size; + + // Program index for next boot + std::atomic m_next_program_index = -1; +}; diff --git a/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm b/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm new file mode 100644 index 0000000000..18793ca9fe --- /dev/null +++ b/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm @@ -0,0 +1,528 @@ +// +// EmulationSession.m - Sudachi +// Created by Jarrod Norwell on 1/20/24. +// + +#import "EmulationSession.h" + +#include + +#include +#include +#include +#include +#include + +#include "common/fs/fs.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/savedata_factory.h" +#include "core/loader/nro.h" +#include "frontend_common/content_manager.h" + +#include "common/detached_tasks.h" +#include "common/dynamic_library.h" +#include "common/fs/path_util.h" +#include "common/logging/backend.h" +#include "common/logging/log.h" +#include "common/microprofile.h" +#include "common/scm_rev.h" +#include "common/scope_exit.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/cpu_manager.h" +#include "core/crypto/key_manager.h" +#include "core/file_sys/card_image.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/fs_filesystem.h" +#include "core/file_sys/submission_package.h" +#include "core/file_sys/vfs/vfs.h" +#include "core/file_sys/vfs/vfs_real.h" +#include "core/frontend/applets/cabinet.h" +#include "core/frontend/applets/controller.h" +#include "core/frontend/applets/error.h" +#include "core/frontend/applets/general.h" +#include "core/frontend/applets/mii_edit.h" +#include "core/frontend/applets/profile_select.h" +#include "core/frontend/applets/software_keyboard.h" +#include "core/frontend/applets/web_browser.h" +#include "core/hle/service/am/applet_manager.h" +#include "core/hle/service/am/frontend/applets.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/loader.h" +#include "frontend_common/yuzu_config.h" +#include "hid_core/frontend/emulated_controller.h" +#include "hid_core/hid_core.h" +#include "hid_core/hid_types.h" +#include "video_core/renderer_base.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" +#include "video_core/vulkan_common/vulkan_instance.h" +#include "video_core/vulkan_common/vulkan_surface.h" + +#define jconst [[maybe_unused]] const auto +#define jauto [[maybe_unused]] auto + +static EmulationSession s_instance; + +EmulationSession::EmulationSession() { + m_vfs = std::make_shared(); +} + +EmulationSession& EmulationSession::GetInstance() { + return s_instance; +} + +const Core::System& EmulationSession::System() const { + return m_system; +} + +Core::System& EmulationSession::System() { + return m_system; +} + +FileSys::ManualContentProvider* EmulationSession::GetContentProvider() { + return m_manual_provider.get(); +} + +InputCommon::InputSubsystem& EmulationSession::GetInputSubsystem() { + return m_input_subsystem; +} + +const EmulationWindow& EmulationSession::Window() const { + return *m_window; +} + +EmulationWindow& EmulationSession::Window() { + return *m_window; +} + +CA::MetalLayer* EmulationSession::NativeWindow() const { + return m_native_window; +} + +void EmulationSession::SetNativeWindow(CA::MetalLayer* native_window, CGSize size) { + m_native_window = native_window; + m_size = size; +} + +void EmulationSession::InitializeGpuDriver() { + m_vulkan_library = std::make_shared(dlopen("@executable_path/Frameworks/MoltenVK", RTLD_NOW)); +} + +bool EmulationSession::IsRunning() const { + return m_is_running; +} + +bool EmulationSession::IsPaused() const { + return m_is_running && m_is_paused; +} + +const Core::PerfStatsResults& EmulationSession::PerfStats() { + m_perf_stats = m_system.GetAndResetPerfStats(); + return m_perf_stats; +} + +void EmulationSession::SurfaceChanged() { + if (!IsRunning()) { + return; + } + m_window->OnSurfaceChanged(m_native_window, m_size); +} + +void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) { + const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::OpenMode::Read); + if (!file) { + return; + } + + auto loader = Loader::GetLoader(m_system, file); + if (!loader) { + return; + } + + const auto file_type = loader->GetFileType(); + if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { + return; + } + + u64 program_id = 0; + const auto res2 = loader->ReadProgramId(program_id); + if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { + m_manual_provider->AddEntry(FileSys::TitleType::Application, + FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), + program_id, file); + } else if (res2 == Loader::ResultStatus::Success && + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { + const auto nsp = file_type == Loader::FileType::NSP + ? std::make_shared(file) + : FileSys::XCI{file}.GetSecurePartitionNSP(); + for (const auto& title : nsp->GetNCAs()) { + for (const auto& entry : title.second) { + m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first, + entry.second->GetBaseFile()); + } + } + } +} + +void EmulationSession::InitializeSystem(bool reload) { + if (!reload) { + SDL_SetMainReady(); + + // Initialize logging system + Common::Log::Initialize(); + Common::Log::SetColorConsoleBackendEnabled(true); + Common::Log::Start(); + } + + // Initialize filesystem. + m_system.SetFilesystem(m_vfs); + m_system.GetUserChannel().clear(); + m_manual_provider = std::make_unique(); + m_system.SetContentProvider(std::make_unique()); + m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, + m_manual_provider.get()); + m_system.GetFileSystemController().CreateFactories(*m_vfs); + + is_initialized = true; +} + +void EmulationSession::SetAppletId(int applet_id) { + m_applet_id = applet_id; + m_system.GetFrontendAppletHolder().SetCurrentAppletId( + static_cast(m_applet_id)); +} + +Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath, + const std::size_t program_index, + const bool frontend_initiated) { + std::scoped_lock lock(m_mutex); + + // Create the render window. + m_window = std::make_unique(&m_input_subsystem, m_native_window, m_size, m_vulkan_library); + + // Initialize system. + m_system.SetShuttingDown(false); + m_system.ApplySettings(); + YuzuSettings::LogSettings(); + m_system.HIDCore().ReloadInputDevices(); + m_system.SetFrontendAppletSet({ + nullptr, // Amiibo Settings + nullptr, // Controller Selector + nullptr, // Error Display + nullptr, // Mii Editor + nullptr, // Parental Controls + nullptr, // Photo Viewer + nullptr, // Profile Selector + nullptr, // std::move(android_keyboard), // Software Keyboard + nullptr, // Web Browser + }); + + // Initialize filesystem. + ConfigureFilesystemProvider(filepath); + + // Load the ROM. + Service::AM::FrontendAppletParameters params{ + .applet_id = static_cast(m_applet_id), + .launch_type = frontend_initiated ? Service::AM::LaunchType::FrontendInitiated + : Service::AM::LaunchType::ApplicationInitiated, + .program_index = static_cast(program_index), + }; + m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath, params); + if (m_load_result != Core::SystemResultStatus::Success) { + return m_load_result; + } + + // Complete initialization. + m_system.GPU().Start(); + m_system.GetCpuManager().OnGpuReady(); + m_system.RegisterExitCallback([&] { HaltEmulation(); }); + + if (YuzuSettings::values.use_disk_shader_cache.GetValue()) { + m_system.Renderer().ReadRasterizer()->LoadDiskResources( + m_system.GetApplicationProcessProgramID(), std::stop_token{}, + [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); + } + + // Register an ExecuteProgram callback such that Core can execute a sub-program + m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) { + m_next_program_index = program_index_; + EmulationSession::GetInstance().HaltEmulation(); + ChangeProgram(m_next_program_index); + }); + + OnEmulationStarted(); + return Core::SystemResultStatus::Success; +} + + +Core::SystemResultStatus EmulationSession::BootOS() { + std::scoped_lock lock(m_mutex); + + // Create the render window. + m_window = std::make_unique(&m_input_subsystem, m_native_window, m_size, m_vulkan_library); + + // Initialize system. + m_system.SetShuttingDown(false); + m_system.ApplySettings(); + YuzuSettings::LogSettings(); + m_system.HIDCore().ReloadInputDevices(); + m_system.SetFrontendAppletSet({ + nullptr, // Amiibo Settings + nullptr, // Controller Selector + nullptr, // Error Display + nullptr, // Mii Editor + nullptr, // Parental Controls + nullptr, // Photo Viewer + nullptr, // Profile Selector + nullptr, // std::move(android_keyboard), // Software Keyboard + nullptr, // Web Browser + }); + + constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); + auto bis_system = m_system.GetFileSystemController().GetSystemNANDContents(); + + auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); + + m_system.GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::QLaunch); + + const auto filename = qlaunch_applet_nca->GetFullPath(); + + auto params = Service::AM::FrontendAppletParameters { + .program_id = QLaunchId, + .applet_id = Service::AM::AppletId::QLaunch, + .applet_type = Service::AM::AppletType::LibraryApplet + }; + + m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filename, params); + + if (m_load_result != Core::SystemResultStatus::Success) { + return m_load_result; + } + + // Complete initialization. + m_system.GPU().Start(); + m_system.GetCpuManager().OnGpuReady(); + m_system.RegisterExitCallback([&] { HaltEmulation(); }); + + if (YuzuSettings::values.use_disk_shader_cache.GetValue()) { + m_system.Renderer().ReadRasterizer()->LoadDiskResources( + m_system.GetApplicationProcessProgramID(), std::stop_token{}, + [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); + } + + // Register an ExecuteProgram callback such that Core can execute a sub-program + m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) { + m_next_program_index = program_index_; + EmulationSession::GetInstance().HaltEmulation(); + }); + + OnEmulationStarted(); + return Core::SystemResultStatus::Success; +} + +void EmulationSession::ShutdownEmulation() { + std::scoped_lock lock(m_mutex); + + if (m_next_program_index != -1) { + ChangeProgram(m_next_program_index); + m_next_program_index = -1; + } + + m_is_running = false; + + // Unload user input. + m_system.HIDCore().UnloadInputDevices(); + + // Enable all controllers + m_system.HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All}); + + // Shutdown the main emulated process + if (m_load_result == Core::SystemResultStatus::Success) { + m_system.DetachDebugger(); + m_system.ShutdownMainProcess(); + m_detached_tasks.WaitForAllTasks(); + m_load_result = Core::SystemResultStatus::ErrorNotInitialized; + m_window.reset(); + OnEmulationStopped(Core::SystemResultStatus::Success); + return; + } + + // Tear down the render window. + m_window.reset(); +} + +void EmulationSession::PauseEmulation() { + std::scoped_lock lock(m_mutex); + m_system.Pause(); + m_is_paused = true; +} + +void EmulationSession::UnPauseEmulation() { + std::scoped_lock lock(m_mutex); + m_system.Run(); + m_is_paused = false; +} + +void EmulationSession::HaltEmulation() { + std::scoped_lock lock(m_mutex); + m_is_running = false; + m_cv.notify_one(); +} + +void EmulationSession::RunEmulation() { + { + std::scoped_lock lock(m_mutex); + m_is_running = true; + } + + // Load the disk shader cache. + if (YuzuSettings::values.use_disk_shader_cache.GetValue()) { + LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); + m_system.Renderer().ReadRasterizer()->LoadDiskResources( + m_system.GetApplicationProcessProgramID(), std::stop_token{}, LoadDiskCacheProgress); + LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); + } + + void(m_system.Run()); + + if (m_system.DebuggerEnabled()) { + m_system.InitializeDebugger(); + } + + while (true) { + { + [[maybe_unused]] std::unique_lock lock(m_mutex); + if (m_cv.wait_for(lock, std::chrono::milliseconds(800), + [&]() { return !m_is_running; })) { + // Emulation halted. + break; + } + } + } + + // Reset current applet ID. + m_applet_id = static_cast(Service::AM::AppletId::Application); +} + +void EmulationSession::LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, + int max) { + +} + +void EmulationSession::OnEmulationStarted() { + +} + +void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) { + +} + +void EmulationSession::ChangeProgram(std::size_t program_index) { + LOG_INFO(Frontend, "Trying To Switch Program"); + // Halt current emulation session + EmulationSession::GetInstance().HaltEmulation(); + // Save the current state if necessary + + // Shutdown the current emulation session cleanly + // Update the program index + EmulationSession::GetInstance().m_next_program_index = program_index; + + // Initialize the new program + // Start the new emulation session + EmulationSession::GetInstance().RunEmulation(); +} + +u64 EmulationSession::GetProgramId(std::string programId) { + try { + return std::stoull(programId); + } catch (...) { + return 0; + } +} + +static Core::SystemResultStatus RunEmulation(const std::string& filepath, + const size_t program_index, + const bool frontend_initiated) { + MicroProfileOnThreadCreate("EmuThread"); + SCOPE_EXIT { + MicroProfileShutdown(); + }; + + LOG_INFO(Frontend, "starting"); + + if (filepath.empty()) { + LOG_CRITICAL(Frontend, "failed to load: filepath empty!"); + return Core::SystemResultStatus::ErrorLoader; + } + + SCOPE_EXIT { + EmulationSession::GetInstance().ShutdownEmulation(); + }; + + jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath, program_index, + frontend_initiated); + if (result != Core::SystemResultStatus::Success) { + return result; + } + + EmulationSession::GetInstance().RunEmulation(); + + return Core::SystemResultStatus::Success; +} + + + +bool EmulationSession::IsHandheldOnly() { + jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); + + if (npad_style_set.fullkey == 1) { + return false; + } + + if (npad_style_set.handheld == 0) { + return false; + } + + return !YuzuSettings::IsDockedMode(); +} + +void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) { + jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); + controller->SetNpadStyleIndex(static_cast(type)); +} + +void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) { + jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); + + // Ensure that player1 is configured correctly and handheld disconnected + if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) { + jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); + + if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { + handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey); + controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey); + handheld->Disconnect(); + } + } + + // Ensure that handheld is configured correctly and player 1 disconnected + if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) { + jauto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + + if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { + player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); + controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); + player1->Disconnect(); + } + } + + if (!controller->IsConnected()) { + controller->Connect(); + } +} + +void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) { + jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); + controller->Disconnect(); +} diff --git a/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h b/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h new file mode 100644 index 0000000000..d099c88c55 --- /dev/null +++ b/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h @@ -0,0 +1,80 @@ +// +// EmulationWindow.h - Sudachi +// Created by Jarrod Norwell on 1/18/24. +// + +#pragma once + +#import +#import +#import + +#include +#include + +#include "core/frontend/emu_window.h" +#include "core/frontend/graphics_context.h" +#include "input_common/main.h" + +class GraphicsContext_Apple final : public Core::Frontend::GraphicsContext { +public: + explicit GraphicsContext_Apple(std::shared_ptr driver_library) + : m_driver_library{driver_library} {} + + ~GraphicsContext_Apple() = default; + + std::shared_ptr GetDriverLibrary() override { + return m_driver_library; + } + +private: + std::shared_ptr m_driver_library; +}; + +NS_ASSUME_NONNULL_BEGIN + +class EmulationWindow final : public Core::Frontend::EmuWindow { +public: + EmulationWindow(InputCommon::InputSubsystem* input_subsystem, CA::MetalLayer* surface, CGSize size, + std::shared_ptr driver_library); + + ~EmulationWindow() = default; + + void OnSurfaceChanged(CA::MetalLayer* surface, CGSize size); + void OrientationChanged(UIInterfaceOrientation orientation); + void OnFrameDisplayed() override; + + void OnTouchPressed(int id, float x, float y); + void OnTouchMoved(int id, float x, float y); + void OnTouchReleased(int id); + + void OnGamepadButtonEvent(int player_index, int button_id, bool pressed); + void OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y); + void OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, float gyro_y, float gyro_z, float accel_x, float accel_y, float accel_z); + + std::unique_ptr CreateSharedContext() const override { + return {std::make_unique(m_driver_library)}; + } + + + bool HasFirstFrame() const { + return m_first_frame; + } + + bool IsShown() const override { + return true; + }; + +private: + float m_window_width{}; + float m_window_height{}; + CGSize m_size; + bool is_portrait = true; + + InputCommon::InputSubsystem* m_input_subsystem{}; + std::shared_ptr m_driver_library; + + bool m_first_frame = false; +}; + +NS_ASSUME_NONNULL_END diff --git a/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm b/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm new file mode 100644 index 0000000000..a0e388e751 --- /dev/null +++ b/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm @@ -0,0 +1,85 @@ +// +// EmulationWindow.mm - Sudachi +// Created by Jarrod Norwell on 1/18/24. +// + +#import "EmulationWindow.h" +#import "../EmulationSession/EmulationSession.h" + +#include + +#include "common/logging/log.h" +#include "input_common/drivers/touch_screen.h" +#include "input_common/drivers/virtual_amiibo.h" +#include "input_common/drivers/virtual_gamepad.h" +#include "input_common/main.h" + +void EmulationWindow::OnSurfaceChanged(CA::MetalLayer* surface, CGSize size) { + m_size = size; + + m_window_width = size.width; + m_window_height = size.height; + + // Ensures that we emulate with the correct aspect ratio. + // UpdateCurrentFramebufferLayout(m_window_width, m_window_height); + + window_info.render_surface = reinterpret_cast(surface); + window_info.render_surface_scale = [[UIScreen mainScreen] nativeScale]; +} + +void EmulationWindow::OrientationChanged(UIInterfaceOrientation orientation) { + is_portrait = orientation == UIInterfaceOrientationPortrait; +} + +void EmulationWindow::OnTouchPressed(int id, float x, float y) { + const auto [touch_x, touch_y] = MapToTouchScreen(x, y); + EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchPressed(touch_x, + touch_y, id); +} + +void EmulationWindow::OnTouchMoved(int id, float x, float y) { + const auto [touch_x, touch_y] = MapToTouchScreen(x, y); + EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchMoved(touch_x, + touch_y, id); +} + +void EmulationWindow::OnTouchReleased(int id) { + EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchReleased(id); +} + +void EmulationWindow::OnGamepadButtonEvent(int player_index, int button_id, bool pressed) { + m_input_subsystem->GetVirtualGamepad()->SetButtonState(player_index, button_id, pressed); +} + +void EmulationWindow::OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y) { + m_input_subsystem->GetVirtualGamepad()->SetStickPosition(player_index, stick_id, x, y); +} + +void EmulationWindow::OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, + float gyro_y, float gyro_z, float accel_x, + float accel_y, float accel_z) { + m_input_subsystem->GetVirtualGamepad()->SetMotionState(player_index, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z); +} + +void EmulationWindow::OnFrameDisplayed() { + if (!m_first_frame) { + m_first_frame = true; + } +} + +EmulationWindow::EmulationWindow(InputCommon::InputSubsystem* input_subsystem, CA::MetalLayer* surface, CGSize size, + std::shared_ptr driver_library) +: m_input_subsystem{input_subsystem}, m_size{size}, m_driver_library{driver_library} { + LOG_INFO(Frontend, "initializing"); + + if (!surface) { + LOG_CRITICAL(Frontend, "surface is nullptr"); + return; + } + + OnSurfaceChanged(surface, m_size); + window_info.render_surface_scale = [[UIScreen mainScreen] nativeScale]; + window_info.type = Core::Frontend::WindowSystemType::Cocoa; + + m_input_subsystem->Initialize(); +} From 0d80fccbcdceed7f6c2157f4fb75bb9e17e1e00b Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 19 Feb 2026 03:53:04 +0000 Subject: [PATCH 02/74] modernize #1 --- src/ios/Eden/AppUI.swift | 10 +- .../AppUIGameInformation.mm | 90 +-- src/ios/Eden/Wrapper/Config/Config.h | 575 +----------------- src/ios/Eden/Wrapper/Config/Config.mm | 330 +--------- .../DirectoryManager/DirectoryManager.mm | 1 - 5 files changed, 65 insertions(+), 941 deletions(-) diff --git a/src/ios/Eden/AppUI.swift b/src/ios/Eden/AppUI.swift index b8f79528b7..6bd4544b0d 100644 --- a/src/ios/Eden/AppUI.swift +++ b/src/ios/Eden/AppUI.swift @@ -80,13 +80,9 @@ public struct AppUI { public func gyroMoved(x: Float, y: Float, z: Float, accelX: Float, accelY: Float, accelZ: Float, controllerId: Int32, deltaTimestamp: Int32) { // Calling the Objective-C function with both gyroscope and accelerometer data appUIObjC.virtualControllerGyro(controllerId, - deltaTimestamp: deltaTimestamp, - gyroX: x, - gyroY: y, - gyroZ: z, - accelX: accelX, - accelY: accelY, - accelZ: accelZ) + deltaTimestamp: deltaTimestamp, + gyroX: x, gyroY: y, gyroZ: z, + accelX: accelX, accelY: accelY, accelZ: accelZ) } diff --git a/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm index 00c894d078..ef65c723f8 100644 --- a/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm +++ b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm @@ -26,7 +26,7 @@ struct GameMetadata { }; -class SdlConfig final : public YuzuConfig { +class SdlConfig final : public Config { public: explicit SdlConfig(std::optional config_path); ~SdlConfig() override; @@ -59,12 +59,12 @@ protected: void SaveUILayoutValues() override {} void SaveMultiplayerValues() override {} - std::vector& FindRelevantList(YuzuSettings::Category category) override; + std::vector& FindRelevantList(Settings::Category category) override; public: - static const std::array default_buttons; - static const std::array default_motions; - static const std::array, YuzuSettings::NativeAnalog::NumAnalogs> default_analogs; + static const std::array default_buttons; + static const std::array default_motions; + static const std::array, Settings::NativeAnalog::NumAnalogs> default_analogs; static const std::array default_stick_mod; static const std::array default_ringcon_analogs; }; @@ -76,18 +76,18 @@ public: #include "common/logging/log.h" #include "input_common/main.h" -const std::array SdlConfig::default_buttons = { +const std::array SdlConfig::default_buttons = { SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T, SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B, }; -const std::array SdlConfig::default_motions = { +const std::array SdlConfig::default_motions = { SDL_SCANCODE_7, SDL_SCANCODE_8, }; -const std::array, YuzuSettings::NativeAnalog::NumAnalogs> SdlConfig::default_analogs{ +const std::array, Settings::NativeAnalog::NumAnalogs> SdlConfig::default_analogs{ { { SDL_SCANCODE_UP, @@ -141,10 +141,10 @@ void SdlConfig::ReadSdlValues() { } void SdlConfig::ReadSdlControlValues() { - BeginGroup(YuzuSettings::TranslateCategory(YuzuSettings::Category::Controls)); + BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); - YuzuSettings::values.players.SetGlobal(!IsCustomConfig()); - for (std::size_t p = 0; p < YuzuSettings::values.players.GetValue().size(); ++p) { + Settings::values.players.SetGlobal(!IsCustomConfig()); + for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { ReadSdlPlayerValues(p); } if (IsCustomConfig()) { @@ -163,48 +163,48 @@ void SdlConfig::ReadSdlPlayerValues(const std::size_t player_index) { player_prefix.append("player_").append(ToString(player_index)).append("_"); } - auto& player = YuzuSettings::values.players.GetValue()[player_index]; + auto& player = Settings::values.players.GetValue()[player_index]; if (IsCustomConfig()) { const auto profile_name = ReadStringSetting(std::string(player_prefix).append("profile_name")); if (profile_name.empty()) { // Use the global input config - player = YuzuSettings::values.players.GetValue(true)[player_index]; + player = Settings::values.players.GetValue(true)[player_index]; player.profile_name = ""; return; } } - for (int i = 0; i < YuzuSettings::NativeButton::NumButtons; ++i) { + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); auto& player_buttons = player.buttons[i]; player_buttons = ReadStringSetting( - std::string(player_prefix).append(YuzuSettings::NativeButton::mapping[i]), default_param); + std::string(player_prefix).append(Settings::NativeButton::mapping[i]), default_param); if (player_buttons.empty()) { player_buttons = default_param; } } - for (int i = 0; i < YuzuSettings::NativeAnalog::NumAnalogs; ++i) { + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_stick_mod[i], 0.5f); auto& player_analogs = player.analogs[i]; player_analogs = ReadStringSetting( - std::string(player_prefix).append(YuzuSettings::NativeAnalog::mapping[i]), default_param); + std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), default_param); if (player_analogs.empty()) { player_analogs = default_param; } } - for (int i = 0; i < YuzuSettings::NativeMotion::NumMotions; ++i) { + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); auto& player_motions = player.motions[i]; player_motions = ReadStringSetting( - std::string(player_prefix).append(YuzuSettings::NativeMotion::mapping[i]), default_param); + std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), default_param); if (player_motions.empty()) { player_motions = default_param; } @@ -212,22 +212,22 @@ void SdlConfig::ReadSdlPlayerValues(const std::size_t player_index) { } void SdlConfig::ReadDebugControlValues() { - for (int i = 0; i < YuzuSettings::NativeButton::NumButtons; ++i) { + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - auto& debug_pad_buttons = YuzuSettings::values.debug_pad_buttons[i]; + auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i]; debug_pad_buttons = ReadStringSetting( - std::string("debug_pad_").append(YuzuSettings::NativeButton::mapping[i]), default_param); + std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), default_param); if (debug_pad_buttons.empty()) { debug_pad_buttons = default_param; } } - for (int i = 0; i < YuzuSettings::NativeAnalog::NumAnalogs; ++i) { + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_stick_mod[i], 0.5f); - auto& debug_pad_analogs = YuzuSettings::values.debug_pad_analogs[i]; + auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i]; debug_pad_analogs = ReadStringSetting( - std::string("debug_pad_").append(YuzuSettings::NativeAnalog::mapping[i]), default_param); + std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), default_param); if (debug_pad_analogs.empty()) { debug_pad_analogs = default_param; } @@ -237,7 +237,7 @@ void SdlConfig::ReadDebugControlValues() { void SdlConfig::ReadHidbusValues() { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); - auto& ringcon_analogs = YuzuSettings::values.ringcon_analogs; + auto& ringcon_analogs = Settings::values.ringcon_analogs; ringcon_analogs = ReadStringSetting(std::string("ring_controller"), default_param); if (ringcon_analogs.empty()) { @@ -253,10 +253,10 @@ void SdlConfig::SaveSdlValues() { } void SdlConfig::SaveSdlControlValues() { - BeginGroup(YuzuSettings::TranslateCategory(YuzuSettings::Category::Controls)); + BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); - YuzuSettings::values.players.SetGlobal(!IsCustomConfig()); - for (std::size_t p = 0; p < YuzuSettings::values.players.GetValue().size(); ++p) { + Settings::values.players.SetGlobal(!IsCustomConfig()); + for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { SaveSdlPlayerValues(p); } if (IsCustomConfig()) { @@ -275,44 +275,44 @@ void SdlConfig::SaveSdlPlayerValues(const std::size_t player_index) { player_prefix = std::string("player_").append(ToString(player_index)).append("_"); } - const auto& player = YuzuSettings::values.players.GetValue()[player_index]; + const auto& player = Settings::values.players.GetValue()[player_index]; if (IsCustomConfig() && player.profile_name.empty()) { // No custom profile selected return; } - for (int i = 0; i < YuzuSettings::NativeButton::NumButtons; ++i) { + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - WriteStringSetting(std::string(player_prefix).append(YuzuSettings::NativeButton::mapping[i]), + WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]), player.buttons[i], std::make_optional(default_param)); } - for (int i = 0; i < YuzuSettings::NativeAnalog::NumAnalogs; ++i) { + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_stick_mod[i], 0.5f); - WriteStringSetting(std::string(player_prefix).append(YuzuSettings::NativeAnalog::mapping[i]), + WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), player.analogs[i], std::make_optional(default_param)); } - for (int i = 0; i < YuzuSettings::NativeMotion::NumMotions; ++i) { + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); - WriteStringSetting(std::string(player_prefix).append(YuzuSettings::NativeMotion::mapping[i]), + WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), player.motions[i], std::make_optional(default_param)); } } void SdlConfig::SaveDebugControlValues() { - for (int i = 0; i < YuzuSettings::NativeButton::NumButtons; ++i) { + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - WriteStringSetting(std::string("debug_pad_").append(YuzuSettings::NativeButton::mapping[i]), - YuzuSettings::values.debug_pad_buttons[i], + WriteStringSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), + Settings::values.debug_pad_buttons[i], std::make_optional(default_param)); } - for (int i = 0; i < YuzuSettings::NativeAnalog::NumAnalogs; ++i) { + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_stick_mod[i], 0.5f); - WriteStringSetting(std::string("debug_pad_").append(YuzuSettings::NativeAnalog::mapping[i]), - YuzuSettings::values.debug_pad_analogs[i], + WriteStringSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), + Settings::values.debug_pad_analogs[i], std::make_optional(default_param)); } } @@ -320,12 +320,12 @@ void SdlConfig::SaveDebugControlValues() { void SdlConfig::SaveHidbusValues() { const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); - WriteStringSetting(std::string("ring_controller"), YuzuSettings::values.ringcon_analogs, + WriteStringSetting(std::string("ring_controller"), Settings::values.ringcon_analogs, std::make_optional(default_param)); } -std::vector& SdlConfig::FindRelevantList(YuzuSettings::Category category) { - return YuzuSettings::values.linkage.by_category[category]; +std::vector& SdlConfig::FindRelevantList(Settings::Category category) { + return Settings::values.linkage.by_category[category]; } diff --git a/src/ios/Eden/Wrapper/Config/Config.h b/src/ios/Eden/Wrapper/Config/Config.h index 5e29db42cf..4adc23fe44 100644 --- a/src/ios/Eden/Wrapper/Config/Config.h +++ b/src/ios/Eden/Wrapper/Config/Config.h @@ -1,568 +1,19 @@ -// -// Config.h -// AppUI -// -// Created by Jarrod Norwell on 13/3/2024. -// - -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once -namespace DefaultINI { - -const char* android_config_file = R"( - -[ControlsP0] -# The input devices and parameters for each Switch native input -# The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ... -# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..." -# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values - -# Indicates if this player should be connected at boot -connected= - -# for button input, the following devices are available: -# - "keyboard" (default) for keyboard input. Required parameters: -# - "code": the code of the key to bind -# - "sdl" for joystick input using SDL. Required parameters: -# - "guid": SDL identification GUID of the joystick -# - "port": the index of the joystick to bind -# - "button"(optional): the index of the button to bind -# - "hat"(optional): the index of the hat to bind as direction buttons -# - "axis"(optional): the index of the axis to bind -# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right" -# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is -# triggered if the axis value crosses -# - "direction"(only used for axis): "+" means the button is triggered when the axis value -# is greater than the threshold; "-" means the button is triggered when the axis value -# is smaller than the threshold -button_a= -button_b= -button_x= -button_y= -button_lstick= -button_rstick= -button_l= -button_r= -button_zl= -button_zr= -button_plus= -button_minus= -button_dleft= -button_dup= -button_dright= -button_ddown= -button_lstick_left= -button_lstick_up= -button_lstick_right= -button_lstick_down= -button_sl= -button_sr= -button_home= -button_screenshot= - -# for analog input, the following devices are available: -# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters: -# - "up", "down", "left", "right": sub-devices for each direction. -# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00" -# - "modifier": sub-devices as a modifier. -# - "modifier_scale": a float number representing the applied modifier scale to the analog input. -# Must be in range of 0.0-1.0. Defaults to 0.5 -# - "sdl" for joystick input using SDL. Required parameters: -# - "guid": SDL identification GUID of the joystick -# - "port": the index of the joystick to bind -# - "axis_x": the index of the axis to bind as x-axis (default to 0) -# - "axis_y": the index of the axis to bind as y-axis (default to 1) -lstick= -rstick= - -# for motion input, the following devices are available: -# - "keyboard" (default) for emulating random motion input from buttons. Required parameters: -# - "code": the code of the key to bind -# - "sdl" for motion input using SDL. Required parameters: -# - "guid": SDL identification GUID of the joystick -# - "port": the index of the joystick to bind -# - "motion": the index of the motion sensor to bind -# - "cemuhookudp" for motion input using Cemu Hook protocol. Required parameters: -# - "guid": the IP address of the cemu hook server encoded to a hex string. for example 192.168.0.1 = "c0a80001" -# - "port": the port of the cemu hook server -# - "pad": the index of the joystick -# - "motion": the index of the motion sensor of the joystick to bind -motionleft= -motionright= - -[ControlsGeneral] -# To use the debug_pad, prepend debug_pad_ before each button setting above. -# i.e. debug_pad_button_a= - -# Enable debug pad inputs to the guest -# 0 (default): Disabled, 1: Enabled -debug_pad_enabled = - -# Whether to enable or disable vibration -# 0: Disabled, 1 (default): Enabled -vibration_enabled = - -# Whether to enable or disable accurate vibrations -# 0 (default): Disabled, 1: Enabled -enable_accurate_vibrations = - -# Enables controller motion inputs -# 0: Disabled, 1 (default): Enabled -motion_enabled = - -# Defines the udp device's touch screen coordinate system for cemuhookudp devices -# - "min_x", "min_y", "max_x", "max_y" -touch_device= - -# for mapping buttons to touch inputs. -#touch_from_button_map=1 -#touch_from_button_maps_0_name=default -#touch_from_button_maps_0_count=2 -#touch_from_button_maps_0_bind_0=foo -#touch_from_button_maps_0_bind_1=bar -# etc. - -# List of Cemuhook UDP servers, delimited by ','. -# Default: 127.0.0.1:26760 -# Example: 127.0.0.1:26760,123.4.5.67:26761 -udp_input_servers = - -# Enable controlling an axis via a mouse input. -# 0 (default): Off, 1: On -mouse_panning = - -# Set mouse sensitivity. -# Default: 1.0 -mouse_panning_sensitivity = - -# Emulate an analog control stick from keyboard inputs. -# 0 (default): Disabled, 1: Enabled -emulate_analog_keyboard = - -# Enable mouse inputs to the guest -# 0 (default): Disabled, 1: Enabled -mouse_enabled = - -# Enable keyboard inputs to the guest -# 0 (default): Disabled, 1: Enabled -keyboard_enabled = - -[Core] -# Whether to use multi-core for CPU emulation -# 0: Disabled, 1 (default): Enabled -use_multi_core = - -# Enable unsafe extended guest system memory layout (8GB DRAM) -# 0 (default): Disabled, 1: Enabled -use_unsafe_extended_memory_layout = - -[Cpu] -# Adjusts various optimizations. -# Auto-select mode enables choice unsafe optimizations. -# Accurate enables only safe optimizations. -# Unsafe allows any unsafe optimizations. -# 0 (default): Auto-select, 1: Accurate, 2: Enable unsafe optimizations -cpu_accuracy = - -# Allow disabling safe optimizations. -# 0 (default): Disabled, 1: Enabled -cpu_debug_mode = - -# Enable inline page tables optimization (faster guest memory access) -# 0: Disabled, 1 (default): Enabled -cpuopt_page_tables = - -# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps) -# 0: Disabled, 1 (default): Enabled -cpuopt_block_linking = - -# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns) -# 0: Disabled, 1 (default): Enabled -cpuopt_return_stack_buffer = - -# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture) -# 0: Disabled, 1 (default): Enabled -cpuopt_fast_dispatcher = - -# Enable context elimination CPU Optimization (reduce host memory use for guest context) -# 0: Disabled, 1 (default): Enabled -cpuopt_context_elimination = - -# Enable constant propagation CPU optimization (basic IR optimization) -# 0: Disabled, 1 (default): Enabled -cpuopt_const_prop = - -# Enable miscellaneous CPU optimizations (basic IR optimization) -# 0: Disabled, 1 (default): Enabled -cpuopt_misc_ir = - -# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access) -# 0: Disabled, 1 (default): Enabled -cpuopt_reduce_misalign_checks = - -# Enable Host MMU Emulation (faster guest memory access) -# 0: Disabled, 1 (default): Enabled -cpuopt_fastmem = - -# Enable Host MMU Emulation for exclusive memory instructions (faster guest memory access) -# 0: Disabled, 1 (default): Enabled -cpuopt_fastmem_exclusives = - -# Enable fallback on failure of fastmem of exclusive memory instructions (faster guest memory access) -# 0: Disabled, 1 (default): Enabled -cpuopt_recompile_exclusives = - -# Enable optimization to ignore invalid memory accesses (faster guest memory access) -# 0: Disabled, 1 (default): Enabled -cpuopt_ignore_memory_aborts = - -# Enable unfuse FMA (improve performance on CPUs without FMA) -# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. -# 0: Disabled, 1 (default): Enabled -cpuopt_unsafe_unfuse_fma = - -# Enable faster FRSQRTE and FRECPE -# Only enabled if cpu_accuracy is set to Unsafe. -# 0: Disabled, 1 (default): Enabled -cpuopt_unsafe_reduce_fp_error = - -# Enable faster ASIMD instructions (32 bits only) -# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. -# 0: Disabled, 1 (default): Enabled -cpuopt_unsafe_ignore_standard_fpcr = - -# Enable inaccurate NaN handling -# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. -# 0: Disabled, 1 (default): Enabled -cpuopt_unsafe_inaccurate_nan = - -# Disable address space checks (64 bits only) -# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. -# 0: Disabled, 1 (default): Enabled -cpuopt_unsafe_fastmem_check = - -# Enable faster exclusive instructions -# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select. -# 0: Disabled, 1 (default): Enabled -cpuopt_unsafe_ignore_global_monitor = - -[Renderer] -# Which backend API to use. -# 0: OpenGL (unsupported), 1 (default): Vulkan, 2: Null -backend = - -# Whether to enable asynchronous presentation (Vulkan only) -# 0: Off, 1 (default): On -async_presentation = - -# Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied). -# 0 (default): Disabled, 1: Enabled -force_max_clock = - -# Enable graphics API debugging mode. -# 0 (default): Disabled, 1: Enabled -debug = - -# Enable shader feedback. -# 0 (default): Disabled, 1: Enabled -renderer_shader_feedback = - -# Enable Nsight Aftermath crash dumps -# 0 (default): Disabled, 1: Enabled -nsight_aftermath = - -# Disable shader loop safety checks, executing the shader without loop logic changes -# 0 (default): Disabled, 1: Enabled -disable_shader_loop_safety_checks = - -# Which Vulkan physical device to use (defaults to 0) -vulkan_device = - -# 0: 0.5x (360p/540p) [EXPERIMENTAL] -# 1: 0.75x (540p/810p) [EXPERIMENTAL] -# 2 (default): 1x (720p/1080p) -# 3: 2x (1440p/2160p) -# 4: 3x (2160p/3240p) -# 5: 4x (2880p/4320p) -# 6: 5x (3600p/5400p) -# 7: 6x (4320p/6480p) -resolution_setup = - -# Pixel filter to use when up- or down-sampling rendered frames. -# 0: Nearest Neighbor -# 1 (default): Bilinear -# 2: Bicubic -# 3: Gaussian -# 4: ScaleForce -# 5: AMD FidelityFX™️ Super Resolution [Vulkan Only] -scaling_filter = - -# Anti-Aliasing (AA) -# 0 (default): None, 1: FXAA -anti_aliasing = - -# Whether to use fullscreen or borderless window mode -# 0 (Windows default): Borderless window, 1 (All other default): Exclusive fullscreen -fullscreen_mode = - -# Aspect ratio -# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Force 16:10, 4: Stretch to Window -aspect_ratio = - -# Anisotropic filtering -# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x -max_anisotropy = - -# Whether to enable VSync or not. -# OpenGL: Values other than 0 enable VSync -# Vulkan: FIFO is selected if the requested mode is not supported by the driver. -# FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. -# FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down. -# Mailbox can have lower latency than FIFO and does not tear but may drop frames. -# Immediate (no synchronization) just presents whatever is available and can exhibit tearing. -# 0: Immediate (Off), 1 (Default): Mailbox (On), 2: FIFO, 3: FIFO Relaxed -use_vsync = - -# Selects the OpenGL shader backend. NV_gpu_program5 is required for GLASM. If NV_gpu_program5 is -# not available and GLASM is selected, GLSL will be used. -# 0: GLSL, 1 (default): GLASM, 2: SPIR-V -shader_backend = - -# Whether to allow asynchronous shader building. -# 0 (default): Off, 1: On -use_asynchronous_shaders = - -# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory. -# 0 (default): Off, 1: On -use_reactive_flushing = - -# NVDEC emulation. -# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding -nvdec_emulation = - -# Accelerate ASTC texture decoding. -# 0 (default): Off, 1: On -accelerate_astc = - -# Turns on the speed limiter, which will limit the emulation speed to the desired speed limit value -# 0: Off, 1: On (default) -use_speed_limit = - -# Limits the speed of the game to run no faster than this value as a percentage of target speed -# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default) -speed_limit = - -# Whether to use disk based shader cache -# 0: Off, 1 (default): On -use_disk_shader_cache = - -# Which gpu accuracy level to use -# 0 (default): Normal, 1: High, 2: Extreme (Very slow) -gpu_accuracy = - -# Whether to use asynchronous GPU emulation -# 0 : Off (slow), 1 (default): On (fast) -use_asynchronous_gpu_emulation = - -# Inform the guest that GPU operations completed more quickly than they did. -# 0: Off, 1 (default): On -use_fast_gpu_time = - -# Force unmodified buffers to be flushed, which can cost performance. -# 0: Off (default), 1: On -use_pessimistic_flushes = - -# Whether to use garbage collection or not for GPU caches. -# 0 (default): Off, 1: On -use_caches_gc = - -# The clear color for the renderer. What shows up on the sides of the bottom screen. -# Must be in range of 0-255. Defaults to 0 for all. -bg_red = -bg_blue = -bg_green = - -[Audio] -# Which audio output engine to use. -# auto (default): Auto-select -# cubeb: Cubeb audio engine (if available) -# sdl2: SDL2 audio engine (if available) -# null: No audio output -output_engine = - -# Which audio device to use. -# auto (default): Auto-select -output_device = - -# Output volume. -# 100 (default): 100%, 0; mute -volume = - -[Data Storage] -# Whether to create a virtual SD card. -# 1: Yes, 0 (default): No -use_virtual_sd = - -# Whether or not to enable gamecard emulation -# 1: Yes, 0 (default): No -gamecard_inserted = - -# Whether or not the gamecard should be emulated as the current game -# If 'gamecard_inserted' is 0 this setting is irrelevant -# 1: Yes, 0 (default): No -gamecard_current_game = - -# Path to an XCI file to use as the gamecard -# If 'gamecard_inserted' is 0 this setting is irrelevant -# If 'gamecard_current_game' is 1 this setting is irrelevant -gamecard_path = - -[System] -# Whether the system is docked -# 1: Yes, 0 (default): No -use_docked_mode = - -# Sets the seed for the RNG generator built into the switch -# rng_seed will be ignored and randomly generated if rng_seed_enabled is false -rng_seed_enabled = -rng_seed = - -# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service -# This will auto-increment, with the time set being the time the game is started -# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used -custom_rtc_enabled = -custom_rtc = - -# Sets the systems language index -# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese, -# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French, -# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese, 17: Brazilian Portuguese -language_index = - -# The system region that yuzu will use during emulation -# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan -region_index = - -# The system time zone that yuzu will use during emulation -# 0: Auto-select (default), 1: Default (system archive value), Others: Index for specified time zone -time_zone_index = - -# Sets the sound output mode. -# 0: Mono, 1 (default): Stereo, 2: Surround -sound_index = - -[Miscellaneous] -# A filter which removes logs below a certain logging level. -# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical -log_filter = *:Trace - -# Use developer keys -# 0 (default): Disabled, 1: Enabled -use_dev_keys = - -[Debugging] -# Record frame time data, can be found in the log directory. Boolean value -record_frame_times = -# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them -dump_exefs=false -# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them -dump_nso=false -# Determines whether or not yuzu will save the filesystem access log. -enable_fs_access_log=false -# Enables verbose reporting services -reporting_services = -# Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode -# false: Retail/Normal Mode (default), true: Kiosk Mode -quest_flag = -# Determines whether debug asserts should be enabled, which will throw an exception on asserts. -# false: Disabled (default), true: Enabled -use_debug_asserts = -# Determines whether unimplemented HLE service calls should be automatically stubbed. -# false: Disabled (default), true: Enabled -use_auto_stub = -# Enables/Disables the macro JIT compiler -disable_macro_jit=false -# Determines whether to enable the GDB stub and wait for the debugger to attach before running. -# false: Disabled (default), true: Enabled -use_gdbstub=false -# The port to use for the GDB server, if it is enabled. -gdbstub_port=6543 - -[WebService] -# Whether or not to enable telemetry -# 0: No, 1 (default): Yes -enable_telemetry = -# URL for Web API -web_api_url = -# Username and token for yuzu Web Service -# See https://profile.yuzu-emu.org/ for more info -yuzu_username = -yuzu_token = - -[Network] -# Name of the network interface device to use with yuzu LAN play. -# e.g. On *nix: 'enp7s0', 'wlp6s0u1u3u3', 'lo' -# e.g. On Windows: 'Ethernet', 'Wi-Fi' -network_interface = - -[AddOns] -# Used to disable add-ons -# List of title IDs of games that will have add-ons disabled (separated by '|'): -title_ids = -# For each title ID, have a key/value pair called `disabled_` equal to the names of the add-ons to disable (sep. by '|') -# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and - on Super Mario Odyssey -)"; -} // namespace DefaultINI - - -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include - -#include "common/settings.h" - -class INIReader; - -class Config { - bool LoadINI(const std::string& default_contents = "", bool retry = true); - -public: - enum class ConfigType { - GlobalConfig, - PerGameConfig, - InputProfile, +#include +#include "common/common_types.h" +#include "common/settings_setting.h" +#include "common/settings_enums.h" + +namespace IOSSettings { + struct Values { + Settings::Linkage linkage; + Settings::Setting touchscreen{linkage, true, "touchscreen", Settings::Category::Overlay}; }; - explicit Config(const std::string& config_name = "config", - ConfigType config_type = ConfigType::GlobalConfig); - ~Config(); + extern Values values; - void Initialize(const std::string& config_name); - -private: - /** - * Applies a value read from the config to a Setting. - * - * @param group The name of the INI group - * @param setting The yuzu setting to modify - */ - template - void ReadSetting(const std::string& group, YuzuSettings::Setting& setting); - - void ReadValues(); - - const ConfigType type; - std::unique_ptr config; - std::string config_loc; - const bool global; -}; +} // namespace IOSSettings diff --git a/src/ios/Eden/Wrapper/Config/Config.mm b/src/ios/Eden/Wrapper/Config/Config.mm index 3460ee08de..0a01d234df 100644 --- a/src/ios/Eden/Wrapper/Config/Config.mm +++ b/src/ios/Eden/Wrapper/Config/Config.mm @@ -1,330 +1,8 @@ -// -// Config.m - Sudachi -// Created by Jarrod Norwell on 13/3/2024. -// -#import "Config.h" +#include "Config.h" -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +namespace IOSSettings { -#include -#include -#include +Values values; -#include -#include "common/fs/file.h" -#include "common/fs/fs.h" -#include "common/fs/path_util.h" -#include "common/logging/log.h" -#include "common/settings.h" -#include "common/settings_enums.h" -#include "core/hle/service/acc/profile_manager.h" -#include "input_common/main.h" - -namespace FS = Common::FS; - -Config::Config(const std::string& config_name, ConfigType config_type) - : type(config_type), global{config_type == ConfigType::GlobalConfig} { - Initialize(config_name); -} - -Config::~Config() = default; - -bool Config::LoadINI(const std::string& default_contents, bool retry) { - void(FS::CreateParentDir(config_loc)); - config = std::make_unique(FS::PathToUTF8String(config_loc)); - const auto config_loc_str = FS::PathToUTF8String(config_loc); - if (config->ParseError() < 0) { - if (retry) { - LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", - config_loc_str); - - void(FS::CreateParentDir(config_loc)); - void(FS::WriteStringToFile(config_loc, FS::FileType::TextFile, default_contents)); - - config = std::make_unique(config_loc_str); - - return LoadINI(default_contents, false); - } - LOG_ERROR(Config, "Failed."); - return false; - } - LOG_INFO(Config, "Successfully loaded {}", config_loc_str); - return true; -} - -template <> -void Config::ReadSetting(const std::string& group, YuzuSettings::Setting& setting) { - std::string setting_value = config->Get(group, setting.GetLabel(), setting.GetDefault()); - if (setting_value.empty()) { - setting_value = setting.GetDefault(); - } - setting = std::move(setting_value); -} - -template <> -void Config::ReadSetting(const std::string& group, YuzuSettings::Setting& setting) { - setting = config->GetBoolean(group, setting.GetLabel(), setting.GetDefault()); -} - -template -void Config::ReadSetting(const std::string& group, YuzuSettings::Setting& setting) { - setting = static_cast( - config->GetInteger(group, setting.GetLabel(), static_cast(setting.GetDefault()))); -} - -void Config::ReadValues() { - ReadSetting("ControlsGeneral", YuzuSettings::values.mouse_enabled); - ReadSetting("ControlsGeneral", YuzuSettings::values.touch_device); - ReadSetting("ControlsGeneral", YuzuSettings::values.keyboard_enabled); - ReadSetting("ControlsGeneral", YuzuSettings::values.debug_pad_enabled); - ReadSetting("ControlsGeneral", YuzuSettings::values.vibration_enabled); - ReadSetting("ControlsGeneral", YuzuSettings::values.enable_accurate_vibrations); - ReadSetting("ControlsGeneral", YuzuSettings::values.motion_enabled); - YuzuSettings::values.touchscreen.enabled = - config->GetBoolean("ControlsGeneral", "touch_enabled", true); - YuzuSettings::values.touchscreen.rotation_angle = - config->GetInteger("ControlsGeneral", "touch_angle", 0); - YuzuSettings::values.touchscreen.diameter_x = - config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); - YuzuSettings::values.touchscreen.diameter_y = - config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); - - int num_touch_from_button_maps = - config->GetInteger("ControlsGeneral", "touch_from_button_map", 0); - if (num_touch_from_button_maps > 0) { - for (int i = 0; i < num_touch_from_button_maps; ++i) { - YuzuSettings::TouchFromButtonMap map; - map.name = config->Get("ControlsGeneral", - std::string("touch_from_button_maps_") + std::to_string(i) + - std::string("_name"), - "default"); - const int num_touch_maps = config->GetInteger( - "ControlsGeneral", - std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"), - 0); - map.buttons.reserve(num_touch_maps); - - for (int j = 0; j < num_touch_maps; ++j) { - std::string touch_mapping = - config->Get("ControlsGeneral", - std::string("touch_from_button_maps_") + std::to_string(i) + - std::string("_bind_") + std::to_string(j), - ""); - map.buttons.emplace_back(std::move(touch_mapping)); - } - - YuzuSettings::values.touch_from_button_maps.emplace_back(std::move(map)); - } - } else { - YuzuSettings::values.touch_from_button_maps.emplace_back( - YuzuSettings::TouchFromButtonMap{"default", {}}); - num_touch_from_button_maps = 1; - } - YuzuSettings::values.touch_from_button_map_index = std::clamp( - YuzuSettings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1); - - ReadSetting("ControlsGeneral", YuzuSettings::values.udp_input_servers); - - // Data Storage - ReadSetting("Data Storage", YuzuSettings::values.use_virtual_sd); - FS::SetYuzuPath(FS::YuzuPath::NANDDir, - config->Get("Data Storage", "nand_directory", - FS::GetYuzuPathString(FS::YuzuPath::NANDDir))); - FS::SetYuzuPath(FS::YuzuPath::SDMCDir, - config->Get("Data Storage", "sdmc_directory", - FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))); - FS::SetYuzuPath(FS::YuzuPath::LoadDir, - config->Get("Data Storage", "load_directory", - FS::GetYuzuPathString(FS::YuzuPath::LoadDir))); - FS::SetYuzuPath(FS::YuzuPath::DumpDir, - config->Get("Data Storage", "dump_directory", - FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); - ReadSetting("Data Storage", YuzuSettings::values.gamecard_inserted); - ReadSetting("Data Storage", YuzuSettings::values.gamecard_current_game); - ReadSetting("Data Storage", YuzuSettings::values.gamecard_path); - - // System - ReadSetting("System", YuzuSettings::values.current_user); - YuzuSettings::values.current_user = std::clamp(YuzuSettings::values.current_user.GetValue(), 0, - Service::Account::MAX_USERS - 1); - - // Enable docked mode by default on iOS - YuzuSettings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false) - ? YuzuSettings::ConsoleMode::Docked - : YuzuSettings::ConsoleMode::Handheld); - - const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false); - if (rng_seed_enabled) { - YuzuSettings::values.rng_seed.SetValue(config->GetInteger("System", "rng_seed", 0)); - } else { - YuzuSettings::values.rng_seed.SetValue(0); - } - YuzuSettings::values.rng_seed_enabled.SetValue(rng_seed_enabled); - - const auto custom_rtc_enabled = config->GetBoolean("System", "custom_rtc_enabled", false); - if (custom_rtc_enabled) { - YuzuSettings::values.custom_rtc = config->GetInteger("System", "custom_rtc", 0); - } else { - YuzuSettings::values.custom_rtc = 0; - } - YuzuSettings::values.custom_rtc_enabled = custom_rtc_enabled; - - ReadSetting("System", YuzuSettings::values.language_index); - ReadSetting("System", YuzuSettings::values.region_index); - ReadSetting("System", YuzuSettings::values.time_zone_index); - ReadSetting("System", YuzuSettings::values.sound_index); - - // Core - ReadSetting("Core", YuzuSettings::values.use_multi_core); - ReadSetting("Core", YuzuSettings::values.memory_layout_mode); - - // Cpu - ReadSetting("Cpu", YuzuSettings::values.cpu_accuracy); - ReadSetting("Cpu", YuzuSettings::values.cpu_debug_mode); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_page_tables); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_block_linking); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_return_stack_buffer); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_fast_dispatcher); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_context_elimination); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_const_prop); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_misc_ir); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_reduce_misalign_checks); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_fastmem); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_fastmem_exclusives); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_recompile_exclusives); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_ignore_memory_aborts); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_unfuse_fma); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_reduce_fp_error); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_ignore_standard_fpcr); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_inaccurate_nan); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_fastmem_check); - ReadSetting("Cpu", YuzuSettings::values.cpuopt_unsafe_ignore_global_monitor); - - // Renderer - ReadSetting("Renderer", YuzuSettings::values.renderer_backend); - ReadSetting("Renderer", YuzuSettings::values.renderer_debug); - ReadSetting("Renderer", YuzuSettings::values.renderer_shader_feedback); - ReadSetting("Renderer", YuzuSettings::values.enable_nsight_aftermath); - ReadSetting("Renderer", YuzuSettings::values.disable_shader_loop_safety_checks); - ReadSetting("Renderer", YuzuSettings::values.vulkan_device); - - ReadSetting("Renderer", YuzuSettings::values.resolution_setup); - ReadSetting("Renderer", YuzuSettings::values.scaling_filter); - ReadSetting("Renderer", YuzuSettings::values.fsr_sharpening_slider); - ReadSetting("Renderer", YuzuSettings::values.anti_aliasing); - ReadSetting("Renderer", YuzuSettings::values.fullscreen_mode); - ReadSetting("Renderer", YuzuSettings::values.aspect_ratio); - ReadSetting("Renderer", YuzuSettings::values.max_anisotropy); - ReadSetting("Renderer", YuzuSettings::values.use_speed_limit); - ReadSetting("Renderer", YuzuSettings::values.speed_limit); - ReadSetting("Renderer", YuzuSettings::values.use_disk_shader_cache); - ReadSetting("Renderer", YuzuSettings::values.use_asynchronous_gpu_emulation); - ReadSetting("Renderer", YuzuSettings::values.vsync_mode); - ReadSetting("Renderer", YuzuSettings::values.shader_backend); - ReadSetting("Renderer", YuzuSettings::values.use_asynchronous_shaders); - ReadSetting("Renderer", YuzuSettings::values.nvdec_emulation); - ReadSetting("Renderer", YuzuSettings::values.use_fast_gpu_time); - ReadSetting("Renderer", YuzuSettings::values.use_vulkan_driver_pipeline_cache); - - ReadSetting("Renderer", YuzuSettings::values.bg_red); - ReadSetting("Renderer", YuzuSettings::values.bg_green); - ReadSetting("Renderer", YuzuSettings::values.bg_blue); - - // Use GPU accuracy normal by default on Android - YuzuSettings::values.gpu_accuracy = static_cast(config->GetInteger( - "Renderer", "gpu_accuracy", static_cast(YuzuSettings::GpuAccuracy::Normal))); - - // Use GPU default anisotropic filtering on Android - YuzuSettings::values.max_anisotropy = - static_cast(config->GetInteger("Renderer", "max_anisotropy", 1)); - - // Disable ASTC compute by default on iOS - YuzuSettings::values.accelerate_astc.SetValue( - config->GetBoolean("Renderer", "accelerate_astc", false) ? YuzuSettings::AstcDecodeMode::Gpu - : YuzuSettings::AstcDecodeMode::Cpu); - - // Enable asynchronous presentation by default on Android - YuzuSettings::values.async_presentation = - config->GetBoolean("Renderer", "async_presentation", true); - - // Disable force_max_clock by default on Android - YuzuSettings::values.renderer_force_max_clock = - config->GetBoolean("Renderer", "force_max_clock", false); - - // Disable use_reactive_flushing by default on Android - YuzuSettings::values.use_reactive_flushing = - config->GetBoolean("Renderer", "use_reactive_flushing", false); - - // Audio - ReadSetting("Audio", YuzuSettings::values.sink_id); - ReadSetting("Audio", YuzuSettings::values.audio_output_device_id); - ReadSetting("Audio", YuzuSettings::values.volume); - - // Miscellaneous - // log_filter has a different default here than from common - YuzuSettings::values.log_filter = "*:Info"; - ReadSetting("Miscellaneous", YuzuSettings::values.use_dev_keys); - - // Debugging - YuzuSettings::values.record_frame_times = - config->GetBoolean("Debugging", "record_frame_times", false); - ReadSetting("Debugging", YuzuSettings::values.dump_exefs); - ReadSetting("Debugging", YuzuSettings::values.dump_nso); - ReadSetting("Debugging", YuzuSettings::values.enable_fs_access_log); - ReadSetting("Debugging", YuzuSettings::values.reporting_services); - ReadSetting("Debugging", YuzuSettings::values.quest_flag); - ReadSetting("Debugging", YuzuSettings::values.use_debug_asserts); - ReadSetting("Debugging", YuzuSettings::values.use_auto_stub); - ReadSetting("Debugging", YuzuSettings::values.disable_macro_jit); - ReadSetting("Debugging", YuzuSettings::values.disable_macro_hle); - ReadSetting("Debugging", YuzuSettings::values.use_gdbstub); - ReadSetting("Debugging", YuzuSettings::values.gdbstub_port); - - const auto title_list = config->Get("AddOns", "title_ids", ""); - std::stringstream ss(title_list); - std::string line; - while (std::getline(ss, line, '|')) { - const auto title_id = std::strtoul(line.c_str(), nullptr, 16); - const auto disabled_list = config->Get("AddOns", "disabled_" + line, ""); - - std::stringstream inner_ss(disabled_list); - std::string inner_line; - std::vector out; - while (std::getline(inner_ss, inner_line, '|')) { - out.push_back(inner_line); - } - - YuzuSettings::values.disabled_addons.insert_or_assign(title_id, out); - } - - // Web Service - ReadSetting("WebService", YuzuSettings::values.enable_telemetry); - ReadSetting("WebService", YuzuSettings::values.web_api_url); - ReadSetting("WebService", YuzuSettings::values.yuzu_username); - ReadSetting("WebService", YuzuSettings::values.yuzu_token); - - // Network - ReadSetting("Network", YuzuSettings::values.network_interface); -} - -void Config::Initialize(const std::string& config_name) { - const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir); - const auto config_file = fmt::format("{}.ini", config_name); - - switch (type) { - case ConfigType::GlobalConfig: - config_loc = FS::PathToUTF8String(fs_config_loc / config_file); - break; - case ConfigType::PerGameConfig: - config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file)); - break; - case ConfigType::InputProfile: - config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file); - LoadINI(DefaultINI::android_config_file); - return; - } - LoadINI(DefaultINI::android_config_file); - ReadValues(); -} +} // namespace IOSSettings diff --git a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm index 428244fea1..857002adfc 100644 --- a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm +++ b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm @@ -4,7 +4,6 @@ // #import - #import "DirectoryManager.h" NSURL *DocumentsDirectory() { From 9e16918f3c74c0759ce4640044e400608cd77cf9 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 19 Feb 2026 03:53:16 +0000 Subject: [PATCH 03/74] loicense --- src/ios/Eden/AppUI-Bridging-Header.h | 3 +++ src/ios/Eden/AppUI.swift | 3 +++ .../AppUIGameInformation/AppUIGameInformation.h | 3 +++ .../AppUIGameInformation/AppUIGameInformation.mm | 3 +++ src/ios/Eden/Wrapper/AppUIObjC.h | 3 +++ src/ios/Eden/Wrapper/AppUIObjC.mm | 15 +++++++++------ src/ios/Eden/Wrapper/Config/Config.mm | 3 +++ .../Wrapper/DirectoryManager/DirectoryManager.h | 3 +++ .../Wrapper/DirectoryManager/DirectoryManager.mm | 3 +++ .../Wrapper/EmulationSession/EmulationSession.h | 3 +++ .../Wrapper/EmulationSession/EmulationSession.mm | 15 +++++++++------ .../Wrapper/EmulationWindow/EmulationWindow.h | 3 +++ .../Wrapper/EmulationWindow/EmulationWindow.mm | 3 +++ 13 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/ios/Eden/AppUI-Bridging-Header.h b/src/ios/Eden/AppUI-Bridging-Header.h index a75f43550d..091fe7117f 100644 --- a/src/ios/Eden/AppUI-Bridging-Header.h +++ b/src/ios/Eden/AppUI-Bridging-Header.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // // AppUI-Bridging-Header.h - Sudachi // Created by Jarrod Norwell on 4/3/2024. diff --git a/src/ios/Eden/AppUI.swift b/src/ios/Eden/AppUI.swift index 6bd4544b0d..0dd7ac4dd8 100644 --- a/src/ios/Eden/AppUI.swift +++ b/src/ios/Eden/AppUI.swift @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // // AppUI.swift - Sudachi // Created by Jarrod Norwell on 4/3/2024. diff --git a/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h index 5040775ad0..ef8537faee 100644 --- a/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h +++ b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // // AppUIGameInformation.h - Sudachi // Created by Jarrod Norwell on 1/20/24. diff --git a/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm index ef65c723f8..33ff14a875 100644 --- a/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm +++ b/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + // // AppUIGameInformation.mm - Sudachi // Created by Jarrod Norwell on 1/20/24. diff --git a/src/ios/Eden/Wrapper/AppUIObjC.h b/src/ios/Eden/Wrapper/AppUIObjC.h index d3594d74d1..1a79babf4f 100644 --- a/src/ios/Eden/Wrapper/AppUIObjC.h +++ b/src/ios/Eden/Wrapper/AppUIObjC.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // // AppUIObjC.h - Sudachi // Created by Jarrod Norwell on 1/8/24. diff --git a/src/ios/Eden/Wrapper/AppUIObjC.mm b/src/ios/Eden/Wrapper/AppUIObjC.mm index 065ce6d046..81070bb3fb 100644 --- a/src/ios/Eden/Wrapper/AppUIObjC.mm +++ b/src/ios/Eden/Wrapper/AppUIObjC.mm @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // // AppUIObjC.mm - Sudachi // Created by Jarrod Norwell on 1/8/24. @@ -79,12 +82,12 @@ EmulationSession::GetInstance().InitializeGpuDriver(); - YuzuSettings::values.dump_shaders.SetValue(true); - YuzuSettings::values.use_asynchronous_shaders.SetValue(true); - // YuzuSettings::values.astc_recompression.SetValue(YuzuSettings::AstcRecompression::Bc3); - YuzuSettings::values.shader_backend.SetValue(YuzuSettings::ShaderBackend::SpirV); - // YuzuSettings::values.resolution_setup.SetValue(YuzuSettings::ResolutionSetup::Res1X); - // YuzuSettings::values.scaling_filter.SetValue(YuzuSettings::ScalingFilter::Bilinear); + Settings::values.dump_shaders.SetValue(true); + Settings::values.use_asynchronous_shaders.SetValue(true); + // Settings::values.astc_recompression.SetValue(Settings::AstcRecompression::Bc3); + Settings::values.shader_backend.SetValue(Settings::ShaderBackend::SpirV); + // Settings::values.resolution_setup.SetValue(Settings::ResolutionSetup::Res1X); + // Settings::values.scaling_filter.SetValue(Settings::ScalingFilter::Bilinear); } return self; } diff --git a/src/ios/Eden/Wrapper/Config/Config.mm b/src/ios/Eden/Wrapper/Config/Config.mm index 0a01d234df..5b063d79f9 100644 --- a/src/ios/Eden/Wrapper/Config/Config.mm +++ b/src/ios/Eden/Wrapper/Config/Config.mm @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + #include "Config.h" diff --git a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h index 3965b6c2bb..0fc74a4a93 100644 --- a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h +++ b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // // DirectoryManager.h - Sudachi // Created by Jarrod Norwell on 1/18/24. diff --git a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm index 857002adfc..ad8b37c932 100644 --- a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm +++ b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // // DirectoryManager.mm - Sudachi // Created by Jarrod Norwell on 1/18/24. diff --git a/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h b/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h index 291d414b37..c3692eede6 100644 --- a/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h +++ b/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // // EmulationSession.h - Sudachi // Created by Jarrod Norwell on 1/20/24. diff --git a/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm b/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm index 18793ca9fe..2575f053e2 100644 --- a/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm +++ b/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // // EmulationSession.m - Sudachi // Created by Jarrod Norwell on 1/20/24. @@ -204,7 +207,7 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string // Initialize system. m_system.SetShuttingDown(false); m_system.ApplySettings(); - YuzuSettings::LogSettings(); + Settings::LogSettings(); m_system.HIDCore().ReloadInputDevices(); m_system.SetFrontendAppletSet({ nullptr, // Amiibo Settings @@ -238,7 +241,7 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string m_system.GetCpuManager().OnGpuReady(); m_system.RegisterExitCallback([&] { HaltEmulation(); }); - if (YuzuSettings::values.use_disk_shader_cache.GetValue()) { + if (Settings::values.use_disk_shader_cache.GetValue()) { m_system.Renderer().ReadRasterizer()->LoadDiskResources( m_system.GetApplicationProcessProgramID(), std::stop_token{}, [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); @@ -265,7 +268,7 @@ Core::SystemResultStatus EmulationSession::BootOS() { // Initialize system. m_system.SetShuttingDown(false); m_system.ApplySettings(); - YuzuSettings::LogSettings(); + Settings::LogSettings(); m_system.HIDCore().ReloadInputDevices(); m_system.SetFrontendAppletSet({ nullptr, // Amiibo Settings @@ -305,7 +308,7 @@ Core::SystemResultStatus EmulationSession::BootOS() { m_system.GetCpuManager().OnGpuReady(); m_system.RegisterExitCallback([&] { HaltEmulation(); }); - if (YuzuSettings::values.use_disk_shader_cache.GetValue()) { + if (Settings::values.use_disk_shader_cache.GetValue()) { m_system.Renderer().ReadRasterizer()->LoadDiskResources( m_system.GetApplicationProcessProgramID(), std::stop_token{}, [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); @@ -377,7 +380,7 @@ void EmulationSession::RunEmulation() { } // Load the disk shader cache. - if (YuzuSettings::values.use_disk_shader_cache.GetValue()) { + if (Settings::values.use_disk_shader_cache.GetValue()) { LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); m_system.Renderer().ReadRasterizer()->LoadDiskResources( m_system.GetApplicationProcessProgramID(), std::stop_token{}, LoadDiskCacheProgress); @@ -484,7 +487,7 @@ bool EmulationSession::IsHandheldOnly() { return false; } - return !YuzuSettings::IsDockedMode(); + return !Settings::IsDockedMode(); } void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) { diff --git a/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h b/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h index d099c88c55..4d8b0b888f 100644 --- a/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h +++ b/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // // EmulationWindow.h - Sudachi // Created by Jarrod Norwell on 1/18/24. diff --git a/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm b/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm index a0e388e751..a82c5b5a7f 100644 --- a/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm +++ b/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // // EmulationWindow.mm - Sudachi // Created by Jarrod Norwell on 1/18/24. From 70620eb63581059d5f9d768cedf8eb7fe3f40122 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 19 Feb 2026 04:02:03 +0000 Subject: [PATCH 04/74] flatten --- .../AppUIGameInformation.h | 0 .../AppUIGameInformation.mm | 8 +++++--- src/ios/Eden/{Wrapper => }/AppUIObjC.h | 0 src/ios/Eden/{Wrapper => }/AppUIObjC.mm | 4 +++- src/ios/Eden/{Wrapper/Config => }/Config.h | 0 src/ios/Eden/{Wrapper/Config => }/Config.mm | 0 .../EmulationSession => }/EmulationSession.h | 2 +- .../EmulationSession => }/EmulationSession.mm | 0 .../EmulationWindow => }/EmulationWindow.h | 0 .../EmulationWindow => }/EmulationWindow.mm | 10 +++------- .../DirectoryManager/DirectoryManager.h | 13 ------------- .../DirectoryManager/DirectoryManager.mm | 18 ------------------ 12 files changed, 12 insertions(+), 43 deletions(-) rename src/ios/Eden/{Wrapper/AppUIGameInformation => }/AppUIGameInformation.h (100%) rename src/ios/Eden/{Wrapper/AppUIGameInformation => }/AppUIGameInformation.mm (97%) rename src/ios/Eden/{Wrapper => }/AppUIObjC.h (100%) rename src/ios/Eden/{Wrapper => }/AppUIObjC.mm (97%) rename src/ios/Eden/{Wrapper/Config => }/Config.h (100%) rename src/ios/Eden/{Wrapper/Config => }/Config.mm (100%) rename src/ios/Eden/{Wrapper/EmulationSession => }/EmulationSession.h (98%) rename src/ios/Eden/{Wrapper/EmulationSession => }/EmulationSession.mm (100%) rename src/ios/Eden/{Wrapper/EmulationWindow => }/EmulationWindow.h (100%) rename src/ios/Eden/{Wrapper/EmulationWindow => }/EmulationWindow.mm (85%) delete mode 100644 src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h delete mode 100644 src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm diff --git a/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h b/src/ios/Eden/AppUIGameInformation.h similarity index 100% rename from src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.h rename to src/ios/Eden/AppUIGameInformation.h diff --git a/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm b/src/ios/Eden/AppUIGameInformation.mm similarity index 97% rename from src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm rename to src/ios/Eden/AppUIGameInformation.mm index 33ff14a875..3397faa1a7 100644 --- a/src/ios/Eden/Wrapper/AppUIGameInformation/AppUIGameInformation.mm +++ b/src/ios/Eden/AppUIGameInformation.mm @@ -6,9 +6,9 @@ // Created by Jarrod Norwell on 1/20/24. // +#import #import "AppUIGameInformation.h" -#import "../DirectoryManager/DirectoryManager.h" -#import "../EmulationSession/EmulationSession.h" +#import "EmulationSession/EmulationSession.h" #include "common/fs/fs.h" #include "common/fs/path_util.h" @@ -382,7 +382,9 @@ GameMetadata CacheGameMetadata(const std::string& path) { GameMetadata GameMetadata(const std::string& path, bool reload = false) { if (!EmulationSession::GetInstance().IsInitialized()) { - Common::FS::SetAppDirectory(DirectoryManager::AppUIDirectory()); + NSURL *dir_url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject]; + const char *directory_cstr = [[dir_url path] UTF8String]; + Common::FS::SetAppDirectory(directory_cstr); EmulationSession::GetInstance().System().Initialize(); EmulationSession::GetInstance().InitializeSystem(false); diff --git a/src/ios/Eden/Wrapper/AppUIObjC.h b/src/ios/Eden/AppUIObjC.h similarity index 100% rename from src/ios/Eden/Wrapper/AppUIObjC.h rename to src/ios/Eden/AppUIObjC.h diff --git a/src/ios/Eden/Wrapper/AppUIObjC.mm b/src/ios/Eden/AppUIObjC.mm similarity index 97% rename from src/ios/Eden/Wrapper/AppUIObjC.mm rename to src/ios/Eden/AppUIObjC.mm index 81070bb3fb..97cdc894b5 100644 --- a/src/ios/Eden/Wrapper/AppUIObjC.mm +++ b/src/ios/Eden/AppUIObjC.mm @@ -73,8 +73,10 @@ if (self = [super init]) { _gameInformation = [AppUIGameInformation sharedInstance]; + NSURL *dir_url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject]; + const char *directory_cstr = [[dir_url path] UTF8String]; - Common::FS::SetAppDirectory(DirectoryManager::AppUIDirectory()); + Common::FS::SetAppDirectory(directory_cstr); Config{"config", Config::ConfigType::GlobalConfig}; EmulationSession::GetInstance().System().Initialize(); diff --git a/src/ios/Eden/Wrapper/Config/Config.h b/src/ios/Eden/Config.h similarity index 100% rename from src/ios/Eden/Wrapper/Config/Config.h rename to src/ios/Eden/Config.h diff --git a/src/ios/Eden/Wrapper/Config/Config.mm b/src/ios/Eden/Config.mm similarity index 100% rename from src/ios/Eden/Wrapper/Config/Config.mm rename to src/ios/Eden/Config.mm diff --git a/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h b/src/ios/Eden/EmulationSession.h similarity index 98% rename from src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h rename to src/ios/Eden/EmulationSession.h index c3692eede6..56c1930df6 100644 --- a/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.h +++ b/src/ios/Eden/EmulationSession.h @@ -11,7 +11,7 @@ #import #import -#import "../EmulationWindow/EmulationWindow.h" +#import "EmulationWindow/EmulationWindow.h" #include "common/detached_tasks.h" #include "core/core.h" diff --git a/src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm b/src/ios/Eden/EmulationSession.mm similarity index 100% rename from src/ios/Eden/Wrapper/EmulationSession/EmulationSession.mm rename to src/ios/Eden/EmulationSession.mm diff --git a/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h b/src/ios/Eden/EmulationWindow.h similarity index 100% rename from src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.h rename to src/ios/Eden/EmulationWindow.h diff --git a/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm b/src/ios/Eden/EmulationWindow.mm similarity index 85% rename from src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm rename to src/ios/Eden/EmulationWindow.mm index a82c5b5a7f..f5c53309b4 100644 --- a/src/ios/Eden/Wrapper/EmulationWindow/EmulationWindow.mm +++ b/src/ios/Eden/EmulationWindow.mm @@ -7,7 +7,7 @@ // #import "EmulationWindow.h" -#import "../EmulationSession/EmulationSession.h" +#import "EmulationSession/EmulationSession.h" #include @@ -58,9 +58,7 @@ void EmulationWindow::OnGamepadJoystickEvent(int player_index, int stick_id, flo m_input_subsystem->GetVirtualGamepad()->SetStickPosition(player_index, stick_id, x, y); } -void EmulationWindow::OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, - float gyro_y, float gyro_z, float accel_x, - float accel_y, float accel_z) { +void EmulationWindow::OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, float gyro_y, float gyro_z, float accel_x, float accel_y, float accel_z) { m_input_subsystem->GetVirtualGamepad()->SetMotionState(player_index, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z); } @@ -70,9 +68,7 @@ void EmulationWindow::OnFrameDisplayed() { } } -EmulationWindow::EmulationWindow(InputCommon::InputSubsystem* input_subsystem, CA::MetalLayer* surface, CGSize size, - std::shared_ptr driver_library) -: m_input_subsystem{input_subsystem}, m_size{size}, m_driver_library{driver_library} { +EmulationWindow::EmulationWindow(InputCommon::InputSubsystem* input_subsystem, CA::MetalLayer* surface, CGSize size, std::shared_ptr driver_library) : m_input_subsystem{input_subsystem}, m_size{size}, m_driver_library{driver_library} { LOG_INFO(Frontend, "initializing"); if (!surface) { diff --git a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h deleted file mode 100644 index 0fc74a4a93..0000000000 --- a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.h +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// -// DirectoryManager.h - Sudachi -// Created by Jarrod Norwell on 1/18/24. -// - -#pragma once - -namespace DirectoryManager { -const char* AppUIDirectory(void); -} diff --git a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm b/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm deleted file mode 100644 index ad8b37c932..0000000000 --- a/src/ios/Eden/Wrapper/DirectoryManager/DirectoryManager.mm +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// -// DirectoryManager.mm - Sudachi -// Created by Jarrod Norwell on 1/18/24. -// - -#import -#import "DirectoryManager.h" - -NSURL *DocumentsDirectory() { - return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject]; -} - -const char* DirectoryManager::AppUIDirectory(void) { - return [[DocumentsDirectory() path] UTF8String]; -} From a89c8ba5fc9eb60b8de878db638b3b44293fe529 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 19 Feb 2026 04:21:48 +0000 Subject: [PATCH 05/74] flatten + cmake --- src/CMakeLists.txt | 4 +++ src/ios/{Eden => }/AppUI-Bridging-Header.h | 0 src/ios/{Eden => }/AppUI.swift | 0 src/ios/{Eden => }/AppUIGameInformation.h | 0 src/ios/{Eden => }/AppUIGameInformation.mm | 0 src/ios/{Eden => }/AppUIObjC.h | 0 src/ios/{Eden => }/AppUIObjC.mm | 0 src/ios/CMakeLists.txt | 29 ++++++++++++++++++++++ src/ios/{Eden => }/Config.h | 0 src/ios/{Eden => }/Config.mm | 0 src/ios/{Eden => }/EmulationSession.h | 0 src/ios/{Eden => }/EmulationSession.mm | 0 src/ios/{Eden => }/EmulationWindow.h | 0 src/ios/{Eden => }/EmulationWindow.mm | 0 14 files changed, 33 insertions(+) rename src/ios/{Eden => }/AppUI-Bridging-Header.h (100%) rename src/ios/{Eden => }/AppUI.swift (100%) rename src/ios/{Eden => }/AppUIGameInformation.h (100%) rename src/ios/{Eden => }/AppUIGameInformation.mm (100%) rename src/ios/{Eden => }/AppUIObjC.h (100%) rename src/ios/{Eden => }/AppUIObjC.mm (100%) create mode 100644 src/ios/CMakeLists.txt rename src/ios/{Eden => }/Config.h (100%) rename src/ios/{Eden => }/Config.mm (100%) rename src/ios/{Eden => }/EmulationSession.h (100%) rename src/ios/{Eden => }/EmulationSession.mm (100%) rename src/ios/{Eden => }/EmulationWindow.h (100%) rename src/ios/{Eden => }/EmulationWindow.mm (100%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 21affffb6b..c9de48d8c9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -255,4 +255,8 @@ if (ANDROID) target_include_directories(yuzu-android PRIVATE android/app/src/main) endif() +if (IOS) + add_subdirectory(ios) +endif() + include(GenerateDepHashes) diff --git a/src/ios/Eden/AppUI-Bridging-Header.h b/src/ios/AppUI-Bridging-Header.h similarity index 100% rename from src/ios/Eden/AppUI-Bridging-Header.h rename to src/ios/AppUI-Bridging-Header.h diff --git a/src/ios/Eden/AppUI.swift b/src/ios/AppUI.swift similarity index 100% rename from src/ios/Eden/AppUI.swift rename to src/ios/AppUI.swift diff --git a/src/ios/Eden/AppUIGameInformation.h b/src/ios/AppUIGameInformation.h similarity index 100% rename from src/ios/Eden/AppUIGameInformation.h rename to src/ios/AppUIGameInformation.h diff --git a/src/ios/Eden/AppUIGameInformation.mm b/src/ios/AppUIGameInformation.mm similarity index 100% rename from src/ios/Eden/AppUIGameInformation.mm rename to src/ios/AppUIGameInformation.mm diff --git a/src/ios/Eden/AppUIObjC.h b/src/ios/AppUIObjC.h similarity index 100% rename from src/ios/Eden/AppUIObjC.h rename to src/ios/AppUIObjC.h diff --git a/src/ios/Eden/AppUIObjC.mm b/src/ios/AppUIObjC.mm similarity index 100% rename from src/ios/Eden/AppUIObjC.mm rename to src/ios/AppUIObjC.mm diff --git a/src/ios/CMakeLists.txt b/src/ios/CMakeLists.txt new file mode 100644 index 0000000000..73f95b8ee5 --- /dev/null +++ b/src/ios/CMakeLists.txt @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +enable_language(Swift OBJC) + +add_executable(eden-ios + AppUI-Bridging-Header.h + AppUI.swift + AppUIGameInformation.h + AppUIGameInformation.mm + AppUIObjC.h + AppUIObjC.mm + Config.h + Config.mm + EmulationSession.h + EmulationSession.mm + EmulationWindow.h + EmulationWindow.mm +) + +target_link_libraries(eden-ios PRIVATE common core input_common frontend_common video_core glad) +target_link_libraries(eden-ios PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) +target_link_libraries(eden-ios PRIVATE SDL2::SDL2) +create_target_directory_groups(eden-ios) +target_compile_options(eden-ios PRIVATE + -Wno-conversion + -Wno-unused-variable + -Wno-unused-parameter + -Wno-missing-field-initializers) diff --git a/src/ios/Eden/Config.h b/src/ios/Config.h similarity index 100% rename from src/ios/Eden/Config.h rename to src/ios/Config.h diff --git a/src/ios/Eden/Config.mm b/src/ios/Config.mm similarity index 100% rename from src/ios/Eden/Config.mm rename to src/ios/Config.mm diff --git a/src/ios/Eden/EmulationSession.h b/src/ios/EmulationSession.h similarity index 100% rename from src/ios/Eden/EmulationSession.h rename to src/ios/EmulationSession.h diff --git a/src/ios/Eden/EmulationSession.mm b/src/ios/EmulationSession.mm similarity index 100% rename from src/ios/Eden/EmulationSession.mm rename to src/ios/EmulationSession.mm diff --git a/src/ios/Eden/EmulationWindow.h b/src/ios/EmulationWindow.h similarity index 100% rename from src/ios/Eden/EmulationWindow.h rename to src/ios/EmulationWindow.h diff --git a/src/ios/Eden/EmulationWindow.mm b/src/ios/EmulationWindow.mm similarity index 100% rename from src/ios/Eden/EmulationWindow.mm rename to src/ios/EmulationWindow.mm From 56e207210c6c84ac34c1fca454beafdedf42138b Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 19 Feb 2026 04:21:54 +0000 Subject: [PATCH 06/74] license headers --- .ci/license-header.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/license-header.sh b/.ci/license-header.sh index 70a842e01d..9660f5db58 100755 --- a/.ci/license-header.sh +++ b/.ci/license-header.sh @@ -115,7 +115,7 @@ for file in $FILES; do *.cmake|*.sh|*CMakeLists.txt) begin="#" ;; - *.kt*|*.cpp|*.h) + *.kt*|*.cpp|*.h|*.swift|*.mm) begin="//" ;; *) From cfab3594397be953230720ba4544898db31f54a7 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 19 Feb 2026 04:22:20 +0000 Subject: [PATCH 07/74] license --- src/ios/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ios/CMakeLists.txt b/src/ios/CMakeLists.txt index 73f95b8ee5..6ddc7eb109 100644 --- a/src/ios/CMakeLists.txt +++ b/src/ios/CMakeLists.txt @@ -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 enable_language(Swift OBJC) From a751088abf36a2c95ac40e485e9f6d23718a5e78 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 21 Feb 2026 23:39:01 +0000 Subject: [PATCH 08/74] ios toolchain cmake --- .ci/ios/build.sh | 27 + .ci/ios/ios-toolchain.cmake | 1177 +++++++++++++++++++++++++++++++++++ 2 files changed, 1204 insertions(+) create mode 100644 .ci/ios/build.sh create mode 100644 .ci/ios/ios-toolchain.cmake diff --git a/.ci/ios/build.sh b/.ci/ios/build.sh new file mode 100644 index 0000000000..f179a6c929 --- /dev/null +++ b/.ci/ios/build.sh @@ -0,0 +1,27 @@ +#!/bin/sh -ex + +WORK_DIR="$PWD" +if [ -z "$NPROC" ]; then + NPROC="$(nproc)" +fi + +cmake -G Xcode -B build \ + -DCMAKE_TOOLCHAIN_FILE="$WORK_DIR/ios-toolchain.cmake" \ + -DPLATFORM=OS64 \ + -DENABLE_LIBUSB=OFF \ + -DENABLE_UPDATE_CHECKER=OFF \ + -DENABLE_QT=OFF \ + -DENABLE_OPENSSL=OFF \ + -DENABLE_WEB_SERVICE=OFF \ + -DENABLE_CUBEB=OFF \ + -DYUZU_ROOM=OFF \ + -DYUZU_ROOM_STANDALONE=OFF \ + -DYUZU_CMD=OFF \ + -DUSE_DISCORD_PRESENCE=OFF \ + -DYUZU_USE_EXTERNAL_FFMPEG=ON \ + -DYUZU_USE_CPM=ON \ + -DYUZU_USE_EXTERNAL_SDL2=ON \ + -DCPMUTIL_FORCE_BUNDLED=ON \ + -DCMAKE_BUILD_TYPE=Release + +cmake --build build -- -j${NPROC} diff --git a/.ci/ios/ios-toolchain.cmake b/.ci/ios/ios-toolchain.cmake new file mode 100644 index 0000000000..3ee3940a38 --- /dev/null +++ b/.ci/ios/ios-toolchain.cmake @@ -0,0 +1,1177 @@ +# This file is part of the ios-cmake project. It was retrieved from +# https://github.com/leetal/ios-cmake.git, which is a fork of +# https://github.com/gerstrong/ios-cmake.git, which is a fork of +# https://github.com/cristeab/ios-cmake.git, which is a fork of +# https://code.google.com/p/ios-cmake/. Which in turn is based off of +# the Platform/Darwin.cmake and Platform/UnixPaths.cmake files which +# are included with CMake 2.8.4 +# +# The ios-cmake project is licensed under the new BSD license. +# +# Copyright (c) 2014, Bogdan Cristea and LTE Engineering Software, +# Kitware, Inc., Insight Software Consortium. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# This file is based on the Platform/Darwin.cmake and +# Platform/UnixPaths.cmake files which are included with CMake 2.8.4 +# It has been altered for iOS development. +# +# Updated by Alex Stewart (alexs.mac@gmail.com) +# +# ***************************************************************************** +# Now maintained by Alexander Widerberg (widerbergaren [at] gmail.com) +# under the BSD-3-Clause license +# https://github.com/leetal/ios-cmake +# ***************************************************************************** +# +# INFORMATION / HELP +# +############################################################################### +# OPTIONS # +############################################################################### +# +# PLATFORM: (default "OS64") +# OS = Build for iPhoneOS. +# OS64 = Build for arm64 iphoneOS. +# OS64COMBINED = Build for arm64 x86_64 iphoneOS + iphoneOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with "-G Xcode" argument in combination with the "cmake --install" CMake build step) +# SIMULATOR = Build for x86 i386 iphoneOS Simulator. +# SIMULATOR64 = Build for x86_64 iphoneOS Simulator. +# SIMULATORARM64 = Build for arm64 iphoneOS Simulator. +# SIMULATOR64COMBINED = Build for arm64 x86_64 iphoneOS Simulator. Combined into FAT STATIC lib (supported on 3.14+ of CMakewith "-G Xcode" argument ONLY) +# TVOS = Build for arm64 tvOS. +# TVOSCOMBINED = Build for arm64 x86_64 tvOS + tvOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with "-G Xcode" argument in combination with the "cmake --install" CMake build step) +# SIMULATOR_TVOS = Build for x86_64 tvOS Simulator. +# SIMULATORARM64_TVOS = Build for arm64 tvOS Simulator. +# VISIONOSCOMBINED = Build for arm64 visionOS + visionOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with "-G Xcode" argument in combination with the "cmake --install" CMake build step) +# VISIONOS = Build for arm64 visionOS. +# SIMULATOR_VISIONOS = Build for arm64 visionOS Simulator. +# WATCHOS = Build for armv7k arm64_32 for watchOS. +# WATCHOSCOMBINED = Build for armv7k arm64_32 x86_64 watchOS + watchOS Simulator. Combined into FAT STATIC lib (only supported on 3.14+ of CMake with "-G Xcode" argument in combination with the "cmake --install" CMake build step) +# SIMULATOR_WATCHOS = Build for x86_64 for watchOS Simulator. +# SIMULATORARM64_WATCHOS = Build for arm64 for watchOS Simulator. +# SIMULATOR_WATCHOSCOMBINED = Build for arm64 x86_64 for watchOS Simulator. Combined into FAT STATIC lib (supported on 3.14+ of CMakewith "-G Xcode" argument ONLY) +# MAC = Build for x86_64 macOS. +# MAC_ARM64 = Build for Apple Silicon macOS. +# MAC_UNIVERSAL = Combined build for x86_64 and Apple Silicon on macOS. +# MAC_CATALYST = Build for x86_64 macOS with Catalyst support (iOS toolchain on macOS). +# Note: The build argument "MACOSX_DEPLOYMENT_TARGET" can be used to control min-version of macOS +# MAC_CATALYST_ARM64 = Build for Apple Silicon macOS with Catalyst support (iOS toolchain on macOS). +# Note: The build argument "MACOSX_DEPLOYMENT_TARGET" can be used to control min-version of macOS +# MAC_CATALYST_UNIVERSAL = Combined build for x86_64 and Apple Silicon on Catalyst. +# +# CMAKE_OSX_SYSROOT: Path to the SDK to use. By default this is +# automatically determined from PLATFORM and xcodebuild, but +# can also be manually specified (although this should not be required). +# +# CMAKE_DEVELOPER_ROOT: Path to the Developer directory for the platform +# being compiled for. By default, this is automatically determined from +# CMAKE_OSX_SYSROOT, but can also be manually specified (although this should +# not be required). +# +# DEPLOYMENT_TARGET: Minimum SDK version to target. Default 6.0 on watchOS, 13.0 on tvOS+iOS/iPadOS, 11.0 on macOS, 1.0 on visionOS +# +# NAMED_LANGUAGE_SUPPORT: +# ON (default) = Will require "enable_language(OBJC) and/or enable_language(OBJCXX)" for full OBJC|OBJCXX support +# OFF = Will embed the OBJC and OBJCXX flags into the CMAKE_C_FLAGS and CMAKE_CXX_FLAGS (legacy behavior, CMake version < 3.16) +# +# ENABLE_BITCODE: (ON|OFF) Enables or disables bitcode support. Default OFF +# +# ENABLE_ARC: (ON|OFF) Enables or disables ARC support. Default ON (ARC enabled by default) +# +# ENABLE_VISIBILITY: (ON|OFF) Enables or disables symbol visibility support. Default OFF (visibility hidden by default) +# +# ENABLE_STRICT_TRY_COMPILE: (ON|OFF) Enables or disables strict try_compile() on all Check* directives (will run linker +# to actually check if linking is possible). Default OFF (will set CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY) +# +# ARCHS: (armv7 armv7s armv7k arm64 arm64_32 i386 x86_64) If specified, will override the default architectures for the given PLATFORM +# OS = armv7 armv7s arm64 (if applicable) +# OS64 = arm64 (if applicable) +# SIMULATOR = i386 +# SIMULATOR64 = x86_64 +# SIMULATORARM64 = arm64 +# TVOS = arm64 +# SIMULATOR_TVOS = x86_64 (i386 has since long been deprecated) +# SIMULATORARM64_TVOS = arm64 +# WATCHOS = armv7k arm64_32 (if applicable) +# SIMULATOR_WATCHOS = x86_64 (i386 has since long been deprecated) +# SIMULATORARM64_WATCHOS = arm64 +# MAC = x86_64 +# MAC_ARM64 = arm64 +# MAC_UNIVERSAL = x86_64 arm64 +# MAC_CATALYST = x86_64 +# MAC_CATALYST_ARM64 = arm64 +# MAC_CATALYST_UNIVERSAL = x86_64 arm64 +# +# NOTE: When manually specifying ARCHS, put a semi-colon between the entries. E.g., -DARCHS="armv7;arm64" +# +############################################################################### +# END OPTIONS # +############################################################################### +# +# This toolchain defines the following properties (available via get_property()) for use externally: +# +# PLATFORM: The currently targeted platform. +# XCODE_VERSION: Version number (not including Build version) of Xcode detected. +# SDK_VERSION: Version of SDK being used. +# OSX_ARCHITECTURES: Architectures being compiled for (generated from PLATFORM). +# APPLE_TARGET_TRIPLE: Used by autoconf build systems. NOTE: If "ARCHS" is overridden, this will *NOT* be set! +# +# This toolchain defines the following macros for use externally: +# +# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE XCODE_VARIANT) +# A convenience macro for setting xcode specific properties on targets. +# Available variants are: All, Release, RelWithDebInfo, Debug, MinSizeRel +# example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET "3.1" "all"). +# +# find_host_package (PROGRAM ARGS) +# A macro used to find executable programs on the host system, not within the +# environment. Thanks to the android-cmake project for providing the +# command. +# + +cmake_minimum_required(VERSION 3.8.0) + +# CMake invokes the toolchain file twice during the first build, but only once during subsequent rebuilds. +# NOTE: To improve single-library build-times, provide the flag "OS_SINGLE_BUILD" as a build argument. +if(DEFINED OS_SINGLE_BUILD AND DEFINED ENV{_IOS_TOOLCHAIN_HAS_RUN}) + return() +endif() +set(ENV{_IOS_TOOLCHAIN_HAS_RUN} true) + +# List of supported platform values +list(APPEND _supported_platforms + "OS" "OS64" "OS64COMBINED" "SIMULATOR" "SIMULATOR64" "SIMULATORARM64" "SIMULATOR64COMBINED" + "TVOS" "TVOSCOMBINED" "SIMULATOR_TVOS" "SIMULATORARM64_TVOS" + "WATCHOS" "WATCHOSCOMBINED" "SIMULATOR_WATCHOS" "SIMULATORARM64_WATCHOS" "SIMULATOR_WATCHOSCOMBINED" + "MAC" "MAC_ARM64" "MAC_UNIVERSAL" + "VISIONOS" "SIMULATOR_VISIONOS" "VISIONOSCOMBINED" + "MAC_CATALYST" "MAC_CATALYST_ARM64" "MAC_CATALYST_UNIVERSAL") + +# Cache what generator is used +set(USED_CMAKE_GENERATOR "${CMAKE_GENERATOR}") + +# Check if using a CMake version capable of building combined FAT builds (simulator and target slices combined in one static lib) +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14") + set(MODERN_CMAKE YES) +endif() + +# Get the Xcode version being used. +# Problem: CMake runs toolchain files multiple times, but can't read cache variables on some runs. +# Workaround: On the first run (in which cache variables are always accessible), set an intermediary environment variable. +# +# NOTE: This pattern is used in many places in this toolchain to speed up checks of all sorts +if(DEFINED XCODE_VERSION_INT) + # Environment variables are always preserved. + set(ENV{_XCODE_VERSION_INT} "${XCODE_VERSION_INT}") +elseif(DEFINED ENV{_XCODE_VERSION_INT}) + set(XCODE_VERSION_INT "$ENV{_XCODE_VERSION_INT}") +elseif(NOT DEFINED XCODE_VERSION_INT) + find_program(XCODEBUILD_EXECUTABLE xcodebuild) + if(NOT XCODEBUILD_EXECUTABLE) + message(FATAL_ERROR "xcodebuild not found. Please install either the standalone commandline tools or Xcode.") + endif() + execute_process(COMMAND ${XCODEBUILD_EXECUTABLE} -version + OUTPUT_VARIABLE XCODE_VERSION_INT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + string(REGEX MATCH "Xcode [0-9\\.]+" XCODE_VERSION_INT "${XCODE_VERSION_INT}") + string(REGEX REPLACE "Xcode ([0-9\\.]+)" "\\1" XCODE_VERSION_INT "${XCODE_VERSION_INT}") + set(XCODE_VERSION_INT "${XCODE_VERSION_INT}" CACHE INTERNAL "") +endif() + +# Assuming that xcode 12.0 is installed you most probably have ios sdk 14.0 or later installed (tested on Big Sur) +# if you don't set a deployment target it will be set the way you only get 64-bit builds +#if(NOT DEFINED DEPLOYMENT_TARGET AND XCODE_VERSION_INT VERSION_GREATER 12.0) +# Temporarily fix the arm64 issues in CMake install-combined by excluding arm64 for simulator builds (needed for Apple Silicon...) +# set(CMAKE_XCODE_ATTRIBUTE_EXCLUDED_ARCHS[sdk=iphonesimulator*] "arm64") +#endif() + +# Check if the platform variable is set +if(DEFINED PLATFORM) + # Environment variables are always preserved. + set(ENV{_PLATFORM} "${PLATFORM}") +elseif(DEFINED ENV{_PLATFORM}) + set(PLATFORM "$ENV{_PLATFORM}") +elseif(NOT DEFINED PLATFORM) + message(FATAL_ERROR "PLATFORM argument not set. Bailing configure since I don't know what target you want to build for!") +endif () + +if(PLATFORM MATCHES ".*COMBINED" AND NOT CMAKE_GENERATOR MATCHES "Xcode") + message(FATAL_ERROR "The combined builds support requires Xcode to be used as a generator via '-G Xcode' command-line argument in CMake") +endif() + +# Safeguard that the platform value is set and is one of the supported values +list(FIND _supported_platforms ${PLATFORM} contains_PLATFORM) +if("${contains_PLATFORM}" EQUAL "-1") + string(REPLACE ";" "\n * " _supported_platforms_formatted "${_supported_platforms}") + message(FATAL_ERROR " Invalid PLATFORM specified! Current value: ${PLATFORM}.\n" + " Supported PLATFORM values: \n * ${_supported_platforms_formatted}") +endif() + +# Check if Apple Silicon is supported +if(PLATFORM MATCHES "^(MAC_ARM64)$|^(MAC_CATALYST_ARM64)$|^(MAC_UNIVERSAL)$|^(MAC_CATALYST_UNIVERSAL)$" AND ${CMAKE_VERSION} VERSION_LESS "3.19.5") + message(FATAL_ERROR "Apple Silicon builds requires a minimum of CMake 3.19.5") +endif() + +# Touch the toolchain variable to suppress the "unused variable" warning. +# This happens if CMake is invoked with the same command line the second time. +if(CMAKE_TOOLCHAIN_FILE) +endif() + +# Fix for PThread library not in path +set(CMAKE_THREAD_LIBS_INIT "-lpthread") +set(CMAKE_HAVE_THREADS_LIBRARY 1) +set(CMAKE_USE_WIN32_THREADS_INIT 0) +set(CMAKE_USE_PTHREADS_INIT 1) + +# Specify named language support defaults. +if(NOT DEFINED NAMED_LANGUAGE_SUPPORT AND ${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16") + set(NAMED_LANGUAGE_SUPPORT ON) + message(STATUS "[DEFAULTS] Using explicit named language support! E.g., enable_language(CXX) is needed in the project files.") +elseif(NOT DEFINED NAMED_LANGUAGE_SUPPORT AND ${CMAKE_VERSION} VERSION_LESS "3.16") + set(NAMED_LANGUAGE_SUPPORT OFF) + message(STATUS "[DEFAULTS] Disabling explicit named language support. Falling back to legacy behavior.") +elseif(DEFINED NAMED_LANGUAGE_SUPPORT AND ${CMAKE_VERSION} VERSION_LESS "3.16") + message(FATAL_ERROR "CMake named language support for OBJC and OBJCXX was added in CMake 3.16.") +endif() +set(NAMED_LANGUAGE_SUPPORT_INT ${NAMED_LANGUAGE_SUPPORT} CACHE BOOL + "Whether or not to enable explicit named language support" FORCE) + +# Specify the minimum version of the deployment target. +if(NOT DEFINED DEPLOYMENT_TARGET) + if (PLATFORM MATCHES "WATCHOS") + # Unless specified, SDK version 6.0 is used by default as minimum target version (watchOS). + set(DEPLOYMENT_TARGET "6.0") + elseif(PLATFORM STREQUAL "MAC") + # Unless specified, SDK version 11.0 (Big Sur) is used by default as the minimum target version (macOS on x86). + set(DEPLOYMENT_TARGET "11.0") + elseif(PLATFORM STREQUAL "VISIONOS" OR PLATFORM STREQUAL "SIMULATOR_VISIONOS" OR PLATFORM STREQUAL "VISIONOSCOMBINED") + # Unless specified, SDK version 1.0 is used by default as minimum target version (visionOS). + set(DEPLOYMENT_TARGET "1.0") + elseif(PLATFORM STREQUAL "MAC_ARM64") + # Unless specified, SDK version 11.0 (Big Sur) is used by default as the minimum target version (macOS on arm). + set(DEPLOYMENT_TARGET "11.0") + elseif(PLATFORM STREQUAL "MAC_UNIVERSAL") + # Unless specified, SDK version 11.0 (Big Sur) is used by default as minimum target version for universal builds. + set(DEPLOYMENT_TARGET "11.0") + elseif(PLATFORM STREQUAL "MAC_CATALYST" OR PLATFORM STREQUAL "MAC_CATALYST_ARM64" OR PLATFORM STREQUAL "MAC_CATALYST_UNIVERSAL") + # Unless specified, SDK version 13.1 is used by default as the minimum target version (mac catalyst minimum requirement). + set(DEPLOYMENT_TARGET "13.1") + else() + # Unless specified, SDK version 13.0 is used by default as the minimum target version (iOS, tvOS). + set(DEPLOYMENT_TARGET "13.0") + endif() + message(STATUS "[DEFAULTS] Using the default min-version since DEPLOYMENT_TARGET not provided!") +elseif(DEFINED DEPLOYMENT_TARGET AND PLATFORM MATCHES "^MAC_CATALYST" AND ${DEPLOYMENT_TARGET} VERSION_LESS "13.1") + message(FATAL_ERROR "Mac Catalyst builds requires a minimum deployment target of 13.1!") +endif() + +# Store the DEPLOYMENT_TARGET in the cache +set(DEPLOYMENT_TARGET "${DEPLOYMENT_TARGET}" CACHE INTERNAL "") + +# Handle the case where we are targeting iOS and a version above 10.3.4 (32-bit support dropped officially) +if(PLATFORM STREQUAL "OS" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4) + set(PLATFORM "OS64") + message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.") +elseif(PLATFORM STREQUAL "SIMULATOR" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4) + set(PLATFORM "SIMULATOR64") + message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.") +endif() + +set(PLATFORM_INT "${PLATFORM}") + +if(DEFINED ARCHS) + string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") +endif() + +# Determine the platform name and architectures for use in xcodebuild commands +# from the specified PLATFORM_INT name. +if(PLATFORM_INT STREQUAL "OS") + set(SDK_NAME iphoneos) + if(NOT ARCHS) + set(ARCHS armv7 armv7s arm64) + set(APPLE_TARGET_TRIPLE_INT arm-apple-ios${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}) + endif() +elseif(PLATFORM_INT STREQUAL "OS64") + set(SDK_NAME iphoneos) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 10.0) + set(ARCHS arm64) # FIXME: Add arm64e when Apple has fixed the integration issues with it, libarclite_iphoneos.a is currently missing bitcode markers for example + else() + set(ARCHS arm64) + endif() + set(APPLE_TARGET_TRIPLE_INT arm64-apple-ios${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}) + endif() +elseif(PLATFORM_INT STREQUAL "OS64COMBINED") + set(SDK_NAME iphoneos) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 12.0) + set(ARCHS arm64 x86_64) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64 arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "x86_64 arm64") + else() + set(ARCHS arm64 x86_64) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "x86_64") + endif() + set(APPLE_TARGET_TRIPLE_INT arm64-x86_64-apple-ios${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the OS64COMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR64COMBINED") + set(SDK_NAME iphonesimulator) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 12.0) + set(ARCHS arm64 x86_64) # FIXME: Add arm64e when Apple have fixed the integration issues with it, libarclite_iphoneos.a is currently missing bitcode markers for example + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64 arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] "") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "x86_64 arm64") + else() + set(ARCHS arm64 x86_64) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] "") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "x86_64") + endif() + set(APPLE_TARGET_TRIPLE_INT aarch64-x86_64-apple-ios${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the SIMULATOR64COMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR") + set(SDK_NAME iphonesimulator) + if(NOT ARCHS) + set(ARCHS i386) + set(APPLE_TARGET_TRIPLE_INT i386-apple-ios${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator) + endif() + message(DEPRECATION "SIMULATOR IS DEPRECATED. Consider using SIMULATOR64 instead.") +elseif(PLATFORM_INT STREQUAL "SIMULATOR64") + set(SDK_NAME iphonesimulator) + if(NOT ARCHS) + set(ARCHS x86_64) + set(APPLE_TARGET_TRIPLE_INT x86_64-apple-ios${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATORARM64") + set(SDK_NAME iphonesimulator) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-ios${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "TVOS") + set(SDK_NAME appletvos) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-tvos${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET}) + endif() +elseif (PLATFORM_INT STREQUAL "TVOSCOMBINED") + set(SDK_NAME appletvos) + if(MODERN_CMAKE) + if(NOT ARCHS) + set(ARCHS arm64 x86_64) + set(APPLE_TARGET_TRIPLE_INT arm64-x86_64-apple-tvos${DEPLOYMENT_TARGET}) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=appletvos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=appletvsimulator*] "x86_64 arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=appletvos*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=appletvsimulator*] "x86_64 arm64") + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET}) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the TVOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS") + set(SDK_NAME appletvsimulator) + if(NOT ARCHS) + set(ARCHS x86_64) + set(APPLE_TARGET_TRIPLE_INT x86_64-apple-tvos${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATORARM64_TVOS") + set(SDK_NAME appletvsimulator) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-tvos${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-tvos${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "WATCHOS") + set(SDK_NAME watchos) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 10.0) + set(ARCHS armv7k arm64_32) + set(APPLE_TARGET_TRIPLE_INT arm64_32-apple-watchos${DEPLOYMENT_TARGET}) + else() + set(ARCHS armv7k) + set(APPLE_TARGET_TRIPLE_INT arm-apple-watchos${DEPLOYMENT_TARGET}) + endif() + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}) + endif() +elseif(PLATFORM_INT STREQUAL "WATCHOSCOMBINED") + set(SDK_NAME watchos) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 10.0) + set(ARCHS armv7k arm64_32 x86_64) + set(APPLE_TARGET_TRIPLE_INT arm64_32-x86_64-apple-watchos${DEPLOYMENT_TARGET}) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] "armv7k arm64_32") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] "x86_64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] "armv7k arm64_32") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] "x86_64") + else() + set(ARCHS armv7k i386) + set(APPLE_TARGET_TRIPLE_INT arm-i386-apple-watchos${DEPLOYMENT_TARGET}) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] "armv7k") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] "i386") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] "armv7k") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] "i386") + endif() + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the WATCHOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") + set(SDK_NAME watchsimulator) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 10.0) + set(ARCHS x86_64) + set(APPLE_TARGET_TRIPLE_INT x86_64-apple-watchos${DEPLOYMENT_TARGET}-simulator) + else() + set(ARCHS i386) + set(APPLE_TARGET_TRIPLE_INT i386-apple-watchos${DEPLOYMENT_TARGET}-simulator) + endif() + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATORARM64_WATCHOS") + set(SDK_NAME watchsimulator) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-watchos${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOSCOMBINED") + set(SDK_NAME watchsimulator) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION_INT VERSION_GREATER 12.0) + set(ARCHS arm64 x86_64) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] "") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] "arm64 x86_64") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] "") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] "arm64 x86_64") + set(APPLE_TARGET_TRIPLE_INT arm64_x86_64-apple-watchos${DEPLOYMENT_TARGET}-simulator) + else() + set(ARCHS arm64 i386) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchos*] "") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=watchsimulator*] "i386") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchos*] "") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=watchsimulator*] "i386") + set(APPLE_TARGET_TRIPLE_INT arm64_i386-apple-watchos${DEPLOYMENT_TARGET}-simulator) + endif() + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-watchos${DEPLOYMENT_TARGET}-simulator) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the SIMULATOR_WATCHOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_VISIONOS") + set(SDK_NAME xrsimulator) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-xros${DEPLOYMENT_TARGET}-simulator) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-xros${DEPLOYMENT_TARGET}-simulator) + endif() +elseif(PLATFORM_INT STREQUAL "VISIONOS") + set(SDK_NAME xros) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-xros${DEPLOYMENT_TARGET}) + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-xros${DEPLOYMENT_TARGET}) + endif() +elseif(PLATFORM_INT STREQUAL "VISIONOSCOMBINED") + set(SDK_NAME xros) + if(MODERN_CMAKE) + if(NOT ARCHS) + set(ARCHS arm64) + set(APPLE_TARGET_TRIPLE_INT arm64-apple-xros${DEPLOYMENT_TARGET}) + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=xros*] "arm64") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=xrsimulator*] "arm64") + else() + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-xros${DEPLOYMENT_TARGET}) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the VISIONOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "MAC" OR PLATFORM_INT STREQUAL "MAC_CATALYST") + set(SDK_NAME macosx) + if(NOT ARCHS) + set(ARCHS x86_64) + endif() + string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") + if(PLATFORM_INT STREQUAL "MAC") + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-macosx${DEPLOYMENT_TARGET}) + elseif(PLATFORM_INT STREQUAL "MAC_CATALYST") + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-macabi) + endif() +elseif(PLATFORM_INT MATCHES "^(MAC_ARM64)$|^(MAC_CATALYST_ARM64)$") + set(SDK_NAME macosx) + if(NOT ARCHS) + set(ARCHS arm64) + endif() + string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") + if(PLATFORM_INT STREQUAL "MAC_ARM64") + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-macosx${DEPLOYMENT_TARGET}) + elseif(PLATFORM_INT STREQUAL "MAC_CATALYST_ARM64") + set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-ios${DEPLOYMENT_TARGET}-macabi) + endif() +elseif(PLATFORM_INT STREQUAL "MAC_UNIVERSAL") + set(SDK_NAME macosx) + if(NOT ARCHS) + set(ARCHS "x86_64;arm64") + endif() + # For universal builds, don't set target triple - let CMake handle it + # string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") + # set(APPLE_TARGET_TRIPLE_INT ${ARCHS_SPLIT}-apple-macosx${DEPLOYMENT_TARGET}) +elseif(PLATFORM_INT STREQUAL "MAC_CATALYST_UNIVERSAL") + set(SDK_NAME macosx) + if(NOT ARCHS) + set(ARCHS "x86_64;arm64") + endif() + string(REPLACE ";" "-" ARCHS_SPLIT "${ARCHS}") + set(APPLE_TARGET_TRIPLE_INT apple-ios${DEPLOYMENT_TARGET}-macabi) +else() + message(FATAL_ERROR "Invalid PLATFORM: ${PLATFORM_INT}") +endif() + +string(REPLACE ";" " " ARCHS_SPACED "${ARCHS}") + +if(MODERN_CMAKE AND PLATFORM_INT MATCHES ".*COMBINED" AND NOT CMAKE_GENERATOR MATCHES "Xcode") + message(FATAL_ERROR "The COMBINED options only work with Xcode generator, -G Xcode") +endif() + +if(CMAKE_GENERATOR MATCHES "Xcode" AND PLATFORM_INT MATCHES "^MAC_CATALYST") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") + set(CMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS "macosx") + set(CMAKE_XCODE_ATTRIBUTE_SUPPORTS_MACCATALYST "YES") + if(NOT DEFINED MACOSX_DEPLOYMENT_TARGET) + set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "10.15") + else() + set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "${MACOSX_DEPLOYMENT_TARGET}") + endif() +elseif(CMAKE_GENERATOR MATCHES "Xcode") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") + set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "${DEPLOYMENT_TARGET}") + if(NOT PLATFORM_INT MATCHES ".*COMBINED") + set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=${SDK_NAME}*] "${ARCHS_SPACED}") + set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=${SDK_NAME}*] "${ARCHS_SPACED}") + endif() +endif() + +# If the user did not specify the SDK root to use, then query xcodebuild for it. +if(DEFINED CMAKE_OSX_SYSROOT_INT) + # Environment variables are always preserved. + set(ENV{_CMAKE_OSX_SYSROOT_INT} "${CMAKE_OSX_SYSROOT_INT}") +elseif(DEFINED ENV{_CMAKE_OSX_SYSROOT_INT}) + set(CMAKE_OSX_SYSROOT_INT "$ENV{_CMAKE_OSX_SYSROOT_INT}") +elseif(NOT DEFINED CMAKE_OSX_SYSROOT_INT) + execute_process(COMMAND ${XCODEBUILD_EXECUTABLE} -version -sdk ${SDK_NAME} Path + OUTPUT_VARIABLE CMAKE_OSX_SYSROOT_INT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() + +if (NOT DEFINED CMAKE_OSX_SYSROOT_INT AND NOT DEFINED CMAKE_OSX_SYSROOT) + message(SEND_ERROR "Please make sure that Xcode is installed and that the toolchain" + "is pointing to the correct path. Please run:" + "sudo xcode-select -s /Applications/Xcode.app/Contents/Developer" + "and see if that fixes the problem for you.") + message(FATAL_ERROR "Invalid CMAKE_OSX_SYSROOT: ${CMAKE_OSX_SYSROOT} " + "does not exist.") +elseif(DEFINED CMAKE_OSX_SYSROOT_INT) + set(CMAKE_OSX_SYSROOT_INT "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "") + # Specify the location or name of the platform SDK to be used in CMAKE_OSX_SYSROOT. + set(CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "") +endif() + +# Use bitcode or not +if(NOT DEFINED ENABLE_BITCODE) + message(STATUS "[DEFAULTS] Disabling bitcode support by default. ENABLE_BITCODE not provided for override!") + set(ENABLE_BITCODE OFF) +endif() +set(ENABLE_BITCODE_INT ${ENABLE_BITCODE} CACHE BOOL + "Whether or not to enable bitcode" FORCE) +# Use ARC or not +if(NOT DEFINED ENABLE_ARC) + # Unless specified, enable ARC support by default + set(ENABLE_ARC ON) + message(STATUS "[DEFAULTS] Enabling ARC support by default. ENABLE_ARC not provided!") +endif() +set(ENABLE_ARC_INT ${ENABLE_ARC} CACHE BOOL "Whether or not to enable ARC" FORCE) +# Use hidden visibility or not +if(NOT DEFINED ENABLE_VISIBILITY) + # Unless specified, disable symbols visibility by default + set(ENABLE_VISIBILITY OFF) + message(STATUS "[DEFAULTS] Hiding symbols visibility by default. ENABLE_VISIBILITY not provided!") +endif() +set(ENABLE_VISIBILITY_INT ${ENABLE_VISIBILITY} CACHE BOOL "Whether or not to hide symbols from the dynamic linker (-fvisibility=hidden)" FORCE) +# Set strict compiler checks or not +if(NOT DEFINED ENABLE_STRICT_TRY_COMPILE) + # Unless specified, disable strict try_compile() + set(ENABLE_STRICT_TRY_COMPILE OFF) + message(STATUS "[DEFAULTS] Using NON-strict compiler checks by default. ENABLE_STRICT_TRY_COMPILE not provided!") +endif() +set(ENABLE_STRICT_TRY_COMPILE_INT ${ENABLE_STRICT_TRY_COMPILE} CACHE BOOL + "Whether or not to use strict compiler checks" FORCE) + +# Get the SDK version information. +if(DEFINED SDK_VERSION) + # Environment variables are always preserved. + set(ENV{_SDK_VERSION} "${SDK_VERSION}") +elseif(DEFINED ENV{_SDK_VERSION}) + set(SDK_VERSION "$ENV{_SDK_VERSION}") +elseif(NOT DEFINED SDK_VERSION) + execute_process(COMMAND ${XCODEBUILD_EXECUTABLE} -sdk ${CMAKE_OSX_SYSROOT_INT} -version SDKVersion + OUTPUT_VARIABLE SDK_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() + +# Find the Developer root for the specific iOS platform being compiled for +# from CMAKE_OSX_SYSROOT. Should be ../../ from SDK specified in +# CMAKE_OSX_SYSROOT. There does not appear to be a direct way to obtain +# this information from xcrun or xcodebuild. +if (NOT DEFINED CMAKE_DEVELOPER_ROOT AND NOT CMAKE_GENERATOR MATCHES "Xcode") + get_filename_component(PLATFORM_SDK_DIR ${CMAKE_OSX_SYSROOT_INT} PATH) + get_filename_component(CMAKE_DEVELOPER_ROOT ${PLATFORM_SDK_DIR} PATH) + if (NOT EXISTS "${CMAKE_DEVELOPER_ROOT}") + message(FATAL_ERROR "Invalid CMAKE_DEVELOPER_ROOT: ${CMAKE_DEVELOPER_ROOT} does not exist.") + endif() +endif() + +# Find the C & C++ compilers for the specified SDK. +if(DEFINED CMAKE_C_COMPILER) + # Environment variables are always preserved. + set(ENV{_CMAKE_C_COMPILER} "${CMAKE_C_COMPILER}") +elseif(DEFINED ENV{_CMAKE_C_COMPILER}) + set(CMAKE_C_COMPILER "$ENV{_CMAKE_C_COMPILER}") + set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER}) +elseif(NOT DEFINED CMAKE_C_COMPILER) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find clang + OUTPUT_VARIABLE CMAKE_C_COMPILER + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER}) +endif() +if(DEFINED CMAKE_CXX_COMPILER) + # Environment variables are always preserved. + set(ENV{_CMAKE_CXX_COMPILER} "${CMAKE_CXX_COMPILER}") +elseif(DEFINED ENV{_CMAKE_CXX_COMPILER}) + set(CMAKE_CXX_COMPILER "$ENV{_CMAKE_CXX_COMPILER}") +elseif(NOT DEFINED CMAKE_CXX_COMPILER) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find clang++ + OUTPUT_VARIABLE CMAKE_CXX_COMPILER + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() +# Find (Apple's) libtool. +if(DEFINED BUILD_LIBTOOL) + # Environment variables are always preserved. + set(ENV{_BUILD_LIBTOOL} "${BUILD_LIBTOOL}") +elseif(DEFINED ENV{_BUILD_LIBTOOL}) + set(BUILD_LIBTOOL "$ENV{_BUILD_LIBTOOL}") +elseif(NOT DEFINED BUILD_LIBTOOL) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find libtool + OUTPUT_VARIABLE BUILD_LIBTOOL + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() +# Find the toolchain's provided install_name_tool if none is found on the host +if(DEFINED CMAKE_INSTALL_NAME_TOOL) + # Environment variables are always preserved. + set(ENV{_CMAKE_INSTALL_NAME_TOOL} "${CMAKE_INSTALL_NAME_TOOL}") +elseif(DEFINED ENV{_CMAKE_INSTALL_NAME_TOOL}) + set(CMAKE_INSTALL_NAME_TOOL "$ENV{_CMAKE_INSTALL_NAME_TOOL}") +elseif(NOT DEFINED CMAKE_INSTALL_NAME_TOOL) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT_INT} -find install_name_tool + OUTPUT_VARIABLE CMAKE_INSTALL_NAME_TOOL_INT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(CMAKE_INSTALL_NAME_TOOL ${CMAKE_INSTALL_NAME_TOOL_INT} CACHE INTERNAL "") +endif() + +# Configure libtool to be used instead of ar + ranlib to build static libraries. +# This is required on Xcode 7+, but should also work on previous versions of +# Xcode. +get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach(lang ${languages}) + set(CMAKE_${lang}_CREATE_STATIC_LIBRARY "${BUILD_LIBTOOL} -static -o " CACHE INTERNAL "") +endforeach() + +# CMake 3.14+ support building for iOS, watchOS, and tvOS out of the box. +if(MODERN_CMAKE) + if(SDK_NAME MATCHES "iphone") + set(CMAKE_SYSTEM_NAME iOS) + elseif(SDK_NAME MATCHES "xros") + set(CMAKE_SYSTEM_NAME visionOS) + elseif(SDK_NAME MATCHES "xrsimulator") + set(CMAKE_SYSTEM_NAME visionOS) + elseif(SDK_NAME MATCHES "macosx") + set(CMAKE_SYSTEM_NAME Darwin) + elseif(SDK_NAME MATCHES "appletv") + set(CMAKE_SYSTEM_NAME tvOS) + elseif(SDK_NAME MATCHES "watch") + set(CMAKE_SYSTEM_NAME watchOS) + endif() + # Provide flags for a combined FAT library build on newer CMake versions + if(PLATFORM_INT MATCHES ".*COMBINED") + set(CMAKE_IOS_INSTALL_COMBINED YES) + if(CMAKE_GENERATOR MATCHES "Xcode") + # Set the SDKROOT Xcode properties to a Xcode-friendly value (the SDK_NAME, E.g, iphoneos) + # This way, Xcode will automatically switch between the simulator and device SDK when building. + set(CMAKE_XCODE_ATTRIBUTE_SDKROOT "${SDK_NAME}") + # Force to not build just one ARCH, but all! + set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH "NO") + endif() + endif() +elseif(NOT DEFINED CMAKE_SYSTEM_NAME AND ${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.10") + # Legacy code path prior to CMake 3.14 or fallback if no CMAKE_SYSTEM_NAME specified + set(CMAKE_SYSTEM_NAME iOS) +elseif(NOT DEFINED CMAKE_SYSTEM_NAME) + # Legacy code path before CMake 3.14 or fallback if no CMAKE_SYSTEM_NAME specified + set(CMAKE_SYSTEM_NAME Darwin) +endif() +# Standard settings. +set(CMAKE_SYSTEM_VERSION ${SDK_VERSION} CACHE INTERNAL "") +set(UNIX ON CACHE BOOL "") +set(APPLE ON CACHE BOOL "") +if(PLATFORM STREQUAL "MAC" OR PLATFORM STREQUAL "MAC_ARM64" OR PLATFORM STREQUAL "MAC_UNIVERSAL") + set(IOS OFF CACHE BOOL "") + set(MACOS ON CACHE BOOL "") +elseif(PLATFORM STREQUAL "MAC_CATALYST" OR PLATFORM STREQUAL "MAC_CATALYST_ARM64" OR PLATFORM STREQUAL "MAC_CATALYST_UNIVERSAL") + set(IOS ON CACHE BOOL "") + set(MACOS ON CACHE BOOL "") +elseif(PLATFORM STREQUAL "VISIONOS" OR PLATFORM STREQUAL "SIMULATOR_VISIONOS" OR PLATFORM STREQUAL "VISIONOSCOMBINED") + set(IOS OFF CACHE BOOL "") + set(VISIONOS ON CACHE BOOL "") +else() + set(IOS ON CACHE BOOL "") +endif() +# Set the architectures for which to build. +set(CMAKE_OSX_ARCHITECTURES ${ARCHS} CACHE INTERNAL "") +# Change the type of target generated for try_compile() so it'll work when cross-compiling, weak compiler checks +if(NOT ENABLE_STRICT_TRY_COMPILE_INT) + set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +endif() +# All iOS/Darwin specific settings - some may be redundant. +if (NOT DEFINED CMAKE_MACOSX_BUNDLE) + set(CMAKE_MACOSX_BUNDLE YES) +endif() +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO") +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO") +set(CMAKE_SHARED_LIBRARY_PREFIX "lib") +set(CMAKE_SHARED_LIBRARY_SUFFIX ".dylib") +set(CMAKE_EXTRA_SHARED_LIBRARY_SUFFIXES ".tbd" ".so") +set(CMAKE_SHARED_MODULE_PREFIX "lib") +set(CMAKE_SHARED_MODULE_SUFFIX ".so") +set(CMAKE_C_COMPILER_ABI ELF) +set(CMAKE_CXX_COMPILER_ABI ELF) +set(CMAKE_C_HAS_ISYSROOT 1) +set(CMAKE_CXX_HAS_ISYSROOT 1) +set(CMAKE_MODULE_EXISTS 1) +set(CMAKE_DL_LIBS "") +set(CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ") +set(CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") +set(CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") +set(CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") + +if(ARCHS MATCHES "((^|;|, )(arm64|arm64e|x86_64))+") + set(CMAKE_C_SIZEOF_DATA_PTR 8) + set(CMAKE_CXX_SIZEOF_DATA_PTR 8) + if(ARCHS MATCHES "((^|;|, )(arm64|arm64e))+") + set(CMAKE_SYSTEM_PROCESSOR "aarch64") + else() + set(CMAKE_SYSTEM_PROCESSOR "x86_64") + endif() +else() + set(CMAKE_C_SIZEOF_DATA_PTR 4) + set(CMAKE_CXX_SIZEOF_DATA_PTR 4) + set(CMAKE_SYSTEM_PROCESSOR "arm") +endif() + +# Note that only Xcode 7+ supports the newer more specific: +# -m${SDK_NAME}-version-min flags, older versions of Xcode use: +# -m(ios/ios-simulator)-version-min instead. +if(${CMAKE_VERSION} VERSION_LESS "3.11") + if(PLATFORM_INT STREQUAL "OS" OR PLATFORM_INT STREQUAL "OS64") + if(XCODE_VERSION_INT VERSION_LESS 7.0) + set(SDK_NAME_VERSION_FLAGS + "-mios-version-min=${DEPLOYMENT_TARGET}") + else() + # Xcode 7.0+ uses flags we can build directly from SDK_NAME. + set(SDK_NAME_VERSION_FLAGS + "-m${SDK_NAME}-version-min=${DEPLOYMENT_TARGET}") + endif() + elseif(PLATFORM_INT STREQUAL "TVOS") + set(SDK_NAME_VERSION_FLAGS + "-mtvos-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS") + set(SDK_NAME_VERSION_FLAGS + "-mtvos-simulator-version-min=${DEPLOYMENT_TARGET}") +elseif(PLATFORM_INT STREQUAL "SIMULATORARM64_TVOS") + set(SDK_NAME_VERSION_FLAGS + "-mtvos-simulator-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "WATCHOS") + set(SDK_NAME_VERSION_FLAGS + "-mwatchos-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") + set(SDK_NAME_VERSION_FLAGS + "-mwatchos-simulator-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "SIMULATORARM64_WATCHOS") + set(SDK_NAME_VERSION_FLAGS + "-mwatchos-simulator-version-min=${DEPLOYMENT_TARGET}") + elseif(PLATFORM_INT STREQUAL "MAC") + set(SDK_NAME_VERSION_FLAGS + "-mmacosx-version-min=${DEPLOYMENT_TARGET}") + else() + # SIMULATOR or SIMULATOR64 both use -mios-simulator-version-min. + set(SDK_NAME_VERSION_FLAGS + "-mios-simulator-version-min=${DEPLOYMENT_TARGET}") + endif() +elseif(NOT PLATFORM_INT MATCHES "^MAC_CATALYST") + # Newer versions of CMake sets the version min flags correctly, skip this for Mac Catalyst targets + set(CMAKE_OSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET} CACHE INTERNAL "Minimum OS X deployment version") +endif() + +if(DEFINED APPLE_TARGET_TRIPLE_INT) + set(APPLE_TARGET_TRIPLE ${APPLE_TARGET_TRIPLE_INT} CACHE INTERNAL "") + set(CMAKE_C_COMPILER_TARGET ${APPLE_TARGET_TRIPLE}) + set(CMAKE_CXX_COMPILER_TARGET ${APPLE_TARGET_TRIPLE}) + set(CMAKE_ASM_COMPILER_TARGET ${APPLE_TARGET_TRIPLE}) +endif() + +if(PLATFORM_INT MATCHES "^MAC_CATALYST") + set(C_TARGET_FLAGS "-isystem ${CMAKE_OSX_SYSROOT_INT}/System/iOSSupport/usr/include -iframework ${CMAKE_OSX_SYSROOT_INT}/System/iOSSupport/System/Library/Frameworks") +endif() + +if(ENABLE_BITCODE_INT) + set(BITCODE "-fembed-bitcode") + set(CMAKE_XCODE_ATTRIBUTE_BITCODE_GENERATION_MODE "bitcode") + set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "YES") +else() + set(BITCODE "") + set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO") +endif() + +if(ENABLE_ARC_INT) + set(FOBJC_ARC "-fobjc-arc") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "YES") +else() + set(FOBJC_ARC "-fno-objc-arc") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "NO") +endif() + +if(NAMED_LANGUAGE_SUPPORT_INT) + set(OBJC_VARS "-fobjc-abi-version=2 -DOBJC_OLD_DISPATCH_PROTOTYPES=0") + set(OBJC_LEGACY_VARS "") +else() + set(OBJC_VARS "") + set(OBJC_LEGACY_VARS "-fobjc-abi-version=2 -DOBJC_OLD_DISPATCH_PROTOTYPES=0") +endif() + +if(NOT ENABLE_VISIBILITY_INT) + foreach(lang ${languages}) + set(CMAKE_${lang}_VISIBILITY_PRESET "hidden" CACHE INTERNAL "") + endforeach() + set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN "YES") + set(VISIBILITY "-fvisibility=hidden -fvisibility-inlines-hidden") +else() + foreach(lang ${languages}) + set(CMAKE_${lang}_VISIBILITY_PRESET "default" CACHE INTERNAL "") + endforeach() + set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN "NO") + set(VISIBILITY "-fvisibility=default") +endif() + +if(DEFINED APPLE_TARGET_TRIPLE) + set(APPLE_TARGET_TRIPLE_FLAG "-target ${APPLE_TARGET_TRIPLE}") +endif() + +#Check if Xcode generator is used since that will handle these flags automagically +if(CMAKE_GENERATOR MATCHES "Xcode") + message(STATUS "Not setting any manual command-line buildflags, since Xcode is selected as the generator. Modifying the Xcode build-settings directly instead.") +else() + set(CMAKE_C_FLAGS "${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${OBJC_LEGACY_VARS} ${BITCODE} ${VISIBILITY} ${CMAKE_C_FLAGS}" CACHE INTERNAL + "Flags used by the compiler during all C build types.") + set(CMAKE_C_FLAGS_DEBUG "-O0 -g ${CMAKE_C_FLAGS_DEBUG}") + set(CMAKE_C_FLAGS_MINSIZEREL "-DNDEBUG -Os ${CMAKE_C_FLAGS_MINSIZEREL}") + set(CMAKE_C_FLAGS_RELWITHDEBINFO "-DNDEBUG -O2 -g ${CMAKE_C_FLAGS_RELWITHDEBINFO}") + set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O3 ${CMAKE_C_FLAGS_RELEASE}") + set(CMAKE_CXX_FLAGS "${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${OBJC_LEGACY_VARS} ${BITCODE} ${VISIBILITY} ${CMAKE_CXX_FLAGS}" CACHE INTERNAL + "Flags used by the compiler during all CXX build types.") + set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g ${CMAKE_CXX_FLAGS_DEBUG}") + set(CMAKE_CXX_FLAGS_MINSIZEREL "-DNDEBUG -Os ${CMAKE_CXX_FLAGS_MINSIZEREL}") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-DNDEBUG -O2 -g ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -O3 ${CMAKE_CXX_FLAGS_RELEASE}") + if(NAMED_LANGUAGE_SUPPORT_INT) + set(CMAKE_OBJC_FLAGS "${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${BITCODE} ${VISIBILITY} ${FOBJC_ARC} ${OBJC_VARS} ${CMAKE_OBJC_FLAGS}" CACHE INTERNAL + "Flags used by the compiler during all OBJC build types.") + set(CMAKE_OBJC_FLAGS_DEBUG "-O0 -g ${CMAKE_OBJC_FLAGS_DEBUG}") + set(CMAKE_OBJC_FLAGS_MINSIZEREL "-DNDEBUG -Os ${CMAKE_OBJC_FLAGS_MINSIZEREL}") + set(CMAKE_OBJC_FLAGS_RELWITHDEBINFO "-DNDEBUG -O2 -g ${CMAKE_OBJC_FLAGS_RELWITHDEBINFO}") + set(CMAKE_OBJC_FLAGS_RELEASE "-DNDEBUG -O3 ${CMAKE_OBJC_FLAGS_RELEASE}") + set(CMAKE_OBJCXX_FLAGS "${C_TARGET_FLAGS} ${APPLE_TARGET_TRIPLE_FLAG} ${SDK_NAME_VERSION_FLAGS} ${BITCODE} ${VISIBILITY} ${FOBJC_ARC} ${OBJC_VARS} ${CMAKE_OBJCXX_FLAGS}" CACHE INTERNAL + "Flags used by the compiler during all OBJCXX build types.") + set(CMAKE_OBJCXX_FLAGS_DEBUG "-O0 -g ${CMAKE_OBJCXX_FLAGS_DEBUG}") + set(CMAKE_OBJCXX_FLAGS_MINSIZEREL "-DNDEBUG -Os ${CMAKE_OBJCXX_FLAGS_MINSIZEREL}") + set(CMAKE_OBJCXX_FLAGS_RELWITHDEBINFO "-DNDEBUG -O2 -g ${CMAKE_OBJCXX_FLAGS_RELWITHDEBINFO}") + set(CMAKE_OBJCXX_FLAGS_RELEASE "-DNDEBUG -O3 ${CMAKE_OBJCXX_FLAGS_RELEASE}") + endif() + set(CMAKE_C_LINK_FLAGS "${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}" CACHE INTERNAL + "Flags used by the compiler for all C link types.") + set(CMAKE_CXX_LINK_FLAGS "${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}" CACHE INTERNAL + "Flags used by the compiler for all CXX link types.") + if(NAMED_LANGUAGE_SUPPORT_INT) + set(CMAKE_OBJC_LINK_FLAGS "${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_OBJC_LINK_FLAGS}" CACHE INTERNAL + "Flags used by the compiler for all OBJC link types.") + set(CMAKE_OBJCXX_LINK_FLAGS "${C_TARGET_FLAGS} ${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_OBJCXX_LINK_FLAGS}" CACHE INTERNAL + "Flags used by the compiler for all OBJCXX link types.") + endif() + set(CMAKE_ASM_FLAGS "${CMAKE_C_FLAGS} -x assembler-with-cpp" CACHE INTERNAL + "Flags used by the compiler for all ASM build types.") +endif() + +## Print status messages to inform of the current state +message(STATUS "Configuring ${SDK_NAME} build for platform: ${PLATFORM_INT}, architecture(s): ${ARCHS}") +message(STATUS "Using SDK: ${CMAKE_OSX_SYSROOT_INT}") +message(STATUS "Using C compiler: ${CMAKE_C_COMPILER}") +message(STATUS "Using CXX compiler: ${CMAKE_CXX_COMPILER}") +message(STATUS "Using libtool: ${BUILD_LIBTOOL}") +message(STATUS "Using install name tool: ${CMAKE_INSTALL_NAME_TOOL}") +if(DEFINED APPLE_TARGET_TRIPLE) + message(STATUS "Autoconf target triple: ${APPLE_TARGET_TRIPLE}") +endif() +message(STATUS "Using minimum deployment version: ${DEPLOYMENT_TARGET}" + " (SDK version: ${SDK_VERSION})") +if(MODERN_CMAKE) + message(STATUS "Merging integrated CMake 3.14+ iOS,tvOS,watchOS,macOS toolchain(s) with this toolchain!") + if(PLATFORM_INT MATCHES ".*COMBINED") + message(STATUS "Will combine built (static) artifacts into FAT lib...") + endif() +endif() +if(CMAKE_GENERATOR MATCHES "Xcode") + message(STATUS "Using Xcode version: ${XCODE_VERSION_INT}") +endif() +message(STATUS "CMake version: ${CMAKE_VERSION}") +if(DEFINED SDK_NAME_VERSION_FLAGS) + message(STATUS "Using version flags: ${SDK_NAME_VERSION_FLAGS}") +endif() +message(STATUS "Using a data_ptr size of: ${CMAKE_CXX_SIZEOF_DATA_PTR}") +if(ENABLE_BITCODE_INT) + message(STATUS "Bitcode: Enabled") +else() + message(STATUS "Bitcode: Disabled") +endif() + +if(ENABLE_ARC_INT) + message(STATUS "ARC: Enabled") +else() + message(STATUS "ARC: Disabled") +endif() + +if(ENABLE_VISIBILITY_INT) + message(STATUS "Hiding symbols: Disabled") +else() + message(STATUS "Hiding symbols: Enabled") +endif() + +# Set global properties +set_property(GLOBAL PROPERTY PLATFORM "${PLATFORM}") +set_property(GLOBAL PROPERTY APPLE_TARGET_TRIPLE "${APPLE_TARGET_TRIPLE_INT}") +set_property(GLOBAL PROPERTY SDK_VERSION "${SDK_VERSION}") +set_property(GLOBAL PROPERTY XCODE_VERSION "${XCODE_VERSION_INT}") +set_property(GLOBAL PROPERTY OSX_ARCHITECTURES "${CMAKE_OSX_ARCHITECTURES}") + +# Export configurable variables for the try_compile() command. +set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES + PLATFORM + XCODE_VERSION_INT + SDK_VERSION + NAMED_LANGUAGE_SUPPORT + DEPLOYMENT_TARGET + CMAKE_DEVELOPER_ROOT + CMAKE_OSX_SYSROOT_INT + ENABLE_BITCODE + ENABLE_ARC + CMAKE_ASM_COMPILER + CMAKE_C_COMPILER + CMAKE_C_COMPILER_TARGET + CMAKE_CXX_COMPILER + CMAKE_CXX_COMPILER_TARGET + BUILD_LIBTOOL + CMAKE_INSTALL_NAME_TOOL + CMAKE_C_FLAGS + CMAKE_C_DEBUG + CMAKE_C_MINSIZEREL + CMAKE_C_RELWITHDEBINFO + CMAKE_C_RELEASE + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_CXX_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS_RELEASE + CMAKE_C_LINK_FLAGS + CMAKE_CXX_LINK_FLAGS + CMAKE_ASM_FLAGS +) + +if(NAMED_LANGUAGE_SUPPORT_INT) + list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES + CMAKE_OBJC_FLAGS + CMAKE_OBJC_DEBUG + CMAKE_OBJC_MINSIZEREL + CMAKE_OBJC_RELWITHDEBINFO + CMAKE_OBJC_RELEASE + CMAKE_OBJCXX_FLAGS + CMAKE_OBJCXX_DEBUG + CMAKE_OBJCXX_MINSIZEREL + CMAKE_OBJCXX_RELWITHDEBINFO + CMAKE_OBJCXX_RELEASE + CMAKE_OBJC_LINK_FLAGS + CMAKE_OBJCXX_LINK_FLAGS + ) +endif() + +set(CMAKE_PLATFORM_HAS_INSTALLNAME 1) +set(CMAKE_SHARED_LINKER_FLAGS "-rpath @executable_path/Frameworks -rpath @loader_path/Frameworks") +set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -Wl,-headerpad_max_install_names") +set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -Wl,-headerpad_max_install_names") +set(CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,") +set(CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,") +set(CMAKE_FIND_LIBRARY_SUFFIXES ".tbd" ".dylib" ".so" ".a") +set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "-install_name") + +# Set the find root to the SDK developer roots. +# Note: CMAKE_FIND_ROOT_PATH is only useful when cross-compiling. Thus, do not set on macOS builds. +if(NOT PLATFORM_INT MATCHES "^MAC.*$") + list(APPEND CMAKE_FIND_ROOT_PATH "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "") + set(CMAKE_IGNORE_PATH "/System/Library/Frameworks;/usr/local/lib;/opt/homebrew" CACHE INTERNAL "") +endif() + +# Default to searching for frameworks first. +IF(NOT DEFINED CMAKE_FIND_FRAMEWORK) + set(CMAKE_FIND_FRAMEWORK FIRST) +ENDIF(NOT DEFINED CMAKE_FIND_FRAMEWORK) + +# Set up the default search directories for frameworks. +if(PLATFORM_INT MATCHES "^MAC_CATALYST") + set(CMAKE_FRAMEWORK_PATH + ${CMAKE_DEVELOPER_ROOT}/Library/PrivateFrameworks + ${CMAKE_OSX_SYSROOT_INT}/System/Library/Frameworks + ${CMAKE_OSX_SYSROOT_INT}/System/iOSSupport/System/Library/Frameworks + ${CMAKE_FRAMEWORK_PATH} CACHE INTERNAL "") +else() + set(CMAKE_FRAMEWORK_PATH + ${CMAKE_DEVELOPER_ROOT}/Library/PrivateFrameworks + ${CMAKE_OSX_SYSROOT_INT}/System/Library/Frameworks + ${CMAKE_FRAMEWORK_PATH} CACHE INTERNAL "") +endif() + +# By default, search both the specified iOS SDK and the remainder of the host filesystem. +if(NOT CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH CACHE INTERNAL "") +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH CACHE INTERNAL "") +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH CACHE INTERNAL "") +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH CACHE INTERNAL "") +endif() + +# +# Some helper-macros below to simplify and beautify the CMakeFile +# + +# This little macro lets you set any Xcode specific property. +macro(set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE XCODE_RELVERSION) + set(XCODE_RELVERSION_I "${XCODE_RELVERSION}") + if(XCODE_RELVERSION_I STREQUAL "All") + set_property(TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY} "${XCODE_VALUE}") + else() + set_property(TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY}[variant=${XCODE_RELVERSION_I}] "${XCODE_VALUE}") + endif() +endmacro(set_xcode_property) + +# This macro lets you find executable programs on the host system. +macro(find_host_package) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER) + set(_TOOLCHAIN_IOS ${IOS}) + set(IOS OFF) + find_package(${ARGN}) + set(IOS ${_TOOLCHAIN_IOS}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) +endmacro(find_host_package) From 7f97290516df0c84f9f727f32b12832271fc982f Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 21 Feb 2026 23:39:48 +0000 Subject: [PATCH 09/74] license --- .ci/ios/build.sh | 3 +++ .ci/ios/ios-toolchain.cmake | 3 +++ 2 files changed, 6 insertions(+) mode change 100644 => 100755 .ci/ios/build.sh diff --git a/.ci/ios/build.sh b/.ci/ios/build.sh old mode 100644 new mode 100755 index f179a6c929..24837e5408 --- a/.ci/ios/build.sh +++ b/.ci/ios/build.sh @@ -1,5 +1,8 @@ #!/bin/sh -ex +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + WORK_DIR="$PWD" if [ -z "$NPROC" ]; then NPROC="$(nproc)" diff --git a/.ci/ios/ios-toolchain.cmake b/.ci/ios/ios-toolchain.cmake index 3ee3940a38..b0defa25aa 100644 --- a/.ci/ios/ios-toolchain.cmake +++ b/.ci/ios/ios-toolchain.cmake @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # This file is part of the ios-cmake project. It was retrieved from # https://github.com/leetal/ios-cmake.git, which is a fork of # https://github.com/gerstrong/ios-cmake.git, which is a fork of From 3f1f3db6d84b7d616288d84c9e6c1e56b55ee535 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 21 Feb 2026 23:40:46 +0000 Subject: [PATCH 10/74] fx --- .ci/ios/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/ios/build.sh b/.ci/ios/build.sh index 24837e5408..bb905c1894 100755 --- a/.ci/ios/build.sh +++ b/.ci/ios/build.sh @@ -9,7 +9,7 @@ if [ -z "$NPROC" ]; then fi cmake -G Xcode -B build \ - -DCMAKE_TOOLCHAIN_FILE="$WORK_DIR/ios-toolchain.cmake" \ + -DCMAKE_TOOLCHAIN_FILE="$WORK_DIR/.ci/ios/ios-toolchain.cmake" \ -DPLATFORM=OS64 \ -DENABLE_LIBUSB=OFF \ -DENABLE_UPDATE_CHECKER=OFF \ From 74a0456a5fd25ed2fcd82f696ceadd71dbd54132 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sun, 22 Feb 2026 02:53:02 +0000 Subject: [PATCH 11/74] fx --- .ci/ios/build.sh | 3 +++ .../cmake-modules/DetectArchitecture.cmake | 19 ++++++++++++------- externals/ffmpeg/CMakeLists.txt | 4 ++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.ci/ios/build.sh b/.ci/ios/build.sh index bb905c1894..64c52733e6 100755 --- a/.ci/ios/build.sh +++ b/.ci/ios/build.sh @@ -8,9 +8,12 @@ if [ -z "$NPROC" ]; then NPROC="$(nproc)" fi +[ ! -z "$IOS_SDK" ] + cmake -G Xcode -B build \ -DCMAKE_TOOLCHAIN_FILE="$WORK_DIR/.ci/ios/ios-toolchain.cmake" \ -DPLATFORM=OS64 \ + -DCOCOA_LIBRARY="$IOS_SDK/System/Library/Frameworks/Cocoa.framework" \ -DENABLE_LIBUSB=OFF \ -DENABLE_UPDATE_CHECKER=OFF \ -DENABLE_QT=OFF \ diff --git a/externals/cmake-modules/DetectArchitecture.cmake b/externals/cmake-modules/DetectArchitecture.cmake index 105963c8c2..a2f78b86de 100644 --- a/externals/cmake-modules/DetectArchitecture.cmake +++ b/externals/cmake-modules/DetectArchitecture.cmake @@ -38,13 +38,18 @@ This file is based off of Yuzu and Dynarmic. if (CMAKE_OSX_ARCHITECTURES) set(MULTIARCH_BUILD 1) set(ARCHITECTURE "${CMAKE_OSX_ARCHITECTURES}") - - # hope and pray the architecture names match - foreach(ARCH IN ${CMAKE_OSX_ARCHITECTURES}) - set(ARCHITECTURE_${ARCH} 1 PARENT_SCOPE) - add_definitions(-DARCHITECTURE_${ARCH}=1) - endforeach() - + if (IOS) + # TODO: Right... the toolchain file won't properly accomodate OSX_ARCHITECTURE + # they aren't defining it as a list properly I assume? + set(ARCHITECTURE_arm64 1 PARENT_SCOPE) + add_definitions(-DARCHITECTURE_arm64=1) + else () + # hope and pray the architecture names match + foreach(ARCH IN ${CMAKE_OSX_ARCHITECTURES}) + set(ARCHITECTURE_${ARCH} 1 PARENT_SCOPE) + add_definitions(-DARCHITECTURE_${ARCH}=1) + endforeach() + endif() return() endif() diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index 3140f8e545..328cf5ec21 100644 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -11,9 +11,9 @@ set(FFmpeg_HWACCEL_FLAGS) set(FFmpeg_HWACCEL_INCLUDE_DIRS) set(FFmpeg_HWACCEL_LDFLAGS) -if (UNIX AND NOT ANDROID) +if (UNIX AND NOT ANDROID AND NOT IOS) find_package(PkgConfig REQUIRED) - if (NOT ANDROID) + if (NOT ANDROID AND NOT IOS) pkg_check_modules(LIBVA libva) pkg_check_modules(CUDA cuda) pkg_check_modules(FFNVCODEC ffnvcodec) From d001c9bcbae30ede998dc6b099228a2af4473139 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sun, 22 Feb 2026 03:18:09 +0000 Subject: [PATCH 12/74] fix ffmpeg --- CMakeLists.txt | 7 ++++++- externals/ffmpeg/CMakeLists.txt | 21 +++++++++++++++++---- src/ios/CMakeLists.txt | 2 +- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4490df21cb..6b22be72a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -487,7 +487,12 @@ if (APPLE) # Umbrella framework for everything GUI-related find_library(COCOA_LIBRARY Cocoa REQUIRED) find_library(IOKIT_LIBRARY IOKit REQUIRED) - set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY}) + if (IOS) + find_library(OBJC_LIBRARY objc REQUIRED) + set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY} ${OBJC_LIBRARY}) + else() + set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY}) + endif() elseif (WIN32) # Target Windows 10 add_compile_definitions(_WIN32_WINNT=0x0A00 WINVER=0x0A00) diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index 328cf5ec21..7428c44695 100644 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -182,6 +182,10 @@ else() find_program(BASH_PROGRAM bash REQUIRED) set(FFmpeg_CROSS_COMPILE_FLAGS "") + # `configure` parameters builds only exactly what yuzu needs from FFmpeg + # `--disable-vdpau` is needed to avoid linking issues + set(FFmpeg_CC ${CMAKE_C_COMPILER_LAUNCHER} ${CMAKE_C_COMPILER}) + set(FFmpeg_CXX ${CMAKE_CXX_COMPILER_LAUNCHER} ${CMAKE_CXX_COMPILER}) if (ANDROID) string(TOLOWER "${CMAKE_HOST_SYSTEM_NAME}" FFmpeg_HOST_SYSTEM_NAME) set(TOOLCHAIN "${ANDROID_NDK}/toolchains/llvm/prebuilt/${FFmpeg_HOST_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}") @@ -197,12 +201,21 @@ else() --extra-ldflags="--ld-path=${TOOLCHAIN}/bin/ld.lld" --extra-ldflags="-nostdlib" ) + elseif(IOS) + execute_process(COMMAND xcrun --sdk iphoneos --show-sdk-path OUTPUT_VARIABLE SYSROOT) + set(FFmpeg_CC "xcrun --sdk iphoneos clang -arch arm64") + set(FFmpeg_CXX "xcrun --sdk iphoneos clang++ -arch arm64") + list(APPEND FFmpeg_CROSS_COMPILE_FLAGS + --arch=arm64 + --enable-cross-compile + --sysroot=${SYSROOT} + --target-os=darwin + --extra-ldflags="-miphoneos-version-min=16.0" + --install-name-dir="@rpath" + --disable-audiotoolbox + ) endif() - # `configure` parameters builds only exactly what yuzu needs from FFmpeg - # `--disable-vdpau` is needed to avoid linking issues - set(FFmpeg_CC ${CMAKE_C_COMPILER_LAUNCHER} ${CMAKE_C_COMPILER}) - set(FFmpeg_CXX ${CMAKE_CXX_COMPILER_LAUNCHER} ${CMAKE_CXX_COMPILER}) add_custom_command( OUTPUT ${FFmpeg_MAKEFILE} diff --git a/src/ios/CMakeLists.txt b/src/ios/CMakeLists.txt index 6ddc7eb109..dc2f8b53fc 100644 --- a/src/ios/CMakeLists.txt +++ b/src/ios/CMakeLists.txt @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later -enable_language(Swift OBJC) +enable_language(Swift OBJCXX) add_executable(eden-ios AppUI-Bridging-Header.h From 2089d508a246accbe05d9e17d90faf366c59d348 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sun, 22 Feb 2026 22:02:56 +0000 Subject: [PATCH 13/74] fix license --- .ci/license-header.sh | 2 +- externals/ffmpeg/CMakeLists.txt | 2 +- src/ios/AppUIGameInformation.mm | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.ci/license-header.sh b/.ci/license-header.sh index 9660f5db58..17129e1bf6 100755 --- a/.ci/license-header.sh +++ b/.ci/license-header.sh @@ -193,7 +193,7 @@ if [ "$UPDATE" = "true" ]; then begin="#" shell=true ;; - *.kt*|*.cpp|*.h) + *.kt*|*.cpp|*.h|*.swift|*.mm) begin="//" shell="false" ;; diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index 7428c44695..9387ab3db0 100644 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -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: 2021 yuzu Emulator Project diff --git a/src/ios/AppUIGameInformation.mm b/src/ios/AppUIGameInformation.mm index 3397faa1a7..2a76e7dc7d 100644 --- a/src/ios/AppUIGameInformation.mm +++ b/src/ios/AppUIGameInformation.mm @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later From 94cc8727e8f3b76bed505aa5dec992e5c754b762 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sun, 22 Feb 2026 22:50:56 +0000 Subject: [PATCH 14/74] fixes for ios spirv tools --- .patch/spirv-tools/0003-ios-fix.patch | 21 +++++++++++++++++++++ externals/cpmfile.json | 3 ++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 .patch/spirv-tools/0003-ios-fix.patch diff --git a/.patch/spirv-tools/0003-ios-fix.patch b/.patch/spirv-tools/0003-ios-fix.patch new file mode 100644 index 0000000000..2486eba82a --- /dev/null +++ b/.patch/spirv-tools/0003-ios-fix.patch @@ -0,0 +1,21 @@ +diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt +index 7ab2319..0d6b382 100644 +--- a/source/CMakeLists.txt ++++ b/source/CMakeLists.txt +@@ -151,9 +151,6 @@ add_custom_command(OUTPUT ${SPIRV_TOOLS_BUILD_VERSION_INC} + COMMENT "Update build-version.inc in the SPIRV-Tools build directory (if necessary).") + # Convenience target for standalone generation of the build-version.inc file. + # This is not required for any dependence chain. +-add_custom_target(spirv-tools-build-version +- DEPENDS ${SPIRV_TOOLS_BUILD_VERSION_INC}) +-set_property(TARGET spirv-tools-build-version PROPERTY FOLDER "SPIRV-Tools build") + + list(APPEND PCH_DEPENDS + ${CORE_TABLES_HEADER_INC_FILE} +@@ -339,7 +336,7 @@ function(spirv_tools_default_target_options target) + set_property(TARGET ${target} PROPERTY FOLDER "SPIRV-Tools libraries") + spvtools_check_symbol_exports(${target}) + add_dependencies(${target} +- spirv-tools-build-version core_tables extinst_tables) ++ core_tables extinst_tables) + endfunction() diff --git a/externals/cpmfile.json b/externals/cpmfile.json index 9644647638..106c725562 100644 --- a/externals/cpmfile.json +++ b/externals/cpmfile.json @@ -110,7 +110,8 @@ ], "patches": [ "0001-netbsd-fix.patch", - "0002-allow-static-only.patch" + "0002-allow-static-only.patch", + "0003-ios-fix.patch" ] }, "spirv-headers": { From dbd0c65df0039abe523103b745dfb9a612d9742f Mon Sep 17 00:00:00 2001 From: lizzie Date: Sun, 22 Feb 2026 23:03:43 +0000 Subject: [PATCH 15/74] fix spirv-tools --- .ci/ios/build.sh | 1 + .patch/spirv-tools/0003-ios-fix.patch | 24 ++++++++++++++++++------ CMakeLists.txt | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/.ci/ios/build.sh b/.ci/ios/build.sh index 64c52733e6..61bd45f593 100755 --- a/.ci/ios/build.sh +++ b/.ci/ios/build.sh @@ -22,6 +22,7 @@ cmake -G Xcode -B build \ -DENABLE_CUBEB=OFF \ -DYUZU_ROOM=OFF \ -DYUZU_ROOM_STANDALONE=OFF \ + -DYUZU_STATIC_ROOM=OFF \ -DYUZU_CMD=OFF \ -DUSE_DISCORD_PRESENCE=OFF \ -DYUZU_USE_EXTERNAL_FFMPEG=ON \ diff --git a/.patch/spirv-tools/0003-ios-fix.patch b/.patch/spirv-tools/0003-ios-fix.patch index 2486eba82a..7d5710ef68 100644 --- a/.patch/spirv-tools/0003-ios-fix.patch +++ b/.patch/spirv-tools/0003-ios-fix.patch @@ -1,21 +1,33 @@ diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt -index 7ab2319..0d6b382 100644 +index 7ab2319..333e325 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt -@@ -151,9 +151,6 @@ add_custom_command(OUTPUT ${SPIRV_TOOLS_BUILD_VERSION_INC} +@@ -151,9 +151,11 @@ add_custom_command(OUTPUT ${SPIRV_TOOLS_BUILD_VERSION_INC} COMMENT "Update build-version.inc in the SPIRV-Tools build directory (if necessary).") # Convenience target for standalone generation of the build-version.inc file. # This is not required for any dependence chain. -add_custom_target(spirv-tools-build-version - DEPENDS ${SPIRV_TOOLS_BUILD_VERSION_INC}) -set_property(TARGET spirv-tools-build-version PROPERTY FOLDER "SPIRV-Tools build") ++if (NOT IOS) ++ add_custom_target(spirv-tools-build-version ++ DEPENDS ${SPIRV_TOOLS_BUILD_VERSION_INC}) ++ set_property(TARGET spirv-tools-build-version PROPERTY FOLDER "SPIRV-Tools build") ++endif() list(APPEND PCH_DEPENDS - ${CORE_TABLES_HEADER_INC_FILE} -@@ -339,7 +336,7 @@ function(spirv_tools_default_target_options target) + ${CORE_TABLES_HEADER_INC_FILE} +@@ -338,8 +340,11 @@ function(spirv_tools_default_target_options target) + ) set_property(TARGET ${target} PROPERTY FOLDER "SPIRV-Tools libraries") spvtools_check_symbol_exports(${target}) - add_dependencies(${target} +- add_dependencies(${target} - spirv-tools-build-version core_tables extinst_tables) -+ core_tables extinst_tables) ++ if (IOS) ++ add_dependencies(${target} core_tables extinst_tables) ++ else () ++ add_dependencies(${target} spirv-tools-build-version core_tables extinst_tables) ++ endif() endfunction() + + if (SPIRV_TOOLS_BUILD_SHARED) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b22be72a3..e64cac04c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -212,7 +212,7 @@ option(YUZU_LEGACY "Apply patches that improve compatibility with older GPUs (e. option(NIGHTLY_BUILD "Use Nightly qualifiers in the update checker and build metadata" OFF) -cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF) +cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID AND NOT IOS" OFF) cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF) cmake_dependent_option(YUZU_CMD "Compile the eden-cli executable" ON "NOT ANDROID" OFF) From 4a065abff2aeab4acda64b646175566898d00178 Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 23 Feb 2026 02:55:16 +0000 Subject: [PATCH 16/74] fx --- externals/ffmpeg/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index 9387ab3db0..f91e58bdaa 100644 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -203,8 +203,8 @@ else() ) elseif(IOS) execute_process(COMMAND xcrun --sdk iphoneos --show-sdk-path OUTPUT_VARIABLE SYSROOT) - set(FFmpeg_CC "xcrun --sdk iphoneos clang -arch arm64") - set(FFmpeg_CXX "xcrun --sdk iphoneos clang++ -arch arm64") + set(FFmpeg_CC xcrun --sdk iphoneos clang -arch arm64) + set(FFmpeg_CXX xcrun --sdk iphoneos clang++ -arch arm64) list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --arch=arm64 --enable-cross-compile From 94dcf09617e13875697525ce4185c2c317711bd7 Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 23 Feb 2026 04:31:43 +0000 Subject: [PATCH 17/74] fix1 --- .ci/ios/build.sh | 1 - CMakeLists.txt | 2 +- docs/Caveats.md | 11 +++++++++++ externals/cmake-modules/DetectPlatform.cmake | 6 ++++++ externals/ffmpeg/CMakeLists.txt | 7 ++++--- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/.ci/ios/build.sh b/.ci/ios/build.sh index 61bd45f593..808fe05976 100755 --- a/.ci/ios/build.sh +++ b/.ci/ios/build.sh @@ -26,7 +26,6 @@ cmake -G Xcode -B build \ -DYUZU_CMD=OFF \ -DUSE_DISCORD_PRESENCE=OFF \ -DYUZU_USE_EXTERNAL_FFMPEG=ON \ - -DYUZU_USE_CPM=ON \ -DYUZU_USE_EXTERNAL_SDL2=ON \ -DCPMUTIL_FORCE_BUNDLED=ON \ -DCMAKE_BUILD_TYPE=Release diff --git a/CMakeLists.txt b/CMakeLists.txt index e64cac04c6..5bf2f2b152 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -283,7 +283,7 @@ if (YUZU_ROOM) add_compile_definitions(YUZU_ROOM) endif() -if ((ANDROID OR APPLE OR UNIX) AND (NOT PLATFORM_LINUX OR ANDROID) AND NOT WIN32) +if ((ANDROID OR APPLE OR UNIX OR IOS) AND (NOT PLATFORM_LINUX OR ANDROID) AND NOT WIN32) if(CXX_APPLE OR CXX_CLANG) # libc++ has stop_token and jthread as experimental set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library") diff --git a/docs/Caveats.md b/docs/Caveats.md index 39b5ab15e6..2223c04d9a 100644 --- a/docs/Caveats.md +++ b/docs/Caveats.md @@ -4,6 +4,7 @@ - [Arch Linux](#arch-linux) - [Gentoo Linux](#gentoo-linux) - [macOS](#macos) +- [iOS](#ios) - [Solaris](#solaris) - [HaikuOS](#haikuos) - [OpenBSD](#openbsd) @@ -31,6 +32,16 @@ If you're having issues with building, always consult that ebuild. macOS is largely untested. Expect crashes, significant Vulkan issues, and other fun stuff. +## iOS + +iOS has a dedicated build script, we **highly** recommend using that instead of doing anything else, we don't support any other configuration than the one present in said build script. + +To build, it's simply as easy as doing +```sh +chmod +x .ci/ios/build.sh +.ci/ios/build.sh +``` + ## Solaris Always consult [the OpenIndiana package list](https://pkg.openindiana.org/hipster/en/index.shtml) to cross-verify availability. diff --git a/externals/cmake-modules/DetectPlatform.cmake b/externals/cmake-modules/DetectPlatform.cmake index 6475884f1f..bac5180770 100644 --- a/externals/cmake-modules/DetectPlatform.cmake +++ b/externals/cmake-modules/DetectPlatform.cmake @@ -51,6 +51,12 @@ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") set(CXX_APPLE ON) endif() +# This fixes some quirks with xcrun or weird iOS toolchain cmake files +if (IOS) + unser(CXX_CLANG) + set(CXX_APPLE ON) +endif() + # https://gitlab.kitware.com/cmake/cmake/-/merge_requests/11112 # This works totally fine on MinGW64, but not CLANG{,ARM}64 if(MINGW AND CXX_CLANG) diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index f91e58bdaa..b8d7ae3a12 100644 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -203,15 +203,16 @@ else() ) elseif(IOS) execute_process(COMMAND xcrun --sdk iphoneos --show-sdk-path OUTPUT_VARIABLE SYSROOT) + # Lovely extra newline apple adds that **we** must remove... thank you apple! + string(STRIP "${SYSROOT}" SYSROOT) set(FFmpeg_CC xcrun --sdk iphoneos clang -arch arm64) set(FFmpeg_CXX xcrun --sdk iphoneos clang++ -arch arm64) list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --arch=arm64 --enable-cross-compile - --sysroot=${SYSROOT} - --target-os=darwin + --sysroot="${SYSROOT}" --extra-ldflags="-miphoneos-version-min=16.0" - --install-name-dir="@rpath" + --install-name-dir='@rpath' --disable-audiotoolbox ) endif() From ab77cb7537c2abf504d8518c72f581fc67ef91c7 Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 23 Feb 2026 05:15:17 +0000 Subject: [PATCH 18/74] stupid macos --- .ci/ios/build.sh | 1 + externals/cmake-modules/DetectPlatform.cmake | 2 +- src/common/host_memory.cpp | 6 +++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.ci/ios/build.sh b/.ci/ios/build.sh index 808fe05976..ca37ce9da2 100755 --- a/.ci/ios/build.sh +++ b/.ci/ios/build.sh @@ -13,6 +13,7 @@ fi cmake -G Xcode -B build \ -DCMAKE_TOOLCHAIN_FILE="$WORK_DIR/.ci/ios/ios-toolchain.cmake" \ -DPLATFORM=OS64 \ + -DDEPLOYMENT_TARGET=16.0 \ -DCOCOA_LIBRARY="$IOS_SDK/System/Library/Frameworks/Cocoa.framework" \ -DENABLE_LIBUSB=OFF \ -DENABLE_UPDATE_CHECKER=OFF \ diff --git a/externals/cmake-modules/DetectPlatform.cmake b/externals/cmake-modules/DetectPlatform.cmake index bac5180770..eec94d839c 100644 --- a/externals/cmake-modules/DetectPlatform.cmake +++ b/externals/cmake-modules/DetectPlatform.cmake @@ -53,7 +53,7 @@ endif() # This fixes some quirks with xcrun or weird iOS toolchain cmake files if (IOS) - unser(CXX_CLANG) + unset(CXX_CLANG) set(CXX_APPLE ON) endif() diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index a75152eec0..d31ec5f224 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -27,11 +27,15 @@ #include #elif defined(__APPLE__) #include -#include #include #include #endif +// Not available on iOS for some fucking stupid reason... +#if defined(__APPLE__) && TARGET_OS_MAC +#include +#endif + // FreeBSD #ifndef MAP_NORESERVE #define MAP_NORESERVE 0 From e7717708f13cc4d53a31711a4c69a31d4e0cc69b Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 23 Feb 2026 05:24:15 +0000 Subject: [PATCH 19/74] fix stuff --- .ci/ios/build.sh | 2 ++ src/common/device_power_state.cpp | 3 +-- src/common/host_memory.cpp | 10 +++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.ci/ios/build.sh b/.ci/ios/build.sh index ca37ce9da2..2f15472178 100755 --- a/.ci/ios/build.sh +++ b/.ci/ios/build.sh @@ -15,6 +15,8 @@ cmake -G Xcode -B build \ -DPLATFORM=OS64 \ -DDEPLOYMENT_TARGET=16.0 \ -DCOCOA_LIBRARY="$IOS_SDK/System/Library/Frameworks/Cocoa.framework" \ + -DCMAKE_C_COMPILER="$(xcrun --sdk iphoneos clang -arch arm64)" \ + -DCMAKE_CXX_COMPILER="$(xcrun --sdk iphoneos clang++ -arch arm64)" \ -DENABLE_LIBUSB=OFF \ -DENABLE_UPDATE_CHECKER=OFF \ -DENABLE_QT=OFF \ diff --git a/src/common/device_power_state.cpp b/src/common/device_power_state.cpp index 2dfa7dc305..8419434ef2 100644 --- a/src/common/device_power_state.cpp +++ b/src/common/device_power_state.cpp @@ -14,11 +14,10 @@ extern std::atomic g_has_battery; #elif defined(__APPLE__) #include -#if TARGET_OS_MAC +#if defined(TARGET_OS_MAC) && TARGET_OS_MAC #include #include #endif - #elif defined(__linux__) #include #include diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index d31ec5f224..e680006b8f 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -27,15 +27,15 @@ #include #elif defined(__APPLE__) #include +#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +// Not available on iOS for some fucking stupid reason... +#else +#include +#endif #include #include #endif -// Not available on iOS for some fucking stupid reason... -#if defined(__APPLE__) && TARGET_OS_MAC -#include -#endif - // FreeBSD #ifndef MAP_NORESERVE #define MAP_NORESERVE 0 From 2649b82ff5a8dbf6c849317fb39a0f0b11213e1e Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 23 Feb 2026 07:02:00 +0000 Subject: [PATCH 20/74] fx --- .ci/ios/build.sh | 6 ++---- src/common/device_power_state.cpp | 9 +++++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.ci/ios/build.sh b/.ci/ios/build.sh index 2f15472178..9ab6cfaaa3 100755 --- a/.ci/ios/build.sh +++ b/.ci/ios/build.sh @@ -4,9 +4,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later WORK_DIR="$PWD" -if [ -z "$NPROC" ]; then - NPROC="$(nproc)" -fi +export IOS_SDK="$(xcrun --sdk iphoneos --show-sdk-path)" [ ! -z "$IOS_SDK" ] @@ -33,4 +31,4 @@ cmake -G Xcode -B build \ -DCPMUTIL_FORCE_BUNDLED=ON \ -DCMAKE_BUILD_TYPE=Release -cmake --build build -- -j${NPROC} +cmake --build build diff --git a/src/common/device_power_state.cpp b/src/common/device_power_state.cpp index 8419434ef2..c777cb4009 100644 --- a/src/common/device_power_state.cpp +++ b/src/common/device_power_state.cpp @@ -15,9 +15,13 @@ extern std::atomic g_has_battery; #elif defined(__APPLE__) #include #if defined(TARGET_OS_MAC) && TARGET_OS_MAC +#if TARGET_OS_IPHONE +// ios doesnt have this +#else #include #include #endif +#endif #elif defined(__linux__) #include #include @@ -47,7 +51,9 @@ namespace Common { info.percentage = g_battery_percentage.load(std::memory_order_relaxed); info.charging = g_is_charging.load(std::memory_order_relaxed); info.has_battery = g_has_battery.load(std::memory_order_relaxed); - +#elif defined(__APPLE__) && TARGET_OS_IPHONE + // Not implemented + info.has_battery = false; #elif defined(__APPLE__) && TARGET_OS_MAC CFTypeRef info_ref = IOPSCopyPowerSourcesInfo(); CFArrayRef sources = IOPSCopyPowerSourcesList(info_ref); @@ -95,7 +101,6 @@ namespace Common { #else info.has_battery = false; #endif - return info; } } From f23af26096ec1beac8de57df4c19f574a54b42f0 Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 23 Feb 2026 18:27:43 +0000 Subject: [PATCH 21/74] fix boost --- .patch/boost/0002-ios-fix.patch | 0 cpmfile.json | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .patch/boost/0002-ios-fix.patch diff --git a/.patch/boost/0002-ios-fix.patch b/.patch/boost/0002-ios-fix.patch new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cpmfile.json b/cpmfile.json index 774f160360..e87d031695 100644 --- a/cpmfile.json +++ b/cpmfile.json @@ -17,7 +17,8 @@ "version": "1.57", "find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem", "patches": [ - "0001-clang-cl.patch" + "0001-clang-cl.patch", + "0002-ios-fix.patch" ] }, "fmt": { From 93f69afe379c5004dd7195fd36d516b4fd6ce4c4 Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 24 Feb 2026 00:43:59 +0000 Subject: [PATCH 22/74] fx --- .patch/boost/0002-ios-fix.patch | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.patch/boost/0002-ios-fix.patch b/.patch/boost/0002-ios-fix.patch index e69de29bb2..7a68ef6137 100644 --- a/.patch/boost/0002-ios-fix.patch +++ b/.patch/boost/0002-ios-fix.patch @@ -0,0 +1,22 @@ +diff --git a/libs/process/src/shell.cpp b/libs/process/src/shell.cpp +index bf4bbfd8..e45ba39c 100644 +--- a/libs/process/src/shell.cpp ++++ b/libs/process/src/shell.cpp +@@ -19,7 +19,7 @@ + #if defined(BOOST_PROCESS_V2_WINDOWS) + #include + #include +-#elif !defined(__OpenBSD__) && !defined(__ANDROID__) ++#elif !defined(__OpenBSD__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IPHONE) + #include + #endif + +@@ -99,7 +99,7 @@ auto shell::args() const-> args_type + return input_.c_str(); + } + +-#elif !defined(__OpenBSD__) && !defined(__ANDROID__) ++#elif !defined(__OpenBSD__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IPHONE) + + void shell::parse_() + { From 8d99782267b3a495d0ea634f89686cca12bd5087 Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 24 Feb 2026 00:58:36 +0000 Subject: [PATCH 23/74] fx --- .patch/boost/0002-ios-fix.patch | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.patch/boost/0002-ios-fix.patch b/.patch/boost/0002-ios-fix.patch index 7a68ef6137..4f5d495154 100644 --- a/.patch/boost/0002-ios-fix.patch +++ b/.patch/boost/0002-ios-fix.patch @@ -1,5 +1,5 @@ diff --git a/libs/process/src/shell.cpp b/libs/process/src/shell.cpp -index bf4bbfd8..e45ba39c 100644 +index bf4bbfd8..bc4aae89 100644 --- a/libs/process/src/shell.cpp +++ b/libs/process/src/shell.cpp @@ -19,7 +19,7 @@ @@ -11,6 +11,15 @@ index bf4bbfd8..e45ba39c 100644 #include #endif +@@ -30,7 +30,7 @@ BOOST_PROCESS_V2_DECL const error_category& get_shell_category() + { + return system_category(); + } +-#elif !defined(__OpenBSD__) && !defined(__ANDROID__) ++#elif !defined(__OpenBSD__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IPHONE) + + struct shell_category_t final : public error_category + { @@ -99,7 +99,7 @@ auto shell::args() const-> args_type return input_.c_str(); } From 7e880929986f792f0135726af0535f5cf2bd3233 Mon Sep 17 00:00:00 2001 From: lizzie Date: Wed, 25 Feb 2026 20:27:26 +0000 Subject: [PATCH 24/74] fix dynarmic i hope --- src/dynarmic/src/dynarmic/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dynarmic/src/dynarmic/CMakeLists.txt b/src/dynarmic/src/dynarmic/CMakeLists.txt index 3d2ea4d42e..7abee86522 100644 --- a/src/dynarmic/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/src/dynarmic/CMakeLists.txt @@ -69,6 +69,7 @@ add_library(dynarmic STATIC frontend/decoder/matcher.h frontend/imm.cpp frontend/imm.h + interface/halt_reason.h interface/exclusive_monitor.h interface/optimization_flags.h ir/acc_type.h From 7de5eb6884bd3c4b4f8912406e30dc26497e9fbc Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Sat, 28 Feb 2026 16:05:06 +0100 Subject: [PATCH 25/74] [android] fix persist manual game pause after android sleep/wake (#3651) if user invokes the "pause game" option from the menu while in game, as expected this suspends the process till user manually hits resume.. except for one case: Android sleep/wake lifecycle. If user manually pauses a running game, then sleeps their device, then wakes their device; the game will self-resume without user pressing "resume game". Expected behavior IMO is that if user left the game process in manually paused state, app should respect this and persist the pause on system wake, so that user may manually press "resume game" to unfreeze the process. Simple fix is to have a few params for user initiated pause and resume, and update the pause and run methods to handle as described above. Please let me know if there is a cleaner way to implement! Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3651 Reviewed-by: crueter Reviewed-by: CamilleLaVey Reviewed-by: DraVee Co-authored-by: xXJSONDeruloXx Co-committed-by: xXJSONDeruloXx --- .../java/org/yuzu/yuzu_emu/NativeLibrary.kt | 4 + .../yuzu_emu/activities/EmulationActivity.kt | 8 +- .../yuzu_emu/fragments/EmulationFragment.kt | 144 +++++++++++++++--- .../org/yuzu/yuzu_emu/overlay/InputOverlay.kt | 7 +- .../src/main/jni/emu_window/emu_window.cpp | 11 ++ src/android/app/src/main/jni/native.cpp | 41 ++++- .../src/main/res/drawable/circle_white.xml | 4 + .../main/res/layout/fragment_emulation.xml | 28 ++++ 8 files changed, 220 insertions(+), 27 deletions(-) create mode 100644 src/android/app/src/main/res/drawable/circle_white.xml diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index e49f466462..1f0acf2835 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -152,6 +152,10 @@ object NativeLibrary { external fun surfaceDestroyed() + external fun getAppletCaptureBuffer(): ByteArray + external fun getAppletCaptureWidth(): Int + external fun getAppletCaptureHeight(): Int + /** * Unpauses emulation from a paused state. */ diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 42d4f687f4..2764d7eac6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -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: 2023 yuzu Emulator Project @@ -204,9 +204,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager } override fun onPause() { - super.onPause() nfcReader.stopScanning() stopMotionSensorListener() + super.onPause() } override fun onDestroy() { @@ -339,6 +339,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager } override fun onSensorChanged(event: SensorEvent) { + if (!NativeLibrary.isRunning() || NativeLibrary.isPaused()) { + return + } + val rotation = this.display?.rotation if (rotation == Surface.ROTATION_90) { flipMotionOrientation = true diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index e8739e2d23..435fe5fe2c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -15,6 +15,7 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.ActivityInfo import android.content.res.Configuration +import android.graphics.Bitmap import android.net.Uri import android.os.BatteryManager import android.os.BatteryManager.* @@ -97,6 +98,7 @@ import org.yuzu.yuzu_emu.utils.collect import org.yuzu.yuzu_emu.utils.CustomSettingsHandler import java.io.ByteArrayOutputStream import java.io.File +import java.nio.ByteBuffer import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine import kotlin.or @@ -141,6 +143,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { private var wasInputOverlayAutoHidden = false private var overlayTouchActive = false + private var pausedFrameBitmap: Bitmap? = null var shouldUseCustom = false private var isQuickSettingsMenuOpen = false @@ -703,6 +706,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { binding.inGameMenu.menu.findItem(R.id.menu_quick_settings)?.isVisible = BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean() + binding.pausedIcon.setOnClickListener { + if (this::emulationState.isInitialized && emulationState.isPaused) { + resumeEmulationFromUi() + } + } + binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply { val lockMode = IntSetting.LOCK_DRAWER.getInt() val titleId = if (lockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) { @@ -728,11 +737,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { when (it.itemId) { R.id.menu_pause_emulation -> { if (emulationState.isPaused) { - emulationState.run(false) - updatePauseMenuEntry(false) + resumeEmulationFromUi() } else { - emulationState.pause() - updatePauseMenuEntry(true) + pauseEmulationAndCaptureFrame() } binding.inGameMenu.requestFocus() true @@ -826,6 +833,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } R.id.menu_exit -> { + clearPausedFrame() emulationState.stop() NativeConfig.reloadGlobalConfig() emulationViewModel.setIsEmulationStopping(true) @@ -1197,6 +1205,71 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } + private fun pauseEmulationAndCaptureFrame() { + emulationState.pause() + updatePauseMenuEntry(true) + capturePausedFrameFromCore() + updatePausedFrameVisibility() + } + + private fun capturePausedFrameFromCore() { + lifecycleScope.launch(Dispatchers.Default) { + val frameData = NativeLibrary.getAppletCaptureBuffer() + val width = NativeLibrary.getAppletCaptureWidth() + val height = NativeLibrary.getAppletCaptureHeight() + if (frameData.isEmpty() || width <= 0 || height <= 0) { + Log.warning( + "[EmulationFragment] Paused frame capture returned empty/invalid data. " + + "size=${frameData.size}, width=$width, height=$height" + ) + return@launch + } + + val expectedSize = width * height * 4 + if (frameData.size < expectedSize) { + Log.warning( + "[EmulationFragment] Paused frame buffer smaller than expected. " + + "size=${frameData.size}, expected=$expectedSize" + ) + return@launch + } + + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(frameData, 0, expectedSize)) + + withContext(Dispatchers.Main) { + pausedFrameBitmap?.recycle() + pausedFrameBitmap = bitmap + updatePausedFrameVisibility() + } + } + } + + private fun updatePausedFrameVisibility() { + val b = _binding ?: return + val showPausedUi = this::emulationState.isInitialized && emulationState.isPaused + b.pausedIcon.setVisible(showPausedUi) + + val bitmap = if (showPausedUi) pausedFrameBitmap else null + b.pausedFrameImage.setImageBitmap(bitmap) + b.pausedFrameImage.setVisible(bitmap != null) + } + + private fun resumeEmulationFromUi() { + clearPausedFrame() + emulationState.resume() + updatePauseMenuEntry(emulationState.isPaused) + updatePausedFrameVisibility() + } + + private fun clearPausedFrame() { + val b = _binding + b?.pausedFrameImage?.setVisible(false) + b?.pausedFrameImage?.setImageDrawable(null) + pausedFrameBitmap?.recycle() + pausedFrameBitmap = null + } + private fun handleLoadAmiiboSelection(): Boolean { val binding = _binding ?: return true @@ -1290,8 +1363,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { override fun onPause() { if (this::emulationState.isInitialized) { if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) { - emulationState.pause() - updatePauseMenuEntry(true) + pauseEmulationAndCaptureFrame() + } else { + updatePausedFrameVisibility() } } super.onPause() @@ -1301,6 +1375,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { super.onDestroyView() amiiboLoadJob?.cancel() amiiboLoadJob = null + clearPausedFrame() _binding?.surfaceInputOverlay?.touchEventListener = null _binding = null isAmiiboPickerOpen = false @@ -1321,6 +1396,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { b.inGameMenu.post { if (!this::emulationState.isInitialized || _binding == null) return@post updatePauseMenuEntry(emulationState.isPaused) + updatePausedFrameVisibility() } } @@ -1760,6 +1836,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { // Only update surface reference, don't trigger state changes emulationState.updateSurfaceReference(holder.surface) } + updatePausedFrameVisibility() } override fun surfaceDestroyed(holder: SurfaceHolder) { @@ -2090,6 +2167,29 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } + @Synchronized + fun resume() { + if (state != State.PAUSED) { + Log.warning("[EmulationFragment] Resume called while emulation is not paused.") + return + } + if (!emulationCanStart.invoke()) { + Log.warning("[EmulationFragment] Resume blocked by emulationCanStart check.") + return + } + val currentSurface = surface + if (currentSurface == null || !currentSurface.isValid) { + Log.debug("[EmulationFragment] Resume requested with invalid surface.") + return + } + + NativeLibrary.surfaceChanged(currentSurface) + Log.debug("[EmulationFragment] Resuming emulation.") + NativeLibrary.unpauseEmulation() + NativeLibrary.playTimeManagerStart() + state = State.RUNNING + } + @Synchronized fun changeProgram(programIndex: Int) { emulationThread.join() @@ -2111,7 +2211,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { @Synchronized fun updateSurface() { - if (surface != null) { + if (surface != null && state == State.RUNNING) { NativeLibrary.surfaceChanged(surface) } } @@ -2127,20 +2227,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { @Synchronized fun clearSurface() { if (surface == null) { - Log.warning("[EmulationFragment] clearSurface called, but surface already null.") + Log.debug("[EmulationFragment] clearSurface called, but surface already null.") } else { + if (state == State.RUNNING) { + pause() + } + NativeLibrary.surfaceDestroyed() surface = null Log.debug("[EmulationFragment] Surface destroyed.") when (state) { - State.RUNNING -> { - state = State.PAUSED - } - - State.PAUSED -> Log.warning( + State.PAUSED -> Log.debug( "[EmulationFragment] Surface cleared while emulation paused." ) - else -> Log.warning( + else -> Log.debug( "[EmulationFragment] Surface cleared while emulation stopped." ) } @@ -2148,29 +2248,35 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } private fun runWithValidSurface(programIndex: Int = 0) { - NativeLibrary.surfaceChanged(surface) if (!emulationCanStart.invoke()) { return } + val currentSurface = surface + if (currentSurface == null || !currentSurface.isValid) { + Log.debug("[EmulationFragment] runWithValidSurface called with invalid surface.") + return + } when (state) { State.STOPPED -> { + NativeLibrary.surfaceChanged(currentSurface) emulationThread = Thread({ Log.debug("[EmulationFragment] Starting emulation thread.") NativeLibrary.run(gamePath, programIndex, true) }, "NativeEmulation") emulationThread.start() + state = State.RUNNING } State.PAUSED -> { - Log.debug("[EmulationFragment] Resuming emulation.") - NativeLibrary.unpauseEmulation() - NativeLibrary.playTimeManagerStart() + Log.debug( + "[EmulationFragment] Surface restored while emulation paused; " + + "waiting for explicit resume." + ) } else -> Log.debug("[EmulationFragment] Bug, run called while already running.") } - state = State.RUNNING } private enum class State { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt index e18077c673..d3b5d86174 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt @@ -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 package org.yuzu.yuzu_emu.overlay @@ -20,7 +20,6 @@ import android.os.Looper import android.util.AttributeSet import android.view.HapticFeedbackConstants import android.view.MotionEvent -import android.view.SurfaceView import android.view.View import android.view.View.OnTouchListener import android.view.WindowInsets @@ -42,10 +41,10 @@ import org.yuzu.yuzu_emu.utils.NativeConfig /** * Draws the interactive input overlay on top of the - * [SurfaceView] that is rendering emulation. + * emulation rendering surface. */ class InputOverlay(context: Context, attrs: AttributeSet?) : - SurfaceView(context, attrs), + View(context, attrs), OnTouchListener { private val overlayButtons: MutableSet = HashSet() private val overlayDpads: MutableSet = HashSet() diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index 06db553691..4e90cad570 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -14,6 +17,14 @@ #include "jni/native.h" void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { + if (!surface) { + LOG_INFO(Frontend, "EmuWindow_Android::OnSurfaceChanged received null surface"); + m_window_width = 0; + m_window_height = 0; + window_info.render_surface = nullptr; + return; + } + m_window_width = ANativeWindow_getWidth(surface); m_window_height = ANativeWindow_getHeight(surface); diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 2e50bb1069..c429f4a1e4 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -89,6 +89,8 @@ #include "jni/native.h" #include "video_core/renderer_base.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" +#include "video_core/capture.h" +#include "video_core/textures/decoders.h" #include "video_core/vulkan_common/vulkan_instance.h" #include "video_core/vulkan_common/vulkan_surface.h" #include "video_core/shader_notify.h" @@ -780,9 +782,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject i } void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jobject instance) { - ANativeWindow_release(EmulationSession::GetInstance().NativeWindow()); + if (auto* native_window = EmulationSession::GetInstance().NativeWindow(); native_window) { + ANativeWindow_release(native_window); + } EmulationSession::GetInstance().SetNativeWindow(nullptr); - EmulationSession::GetInstance().SurfaceChanged(); } void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject instance, @@ -969,6 +972,40 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz return static_cast(EmulationSession::GetInstance().IsPaused()); } +jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureBuffer(JNIEnv* env, jclass clazz) { + using namespace VideoCore::Capture; + + if (!EmulationSession::GetInstance().IsRunning()) { + return env->NewByteArray(0); + } + + const auto tiled = EmulationSession::GetInstance().System().GPU().GetAppletCaptureBuffer(); + if (tiled.size() < TiledSize) { + return env->NewByteArray(0); + } + + std::vector linear(LinearWidth * LinearHeight * BytesPerPixel); + Tegra::Texture::UnswizzleTexture(linear, tiled, BytesPerPixel, LinearWidth, LinearHeight, + LinearDepth, BlockHeight, BlockDepth); + + auto buffer = env->NewByteArray(static_cast(linear.size())); + if (!buffer) { + return env->NewByteArray(0); + } + + env->SetByteArrayRegion(buffer, 0, static_cast(linear.size()), + reinterpret_cast(linear.data())); + return buffer; +} + +jint Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureWidth(JNIEnv* env, jclass clazz) { + return static_cast(VideoCore::Capture::LinearWidth); +} + +jint Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureHeight(JNIEnv* env, jclass clazz) { + return static_cast(VideoCore::Capture::LinearHeight); +} + void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz, jboolean reload) { // Initialize the emulated system. diff --git a/src/android/app/src/main/res/drawable/circle_white.xml b/src/android/app/src/main/res/drawable/circle_white.xml new file mode 100644 index 0000000000..c94e68679a --- /dev/null +++ b/src/android/app/src/main/res/drawable/circle_white.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml index 7f5f039d5e..db363bc723 100644 --- a/src/android/app/src/main/res/layout/fragment_emulation.xml +++ b/src/android/app/src/main/res/layout/fragment_emulation.xml @@ -108,6 +108,22 @@ + + + + + + + + Date: Sat, 28 Feb 2026 19:34:55 +0100 Subject: [PATCH 26/74] [android] add IntSetting for static theme and set green as default (#3655) Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3655 Reviewed-by: CamilleLaVey Co-authored-by: PavelBARABANOV Co-committed-by: PavelBARABANOV --- .../features/settings/model/IntSetting.kt | 1 + .../settings/ui/SettingsFragmentPresenter.kt | 19 ++++++++++--------- .../yuzu_emu/utils/DirectoryInitialization.kt | 6 ++++++ .../org/yuzu/yuzu_emu/utils/ThemeHelper.kt | 4 ++-- .../app/src/main/jni/android_settings.h | 1 + .../app/src/main/res/values/strings.xml | 4 ++-- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index a24a2e1776..4c70d2b35b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt @@ -34,6 +34,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting { MAX_ANISOTROPY("max_anisotropy"), THEME("theme"), THEME_MODE("theme_mode"), + STATIC_THEME_COLOR("static_theme_color"), APP_LANGUAGE("app_language"), OVERLAY_SCALE("control_scale"), OVERLAY_OPACITY("control_opacity"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 332617804e..0fc4fb0b7f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1124,23 +1124,24 @@ class SettingsFragmentPresenter( } val staticThemeColor: AbstractIntSetting = object : AbstractIntSetting { - val preferences = PreferenceManager.getDefaultSharedPreferences( - YuzuApplication.appContext - ) override fun getInt(needsGlobal: Boolean): Int = - preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0) + IntSetting.STATIC_THEME_COLOR.getInt(needsGlobal) + override fun setInt(value: Int) { - preferences.edit() { putInt(Settings.PREF_STATIC_THEME_COLOR, value) } + IntSetting.STATIC_THEME_COLOR.setInt(value) settingsViewModel.setShouldRecreate(true) } - override val key: String = Settings.PREF_STATIC_THEME_COLOR + override val key: String = IntSetting.STATIC_THEME_COLOR.key override val isRuntimeModifiable: Boolean = true + override fun getValueAsString(needsGlobal: Boolean): String = - preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0).toString() - override val defaultValue: Any = 0 + IntSetting.STATIC_THEME_COLOR.getValueAsString(needsGlobal) + + override val defaultValue: Any = IntSetting.STATIC_THEME_COLOR.defaultValue + override fun reset() { - preferences.edit() { putInt(Settings.PREF_STATIC_THEME_COLOR, 0) } + IntSetting.STATIC_THEME_COLOR.reset() settingsViewModel.setShouldRecreate(true) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt index 6318aa71f2..f47c60491b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt @@ -61,6 +61,12 @@ object DirectoryInitialization { saveConfig = true } + val staticThemeColor = preferences.migratePreference(Settings.PREF_STATIC_THEME_COLOR) + if (staticThemeColor != null) { + IntSetting.STATIC_THEME_COLOR.setInt(staticThemeColor) + saveConfig = true + } + val blackBackgrounds = preferences.migratePreference(Settings.PREF_BLACK_BACKGROUNDS) if (blackBackgrounds != null) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt index 3e138c0244..83335b5e1c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.utils @@ -52,7 +52,7 @@ object ThemeHelper { } private fun getSelectedStaticThemeColor(): Int { - val themeIndex = preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0) + val themeIndex = IntSetting.STATIC_THEME_COLOR.getInt(false) val themes = arrayOf( R.style.Theme_Eden_Main, R.style.Theme_Yuzu_Main_Violet, diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index 4090330d78..606ce2ce84 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h @@ -56,6 +56,7 @@ namespace AndroidSettings { Settings::Setting theme{linkage, 0, "theme", Settings::Category::Android}; Settings::Setting theme_mode{linkage, -1, "theme_mode", Settings::Category::Android}; + Settings::Setting static_theme_color{linkage, 5, "static_theme_color", Settings::Category::Android}; Settings::Setting black_backgrounds{linkage, false, "black_backgrounds", Settings::Category::Android}; Settings::Setting app_language{linkage, 0, "app_language", Settings::Category::Android}; diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index fc1334863d..7d094effcb 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -1222,12 +1222,12 @@ Theme Color - Eden (Default) + Eden Violet Blue Cyan Red - Green + Green (Default) Yellow Orange Pink From 281d7a468e3c61706f9687a4bcdd4c2c097f24c9 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sun, 1 Mar 2026 01:53:04 +0100 Subject: [PATCH 27/74] [ports] Remove ENABLE_OPENSSL and mbedtls nixos remnants (#3638) shell.nix still had references to mbedtls Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3638 Reviewed-by: MaranBr Reviewed-by: DraVee Co-authored-by: lizzie Co-committed-by: lizzie --- shell.nix | 2 +- src/android/app/build.gradle.kts | 1 - src/android/app/src/main/jni/CMakeLists.txt | 7 ++----- src/core/crypto/aes_util.cpp | 15 ++++----------- src/core/crypto/sha_util.cpp | 4 ---- src/core/crypto/sha_util.h | 19 ------------------- 6 files changed, 7 insertions(+), 41 deletions(-) delete mode 100644 src/core/crypto/sha_util.cpp delete mode 100644 src/core/crypto/sha_util.h diff --git a/shell.nix b/shell.nix index d835b43ed1..d8257b88d9 100644 --- a/shell.nix +++ b/shell.nix @@ -9,7 +9,7 @@ pkgs.mkShellNoCC { # libraries openssl boost fmt nlohmann_json lz4 zlib zstd enet libopus vulkan-headers vulkan-utility-libraries - spirv-tools spirv-headers vulkan-loader unzip mbedtls + spirv-tools spirv-headers vulkan-loader unzip glslang python3 httplib cpp-jwt ffmpeg-headless libusb1 cubeb # eden diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 622ae53a77..3279a2202f 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -80,7 +80,6 @@ android { listOf( "-DENABLE_QT=0", // Don't use QT "-DENABLE_WEB_SERVICE=1", // Enable web service - "-DENABLE_OPENSSL=ON", "-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work "-DYUZU_USE_CPM=ON", "-DCPMUTIL_FORCE_BUNDLED=ON", diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index 6cdacea320..c68e206d24 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt @@ -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: 2023 yuzu Emulator Project @@ -27,10 +27,7 @@ if (ARCHITECTURE_arm64) target_link_libraries(yuzu-android PRIVATE adrenotools) endif() -if (ENABLE_OPENSSL OR ENABLE_WEB_SERVICE) - target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt) -endif() - +target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt) if (ENABLE_UPDATE_CHECKER) target_compile_definitions(yuzu-android PUBLIC ENABLE_UPDATE_CHECKER) endif() diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp index 1189e45bd8..55f436f532 100644 --- a/src/core/crypto/aes_util.cpp +++ b/src/core/crypto/aes_util.cpp @@ -38,7 +38,6 @@ struct CipherContext { static inline const std::string GetCipherName(Mode mode, u32 key_size) { std::string cipher; std::size_t effective_bits = key_size * 8; - switch (mode) { case Mode::CTR: cipher = "CTR"; @@ -53,7 +52,6 @@ static inline const std::string GetCipherName(Mode mode, u32 key_size) { default: UNREACHABLE(); } - return fmt::format("AES-{}-{}", effective_bits, cipher); }; @@ -87,8 +85,7 @@ static EVP_CIPHER *GetCipher(Mode mode, u32 key_size) { // TODO: WHY TEMPLATE??????? template -Crypto::AESCipher::AESCipher(Key key, Mode mode) - : ctx(std::make_unique()) { +Crypto::AESCipher::AESCipher(Key key, Mode mode) : ctx(std::make_unique()) { ctx->encryption_context = EVP_CIPHER_CTX_new(); ctx->decryption_context = EVP_CIPHER_CTX_new(); @@ -99,9 +96,7 @@ Crypto::AESCipher::AESCipher(Key key, Mode mode) UNIMPLEMENTED(); } - ASSERT_MSG(ctx->encryption_context && ctx->decryption_context && ctx->cipher, - "OpenSSL cipher context failed init!"); - + ASSERT(ctx->encryption_context && ctx->decryption_context && ctx->cipher && "OpenSSL cipher context failed init!"); // now init ciphers ASSERT(EVP_CipherInit_ex2(ctx->encryption_context, ctx->cipher, key.data(), NULL, 1, NULL)); ASSERT(EVP_CipherInit_ex2(ctx->decryption_context, ctx->cipher, key.data(), NULL, 0, NULL)); @@ -165,8 +160,7 @@ void AESCipher::Transcode(const u8* src, std::size_t size, u8* des template void AESCipher::XTSTranscode(const u8* src, std::size_t size, u8* dest, std::size_t sector_id, std::size_t sector_size, Op op) { - ASSERT_MSG(size % sector_size == 0, "XTS decryption size must be a multiple of sector size."); - + ASSERT(size % sector_size == 0 && "XTS decryption size must be a multiple of sector size."); for (std::size_t i = 0; i < size; i += sector_size) { SetIV(CalculateNintendoTweak(sector_id++)); Transcode(src + i, sector_size, dest + i, op); @@ -177,8 +171,7 @@ template void AESCipher::SetIV(std::span data) { const int ret_enc = EVP_CipherInit_ex(ctx->encryption_context, nullptr, nullptr, nullptr, data.data(), -1); const int ret_dec = EVP_CipherInit_ex(ctx->decryption_context, nullptr, nullptr, nullptr, data.data(), -1); - - ASSERT_MSG(ret_enc == 1 && ret_dec == 1, "Failed to set IV on OpenSSL contexts"); + ASSERT(ret_enc == 1 && ret_dec == 1 && "Failed to set IV on OpenSSL contexts"); } template class AESCipher; diff --git a/src/core/crypto/sha_util.cpp b/src/core/crypto/sha_util.cpp deleted file mode 100644 index 7a2c048389..0000000000 --- a/src/core/crypto/sha_util.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -namespace Crypto {} // namespace Crypto diff --git a/src/core/crypto/sha_util.h b/src/core/crypto/sha_util.h deleted file mode 100644 index 5c2c43dbdb..0000000000 --- a/src/core/crypto/sha_util.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/assert.h" -#include "core/file_sys/vfs.h" -#include "key_manager.h" -#include "mbedtls/cipher.h" - -namespace Crypto { -typedef std::array SHA256Hash; - -inline SHA256Hash operator"" _HASH(const char* data, size_t len) { - if (len != 0x40) - return {}; -} - -} // namespace Crypto From 2991bd18ef18d46069344e816591e34f05dff397 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sun, 1 Mar 2026 01:53:15 +0100 Subject: [PATCH 28/74] [vk] Enable VK_IMG_filter_cubic on powervr (#3643) in powervr, the enum for cubic filtering is an alias for an ext, however in true powervr fashion they want you to use their stupid VK_IMG and knowing powevr it probably bugs out if you dont Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3643 Reviewed-by: DraVee Reviewed-by: MaranBr Co-authored-by: lizzie Co-committed-by: lizzie --- src/video_core/renderer_vulkan/present/util.cpp | 7 ++++--- src/video_core/vulkan_common/vulkan_device.h | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/video_core/renderer_vulkan/present/util.cpp b/src/video_core/renderer_vulkan/present/util.cpp index 29a1c34976..a2c4727703 100644 --- a/src/video_core/renderer_vulkan/present/util.cpp +++ b/src/video_core/renderer_vulkan/present/util.cpp @@ -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 @@ -6,6 +6,7 @@ #include "common/assert.h" #include +#include #include "video_core/renderer_vulkan/present/util.h" namespace Vulkan { @@ -629,8 +630,8 @@ vk::Sampler CreateCubicSampler(const Device& device, VkCubicFilterWeightsQCOM qc .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = nullptr, .flags = 0, - .magFilter = VK_FILTER_CUBIC_EXT, - .minFilter = VK_FILTER_CUBIC_EXT, + .magFilter = device.IsExtFilterCubicSupported() ? VK_FILTER_CUBIC_EXT : VK_FILTER_LINEAR, + .minFilter = device.IsExtFilterCubicSupported() ? VK_FILTER_CUBIC_EXT : VK_FILTER_LINEAR, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 7d738a81df..d3623d1186 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -107,6 +107,7 @@ VK_DEFINE_HANDLE(VmaAllocator) EXTENSION(NV, VIEWPORT_SWIZZLE, viewport_swizzle) \ EXTENSION(EXT, DESCRIPTOR_INDEXING, descriptor_indexing) \ EXTENSION(EXT, FILTER_CUBIC, filter_cubic) \ + EXTENSION(IMG, FILTER_CUBIC, filter_cubic_img) \ EXTENSION(QCOM, FILTER_CUBIC_WEIGHTS, filter_cubic_weights) // Define extensions which must be supported. From a8fc994c0b60be5a823da6466fefb8fb2d689e33 Mon Sep 17 00:00:00 2001 From: xbzk Date: Sun, 1 Mar 2026 23:21:26 +0100 Subject: [PATCH 29/74] pr3655 gradientbordercardview fix (#3658) This complements PR3655, where a change in theme setting caused gradientbordercardview theme color setting mismatch. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3658 Reviewed-by: CamilleLaVey Co-authored-by: xbzk Co-committed-by: xbzk --- .../yuzu/yuzu_emu/views/GradientBorderCardView.kt | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/GradientBorderCardView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/GradientBorderCardView.kt index 8121cfb6fa..d6c0709d57 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/GradientBorderCardView.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/GradientBorderCardView.kt @@ -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: 2023 yuzu Emulator Project @@ -11,8 +11,7 @@ import android.graphics.* import android.util.AttributeSet import com.google.android.material.card.MaterialCardView import org.yuzu.yuzu_emu.R -import org.yuzu.yuzu_emu.features.settings.model.Settings -import androidx.preference.PreferenceManager +import org.yuzu.yuzu_emu.features.settings.model.IntSetting class GradientBorderCardView @JvmOverloads constructor( context: Context, @@ -44,12 +43,7 @@ class GradientBorderCardView @JvmOverloads constructor( } private fun updateThemeState() { - val prefs = PreferenceManager.getDefaultSharedPreferences(context) - val themeIndex = try { - prefs.getInt(Settings.PREF_STATIC_THEME_COLOR, 0) - } catch (e: Exception) { - 0 // Default to Eden theme if error - } + val themeIndex = IntSetting.STATIC_THEME_COLOR.getInt(false) isEdenTheme = themeIndex == 0 invalidate() } From b5f9f8b7438a6a79644d712e267399d618f5d0e1 Mon Sep 17 00:00:00 2001 From: MaranBr Date: Mon, 2 Mar 2026 02:51:50 +0100 Subject: [PATCH 30/74] [vulkan] Fix Vulkan rendering, image layout, and synchronization issues (#3511) This fixes a visual corruption issue that occurred intermittently after loading screens, where some games would start the scene with vertex explosions, artifacts or with all colors blown out, resembling neon. Among the known games affected by this bug are Mario Kart 8 Deluxe, The Legend of Zelda: Breath of the Wild, The Legend of Zelda: Tears of the Kingdom, Kirby and the Forgotten Land, Luigi's Mansion 3, Xenoblade Chronicles 3 and possibly others as well. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3511 Reviewed-by: DraVee Reviewed-by: Lizzie Co-authored-by: MaranBr Co-committed-by: MaranBr --- .../renderer_vulkan/vk_blit_screen.cpp | 45 +++++++++---------- .../renderer_vulkan/vk_master_semaphore.cpp | 10 ++--- .../renderer_vulkan/vk_scheduler.cpp | 16 +++---- .../renderer_vulkan/vk_texture_cache.cpp | 2 +- .../vulkan_common/vulkan_device.cpp | 8 ---- 5 files changed, 31 insertions(+), 50 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 0f54dd5ade..bb7eb9bdaa 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -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 Torzu Emulator Project @@ -22,7 +22,8 @@ BlitScreen::BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory_, const MemoryAllocator& memory_allocator_, PresentManager& present_manager_, Scheduler& scheduler_, const PresentFilters& filters_) : device_memory{device_memory_}, device{device_}, memory_allocator{memory_allocator_}, - present_manager{present_manager_}, scheduler{scheduler_}, filters{filters_}, image_count{1}, + present_manager{present_manager_}, scheduler{scheduler_}, filters{filters_}, + image_count{1}, image_index{0}, swapchain_view_format{VK_FORMAT_B8G8R8A8_UNORM} {} BlitScreen::~BlitScreen() = default; @@ -87,57 +88,49 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame, bool resource_update_required = false; bool presentation_recreate_required = false; - // Recreate dynamic resources if the adapting filter changed if (!window_adapt || scaling_filter != filters.get_scaling_filter()) { resource_update_required = true; } - // Recreate dynamic resources if the image count changed - const size_t old_swapchain_image_count = - std::exchange(image_count, current_swapchain_image_count); - if (old_swapchain_image_count != current_swapchain_image_count) { + if (image_count != current_swapchain_image_count) { resource_update_required = true; + image_count = current_swapchain_image_count; } - // Recreate the presentation frame if the format or dimensions of the window changed - const VkFormat old_swapchain_view_format = - std::exchange(swapchain_view_format, current_swapchain_view_format); - if (old_swapchain_view_format != current_swapchain_view_format || + if (swapchain_view_format != current_swapchain_view_format || layout.width != frame->width || layout.height != frame->height) { resource_update_required = true; presentation_recreate_required = true; + swapchain_view_format = current_swapchain_view_format; } - // If we have a pending resource update, perform it if (resource_update_required) { - // Wait for idle to ensure no resources are in use WaitIdle(); - - // Update window adapt pass SetWindowAdaptPass(); - // Update frame format if needed if (presentation_recreate_required) { present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format, window_adapt->GetRenderPass()); } + + image_index = 0; } - // Add additional layers if needed const VkExtent2D window_size{ .width = layout.screen.GetWidth(), .height = layout.screen.GetHeight(), }; - while (layers.size() < framebuffers.size()) { - layers.emplace_back(device, memory_allocator, scheduler, device_memory, image_count, - window_size, window_adapt->GetDescriptorSetLayout(), filters); + if (layers.size() != framebuffers.size()) { + layers.clear(); + for (size_t i = 0; i < framebuffers.size(); ++i) { + layers.emplace_back(device, memory_allocator, scheduler, device_memory, image_count, + window_size, window_adapt->GetDescriptorSetLayout(), filters); + } } - // Perform the draw window_adapt->Draw(rasterizer, scheduler, image_index, layers, framebuffers, layout, frame); - // Advance to next image if (++image_index >= image_count) { image_index = 0; } @@ -146,16 +139,20 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame, vk::Framebuffer BlitScreen::CreateFramebuffer(const Layout::FramebufferLayout& layout, VkImageView image_view, VkFormat current_view_format) { - const bool format_updated = - std::exchange(swapchain_view_format, current_view_format) != current_view_format; + bool format_updated = swapchain_view_format != current_view_format; + swapchain_view_format = current_view_format; + if (!window_adapt || scaling_filter != filters.get_scaling_filter() || format_updated) { WaitIdle(); SetWindowAdaptPass(); + image_index = 0; } + const VkExtent2D extent{ .width = layout.width, .height = layout.height, }; + return CreateFramebuffer(image_view, extent, window_adapt->GetRenderPass()); } diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index 13ecfd20a6..b535097b36 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp @@ -121,10 +121,8 @@ VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, vk::CommandBuff } } -static constexpr std::array wait_stage_masks{ - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, -}; +static constexpr VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf, @@ -143,7 +141,7 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, const VkSemaphore* p_wait_sems = (num_wait_semaphores > 0) ? &wait_semaphore : nullptr; const VkPipelineStageFlags* p_wait_masks = - (num_wait_semaphores > 0) ? wait_stage_masks.data() : nullptr; + (num_wait_semaphores > 0) ? &wait_stage_mask : nullptr; const VkSemaphore* p_signal_sems = (num_signal_semaphores > 0) ? signal_semaphores.data() : nullptr; const u64 wait_zero = 0; // dummy for binary wait @@ -180,7 +178,7 @@ VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, const VkSemaphore* p_wait_sems = (num_wait_semaphores > 0) ? &wait_semaphore : nullptr; const VkPipelineStageFlags* p_wait_masks = - (num_wait_semaphores > 0) ? wait_stage_masks.data() : nullptr; + (num_wait_semaphores > 0) ? &wait_stage_mask : nullptr; const VkSemaphore* p_signal_sems = (num_signal_semaphores > 0) ? &signal_semaphore : nullptr; const std::array cmdbuffers{*upload_cmdbuf, *cmdbuf}; diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index e526d606dc..0a032cdae0 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -270,8 +270,8 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, }; - upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER); + upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, WRITE_BARRIER); upload_cmdbuf.End(); cmdbuf.End(); @@ -372,15 +372,9 @@ void Scheduler::EndRenderPass() }; } cmdbuf.EndRenderPass(); - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | - VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - 0, - nullptr, - nullptr, - vk::Span(barriers.data(), num_images) // Batched image barriers - ); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images)); }); state.renderpass = nullptr; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 348e49fb6d..d51564dcb3 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -2500,7 +2500,7 @@ void TextureCacheRuntime::TransitionImageLayout(Image& image) { }; scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([barrier](vk::CommandBuffer cmdbuf) { - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier); }); } diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 3d98f2cbf7..15f48ca8f4 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -557,20 +557,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR } if (is_nvidia) { - const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff; const auto arch = GetNvidiaArch(); if (arch >= NvidiaArchitecture::Arch_AmpereOrNewer) { LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math"); features.shader_float16_int8.shaderFloat16 = false; } - if (nv_major_version >= 510) { - LOG_WARNING(Render_Vulkan, - "NVIDIA Drivers >= 510 do not support MSAA->MSAA image blits. " - "MSAA scaling will use 3D helpers. MSAA resolves work normally."); - cant_blit_msaa = true; - } - // Mali/ NVIDIA proprietary drivers: Shader stencil export not supported // Use hardware depth/stencil blits instead when available if (!extensions.shader_stencil_export) { From ea209e6dabdcd99f51dd2ae8b11adeb715b9668f Mon Sep 17 00:00:00 2001 From: MaranBr Date: Mon, 2 Mar 2026 02:53:01 +0100 Subject: [PATCH 31/74] [vulkan] Fine-tuning of frame pacing logic (#3628) It makes fine adjustments to the frame pacing, ensuring better stability and precision. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3628 Reviewed-by: crueter Reviewed-by: Lizzie Co-authored-by: MaranBr Co-committed-by: MaranBr --- src/video_core/renderer_vulkan/vk_scheduler.h | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 2913211480..667f136ee6 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -16,8 +16,8 @@ #include "common/alignment.h" #include "common/common_types.h" -#include "common/settings.h" #include "common/polyfill_thread.h" +#include "common/settings.h" #include "video_core/renderer_vulkan/vk_master_semaphore.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -114,29 +114,36 @@ public: /// Waits for the given GPU tick, optionally pacing frames. void Wait(u64 tick, double target_fps = 0.0) { - if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) { - const auto now = std::chrono::steady_clock::now(); - if (start_time == std::chrono::steady_clock::time_point{} || current_target_fps != target_fps) { - start_time = now; - frame_counter = 0; - current_target_fps = target_fps; - } - frame_counter++; - std::chrono::duration frame_interval(1.0 / current_target_fps); - auto target_time = start_time + frame_interval * frame_counter; - if (target_time > now) { - std::this_thread::sleep_until(target_time); - } else { - start_time = now; - frame_counter = 0; - } - } if (tick > 0) { if (tick >= master_semaphore->CurrentTick()) { Flush(); } master_semaphore->Wait(tick); } + if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) { + auto now = std::chrono::steady_clock::now(); + if (last_target_fps != target_fps) { + frame_interval = std::chrono::duration_cast(std::chrono::duration(1.0 / target_fps)); + max_frame_count = static_cast(0.1 * target_fps); + last_target_fps = target_fps; + frame_counter = 0; + start_time = now; + } + frame_counter++; + auto target_time = start_time + frame_interval * frame_counter; + if (target_time >= now) { + auto sleep_time = target_time - now; + if (sleep_time > std::chrono::milliseconds(15)) { + std::this_thread::sleep_for(sleep_time - std::chrono::milliseconds(1)); + } + while (std::chrono::steady_clock::now() < target_time) { + std::this_thread::yield(); + } + } else if (frame_counter > max_frame_count) { + frame_counter = 0; + start_time = now; + } + } } /// Returns the master timeline semaphore. @@ -281,9 +288,11 @@ private: std::condition_variable_any event_cv; std::jthread worker_thread; + std::chrono::steady_clock::duration frame_interval{}; std::chrono::steady_clock::time_point start_time{}; + double last_target_fps{}; + u64 max_frame_count{}; u64 frame_counter{}; - double current_target_fps{}; }; } // namespace Vulkan From 464212393e296c59488171692df1d096ecfa5607 Mon Sep 17 00:00:00 2001 From: crueter Date: Mon, 2 Mar 2026 05:28:26 +0100 Subject: [PATCH 32/74] [gitignore] add *.tar.zst (#3663) the amount of times I've accidentally committed a tar.zst to this repository... Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3663 --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0be76d85cd..67bdd8adf4 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,4 @@ artifacts *.AppImage* /install* vulkansdk*.exe +*.tar.zst From d720a7b4b4e3c2bc4f7e2c1b16e282ec91766aa8 Mon Sep 17 00:00:00 2001 From: tarako Date: Tue, 3 Mar 2026 01:54:57 +0100 Subject: [PATCH 33/74] [vulkan] Fixed dual-source blending to correctly map shader outputs. (#3637) Fixes Skyward Sword HD eye gitch and a related MoltenVK crash due to the incorrect output mapping. Verified working on mac and android. The test in vk_pipeline_cache.cpp is a bit ugly, but it didn't seem worth it to go lambda/macro just to make it look cleaner. Could change if necessary. Co-authored-by: tarako Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3637 Reviewed-by: CamilleLaVey Co-authored-by: tarako Co-committed-by: tarako --- .../backend/spirv/emit_spirv_special.cpp | 14 +++++++++ .../backend/spirv/spirv_emit_context.cpp | 15 ++++++++-- src/shader_recompiler/runtime_info.h | 3 ++ .../renderer_vulkan/vk_pipeline_cache.cpp | 30 ++++++++++++++++++- 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp index fe5e70a63b..8eca1fac29 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -91,6 +94,17 @@ void AlphaTest(EmitContext& ctx) { } // Anonymous namespace void EmitPrologue(EmitContext& ctx) { + if (ctx.stage == Stage::Fragment && ctx.runtime_info.dual_source_blend) { + // Initialize dual-source blending outputs - prevents MoltenVK crash. + const Id zero{ctx.Const(0.0f)}; + const Id one{ctx.Const(1.0f)}; + const Id default_color{ctx.ConstantComposite(ctx.F32[4], zero, zero, zero, one)}; + for (u32 i = 0; i < 2; ++i) { + if (Sirit::ValidId(ctx.frag_color[i])) { + ctx.OpStore(ctx.frag_color[i], default_color); + } + } + } if (ctx.stage == Stage::VertexB) { const Id zero{ctx.Const(0.0f)}; const Id one{ctx.Const(1.0f)}; diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index cd3394bdf0..b9a24496c9 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1670,13 +1670,22 @@ void EmitContext::DefineOutputs(const IR::Program& program) { break; case Stage::Fragment: for (u32 index = 0; index < 8; ++index) { - if (!info.stores_frag_color[index] && !profile.need_declared_frag_colors) { + const bool need_dual_source = runtime_info.dual_source_blend && index <= 1; + if (!need_dual_source && !info.stores_frag_color[index] && + !profile.need_declared_frag_colors) { continue; } const Id type{GetAttributeType(*this, runtime_info.color_output_types[index])}; frag_color[index] = DefineOutput(*this, type, std::nullopt); - Decorate(frag_color[index], spv::Decoration::Location, index); - Name(frag_color[index], fmt::format("frag_color{}", index)); + // Correct mapping for dual-source blending + if (runtime_info.dual_source_blend && index <= 1) { + Decorate(frag_color[index], spv::Decoration::Location, 0u); + Decorate(frag_color[index], spv::Decoration::Index, index); + Name(frag_color[index], index == 0 ? "frag_color0" : "frag_color0_secondary"); + } else { + Decorate(frag_color[index], spv::Decoration::Location, index); + Name(frag_color[index], fmt::format("frag_color{}", index)); + } } if (info.stores_frag_depth) { frag_depth = DefineOutput(*this, F32[1], std::nullopt); diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 613c598d0c..be10a9bb08 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -110,6 +110,9 @@ struct RuntimeInfo { /// Output types for each color attachment std::array color_output_types{}; + + /// Dual source blending + bool dual_source_blend{}; }; } // namespace Shader diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index f3dd0f90d8..77a4e8616a 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -237,11 +237,38 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program } info.convert_depth_mode = gl_ndc; break; - case Shader::Stage::Fragment: + case Shader::Stage::Fragment: { info.alpha_test_func = MaxwellToCompareFunction( key.state.UnpackComparisonOp(key.state.alpha_test_func.Value())); info.alpha_test_reference = std::bit_cast(key.state.alpha_test_ref); + // Check for dual source blending + const auto& blend0 = key.state.attachments[0]; + if (blend0.enable != 0) { + using F = Maxwell::Blend::Factor; + const auto src_rgb = blend0.SourceRGBFactor(); + const auto dst_rgb = blend0.DestRGBFactor(); + const auto src_a = blend0.SourceAlphaFactor(); + const auto dst_a = blend0.DestAlphaFactor(); + info.dual_source_blend = + src_rgb == F::Source1Color_D3D || src_rgb == F::OneMinusSource1Color_D3D || + src_rgb == F::Source1Alpha_D3D || src_rgb == F::OneMinusSource1Alpha_D3D || + src_rgb == F::Source1Color_GL || src_rgb == F::OneMinusSource1Color_GL || + src_rgb == F::Source1Alpha_GL || src_rgb == F::OneMinusSource1Alpha_GL || + dst_rgb == F::Source1Color_D3D || dst_rgb == F::OneMinusSource1Color_D3D || + dst_rgb == F::Source1Alpha_D3D || dst_rgb == F::OneMinusSource1Alpha_D3D || + dst_rgb == F::Source1Color_GL || dst_rgb == F::OneMinusSource1Color_GL || + dst_rgb == F::Source1Alpha_GL || dst_rgb == F::OneMinusSource1Alpha_GL || + src_a == F::Source1Color_D3D || src_a == F::OneMinusSource1Color_D3D || + src_a == F::Source1Alpha_D3D || src_a == F::OneMinusSource1Alpha_D3D || + src_a == F::Source1Color_GL || src_a == F::OneMinusSource1Color_GL || + src_a == F::Source1Alpha_GL || src_a == F::OneMinusSource1Alpha_GL || + dst_a == F::Source1Color_D3D || dst_a == F::OneMinusSource1Color_D3D || + dst_a == F::Source1Alpha_D3D || dst_a == F::OneMinusSource1Alpha_D3D || + dst_a == F::Source1Color_GL || dst_a == F::OneMinusSource1Color_GL || + dst_a == F::Source1Alpha_GL || dst_a == F::OneMinusSource1Alpha_GL; + } + if (device.IsMoltenVK()) { for (size_t i = 0; i < 8; ++i) { const auto format = static_cast(key.state.color_formats[i]); @@ -258,6 +285,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program } } break; + } default: break; } From 9d2341eaeafb275d3d3e4692812fe121ae24e2e3 Mon Sep 17 00:00:00 2001 From: PavelBARABANOV Date: Tue, 3 Mar 2026 14:27:47 +0100 Subject: [PATCH 34/74] [vk] Disable float16 math on non-MESA AMD drivers as 2026+ versions are broken (#3661) thanks MaranBR Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3661 Reviewed-by: MaranBr Reviewed-by: Maufeat Co-authored-by: PavelBARABANOV Co-committed-by: PavelBARABANOV --- src/video_core/vulkan_common/vulkan_device.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 15f48ca8f4..a2ff3ee6ed 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -586,12 +586,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (is_amd_driver) { // AMD drivers need a higher amount of Sets per Pool in certain circumstances like in XC2. sets_per_pool = 96; + // Disable VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT on AMD GCN4 and lower as it is broken. if (!features.shader_float16_int8.shaderFloat16) { LOG_WARNING(Render_Vulkan, "AMD GCN4 and earlier have broken VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT"); has_broken_cube_compatibility = true; } + + // AMD drivers (2026+) have broken float16 math on DKCR + if (features.shader_float16_int8.shaderFloat16) { + LOG_WARNING(Render_Vulkan, + "AMD drivers (2026+) have broken float16 math"); + features.shader_float16_int8.shaderFloat16 = false; + } } if (is_qualcomm) { From c682306788055c57953aea38fdded95a0c6dfc09 Mon Sep 17 00:00:00 2001 From: wildcard Date: Wed, 4 Mar 2026 22:46:55 +0100 Subject: [PATCH 35/74] [vulkan] Implement push descriptors for compute pipelines (#3666) Implements push descriptor for compute pipelines along with a bug fix, the increment logic was, offset += sizeof(DescriptorUpdateEntry); This only advances the byte offset by a single descriptor slot, regardless of the array's size (descriptorCount).Now suppose if a shader utilized an array of descriptors (eg, layout(binding = 0) uniform sampler2D textures[4]) and if this happened to fit within the MaxPushDescriptors limit, the template would consume 4 * sizeof(DescriptorUpdateEntry) bytes, but the offset for the next binding would only advance by 1 slot. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3666 Reviewed-by: MaranBr Co-authored-by: wildcard Co-committed-by: wildcard --- .../renderer_vulkan/pipeline_helper.h | 8 +++++-- .../renderer_vulkan/vk_compute_pipeline.cpp | 24 ++++++++++++------- .../renderer_vulkan/vk_compute_pipeline.h | 4 ++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index 910e07a606..e88b27b273 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -61,7 +64,8 @@ public: .pDescriptorUpdateEntries = entries.data(), .templateType = type, .descriptorSetLayout = descriptor_set_layout, - .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .pipelineBindPoint = + is_compute ? VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS, .pipelineLayout = pipeline_layout, .set = 0, }); @@ -122,7 +126,7 @@ private: }); ++binding; num_descriptors += descriptors[i].count; - offset += sizeof(DescriptorUpdateEntry); + offset += sizeof(DescriptorUpdateEntry) * descriptors[i].count; } } diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 51b5141a06..6a6fe2b830 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -50,11 +50,14 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel DescriptorLayoutBuilder builder{device}; builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT); - descriptor_set_layout = builder.CreateDescriptorSetLayout(false); + uses_push_descriptor = builder.CanUsePushDescriptor(); + descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor); pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout); descriptor_update_template = - builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, false); - descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info); + builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, uses_push_descriptor); + if (!uses_push_descriptor) { + descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info); + } const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT, .pNext = nullptr, @@ -241,11 +244,16 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, RESCALING_LAYOUT_WORDS_OFFSET, sizeof(rescaling_data), rescaling_data.data()); } - const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()}; - const vk::Device& dev{device.GetLogical()}; - dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0, - descriptor_set, nullptr); + if (uses_push_descriptor) { + cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout, + 0, descriptor_data); + } else { + const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()}; + const vk::Device& dev{device.GetLogical()}; + dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0, + descriptor_set, nullptr); + } }); } diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index d1a1e2c46d..aa84c00e12 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -55,6 +58,7 @@ private: vk::ShaderModule spv_module; vk::DescriptorSetLayout descriptor_set_layout; + bool uses_push_descriptor{false}; DescriptorAllocator descriptor_allocator; vk::PipelineLayout pipeline_layout; vk::DescriptorUpdateTemplate descriptor_update_template; From 7f5de6bcd651f8145e4746c76d0531b6560fd5e4 Mon Sep 17 00:00:00 2001 From: xbzk Date: Wed, 4 Mar 2026 22:51:35 +0100 Subject: [PATCH 36/74] [android/fs] external content loader + nca/xci patches (#3596) Foreword: WHY DON'T EVERYBODY USE ONE FOLDER FOR EACH GAME+CONTENTS? AIN'T THIS THE FORMAT GAMES COME WHEN YOU BUE THEM? DO YOU LIVE WITH ALL YOUR FRIENDS AND HAVE A 2ND HOUSE FOR ALL THE CHILDREN? Nice, i feel better now. This feat extends Maufeat's work on external content loading. It harmonically additions: "...also, if in each game folder X, you find a folder Y, and in this folder Y you detect ONLY a single game, then mount all external content for that game found in that folder Y and its subfolders." Permanent (not toggleable). External Content folders are supported equally. Also: -Reworked several routines for preserving single source of truth between android and other systems; -Fixed the annoying unknown format error for content files, by providing proper format detection. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3596 Reviewed-by: MaranBr Reviewed-by: Lizzie Co-authored-by: xbzk Co-committed-by: xbzk --- .../java/org/yuzu/yuzu_emu/NativeLibrary.kt | 6 + .../org/yuzu/yuzu_emu/model/AddonViewModel.kt | 6 +- .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 4 +- .../org/yuzu/yuzu_emu/utils/GameHelper.kt | 64 ++- .../app/src/main/jni/android_config.cpp | 6 + .../app/src/main/jni/game_metadata.cpp | 5 + src/android/app/src/main/jni/native.cpp | 116 +---- src/android/app/src/main/jni/native.h | 4 + src/common/settings.h | 2 + src/core/file_sys/patch_manager.cpp | 27 +- src/core/file_sys/registered_cache.cpp | 473 +++++++++--------- src/core/file_sys/registered_cache.h | 5 +- src/core/loader/loader.cpp | 70 ++- src/core/loader/loader.h | 22 +- src/core/loader/nsp.cpp | 36 +- src/core/loader/xci.cpp | 14 +- src/yuzu/game/game_list_worker.cpp | 22 +- src/yuzu/main_window.cpp | 19 +- 18 files changed, 477 insertions(+), 424 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 1f0acf2835..397b44c9f9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -607,6 +607,12 @@ object NativeLibrary { */ external fun addFileToFilesystemProvider(path: String) + /** + * Adds a game-folder file to the manual filesystem provider, respecting the internal gate for + * game-folder external-content mounting. + */ + external fun addGameFolderFileToFilesystemProvider(path: String) + /** * Clears all files added to the manual filesystem provider in our EmulationSession instance */ diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt index c682a13cfc..2a0e72be26 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt @@ -127,10 +127,6 @@ class AddonViewModel : ViewModel() { return } - // Check if there are multiple update versions - val updates = _patchList.value.filter { PatchType.from(it.type) == PatchType.Update } - val hasMultipleUpdates = updates.size > 1 - NativeConfig.setDisabledAddons( game!!.programId, _patchList.value.mapNotNull { @@ -140,7 +136,7 @@ class AddonViewModel : ViewModel() { if (PatchType.from(it.type) == PatchType.Update) { if (it.name.contains("(NAND)") || it.name.contains("(SDMC)")) { it.name - } else if (hasMultipleUpdates) { + } else if (it.numericVersion != 0L) { "Update@${it.numericVersion}" } else { it.name diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index 8a4262ebe7..db4cc0f60e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -424,7 +424,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { ) val uriString = result.toString() - val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString } + val folder = gamesViewModel.folders.value.firstOrNull { + it.uriString == uriString && it.type == org.yuzu.yuzu_emu.model.DirectoryType.EXTERNAL_CONTENT + } if (folder != null) { Toast.makeText( applicationContext, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt index fff5fdfb9b..4a3cf61daa 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt @@ -51,11 +51,24 @@ object GameHelper { // Scan External Content directories and register all NSP/XCI files val externalContentDirs = NativeConfig.getExternalContentDirs() - for (externalDir in externalContentDirs) { + val uniqueExternalContentDirs = linkedSetOf() + externalContentDirs.forEach { externalDir -> + if (externalDir.isNotEmpty()) { + uniqueExternalContentDirs.add(externalDir) + } + } + + val mountedContainerUris = mutableSetOf() + for (externalDir in uniqueExternalContentDirs) { if (externalDir.isNotEmpty()) { val externalDirUri = externalDir.toUri() if (FileUtil.isTreeUriValid(externalDirUri)) { - scanExternalContentRecursive(FileUtil.listFiles(externalDirUri), 3) + scanContentContainersRecursive(FileUtil.listFiles(externalDirUri), 3) { + val containerUri = it.uri.toString() + if (mountedContainerUris.add(containerUri)) { + NativeLibrary.addFileToFilesystemProvider(containerUri) + } + } } } } @@ -65,10 +78,13 @@ object GameHelper { val gameDirUri = gameDir.uriString.toUri() val isValid = FileUtil.isTreeUriValid(gameDirUri) if (isValid) { + val scanDepth = if (gameDir.deepScan) 3 else 1 + addGamesRecursive( games, FileUtil.listFiles(gameDirUri), - if (gameDir.deepScan) 3 else 1 + scanDepth, + mountedContainerUris ) } else { badDirs.add(index) @@ -103,9 +119,10 @@ object GameHelper { // be done better imo. private val externalContentExtensions = setOf("nsp", "xci") - private fun scanExternalContentRecursive( + private fun scanContentContainersRecursive( files: Array, - depth: Int + depth: Int, + onContainerFound: (MinimalDocumentFile) -> Unit ) { if (depth <= 0) { return @@ -113,14 +130,15 @@ object GameHelper { files.forEach { if (it.isDirectory) { - scanExternalContentRecursive( + scanContentContainersRecursive( FileUtil.listFiles(it.uri), - depth - 1 + depth - 1, + onContainerFound ) } else { val extension = FileUtil.getExtension(it.uri).lowercase() if (externalContentExtensions.contains(extension)) { - NativeLibrary.addFileToFilesystemProvider(it.uri.toString()) + onContainerFound(it) } } } @@ -129,7 +147,8 @@ object GameHelper { private fun addGamesRecursive( games: MutableList, files: Array, - depth: Int + depth: Int, + mountedContainerUris: MutableSet ) { if (depth <= 0) { return @@ -140,11 +159,20 @@ object GameHelper { addGamesRecursive( games, FileUtil.listFiles(it.uri), - depth - 1 + depth - 1, + mountedContainerUris ) } else { - if (Game.extensions.contains(FileUtil.getExtension(it.uri))) { - val game = getGame(it.uri, true) + val extension = FileUtil.getExtension(it.uri).lowercase() + val filePath = it.uri.toString() + + if (externalContentExtensions.contains(extension) && + mountedContainerUris.add(filePath)) { + NativeLibrary.addGameFolderFileToFilesystemProvider(filePath) + } + + if (Game.extensions.contains(extension)) { + val game = getGame(it.uri, true, false) if (game != null) { games.add(game) } @@ -153,14 +181,20 @@ object GameHelper { } } - fun getGame(uri: Uri, addedToLibrary: Boolean): Game? { + fun getGame( + uri: Uri, + addedToLibrary: Boolean, + registerFilesystemProvider: Boolean = true + ): Game? { val filePath = uri.toString() if (!GameMetadata.getIsValid(filePath)) { return null } - // Needed to update installed content information - NativeLibrary.addFileToFilesystemProvider(filePath) + if (registerFilesystemProvider) { + // Needed to update installed content information + NativeLibrary.addFileToFilesystemProvider(filePath) + } var name = GameMetadata.getTitle(filePath) diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp index 0171e2a7b3..0c5696ef3f 100644 --- a/src/android/app/src/main/jni/android_config.cpp +++ b/src/android/app/src/main/jni/android_config.cpp @@ -33,6 +33,12 @@ void AndroidConfig::ReadAndroidValues() { if (global) { ReadAndroidUIValues(); ReadUIValues(); + BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage)); + Settings::values.ext_content_from_game_dirs = ReadBooleanSetting( + std::string("ext_content_from_game_dirs"), + std::make_optional( + Settings::values.ext_content_from_game_dirs.GetDefault())); + EndGroup(); ReadOverlayValues(); } ReadDriverValues(); diff --git a/src/android/app/src/main/jni/game_metadata.cpp b/src/android/app/src/main/jni/game_metadata.cpp index e9c03b6440..9acba456f1 100644 --- a/src/android/app/src/main/jni/game_metadata.cpp +++ b/src/android/app/src/main/jni/game_metadata.cpp @@ -96,6 +96,11 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobj return false; } + if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) && + !Loader::IsBootableGameContainer(file, file_type)) { + return false; + } + u64 program_id = 0; Loader::ResultStatus res = loader->ReadProgramId(program_id); if (res != Loader::ResultStatus::Success) { diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index c429f4a1e4..3f0029c78a 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -217,107 +217,8 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) return; } - const auto extension = Common::ToLower(filepath.substr(filepath.find_last_of('.') + 1)); - - if (extension == "nsp") { - auto nsp = std::make_shared(file); - if (nsp->GetStatus() == Loader::ResultStatus::Success) { - std::map nsp_versions; - std::map nsp_version_strings; - - for (const auto& [title_id, nca_map] : nsp->GetNCAs()) { - for (const auto& [type_pair, nca] : nca_map) { - const auto& [title_type, content_type] = type_pair; - - if (content_type == FileSys::ContentRecordType::Meta) { - const auto meta_nca = std::make_shared(nca->GetBaseFile()); - if (meta_nca->GetStatus() == Loader::ResultStatus::Success) { - const auto section0 = meta_nca->GetSubdirectories(); - if (!section0.empty()) { - for (const auto& meta_file : section0[0]->GetFiles()) { - if (meta_file->GetExtension() == "cnmt") { - FileSys::CNMT cnmt(meta_file); - nsp_versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion(); - } - } - } - } - } - - if (content_type == FileSys::ContentRecordType::Control && - title_type == FileSys::TitleType::Update) { - auto romfs = nca->GetRomFS(); - if (romfs) { - auto extracted = FileSys::ExtractRomFS(romfs); - if (extracted) { - auto nacp_file = extracted->GetFile("control.nacp"); - if (!nacp_file) { - nacp_file = extracted->GetFile("Control.nacp"); - } - if (nacp_file) { - FileSys::NACP nacp(nacp_file); - auto ver_str = nacp.GetVersionString(); - if (!ver_str.empty()) { - nsp_version_strings[title_id] = ver_str; - } - } - } - } - } - } - } - - for (const auto& [title_id, nca_map] : nsp->GetNCAs()) { - for (const auto& [type_pair, nca] : nca_map) { - const auto& [title_type, content_type] = type_pair; - - if (title_type == FileSys::TitleType::Update) { - u32 version = 0; - auto ver_it = nsp_versions.find(title_id); - if (ver_it != nsp_versions.end()) { - version = ver_it->second; - } - - std::string version_string; - auto str_it = nsp_version_strings.find(title_id); - if (str_it != nsp_version_strings.end()) { - version_string = str_it->second; - } - - m_manual_provider->AddEntryWithVersion( - title_type, content_type, title_id, version, version_string, - nca->GetBaseFile()); - - LOG_DEBUG(Frontend, "Added NSP update entry - TitleID: {:016X}, Version: {}, VersionStr: {}", - title_id, version, version_string); - } else { - // Use regular AddEntry for non-updates - m_manual_provider->AddEntry(title_type, content_type, title_id, - nca->GetBaseFile()); - LOG_DEBUG(Frontend, "Added NSP entry - TitleID: {:016X}, TitleType: {}, ContentType: {}", - title_id, static_cast(title_type), static_cast(content_type)); - } - } - } - return; - } - } - - // Handle XCI files - if (extension == "xci") { - FileSys::XCI xci{file}; - if (xci.GetStatus() == Loader::ResultStatus::Success) { - const auto nsp = xci.GetSecurePartitionNSP(); - if (nsp) { - for (const auto& title : nsp->GetNCAs()) { - for (const auto& entry : title.second) { - m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first, - entry.second->GetBaseFile()); - } - } - } - return; - } + if (m_manual_provider->AddEntriesFromContainer(file)) { + return; } auto loader = Loader::GetLoader(m_system, file); @@ -339,6 +240,13 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) } } +void EmulationSession::ConfigureFilesystemProviderFromGameFolder(const std::string& filepath) { + if (!Settings::values.ext_content_from_game_dirs.GetValue()) { + return; + } + ConfigureFilesystemProvider(filepath); +} + void EmulationSession::InitializeSystem(bool reload) { if (!reload) { // Initialize logging system @@ -1609,6 +1517,12 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* e Common::Android::GetJString(env, jpath)); } +void Java_org_yuzu_yuzu_1emu_NativeLibrary_addGameFolderFileToFilesystemProvider( + JNIEnv* env, jobject jobj, jstring jpath) { + EmulationSession::GetInstance().ConfigureFilesystemProviderFromGameFolder( + Common::Android::GetJString(env, jpath)); +} + void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, jobject jobj) { EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries(); } diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index dfbc8b2943..f2e5c2cfd6 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -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 @@ -46,6 +49,7 @@ public: const Core::PerfStatsResults& PerfStats(); int ShadersBuilding(); void ConfigureFilesystemProvider(const std::string& filepath); + void ConfigureFilesystemProviderFromGameFolder(const std::string& filepath); void InitializeSystem(bool reload); void SetAppletId(int applet_id); Core::SystemResultStatus InitializeEmulation(const std::string& filepath, diff --git a/src/common/settings.h b/src/common/settings.h index 7ea4136576..7c6c0d062f 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -756,6 +756,8 @@ struct Values { Category::DataStorage}; Setting gamecard_path{linkage, std::string(), "gamecard_path", Category::DataStorage}; + Setting ext_content_from_game_dirs{linkage, true, "ext_content_from_game_dirs", + Category::DataStorage}; std::vector external_content_dirs; // Debugging diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 82944ceceb..e9c3bb75c2 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -117,6 +117,12 @@ void AppendCommaIfNotEmpty(std::string& to, std::string_view with) { bool IsDirValidAndNonEmpty(const VirtualDir& dir) { return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty()); } + +bool IsVersionedExternalUpdateDisabled(const std::vector& disabled, u32 version) { + const std::string disabled_key = fmt::format("Update@{}", version); + return std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend() || + std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); +} } // Anonymous namespace PatchManager::PatchManager(u64 title_id_, @@ -155,8 +161,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { if (!update_versions.empty()) { checked_external = true; for (const auto& update_entry : update_versions) { - std::string disabled_key = fmt::format("Update@{}", update_entry.version); - if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) { + if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) { update_disabled = false; enabled_version = update_entry.version; break; @@ -175,8 +180,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { if (!manual_update_versions.empty()) { checked_manual = true; for (const auto& update_entry : manual_update_versions) { - std::string disabled_key = fmt::format("Update@{}", update_entry.version); - if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) { + if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) { update_disabled = false; enabled_version = update_entry.version; break; @@ -580,8 +584,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs if (!update_versions.empty()) { checked_external = true; for (const auto& update_entry : update_versions) { - std::string disabled_key = fmt::format("Update@{}", update_entry.version); - if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) { + if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) { update_disabled = false; enabled_version = update_entry.version; update_raw = external_provider->GetEntryForVersion(update_tid, type, update_entry.version); @@ -600,8 +603,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs if (!manual_update_versions.empty()) { checked_manual = true; for (const auto& update_entry : manual_update_versions) { - std::string disabled_key = fmt::format("Update@{}", update_entry.version); - if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) { + if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) { update_disabled = false; enabled_version = update_entry.version; update_raw = manual_provider->GetEntryForVersion(update_tid, type, update_entry.version); @@ -704,9 +706,8 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { version_str = FormatTitleVersion(update_entry.version); } - std::string disabled_key = fmt::format("Update@{}", update_entry.version); const auto update_disabled = - std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend(); + IsVersionedExternalUpdateDisabled(disabled, update_entry.version); Patch update_patch = {.enabled = !update_disabled, .name = "Update", @@ -732,9 +733,8 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { version_str = FormatTitleVersion(update_entry.version); } - std::string disabled_key = fmt::format("Update@{}", update_entry.version); const auto update_disabled = - std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend(); + IsVersionedExternalUpdateDisabled(disabled, update_entry.version); Patch update_patch = {.enabled = !update_disabled, @@ -771,7 +771,8 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { std::nullopt, std::nullopt, ContentRecordType::Program, update_tid); for (const auto& [slot, entry] : all_updates) { - if (slot == ContentProviderUnionSlot::External) { + if (slot == ContentProviderUnionSlot::External || + slot == ContentProviderUnionSlot::FrontendManual) { continue; } diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 7bf2ad8fcd..020d403c95 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -104,6 +104,206 @@ static std::string GetCNMTName(TitleType type, u64 title_id) { return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); } +static std::shared_ptr OpenContainerAsNsp(const VirtualFile& file, Loader::FileType type) { + if (!file) { + return nullptr; + } + + if (type == Loader::FileType::Unknown || type == Loader::FileType::Error) { + type = Loader::IdentifyFile(file); + if (type == Loader::FileType::Unknown) { + type = Loader::GuessFromFilename(file->GetName()); + } + } + + if (type == Loader::FileType::NSP) { + auto nsp = std::make_shared(file); + return nsp->GetStatus() == Loader::ResultStatus::Success ? nsp : nullptr; + } + + if (type == Loader::FileType::XCI) { + XCI xci(file); + if (xci.GetStatus() != Loader::ResultStatus::Success) { + return nullptr; + } + + auto secure_partition = xci.GetSecurePartitionNSP(); + if (secure_partition == nullptr) { + return nullptr; + } + + return secure_partition; + } + + // SAF-backed files can occasionally fail type-guessing despite being valid NSP/XCI. + // As a last resort, probe both container parsers directly. + { + auto nsp = std::make_shared(file); + if (nsp->GetStatus() == Loader::ResultStatus::Success) { + return nsp; + } + } + { + XCI xci(file); + if (xci.GetStatus() == Loader::ResultStatus::Success) { + auto secure_partition = xci.GetSecurePartitionNSP(); + if (secure_partition != nullptr) { + return secure_partition; + } + } + } + + return nullptr; +} + +template +bool ForEachContainerEntry(const std::shared_ptr& nsp, bool only_content, + std::optional base_program_id, Callback&& on_entry) { + if (!nsp) { + return false; + } + + const auto& ncas = nsp->GetNCAs(); + if (ncas.empty()) { + return false; + } + + std::map versions; + std::map version_strings; + + for (const auto& [title_id, nca_map] : ncas) { + for (const auto& [type_pair, nca] : nca_map) { + if (!nca) { + continue; + } + + const auto& [title_type, content_type] = type_pair; + + if (content_type == ContentRecordType::Meta) { + const auto subdirs = nca->GetSubdirectories(); + if (!subdirs.empty()) { + for (const auto& inner_file : subdirs[0]->GetFiles()) { + if (inner_file->GetExtension() == "cnmt") { + const CNMT cnmt(inner_file); + versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion(); + break; + } + } + } + } + + if (title_type == TitleType::Update && content_type == ContentRecordType::Control) { + const auto romfs = nca->GetRomFS(); + if (!romfs) { + continue; + } + + const auto extracted = ExtractRomFS(romfs); + if (!extracted) { + continue; + } + + auto nacp_file = extracted->GetFile("control.nacp"); + if (!nacp_file) { + nacp_file = extracted->GetFile("Control.nacp"); + } + if (!nacp_file) { + continue; + } + + const NACP nacp(nacp_file); + auto version_string = nacp.GetVersionString(); + if (!version_string.empty()) { + version_strings[title_id] = std::move(version_string); + } + } + } + } + + bool added_entries = false; + for (const auto& [title_id, nca_map] : ncas) { + if (base_program_id.has_value() && GetBaseTitleID(title_id) != *base_program_id) { + continue; + } + + for (const auto& [type_pair, nca] : nca_map) { + const auto& [title_type, content_type] = type_pair; + if (only_content && title_type != TitleType::Update && title_type != TitleType::AOC) { + continue; + } + + auto entry_file = nca ? nca->GetBaseFile() : nullptr; + if (!entry_file) { + continue; + } + + u32 version = 0; + std::string version_string; + + if (title_type == TitleType::Update) { + if (const auto version_it = versions.find(title_id); version_it != versions.end()) { + version = version_it->second; + } + + if (const auto version_str_it = version_strings.find(title_id); + version_str_it != version_strings.end()) { + version_string = version_str_it->second; + } + } + + on_entry(title_type, content_type, title_id, entry_file, version, version_string); + added_entries = true; + } + } + + return added_entries; +} + +static void UpsertExternalVersionEntry(std::vector& multi_version_entries, + u64 title_id, u32 version, + const std::string& version_string, + ContentRecordType content_type, const VirtualFile& file) { + auto it = std::find_if(multi_version_entries.begin(), multi_version_entries.end(), + [title_id, version](const ExternalUpdateEntry& entry) { + return entry.title_id == title_id && entry.version == version; + }); + + if (it == multi_version_entries.end()) { + ExternalUpdateEntry update_entry; + update_entry.title_id = title_id; + update_entry.version = version; + update_entry.version_string = version_string; + update_entry.files[static_cast(content_type)] = file; + multi_version_entries.push_back(std::move(update_entry)); + return; + } + + it->files[static_cast(content_type)] = file; + if (it->version_string.empty() && !version_string.empty()) { + it->version_string = version_string; + } +} + +template +static bool AddExternalEntriesFromContainer(const std::shared_ptr& nsp, EntryMap& entries, + VersionMap& versions, + std::vector& multi_version_entries) { + return ForEachContainerEntry( + nsp, true, std::nullopt, + [&entries, &versions, + &multi_version_entries](TitleType title_type, ContentRecordType content_type, u64 title_id, + const VirtualFile& file, u32 version, + const std::string& version_string) { + entries[{title_id, content_type, title_type}] = file; + + if (title_type == TitleType::Update) { + versions[title_id] = version; + UpsertExternalVersionEntry(multi_version_entries, title_id, version, version_string, + content_type, file); + } + }); +} + ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { switch (type) { case NCAContentType::Program: @@ -1008,6 +1208,26 @@ void ManualContentProvider::AddEntryWithVersion(TitleType title_type, ContentRec } } +bool ManualContentProvider::AddEntriesFromContainer(VirtualFile file, bool only_content, + std::optional base_program_id) { + const auto nsp = OpenContainerAsNsp(file, Loader::FileType::Unknown); + if (!nsp) { + return false; + } + + return ForEachContainerEntry( + nsp, only_content, base_program_id, + [this](TitleType title_type, ContentRecordType content_type, u64 title_id, + const VirtualFile& entry_file, u32 version, const std::string& version_string) { + if (title_type == TitleType::Update) { + AddEntryWithVersion(title_type, content_type, title_id, version, version_string, + entry_file); + } else { + AddEntry(title_type, content_type, title_id, entry_file); + } + }); +} + void ManualContentProvider::ClearAllEntries() { entries.clear(); multi_version_entries.clear(); @@ -1091,14 +1311,6 @@ VirtualFile ManualContentProvider::GetEntryForVersion(u64 title_id, ContentRecor return nullptr; } -bool ManualContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const { - size_t count = 0; - for (const auto& entry : multi_version_entries) - if (entry.title_id == title_id && entry.files[size_t(type)]) - ++count; - return count > 0; -} - ExternalContentProvider::ExternalContentProvider(std::vector load_directories) : load_dirs(std::move(load_directories)) { ExternalContentProvider::Refresh(); @@ -1159,247 +1371,22 @@ void ExternalContentProvider::ScanDirectory(const VirtualDir& dir) { } void ExternalContentProvider::ProcessNSP(const VirtualFile& file) { - auto nsp = NSP(file); - if (nsp.GetStatus() != Loader::ResultStatus::Success) { + const auto nsp = OpenContainerAsNsp(file, Loader::FileType::NSP); + if (!nsp) { return; } LOG_DEBUG(Service_FS, "Processing NSP file: {}", file->GetName()); - - const auto ncas = nsp.GetNCAs(); - - std::map nsp_versions; - std::map nsp_version_strings; // title_id -> NACP version string - - for (const auto& [title_id, nca_map] : ncas) { - for (const auto& [type_pair, nca] : nca_map) { - const auto& [title_type, content_type] = type_pair; - - if (content_type == ContentRecordType::Meta) { - const auto subdirs = nca->GetSubdirectories(); - if (!subdirs.empty()) { - const auto section0 = subdirs[0]; - const auto files = section0->GetFiles(); - for (const auto& inner_file : files) { - if (inner_file->GetExtension() == "cnmt") { - const CNMT cnmt(inner_file); - const auto cnmt_title_id = cnmt.GetTitleID(); - const auto version = cnmt.GetTitleVersion(); - nsp_versions[cnmt_title_id] = version; - versions[cnmt_title_id] = version; - break; - } - } - } - } - - if (content_type == ContentRecordType::Control && title_type == TitleType::Update) { - auto romfs = nca->GetRomFS(); - if (romfs) { - auto extracted = ExtractRomFS(romfs); - if (extracted) { - auto nacp_file = extracted->GetFile("control.nacp"); - if (!nacp_file) { - nacp_file = extracted->GetFile("Control.nacp"); - } - if (nacp_file) { - NACP nacp(nacp_file); - auto ver_str = nacp.GetVersionString(); - if (!ver_str.empty()) { - nsp_version_strings[title_id] = ver_str; - } - } - } - } - } - } - } - - std::map, std::array> version_files; - - for (const auto& [title_id, nca_map] : ncas) { - for (const auto& [type_pair, nca] : nca_map) { - const auto& [title_type, content_type] = type_pair; - - if (title_type != TitleType::AOC && title_type != TitleType::Update) { - continue; - } - - auto nca_file = nsp.GetNCAFile(title_id, content_type, title_type); - if (nca_file != nullptr) { - entries[{title_id, content_type, title_type}] = nca_file; - - if (title_type == TitleType::Update) { - u32 version = 0; - auto ver_it = nsp_versions.find(title_id); - if (ver_it != nsp_versions.end()) { - version = ver_it->second; - } - - version_files[{title_id, version}][size_t(content_type)] = nca_file; - } - - LOG_DEBUG(Service_FS, "Added entry - Title ID: {:016X}, Type: {}, Content: {}", - title_id, static_cast(title_type), static_cast(content_type)); - } - } - } - - for (const auto& [key, files_map] : version_files) { - const auto& [title_id, version] = key; - - std::string ver_str; - auto str_it = nsp_version_strings.find(title_id); - if (str_it != nsp_version_strings.end()) { - ver_str = str_it->second; - } - - bool version_exists = false; - for (auto& existing : multi_version_entries) { - if (existing.title_id == title_id && existing.version == version) { - existing.files = files_map; - if (existing.version_string.empty() && !ver_str.empty()) { - existing.version_string = ver_str; - } - version_exists = true; - break; - } - } - - if (!version_exists && !files_map.empty()) { - ExternalUpdateEntry update_entry{ - .title_id = title_id, - .version = version, - .version_string = ver_str, - .files = files_map - }; - multi_version_entries.push_back(update_entry); - LOG_DEBUG(Service_FS, "Added multi-version update - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}", - title_id, version, ver_str, files_map.size()); - } - } + AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries); } void ExternalContentProvider::ProcessXCI(const VirtualFile& file) { - auto xci = XCI(file); - if (xci.GetStatus() != Loader::ResultStatus::Success) { + const auto nsp = OpenContainerAsNsp(file, Loader::FileType::XCI); + if (!nsp) { return; } - auto nsp = xci.GetSecurePartitionNSP(); - if (nsp == nullptr) { - return; - } - - const auto ncas = nsp->GetNCAs(); - - std::map xci_versions; - std::map xci_version_strings; - - for (const auto& [title_id, nca_map] : ncas) { - for (const auto& [type_pair, nca] : nca_map) { - const auto& [title_type, content_type] = type_pair; - - if (content_type == ContentRecordType::Meta) { - const auto subdirs = nca->GetSubdirectories(); - if (!subdirs.empty()) { - const auto section0 = subdirs[0]; - const auto files = section0->GetFiles(); - for (const auto& inner_file : files) { - if (inner_file->GetExtension() == "cnmt") { - const CNMT cnmt(inner_file); - const auto cnmt_title_id = cnmt.GetTitleID(); - const auto version = cnmt.GetTitleVersion(); - xci_versions[cnmt_title_id] = version; - versions[cnmt_title_id] = version; - break; - } - } - } - } - - if (content_type == ContentRecordType::Control && title_type == TitleType::Update) { - auto romfs = nca->GetRomFS(); - if (romfs) { - auto extracted = ExtractRomFS(romfs); - if (extracted) { - auto nacp_file = extracted->GetFile("control.nacp"); - if (!nacp_file) { - nacp_file = extracted->GetFile("Control.nacp"); - } - if (nacp_file) { - NACP nacp(nacp_file); - auto ver_str = nacp.GetVersionString(); - if (!ver_str.empty()) { - xci_version_strings[title_id] = ver_str; - } - } - } - } - } - } - } - - std::map, std::array> version_files; - - for (const auto& [title_id, nca_map] : ncas) { - for (const auto& [type_pair, nca] : nca_map) { - const auto& [title_type, content_type] = type_pair; - - if (title_type != TitleType::AOC && title_type != TitleType::Update) { - continue; - } - - auto nca_file = nsp->GetNCAFile(title_id, content_type, title_type); - if (nca_file != nullptr) { - entries[{title_id, content_type, title_type}] = nca_file; - - if (title_type == TitleType::Update) { - u32 version = 0; - auto ver_it = xci_versions.find(title_id); - if (ver_it != xci_versions.end()) { - version = ver_it->second; - } - - version_files[{title_id, version}][size_t(content_type)] = nca_file; - } - } - } - } - - for (const auto& [key, files_map] : version_files) { - const auto& [title_id, version] = key; - - std::string ver_str; - auto str_it = xci_version_strings.find(title_id); - if (str_it != xci_version_strings.end()) { - ver_str = str_it->second; - } - - bool version_exists = false; - for (auto& existing : multi_version_entries) { - if (existing.title_id == title_id && existing.version == version) { - existing.files = files_map; - if (existing.version_string.empty() && !ver_str.empty()) { - existing.version_string = ver_str; - } - version_exists = true; - break; - } - } - - if (!version_exists && !files_map.empty()) { - ExternalUpdateEntry update_entry{ - .title_id = title_id, - .version = version, - .version_string = ver_str, - .files = files_map - }; - multi_version_entries.push_back(update_entry); - LOG_DEBUG(Service_FS, "Added multi-version update from XCI - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}", - title_id, version, ver_str, files_map.size()); - } - } + AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries); } bool ExternalContentProvider::HasEntry(u64 title_id, ContentRecordType type) const { @@ -1491,12 +1478,4 @@ VirtualFile ExternalContentProvider::GetEntryForVersion(u64 title_id, ContentRec return nullptr; } -bool ExternalContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const { - size_t count = 0; - for (const auto& entry : multi_version_entries) - if (entry.title_id == title_id && entry.files[size_t(type)]) - ++count; - return count > 1; -} - } // namespace FileSys diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 2e39f74db8..32134d1c48 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -262,6 +263,8 @@ public: VirtualFile file); void AddEntryWithVersion(TitleType title_type, ContentRecordType content_type, u64 title_id, u32 version, const std::string& version_string, VirtualFile file); + bool AddEntriesFromContainer(VirtualFile file, bool only_content = false, + std::optional base_program_id = std::nullopt); void ClearAllEntries(); void Refresh() override; @@ -276,7 +279,6 @@ public: std::vector ListUpdateVersions(u64 title_id) const; VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const; - bool HasMultipleVersions(u64 title_id, ContentRecordType type) const; private: std::map, VirtualFile> entries; @@ -303,7 +305,6 @@ public: std::vector ListUpdateVersions(u64 title_id) const; VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const; - bool HasMultipleVersions(u64 title_id, ContentRecordType type) const; private: void ScanDirectory(const VirtualDir& dir); diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 4379634d03..b4d50227d3 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -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 @@ -9,11 +9,15 @@ #include #include #include +#include #include "common/concepts.h" #include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/string_util.h" #include "core/core.h" +#include "core/file_sys/card_image.h" +#include "core/file_sys/common_funcs.h" +#include "core/file_sys/submission_package.h" #include "core/hle/kernel/k_process.h" #include "core/loader/deconstructed_rom_directory.h" #include "core/loader/kip.h" @@ -37,6 +41,49 @@ std::optional IdentifyFileLoader(FileSys::VirtualFile file) { return std::nullopt; } +std::shared_ptr OpenContainerAsNsp(FileSys::VirtualFile file, FileType type, + u64 program_id = 0, + std::size_t program_index = 0) { + if (!file) { + return nullptr; + } + + if (type == FileType::NSP) { + auto nsp = std::make_shared(file, program_id, program_index); + return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr; + } + + if (type == FileType::XCI) { + FileSys::XCI xci{file, program_id, program_index}; + if (xci.GetStatus() != ResultStatus::Success) { + return nullptr; + } + + auto secure_nsp = xci.GetSecurePartitionNSP(); + if (secure_nsp == nullptr || secure_nsp->GetStatus() != ResultStatus::Success) { + return nullptr; + } + + return secure_nsp; + } + + return nullptr; +} + +bool HasApplicationProgramContent(const std::shared_ptr& nsp) { + if (!nsp) { + return false; + } + + const auto& ncas = nsp->GetNCAs(); + return std::any_of(ncas.cbegin(), ncas.cend(), [](const auto& title_entry) { + const auto& nca_map = title_entry.second; + return nca_map.find( + {FileSys::TitleType::Application, FileSys::ContentRecordType::Program}) != + nca_map.end(); + }); +} + } // namespace FileType IdentifyFile(FileSys::VirtualFile file) { @@ -62,6 +109,27 @@ FileType IdentifyFile(FileSys::VirtualFile file) { } } +bool IsContainerType(FileType type) { + return type == FileType::NSP || type == FileType::XCI; +} + +bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 program_id, + std::size_t program_index) { + if (!file) { + return false; + } + + if (type == FileType::Unknown) { + type = IdentifyFile(file); + } + + if (!IsContainerType(type)) { + return false; + } + + return HasApplicationProgramContent(OpenContainerAsNsp(file, type, program_id, program_index)); +} + FileType GuessFromFilename(const std::string& name) { if (name == "main") return FileType::DeconstructedRomDirectory; diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index f4e932cec9..95ce638da0 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -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 @@ -46,12 +49,29 @@ enum class FileType { }; /** - * Identifies the type of a bootable file based on the magic value in its header. + * Identifies the type of a supported file/container based on its structure. * @param file open file * @return FileType of file */ FileType IdentifyFile(FileSys::VirtualFile file); +/** + * Returns whether the file type represents a container format that can bundle multiple titles + * (currently NSP/XCI). + */ +bool IsContainerType(FileType type); + +/** + * Returns whether a container file is bootable as a game (has Application/Program content). + * + * @param file open file + * @param type optional file type; if Unknown it is auto-detected. + * @param program_id optional program id hint for multi-program containers. + * @param program_index optional program index hint for multi-program containers. + */ +bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type = FileType::Unknown, + u64 program_id = 0, std::size_t program_index = 0); + /** * Guess the type of a bootable file from its name * @param name String name of bootable file diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 3016d5f25f..4333acb70c 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -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 @@ -55,19 +58,30 @@ AppLoader_NSP::~AppLoader_NSP() = default; FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) { const FileSys::NSP nsp(nsp_file); - if (nsp.GetStatus() == ResultStatus::Success) { - // Extracted Type case - if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr && - FileSys::IsDirectoryExeFS(nsp.GetExeFS())) { - return FileType::NSP; + if (nsp.GetStatus() != ResultStatus::Success) { + return FileType::Error; + } + + // Extracted Type case + if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr && + FileSys::IsDirectoryExeFS(nsp.GetExeFS())) { + return FileType::NSP; + } + + // Non-extracted NSPs can legitimately contain only update/DLC content. + // Identify the container format itself; bootability is validated by Load(). + if (!nsp.GetNCAs().empty()) { + return FileType::NSP; + } + + // Fallback when NCAs couldn't be parsed (e.g. missing keys) but the PFS still contains NCAs. + for (const auto& entry : nsp.GetFiles()) { + if (entry == nullptr) { + continue; } - // Non-Extracted Type case - const auto program_id = nsp.GetProgramTitleID(); - if (!nsp.IsExtractedType() && - nsp.GetNCA(program_id, FileSys::ContentRecordType::Program) != nullptr && - AppLoader_NCA::IdentifyType( - nsp.GetNCAFile(program_id, FileSys::ContentRecordType::Program)) == FileType::NCA) { + const auto& name = entry->GetName(); + if (name.size() >= 4 && name.substr(name.size() - 4) == ".nca") { return FileType::NSP; } } diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index e9abb199a1..983184a226 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -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 @@ -44,10 +47,13 @@ AppLoader_XCI::~AppLoader_XCI() = default; FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& xci_file) { const FileSys::XCI xci(xci_file); - if (xci.GetStatus() == ResultStatus::Success && - xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr && - AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) == - FileType::NCA) { + if (xci.GetStatus() != ResultStatus::Success) { + return FileType::Error; + } + + // Identify XCI as a valid container even when it does not include a bootable Program NCA. + // Bootability is handled by AppLoader_XCI::Load(). + if (xci.GetSecurePartitionNSP() != nullptr) { return FileType::XCI; } diff --git a/src/yuzu/game/game_list_worker.cpp b/src/yuzu/game/game_list_worker.cpp index c4504a0d5e..81012e4374 100644 --- a/src/yuzu/game/game_list_worker.cpp +++ b/src/yuzu/game/game_list_worker.cpp @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include #include @@ -16,14 +17,17 @@ #include "common/fs/fs.h" #include "common/fs/path_util.h" +#include "common/settings.h" #include "core/core.h" #include "core/file_sys/card_image.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/fs_filesystem.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" +#include "core/file_sys/romfs.h" #include "core/file_sys/submission_package.h" #include "core/loader/loader.h" #include "yuzu/compatibility_list.h" @@ -375,6 +379,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa return true; } + if (target == ScanTarget::PopulateGameList && + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP) && + !Loader::IsBootableGameContainer(file, file_type)) { + return true; + } + u64 program_id = 0; const auto res2 = loader->ReadProgramId(program_id); @@ -383,18 +393,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa provider->AddEntry(FileSys::TitleType::Application, FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id, file); - } else if (res2 == Loader::ResultStatus::Success && + } else if (Settings::values.ext_content_from_game_dirs.GetValue() && (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { - const auto nsp = file_type == Loader::FileType::NSP - ? std::make_shared(file) - : FileSys::XCI{file}.GetSecurePartitionNSP(); - for (const auto& title : nsp->GetNCAs()) { - for (const auto& entry : title.second) { - provider->AddEntry(entry.first.first, entry.first.second, title.first, - entry.second->GetBaseFile()); - } - } + void(provider->AddEntriesFromContainer(file)); } } else { std::vector program_ids; diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 6ead3c4130..e02e02b413 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -2019,6 +2019,10 @@ void MainWindow::ConfigureFilesystemProvider(const std::string& filepath) { return; } + if (QtCommon::provider->AddEntriesFromContainer(file)) { + return; + } + auto loader = Loader::GetLoader(*QtCommon::system, file); if (!loader) { return; @@ -2033,19 +2037,8 @@ void MainWindow::ConfigureFilesystemProvider(const std::string& filepath) { const auto res2 = loader->ReadProgramId(program_id); if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { QtCommon::provider->AddEntry(FileSys::TitleType::Application, - FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id, - file); - } else if (res2 == Loader::ResultStatus::Success && - (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { - const auto nsp = file_type == Loader::FileType::NSP - ? std::make_shared(file) - : FileSys::XCI{file}.GetSecurePartitionNSP(); - for (const auto& title : nsp->GetNCAs()) { - for (const auto& entry : title.second) { - QtCommon::provider->AddEntry(entry.first.first, entry.first.second, title.first, - entry.second->GetBaseFile()); - } - } + FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), + program_id, file); } } From cdf9b556b25b33cf558c0ed3085d99e129a2fb1c Mon Sep 17 00:00:00 2001 From: wildcard Date: Thu, 5 Mar 2026 00:54:26 +0100 Subject: [PATCH 37/74] [vulkan]fix vuid 02751 (#3573) Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3573 Reviewed-by: CamilleLaVey Reviewed-by: DraVee Co-authored-by: wildcard Co-committed-by: wildcard --- src/video_core/renderer_vulkan/vk_buffer_cache.cpp | 8 ++++++++ src/video_core/vulkan_common/vulkan_device.h | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 6256bc8bd8..f4345262fb 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -108,6 +108,14 @@ VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat // Null buffer not supported, adjust offset and size offset = 0; size = 0; + } else { + // Align offset down to minTexelBufferOffsetAlignment + const u32 alignment = static_cast(device->GetMinTexelBufferOffsetAlignment()); + if (alignment > 1) { + const u32 aligned_offset = offset & ~(alignment - 1); + size += offset - aligned_offset; + offset = aligned_offset; + } } const auto it{std::ranges::find_if(views, [offset, size, format](const BufferView& view) { return offset == view.offset && size == view.size && format == view.format; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index d3623d1186..d29a8cd3f3 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -318,6 +318,11 @@ public: return properties.properties.limits.minStorageBufferOffsetAlignment; } + /// Returns texel buffer offset alignment requirement. + VkDeviceSize GetMinTexelBufferOffsetAlignment() const { + return properties.properties.limits.minTexelBufferOffsetAlignment; + } + /// Returns the maximum range for storage buffers. VkDeviceSize GetMaxStorageBufferRange() const { return properties.properties.limits.maxStorageBufferRange; From 69e47bd2c0c04e0e18c29a23533e8e7724436226 Mon Sep 17 00:00:00 2001 From: wildcard Date: Thu, 5 Mar 2026 00:54:48 +0100 Subject: [PATCH 38/74] [vulkan] Fix wrong stage index in prepare_stage render area check (#3672) The OpenGL correctly uses const auto& info{stage_infos[stage]}; Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3672 Reviewed-by: MaranBr Reviewed-by: DraVee Co-authored-by: wildcard Co-committed-by: wildcard --- src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index d156baa77b..5b11a34232 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -474,7 +474,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { buffer_cache.BindHostStageBuffers(stage); PushImageDescriptors(texture_cache, guest_descriptor_queue, stage_infos[stage], rescaling, samplers_it, views_it); - const auto& info{stage_infos[0]}; + const auto& info{stage_infos[stage]}; if (info.uses_render_area) { render_area.uses_render_area = true; render_area.words = {static_cast(regs.surface_clip.width), From 70c1e9abed3dc70284c4eece3786922b2fc144ed Mon Sep 17 00:00:00 2001 From: nekle Date: Thu, 5 Mar 2026 00:56:25 +0100 Subject: [PATCH 39/74] [android] Try and fix SD Card storage mount points for external paths (#3436) Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3436 Reviewed-by: DraVee Reviewed-by: MaranBr Co-authored-by: nekle Co-committed-by: nekle --- .../java/org/yuzu/yuzu_emu/utils/PathUtil.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PathUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PathUtil.kt index a840b3b846..744e1d149c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PathUtil.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PathUtil.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.utils @@ -80,16 +80,14 @@ object PathUtil { } } - // This really shouldn't be necessary, but the Android API seemingly - // doesn't have a way of doing this? - // Apparently, on certain devices the mount location can vary, so add - // extra cases here if we discover any new ones. fun getRemovableStoragePath(idString: String): String? { - var pathFile: File + val possibleMountPaths = listOf("/mnt/media_rw/$idString", "/storage/$idString") - pathFile = File("/mnt/media_rw/$idString"); - if (pathFile.exists()) { - return pathFile.absolutePath + for (mountPath in possibleMountPaths) { + val pathFile = File(mountPath); + if (pathFile.exists()) { + return pathFile.absolutePath + } } return null From 05f6942befe0ab1209ecc5e2ddb9716f49d65574 Mon Sep 17 00:00:00 2001 From: xbzk Date: Thu, 5 Mar 2026 00:58:49 +0100 Subject: [PATCH 40/74] [android, ui] fixed setting reset to defaults infinite loop (#3659) fixed a bug discovered by Pavel in which the settings' "reset to defaults" dialog would get stuck in a infinite loop, due to a recall prior to cleaning state. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3659 Reviewed-by: MaranBr Reviewed-by: DraVee Reviewed-by: CamilleLaVey Co-authored-by: xbzk Co-committed-by: xbzk --- .../settings/ui/SettingsDialogFragment.kt | 42 +++++++------------ .../settings/ui/SettingsFragmentPresenter.kt | 5 ++- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt index f66f4bac7f..4dcb35c010 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -68,7 +68,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener MaterialAlertDialogBuilder(requireContext()) .setMessage(R.string.reset_setting_confirmation) .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> - when (val item = settingsViewModel.clickedItem) { + val item = settingsViewModel.clickedItem ?: return@setPositiveButton + clearDialogState() + when (item) { is AnalogInputSetting -> { val stickParam = NativeInput.getStickParam( item.playerIndex, @@ -107,12 +109,17 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener } else -> { - settingsViewModel.clickedItem!!.setting.reset() + item.setting.reset() settingsViewModel.setAdapterItemChanged(position) } } } - .setNegativeButton(android.R.string.cancel, null) + .setNegativeButton(android.R.string.cancel) { _: DialogInterface, _: Int -> + clearDialogState() + } + .setOnCancelListener { + clearDialogState() + } .create() } @@ -186,27 +193,6 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener updateButtonState(isValid) } - /* - * xbzk: these two events, along with attachRepeat feature, - * were causing spinbox buttons to respond twice per press - * cutting these out to retain accelerated press functionality - * TODO: clean this out later if no issues arise - * - spinboxBinding.buttonDecrement.setOnClickListener { - val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue - val newValue = current - 1 - spinboxBinding.editValue.setText(newValue.toString()) - updateValidity(newValue) - } - - spinboxBinding.buttonIncrement.setOnClickListener { - val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue - val newValue = current + 1 - spinboxBinding.editValue.setText(newValue.toString()) - updateValidity(newValue) - } - */ - fun attachRepeat(button: View, delta: Int) { val handler = Handler(Looper.getMainLooper()) var runnable: Runnable? = null @@ -439,9 +425,13 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener private fun closeDialog() { settingsViewModel.setAdapterItemChanged(position) + clearDialogState() + dismiss() + } + + private fun clearDialogState() { settingsViewModel.clickedItem = null settingsViewModel.setSliderProgress(-1f) - dismiss() } private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 0fc4fb0b7f..77104e0614 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1066,7 +1066,10 @@ class SettingsFragmentPresenter( IntSetting.THEME.getValueAsString() override val defaultValue: Int = IntSetting.THEME.defaultValue - override fun reset() = IntSetting.THEME.setInt(defaultValue) + override fun reset() { + IntSetting.THEME.setInt(defaultValue) + settingsViewModel.setShouldRecreate(true) + } } add(HeaderSetting(R.string.app_settings)) From 9a07bd0570ceba262b4a918f4927e5674b6f7269 Mon Sep 17 00:00:00 2001 From: lizzie Date: Thu, 5 Mar 2026 07:32:18 +0100 Subject: [PATCH 41/74] [vk] unify VkSurfaceKHR with Android and the rest of platforms; remove technically incorrect nullptr() ctor for handles (#2971) Removes some odd #ifdef-ing that just can use a shrimple opaque type. Also removes nullptr() ctor'ing for vulkan handles and such; it's not incorrect per se like how `void *p = 0;` isn't incorrect, just that, y'know, any static analyzer will go "woah". Also there isn't any guarantee that handles `sizeof(Handle) == sizeof(void*)` so may as well :) Signed-off-by: lizzie lizzie@eden-emu.dev Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2971 Reviewed-by: CamilleLaVey Reviewed-by: MaranBr Co-authored-by: lizzie Co-committed-by: lizzie --- src/video_core/renderer_vulkan/blit_image.cpp | 8 +-- .../renderer_vulkan/renderer_vulkan.cpp | 8 +-- .../renderer_vulkan/vk_compute_pass.cpp | 8 +-- .../renderer_vulkan/vk_compute_pipeline.cpp | 36 ++++++------- .../renderer_vulkan/vk_graphics_pipeline.cpp | 44 ++++++++------- .../renderer_vulkan/vk_present_manager.cpp | 20 ++----- .../renderer_vulkan/vk_present_manager.h | 16 ++---- .../renderer_vulkan/vk_query_cache.cpp | 2 +- .../renderer_vulkan/vk_query_cache.h | 5 +- .../renderer_vulkan/vk_scheduler.cpp | 2 +- src/video_core/renderer_vulkan/vk_scheduler.h | 8 +-- .../renderer_vulkan/vk_swapchain.cpp | 54 +++---------------- src/video_core/renderer_vulkan/vk_swapchain.h | 22 ++------ src/video_core/vulkan_common/vulkan.h | 5 +- .../vulkan_common/vulkan_device.cpp | 2 +- .../vulkan_common/vulkan_surface.cpp | 2 +- src/video_core/vulkan_common/vulkan_wrapper.h | 44 +++++++-------- 17 files changed, 104 insertions(+), 182 deletions(-) diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index 07611ef98c..789f4da2ed 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp @@ -1032,7 +1032,7 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend VkShaderModule frag_shader = *convert_float_to_depth_frag; const std::array stages = MakeStages(*full_screen_vert, frag_shader); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); - pipeline = device.GetLogical().CreateGraphicsPipeline({ + pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -1062,7 +1062,7 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend VkShaderModule frag_shader = *convert_depth_to_float_frag; const std::array stages = MakeStages(*full_screen_vert, frag_shader); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); - pipeline = device.GetLogical().CreateGraphicsPipeline({ + pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -1093,7 +1093,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren } const std::array stages = MakeStages(*full_screen_vert, *module); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); - pipeline = device.GetLogical().CreateGraphicsPipeline({ + pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -1135,7 +1135,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende is_target_depth ? *convert_float_to_depth_frag : *convert_depth_to_float_frag; const std::array stages = MakeStages(*full_screen_vert, frag_shader); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); - pipeline = device.GetLogical().CreateGraphicsPipeline({ + pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index d1e607e75f..cb1b1a5362 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -137,14 +137,8 @@ try memory_allocator, scheduler, swapchain, -#ifdef ANDROID - surface) - , -#else *surface) - , -#endif - blit_swapchain(device_memory, + , blit_swapchain(device_memory, device, memory_allocator, present_manager, diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 22e646afe9..d45a57f7bb 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -285,7 +285,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool, .requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U, }; bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size; - pipeline = device.GetLogical().CreateComputePipeline({ + pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -299,7 +299,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool, .pSpecializationInfo = nullptr, }, .layout = *layout, - .basePipelineHandle = nullptr, + .basePipelineHandle = {}, .basePipelineIndex = 0, }); } @@ -944,7 +944,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_, .codeSize = static_cast(code.size_bytes()), .pCode = code.data(), }); - pipelines[i] = device.GetLogical().CreateComputePipeline({ + pipelines[i] = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -958,7 +958,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_, .pSpecializationInfo = nullptr, }, .layout = *layout, - .basePipelineHandle = nullptr, + .basePipelineHandle = {}, .basePipelineIndex = 0, }); }; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 6a6fe2b830..1a62324c95 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -67,26 +67,24 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) { flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; } - pipeline = device.GetLogical().CreateComputePipeline( - { - .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, - .pNext = nullptr, - .flags = flags, - .stage{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - .pNext = - device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr, - .flags = 0, - .stage = VK_SHADER_STAGE_COMPUTE_BIT, - .module = *spv_module, - .pName = "main", - .pSpecializationInfo = nullptr, - }, - .layout = *pipeline_layout, - .basePipelineHandle = 0, - .basePipelineIndex = 0, + pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{ + .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = flags, + .stage{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = + device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr, + .flags = 0, + .stage = VK_SHADER_STAGE_COMPUTE_BIT, + .module = *spv_module, + .pName = "main", + .pSpecializationInfo = nullptr, }, - *pipeline_cache); + .layout = *pipeline_layout, + .basePipelineHandle = 0, + .basePipelineIndex = 0, + }, *pipeline_cache); // Log compute pipeline creation if (Settings::values.gpu_logging_enabled.GetValue()) { diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 5b11a34232..e989bf6b31 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -946,29 +946,27 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; } - pipeline = device.GetLogical().CreateGraphicsPipeline( - { - .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, - .pNext = nullptr, - .flags = flags, - .stageCount = static_cast(shader_stages.size()), - .pStages = shader_stages.data(), - .pVertexInputState = &vertex_input_ci, - .pInputAssemblyState = &input_assembly_ci, - .pTessellationState = &tessellation_ci, - .pViewportState = &viewport_ci, - .pRasterizationState = &rasterization_ci, - .pMultisampleState = &multisample_ci, - .pDepthStencilState = &depth_stencil_ci, - .pColorBlendState = &color_blend_ci, - .pDynamicState = &dynamic_state_ci, - .layout = *pipeline_layout, - .renderPass = render_pass, - .subpass = 0, - .basePipelineHandle = nullptr, - .basePipelineIndex = 0, - }, - *pipeline_cache); + pipeline = device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = flags, + .stageCount = static_cast(shader_stages.size()), + .pStages = shader_stages.data(), + .pVertexInputState = &vertex_input_ci, + .pInputAssemblyState = &input_assembly_ci, + .pTessellationState = &tessellation_ci, + .pViewportState = &viewport_ci, + .pRasterizationState = &rasterization_ci, + .pMultisampleState = &multisample_ci, + .pDepthStencilState = &depth_stencil_ci, + .pColorBlendState = &color_blend_ci, + .pDynamicState = &dynamic_state_ci, + .layout = *pipeline_layout, + .renderPass = render_pass, + .subpass = 0, + .basePipelineHandle = nullptr, + .basePipelineIndex = 0, + }, *pipeline_cache); // Log graphics pipeline creation if (Settings::values.gpu_logging_enabled.GetValue()) { diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp index 3b5c2e3c01..aa019a4160 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -101,22 +101,14 @@ PresentManager::PresentManager(const vk::Instance& instance_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_, Swapchain& swapchain_, -#ifdef ANDROID - vk::SurfaceKHR& surface_) -#else - VkSurfaceKHR_T* surface_handle_) -#endif + VkSurfaceKHR_T* surface_) : instance{instance_} , render_window{render_window_} , device{device_} , memory_allocator{memory_allocator_} , scheduler{scheduler_} , swapchain{swapchain_} -#ifdef ANDROID , surface{surface_} -#else - , surface_handle{surface_handle_} -#endif , blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())} , use_present_thread{Settings::values.async_presentation.GetValue()} { @@ -299,11 +291,7 @@ void PresentManager::PresentThread(std::stop_token token) { } void PresentManager::RecreateSwapchain(Frame* frame) { -#ifndef ANDROID - swapchain.Create(surface_handle, frame->width, frame->height); // Pass raw pointer -#else - swapchain.Create(*surface, frame->width, frame->height); // Pass raw pointer -#endif + swapchain.Create(surface, frame->width, frame->height); // Pass raw pointer SetImageCount(); } @@ -322,7 +310,7 @@ void PresentManager::CopyToSwapchain(Frame* frame) { // Recreate surface and swapchain if needed. if (requires_recreation) { #ifdef ANDROID - surface = CreateSurface(instance, render_window.GetWindowInfo()); + surface = *CreateSurface(instance, render_window.GetWindowInfo()).address(); #endif RecreateSwapchain(frame); } diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h index aacc9b025a..3d5cc32102 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.h +++ b/src/video_core/renderer_vulkan/vk_present_manager.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -15,8 +15,6 @@ #include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/vulkan_wrapper.h" -struct VkSurfaceKHR_T; - namespace Core::Frontend { class EmuWindow; } // namespace Core::Frontend @@ -46,11 +44,7 @@ public: MemoryAllocator& memory_allocator, Scheduler& scheduler, Swapchain& swapchain, -#ifdef ANDROID - vk::SurfaceKHR& surface); -#else - VkSurfaceKHR_T* surface_handle); -#endif + VkSurfaceKHR_T* surface); ~PresentManager(); /// Returns the last used presentation frame @@ -84,11 +78,7 @@ private: MemoryAllocator& memory_allocator; Scheduler& scheduler; Swapchain& swapchain; -#ifdef ANDROID - vk::SurfaceKHR& surface; -#else - VkSurfaceKHR_T* surface_handle; -#endif + VkSurfaceKHR_T* surface; vk::CommandPool cmdpool; std::vector frames; boost::container::deque present_queue; diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 415259c72c..7cdb3acadd 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -1280,7 +1280,7 @@ void QueryCacheRuntime::EndHostConditionalRendering() { PauseHostConditionalRendering(); impl->hcr_is_set = false; impl->is_hcr_running = false; - impl->hcr_buffer = nullptr; + impl->hcr_buffer = VkBuffer{}; impl->hcr_offset = 0; } diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h index b8dae9bc2d..e2aa4d991e 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.h +++ b/src/video_core/renderer_vulkan/vk_query_cache.h @@ -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-3.0-or-later @@ -35,7 +38,7 @@ public: ~QueryCacheRuntime(); template - void SyncValues(std::span values, VkBuffer base_src_buffer = nullptr); + void SyncValues(std::span values, VkBuffer base_src_buffer = VkBuffer{}); void Barriers(bool is_prebarrier); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 0a032cdae0..947de6a80e 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -377,7 +377,7 @@ void Scheduler::EndRenderPass() VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images)); }); - state.renderpass = nullptr; + state.renderpass = VkRenderPass{}; num_renderpass_images = 0; } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 667f136ee6..00a912f2cd 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -44,10 +44,10 @@ public: ~Scheduler(); /// Sends the current execution context to the GPU. - u64 Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); + u64 Flush(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {}); /// Sends the current execution context to the GPU and waits for it to complete. - void Finish(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); + void Finish(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {}); /// Waits for the worker thread to finish executing everything. After this function returns it's /// safe to touch worker resources. @@ -237,8 +237,8 @@ private: }; struct State { - VkRenderPass renderpass = nullptr; - VkFramebuffer framebuffer = nullptr; + VkRenderPass renderpass{}; + VkFramebuffer framebuffer{}; VkExtent2D render_area = {0, 0}; GraphicsPipeline* graphics_pipeline = nullptr; bool is_rescaling = false; diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index b89e981444..cd8f948d8b 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -109,38 +109,22 @@ VkCompositeAlphaFlagBitsKHR ChooseAlphaFlags(const VkSurfaceCapabilitiesKHR& cap } // Anonymous namespace Swapchain::Swapchain( -#ifdef ANDROID - VkSurfaceKHR surface_, -#else - VkSurfaceKHR_T* surface_handle_, -#endif + VkSurfaceKHR_T* surface_, const Device& device_, Scheduler& scheduler_, u32 width_, u32 height_) -#ifdef ANDROID : surface(surface_) -#else - : surface_handle{surface_handle_} -#endif , device{device_} , scheduler{scheduler_} { -#ifdef ANDROID Create(surface, width_, height_); -#else - Create(surface_handle, width_, height_); -#endif } Swapchain::~Swapchain() = default; void Swapchain::Create( -#ifdef ANDROID - VkSurfaceKHR surface_, -#else - VkSurfaceKHR_T* surface_handle_, -#endif + VkSurfaceKHR_T* surface_, u32 width_, u32 height_) { @@ -148,18 +132,10 @@ void Swapchain::Create( is_suboptimal = false; width = width_; height = height_; -#ifdef ANDROID surface = surface_; -#else - surface_handle = surface_handle_; -#endif const auto physical_device = device.GetPhysical(); -#ifdef ANDROID - const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface)}; -#else - const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface_handle)}; -#endif + const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(VkSurfaceKHR(surface))}; if (capabilities.maxImageExtent.width == 0 || capabilities.maxImageExtent.height == 0) { return; } @@ -254,14 +230,8 @@ void Swapchain::Present(VkSemaphore render_semaphore) { void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) { const auto physical_device{device.GetPhysical()}; - -#ifdef ANDROID - const auto formats{physical_device.GetSurfaceFormatsKHR(surface)}; - const auto present_modes = physical_device.GetSurfacePresentModesKHR(surface); -#else - const auto formats{physical_device.GetSurfaceFormatsKHR(surface_handle)}; - const auto present_modes = physical_device.GetSurfacePresentModesKHR(surface_handle); -#endif + const auto formats{physical_device.GetSurfaceFormatsKHR(VkSurfaceKHR(surface))}; + const auto present_modes = physical_device.GetSurfacePresentModesKHR(VkSurfaceKHR(surface)); has_mailbox = std::find(present_modes.begin(), present_modes.end(), VK_PRESENT_MODE_MAILBOX_KHR) != present_modes.end(); @@ -290,11 +260,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .pNext = nullptr, .flags = 0, -#ifdef ANDROID - .surface = surface, -#else - .surface = surface_handle, -#endif + .surface = VkSurfaceKHR(surface), .minImageCount = requested_image_count, .imageFormat = surface_format.format, .imageColorSpace = surface_format.colorSpace, @@ -313,7 +279,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) { .compositeAlpha = alpha_flags, .presentMode = present_mode, .clipped = VK_FALSE, - .oldSwapchain = nullptr, + .oldSwapchain = VkSwapchainKHR{}, }; const u32 graphics_family{device.GetGraphicsFamily()}; const u32 present_family{device.GetPresentFamily()}; @@ -345,11 +311,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) { swapchain_ci.flags |= VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR; } // Request the size again to reduce the possibility of a TOCTOU race condition. -#ifdef ANDROID - const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface); -#else - const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface_handle); -#endif + const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(VkSurfaceKHR(surface)); swapchain_ci.imageExtent = ChooseSwapExtent(updated_capabilities, width, height); // Don't add code within this and the swapchain creation. swapchain = device.GetLogical().CreateSwapchainKHR(swapchain_ci); diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index 7e99bf8fa7..d926cc118a 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -11,8 +11,6 @@ #include "common/common_types.h" #include "video_core/vulkan_common/vulkan_wrapper.h" -struct VkSurfaceKHR_T; - namespace Layout { struct FramebufferLayout; } @@ -25,11 +23,7 @@ class Scheduler; class Swapchain { public: explicit Swapchain( -#ifdef ANDROID - VkSurfaceKHR surface, -#else - VkSurfaceKHR_T* surface_handle, -#endif + VkSurfaceKHR_T* surface, const Device& device, Scheduler& scheduler, u32 width, @@ -38,11 +32,7 @@ public: /// Creates (or recreates) the swapchain with a given size. void Create( -#ifdef ANDROID - VkSurfaceKHR surface, -#else - VkSurfaceKHR_T* surface_handle, -#endif + VkSurfaceKHR_T* surface, u32 width, u32 height); @@ -128,11 +118,7 @@ private: bool NeedsPresentModeUpdate() const; -#ifdef ANDROID - VkSurfaceKHR surface; -#else - VkSurfaceKHR_T* surface_handle; -#endif + VkSurfaceKHR_T* surface; const Device& device; Scheduler& scheduler; diff --git a/src/video_core/vulkan_common/vulkan.h b/src/video_core/vulkan_common/vulkan.h index 8d2e8e2a37..2cc0f0d7f0 100644 --- a/src/video_core/vulkan_common/vulkan.h +++ b/src/video_core/vulkan_common/vulkan.h @@ -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 2023 yuzu Emulator Project @@ -40,3 +40,6 @@ #undef False #undef None #undef True + +// "Catch-all" handle for both Android and.. the rest of platforms +struct VkSurfaceKHR_T; diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index a2ff3ee6ed..b51c57d380 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -419,7 +419,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR : instance{instance_}, dld{dld_}, physical{physical_}, format_properties(GetFormatProperties(physical)) { // Get suitability and device properties. - const bool is_suitable = GetSuitability(surface != nullptr); + const bool is_suitable = GetSuitability(surface != VkSurfaceKHR{}); const VkDriverId driver_id = properties.driver.driverID; diff --git a/src/video_core/vulkan_common/vulkan_surface.cpp b/src/video_core/vulkan_common/vulkan_surface.cpp index dc65d3960a..761b7759c8 100644 --- a/src/video_core/vulkan_common/vulkan_surface.cpp +++ b/src/video_core/vulkan_common/vulkan_surface.cpp @@ -15,7 +15,7 @@ vk::SurfaceKHR CreateSurface( const vk::Instance& instance, [[maybe_unused]] const Core::Frontend::EmuWindow::WindowSystemInfo& window_info) { [[maybe_unused]] const vk::InstanceDispatch& dld = instance.Dispatch(); - VkSurfaceKHR unsafe_surface = nullptr; + VkSurfaceKHR unsafe_surface = VkSurfaceKHR{}; #ifdef _WIN32 if (window_info.type == Core::Frontend::WindowSystemType::Windows) { diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 5c04132f7b..872fbd858e 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -404,13 +404,13 @@ public: /// Construct a handle transferring the ownership from another handle. Handle(Handle&& rhs) noexcept - : handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, dld{rhs.dld} {} + : handle{std::exchange(rhs.handle, Type{})}, owner{rhs.owner}, dld{rhs.dld} {} /// Assign the current handle transferring the ownership from another handle. /// Destroys any previously held object. Handle& operator=(Handle&& rhs) noexcept { Release(); - handle = std::exchange(rhs.handle, nullptr); + handle = std::exchange(rhs.handle, Type{}); owner = rhs.owner; dld = rhs.dld; return *this; @@ -424,7 +424,7 @@ public: /// Destroys any held object. void reset() noexcept { Release(); - handle = nullptr; + handle = Type{}; } /// Returns the address of the held object. @@ -440,7 +440,7 @@ public: /// Returns true when there's a held object. explicit operator bool() const noexcept { - return handle != nullptr; + return handle != Type{}; } #ifndef ANDROID @@ -455,7 +455,7 @@ public: #endif protected: - Type handle = nullptr; + Type handle{}; OwnerType owner = nullptr; const Dispatch* dld = nullptr; @@ -463,7 +463,7 @@ private: /// Destroys the held object if it exists. void Release() noexcept { if (handle) { - Destroy(owner, handle, *dld); + Destroy(OwnerType(owner), Type(handle), *dld); } } }; @@ -506,7 +506,7 @@ public: /// Destroys any held object. void reset() noexcept { Release(); - handle = nullptr; + handle = {}; } /// Returns the address of the held object. @@ -522,7 +522,7 @@ public: /// Returns true when there's a held object. explicit operator bool() const noexcept { - return handle != nullptr; + return handle != Type{}; } #ifndef ANDROID @@ -537,7 +537,7 @@ public: #endif protected: - Type handle = nullptr; + Type handle{}; const Dispatch* dld = nullptr; private: @@ -607,7 +607,7 @@ private: std::unique_ptr allocations; std::size_t num = 0; VkDevice device = nullptr; - PoolType pool = nullptr; + PoolType pool{}; const DeviceDispatch* dld = nullptr; }; @@ -669,12 +669,12 @@ public: Image& operator=(const Image&) = delete; Image(Image&& rhs) noexcept - : handle{std::exchange(rhs.handle, nullptr)}, usage{rhs.usage}, owner{rhs.owner}, + : handle{std::exchange(rhs.handle, VkImage{})}, usage{rhs.usage}, owner{rhs.owner}, allocator{rhs.allocator}, allocation{rhs.allocation}, dld{rhs.dld} {} Image& operator=(Image&& rhs) noexcept { Release(); - handle = std::exchange(rhs.handle, nullptr); + handle = std::exchange(rhs.handle, VkImage{}); usage = rhs.usage; owner = rhs.owner; allocator = rhs.allocator; @@ -693,11 +693,11 @@ public: void reset() noexcept { Release(); - handle = nullptr; + handle = VkImage{}; } explicit operator bool() const noexcept { - return handle != nullptr; + return handle != VkImage{}; } void SetObjectNameEXT(const char* name) const; @@ -709,7 +709,7 @@ public: private: void Release() const noexcept; - VkImage handle = nullptr; + VkImage handle{}; VkImageUsageFlags usage{}; VkDevice owner = nullptr; VmaAllocator allocator = nullptr; @@ -730,13 +730,13 @@ public: Buffer& operator=(const Buffer&) = delete; Buffer(Buffer&& rhs) noexcept - : handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, allocator{rhs.allocator}, + : handle{std::exchange(rhs.handle, VkBuffer{})}, owner{rhs.owner}, allocator{rhs.allocator}, allocation{rhs.allocation}, mapped{rhs.mapped}, is_coherent{rhs.is_coherent}, dld{rhs.dld} {} Buffer& operator=(Buffer&& rhs) noexcept { Release(); - handle = std::exchange(rhs.handle, nullptr); + handle = std::exchange(rhs.handle, VkBuffer{}); owner = rhs.owner; allocator = rhs.allocator; allocation = rhs.allocation; @@ -756,11 +756,11 @@ public: void reset() noexcept { Release(); - handle = nullptr; + handle = VkBuffer{}; } explicit operator bool() const noexcept { - return handle != nullptr; + return handle != VkBuffer{}; } /// Returns the host mapped memory, an empty span otherwise. @@ -786,7 +786,7 @@ public: private: void Release() const noexcept; - VkBuffer handle = nullptr; + VkBuffer handle{}; VkDevice owner = nullptr; VmaAllocator allocator = nullptr; VmaAllocation allocation = nullptr; @@ -1020,10 +1020,10 @@ public: [[nodiscard]] PipelineLayout CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci) const; [[nodiscard]] Pipeline CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci, - VkPipelineCache cache = nullptr) const; + VkPipelineCache cache = {}) const; [[nodiscard]] Pipeline CreateComputePipeline(const VkComputePipelineCreateInfo& ci, - VkPipelineCache cache = nullptr) const; + VkPipelineCache cache = {}) const; [[nodiscard]] Sampler CreateSampler(const VkSamplerCreateInfo& ci) const; From 529b0694995c84c51515022c52a66c67ceacdc65 Mon Sep 17 00:00:00 2001 From: xbzk Date: Thu, 5 Mar 2026 13:58:46 +0100 Subject: [PATCH 42/74] [android,ui] fixed top disalignment between buttons of each column in settings fragment (#3675) this silly little thing tickles obsessive compulsive disturbed fellas a lot hu3 was shipped along PR 3660, which was rediscussed for other reason, hence this tiny lonely PR. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3675 Reviewed-by: DraVee Reviewed-by: MaranBr Co-authored-by: xbzk Co-committed-by: xbzk --- .../features/fetcher/SpacingItemDecoration.kt | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/fetcher/SpacingItemDecoration.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/fetcher/SpacingItemDecoration.kt index f3d000a739..b3ffcc2a35 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/fetcher/SpacingItemDecoration.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/fetcher/SpacingItemDecoration.kt @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.features.fetcher import android.graphics.Rect import android.view.View +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() { @@ -15,8 +16,20 @@ class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecorat state: RecyclerView.State ) { outRect.bottom = spacing - if (parent.getChildAdapterPosition(view) == 0) { + + val position = parent.getChildAdapterPosition(view) + if (position == RecyclerView.NO_POSITION) return + + if (position == 0) { outRect.top = spacing + return + } + + // If the item is in the first row, but NOT in first column add top spacing as well + val layoutManager = parent.layoutManager + if (layoutManager is GridLayoutManager && layoutManager.spanSizeLookup.getSpanGroupIndex(position, layoutManager.spanCount) == 0) { + outRect.top = spacing + return } } } From 23566a1f7dc639946e6d3935f4951d4d2bce8461 Mon Sep 17 00:00:00 2001 From: MaranBr Date: Fri, 6 Mar 2026 15:02:59 +0100 Subject: [PATCH 43/74] [prepo] Add support for missing PlayReport commands (#3674) This fixes: `[ 433.095195] Debug core\hle\service\service.cpp:operator ():69: Assertion Failed! Unknown / unimplemented function '10107': port='prepo:u' cmd_buf={[0]=0x110006, [1]=0x80000014, [2]=0x1, [3]=0x0, [4]=0x0, [5]=0x191080, [6]=0x5A7350F8, [7]=0x112, [8]=0x5A735158}` Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3674 Reviewed-by: CamilleLaVey Reviewed-by: DraVee Reviewed-by: Maufeat Co-authored-by: MaranBr Co-committed-by: MaranBr --- src/core/hle/service/prepo/prepo.cpp | 8 +++++--- src/core/reporter.h | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp index 4fc59d0e10..bfc5539903 100644 --- a/src/core/hle/service/prepo/prepo.cpp +++ b/src/core/hle/service/prepo/prepo.cpp @@ -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 @@ -28,8 +28,10 @@ public: {10101, &PlayReport::SaveReportWithUser, "SaveReportWithUserOld"}, {10102, &PlayReport::SaveReport, "SaveReportOld2"}, {10103, &PlayReport::SaveReportWithUser, "SaveReportWithUserOld2"}, - {10104, &PlayReport::SaveReport, "SaveReport"}, - {10105, &PlayReport::SaveReportWithUser, "SaveReportWithUser"}, + {10104, &PlayReport::SaveReport, "SaveReportOld3"}, + {10105, &PlayReport::SaveReportWithUser, "SaveReportWithUserOld3"}, + {10106, &PlayReport::SaveReport, "SaveReport"}, + {10107, &PlayReport::SaveReportWithUser, "SaveReportWithUser"}, {10200, &PlayReport::RequestImmediateTransmission, "RequestImmediateTransmission"}, {10300, &PlayReport::GetTransmissionStatus, "GetTransmissionStatus"}, {10400, &PlayReport::GetSystemSessionId, "GetSystemSessionId"}, diff --git a/src/core/reporter.h b/src/core/reporter.h index db1ca3ba0c..1eee8da31f 100644 --- a/src/core/reporter.h +++ b/src/core/reporter.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -53,6 +56,7 @@ public: enum class PlayReportType { Old, Old2, + Old3, New, System, }; From c70b857c4f8324621c3d3f7dfe659856c8000878 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 6 Mar 2026 15:04:38 +0100 Subject: [PATCH 44/74] [video_core/engines] Macro HLE inline (#3653) Should slightly boost perf on android, Desktop is mainly unaffected (for now) Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3653 Reviewed-by: CamilleLaVey Reviewed-by: DraVee Co-authored-by: lizzie Co-committed-by: lizzie --- src/video_core/engines/maxwell_3d.cpp | 21 +- src/video_core/engines/maxwell_3d.h | 2 +- src/video_core/macro.cpp | 1103 ++++++++++--------------- src/video_core/macro.h | 166 +++- 4 files changed, 586 insertions(+), 706 deletions(-) diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 7dbb8f6617..e48f294a5a 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -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 @@ -26,8 +26,15 @@ namespace Tegra::Engines { constexpr u32 MacroRegistersStart = 0xE00; Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_) - : draw_manager{std::make_unique(this)}, system{system_}, - memory_manager{memory_manager_}, macro_engine{GetMacroEngine(*this)}, upload_state{memory_manager, regs.upload} { + : draw_manager{std::make_unique(this)}, system{system_} + , memory_manager{memory_manager_} +#ifdef ARCHITECTURE_x86_64 + , macro_engine(bool(Settings::values.disable_macro_jit)) +#else + , macro_engine(true) +#endif + , upload_state{memory_manager, regs.upload} +{ dirty.flags.flip(); InitializeRegisterDefaults(); execution_mask.reset(); @@ -328,9 +335,9 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume shadow_state.shadow_ram_control = static_cast(nonshadow_argument); return; case MAXWELL3D_REG_INDEX(load_mme.instruction_ptr): - return macro_engine->ClearCode(regs.load_mme.instruction_ptr); + return macro_engine.ClearCode(regs.load_mme.instruction_ptr); case MAXWELL3D_REG_INDEX(load_mme.instruction): - return macro_engine->AddCode(regs.load_mme.instruction_ptr, argument); + return macro_engine.AddCode(regs.load_mme.instruction_ptr, argument); case MAXWELL3D_REG_INDEX(load_mme.start_address): return ProcessMacroBind(argument); case MAXWELL3D_REG_INDEX(falcon[4]): @@ -398,7 +405,7 @@ void Maxwell3D::CallMacroMethod(u32 method, const std::vector& parameters) ((method - MacroRegistersStart) >> 1) % static_cast(macro_positions.size()); // Execute the current macro. - macro_engine->Execute(macro_positions[entry], parameters); + macro_engine.Execute(*this, macro_positions[entry], parameters); draw_manager->DrawDeferred(); } @@ -464,7 +471,7 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, } void Maxwell3D::ProcessMacroUpload(u32 data) { - macro_engine->AddCode(regs.load_mme.instruction_ptr++, data); + macro_engine.AddCode(regs.load_mme.instruction_ptr++, data); } void Maxwell3D::ProcessMacroBind(u32 data) { diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 5312c04b6f..52546e4279 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -3203,7 +3203,7 @@ private: std::vector macro_params; /// Interpreter for the macro codes uploaded to the GPU. - std::optional macro_engine; + MacroEngine macro_engine; Upload::State upload_state; diff --git a/src/video_core/macro.cpp b/src/video_core/macro.cpp index 3fe69be4dd..0d1fe0a52b 100644 --- a/src/video_core/macro.cpp +++ b/src/video_core/macro.cpp @@ -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 @@ -10,6 +10,7 @@ #include #include +#include #ifdef ARCHITECTURE_x86_64 // xbyak hates human beings #ifdef __GNUC__ @@ -73,601 +74,411 @@ bool IsTopologySafe(Maxwell3D::Regs::PrimitiveTopology topology) { } } -class HLEMacroImpl : public CachedMacro { -public: - explicit HLEMacroImpl(Maxwell3D& maxwell3d_) - : CachedMacro(maxwell3d_) - {} -}; +} // Anonymous namespace -/// @note: these macros have two versions, a normal and extended version, with the extended version -/// also assigning the base vertex/instance. -template -class HLE_DrawArraysIndirect final : public HLEMacroImpl { -public: - explicit HLE_DrawArraysIndirect(Maxwell3D& maxwell3d_) - : HLEMacroImpl(maxwell3d_) - {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - auto topology = static_cast(parameters[0]); - if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { - Fallback(parameters); - return; - } - - auto& params = maxwell3d.draw_manager->GetIndirectParams(); - params.is_byte_count = false; - params.is_indexed = false; - params.include_count = false; - params.count_start_address = 0; - params.indirect_start_address = maxwell3d.GetMacroAddress(1); - params.buffer_size = 4 * sizeof(u32); - params.max_draw_counts = 1; - params.stride = 0; - - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - } - - maxwell3d.draw_manager->DrawArrayIndirect(topology); - - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } +void HLE_DrawArraysIndirect::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { + auto topology = static_cast(parameters[0]); + if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { + Fallback(maxwell3d, parameters); + return; } -private: - void Fallback(const std::vector& parameters) { - SCOPE_EXIT { - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } - }; - maxwell3d.RefreshParameters(); - const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); + auto& params = maxwell3d.draw_manager->GetIndirectParams(); + params.is_byte_count = false; + params.is_indexed = false; + params.include_count = false; + params.count_start_address = 0; + params.indirect_start_address = maxwell3d.GetMacroAddress(1); + params.buffer_size = 4 * sizeof(u32); + params.max_draw_counts = 1; + params.stride = 0; - auto topology = static_cast(parameters[0]); - const u32 vertex_first = parameters[3]; - const u32 vertex_count = parameters[1]; - - if (!IsTopologySafe(topology) && size_t(maxwell3d.GetMaxCurrentVertices()) < size_t(vertex_first) + size_t(vertex_count)) { - ASSERT(false && "Faulty draw!"); - return; - } - - const u32 base_instance = parameters[4]; - if (extended) { - maxwell3d.regs.global_base_instance_index = base_instance; - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - } - - maxwell3d.draw_manager->DrawArray(topology, vertex_first, vertex_count, base_instance, - instance_count); - - if (extended) { - maxwell3d.regs.global_base_instance_index = 0; - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } - } -}; - -/* - * @note: these macros have two versions, a normal and extended version, with the extended version - * also assigning the base vertex/instance. - */ -template -class HLE_DrawIndexedIndirect final : public HLEMacroImpl { -public: - explicit HLE_DrawIndexedIndirect(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - auto topology = static_cast(parameters[0]); - if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { - Fallback(parameters); - return; - } - - const u32 estimate = static_cast(maxwell3d.EstimateIndexBufferSize()); - const u32 element_base = parameters[4]; - const u32 base_instance = parameters[5]; - maxwell3d.regs.vertex_id_base = element_base; - maxwell3d.regs.global_base_vertex_index = element_base; - maxwell3d.regs.global_base_instance_index = base_instance; - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); - maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - } - auto& params = maxwell3d.draw_manager->GetIndirectParams(); - params.is_byte_count = false; - params.is_indexed = true; - params.include_count = false; - params.count_start_address = 0; - params.indirect_start_address = maxwell3d.GetMacroAddress(1); - params.buffer_size = 5 * sizeof(u32); - params.max_draw_counts = 1; - params.stride = 0; - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate); - maxwell3d.regs.vertex_id_base = 0x0; - maxwell3d.regs.global_base_vertex_index = 0x0; - maxwell3d.regs.global_base_instance_index = 0x0; - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } - } - -private: - void Fallback(const std::vector& parameters) { - maxwell3d.RefreshParameters(); - const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); - const u32 element_base = parameters[4]; - const u32 base_instance = parameters[5]; - maxwell3d.regs.vertex_id_base = element_base; - maxwell3d.regs.global_base_vertex_index = element_base; - maxwell3d.regs.global_base_instance_index = base_instance; - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); - maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - } - - maxwell3d.draw_manager->DrawIndex(Tegra::Maxwell3D::Regs::PrimitiveTopology(parameters[0]), parameters[3], parameters[1], element_base, base_instance, instance_count); - - maxwell3d.regs.vertex_id_base = 0x0; - maxwell3d.regs.global_base_vertex_index = 0x0; - maxwell3d.regs.global_base_instance_index = 0x0; - if (extended) { - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); - } - } -}; - -class HLE_MultiLayerClear final : public HLEMacroImpl { -public: - explicit HLE_MultiLayerClear(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - ASSERT(parameters.size() == 1); - - const Maxwell3D::Regs::ClearSurface clear_params{parameters[0]}; - const u32 rt_index = clear_params.RT; - const u32 num_layers = maxwell3d.regs.rt[rt_index].depth; - ASSERT(clear_params.layer == 0); - - maxwell3d.regs.clear_surface.raw = clear_params.raw; - maxwell3d.draw_manager->Clear(num_layers); - } -}; - -class HLE_MultiDrawIndexedIndirectCount final : public HLEMacroImpl { -public: - explicit HLE_MultiDrawIndexedIndirectCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - const auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[2]); - if (!IsTopologySafe(topology)) { - Fallback(parameters); - return; - } - - const u32 start_indirect = parameters[0]; - const u32 end_indirect = parameters[1]; - if (start_indirect >= end_indirect) { - // Nothing to do. - return; - } - - const u32 padding = parameters[3]; // padding is in words - - // size of each indirect segment - const u32 indirect_words = 5 + padding; - const u32 stride = indirect_words * sizeof(u32); - const std::size_t draw_count = end_indirect - start_indirect; - const u32 estimate = static_cast(maxwell3d.EstimateIndexBufferSize()); - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - auto& params = maxwell3d.draw_manager->GetIndirectParams(); - params.is_byte_count = false; - params.is_indexed = true; - params.include_count = true; - params.count_start_address = maxwell3d.GetMacroAddress(4); - params.indirect_start_address = maxwell3d.GetMacroAddress(5); - params.buffer_size = stride * draw_count; - params.max_draw_counts = draw_count; - params.stride = stride; - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + if (extended) { maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - maxwell3d.SetHLEReplacementAttributeType(0, 0x648, - Maxwell3D::HLEReplacementAttributeType::DrawID); - maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate); + maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + } + + maxwell3d.draw_manager->DrawArrayIndirect(topology); + + if (extended) { maxwell3d.engine_state = Maxwell3D::EngineHint::None; maxwell3d.replace_table.clear(); } - -private: - void Fallback(const std::vector& parameters) { - SCOPE_EXIT { - // Clean everything. - maxwell3d.regs.vertex_id_base = 0x0; +} +void HLE_DrawArraysIndirect::Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters) { + SCOPE_EXIT { + if (extended) { maxwell3d.engine_state = Maxwell3D::EngineHint::None; maxwell3d.replace_table.clear(); - }; - maxwell3d.RefreshParameters(); - const u32 start_indirect = parameters[0]; - const u32 end_indirect = parameters[1]; - if (start_indirect >= end_indirect) { - // Nothing to do. - return; - } - const auto topology = static_cast(parameters[2]); - const u32 padding = parameters[3]; - const std::size_t max_draws = parameters[4]; - - const u32 indirect_words = 5 + padding; - const std::size_t first_draw = start_indirect; - const std::size_t effective_draws = end_indirect - start_indirect; - const std::size_t last_draw = start_indirect + (std::min)(effective_draws, max_draws); - - for (std::size_t index = first_draw; index < last_draw; index++) { - const std::size_t base = index * indirect_words + 5; - const u32 base_vertex = parameters[base + 3]; - const u32 base_instance = parameters[base + 4]; - maxwell3d.regs.vertex_id_base = base_vertex; - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); - maxwell3d.SetHLEReplacementAttributeType( - 0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - maxwell3d.CallMethod(0x8e3, 0x648, true); - maxwell3d.CallMethod(0x8e4, static_cast(index), true); - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - maxwell3d.draw_manager->DrawIndex(topology, parameters[base + 2], parameters[base], - base_vertex, base_instance, parameters[base + 1]); } + }; + maxwell3d.RefreshParameters(); + const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); + auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[0]); + const u32 vertex_first = parameters[3]; + const u32 vertex_count = parameters[1]; + if (!IsTopologySafe(topology) && size_t(maxwell3d.GetMaxCurrentVertices()) < size_t(vertex_first) + size_t(vertex_count)) { + ASSERT(false && "Faulty draw!"); + return; } -}; - -class HLE_DrawIndirectByteCount final : public HLEMacroImpl { -public: - explicit HLE_DrawIndirectByteCount(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - const bool force = maxwell3d.Rasterizer().HasDrawTransformFeedback(); - - auto topology = static_cast(parameters[0] & 0xFFFFU); - if (!force && (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology))) { - Fallback(parameters); - return; - } - auto& params = maxwell3d.draw_manager->GetIndirectParams(); - params.is_byte_count = true; - params.is_indexed = false; - params.include_count = false; - params.count_start_address = 0; - params.indirect_start_address = maxwell3d.GetMacroAddress(2); - params.buffer_size = 4; - params.max_draw_counts = 1; - params.stride = parameters[1]; - maxwell3d.regs.draw.begin = parameters[0]; - maxwell3d.regs.draw_auto_stride = parameters[1]; - maxwell3d.regs.draw_auto_byte_count = parameters[2]; - - maxwell3d.draw_manager->DrawArrayIndirect(topology); + const u32 base_instance = parameters[4]; + if (extended) { + maxwell3d.regs.global_base_instance_index = base_instance; + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseInstance); } - -private: - void Fallback(const std::vector& parameters) { - maxwell3d.RefreshParameters(); - - maxwell3d.regs.draw.begin = parameters[0]; - maxwell3d.regs.draw_auto_stride = parameters[1]; - maxwell3d.regs.draw_auto_byte_count = parameters[2]; - - maxwell3d.draw_manager->DrawArray( - maxwell3d.regs.draw.topology, 0, - maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1); - } -}; - -class HLE_C713C83D8F63CCF3 final : public HLEMacroImpl { -public: - explicit HLE_C713C83D8F63CCF3(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - const u32 offset = (parameters[0] & 0x3FFFFFFF) << 2; - const u32 address = maxwell3d.regs.shadow_scratch[24]; - auto& const_buffer = maxwell3d.regs.const_buffer; - const_buffer.size = 0x7000; - const_buffer.address_high = (address >> 24) & 0xFF; - const_buffer.address_low = address << 8; - const_buffer.offset = offset; - } -}; - -class HLE_D7333D26E0A93EDE final : public HLEMacroImpl { -public: - explicit HLE_D7333D26E0A93EDE(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - const size_t index = parameters[0]; - const u32 address = maxwell3d.regs.shadow_scratch[42 + index]; - const u32 size = maxwell3d.regs.shadow_scratch[47 + index]; - auto& const_buffer = maxwell3d.regs.const_buffer; - const_buffer.size = size; - const_buffer.address_high = (address >> 24) & 0xFF; - const_buffer.address_low = address << 8; - } -}; - -class HLE_BindShader final : public HLEMacroImpl { -public: - explicit HLE_BindShader(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - auto& regs = maxwell3d.regs; - const u32 index = parameters[0]; - if ((parameters[1] - regs.shadow_scratch[28 + index]) == 0) { - return; - } - - regs.pipelines[index & 0xF].offset = parameters[2]; - maxwell3d.dirty.flags[VideoCommon::Dirty::Shaders] = true; - regs.shadow_scratch[28 + index] = parameters[1]; - regs.shadow_scratch[34 + index] = parameters[2]; - - const u32 address = parameters[4]; - auto& const_buffer = regs.const_buffer; - const_buffer.size = 0x10000; - const_buffer.address_high = (address >> 24) & 0xFF; - const_buffer.address_low = address << 8; - - const size_t bind_group_id = parameters[3] & 0x7F; - auto& bind_group = regs.bind_groups[bind_group_id]; - bind_group.raw_config = 0x11; - maxwell3d.ProcessCBBind(bind_group_id); - } -}; - -class HLE_SetRasterBoundingBox final : public HLEMacroImpl { -public: - explicit HLE_SetRasterBoundingBox(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - const u32 raster_mode = parameters[0]; - auto& regs = maxwell3d.regs; - const u32 raster_enabled = maxwell3d.regs.conservative_raster_enable; - const u32 scratch_data = maxwell3d.regs.shadow_scratch[52]; - regs.raster_bounding_box.raw = raster_mode & 0xFFFFF00F; - regs.raster_bounding_box.pad.Assign(scratch_data & raster_enabled); - } -}; - -template -class HLE_ClearConstBuffer final : public HLEMacroImpl { -public: - explicit HLE_ClearConstBuffer(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - static constexpr std::array zeroes{}; - auto& regs = maxwell3d.regs; - regs.const_buffer.size = u32(base_size); - regs.const_buffer.address_high = parameters[0]; - regs.const_buffer.address_low = parameters[1]; - regs.const_buffer.offset = 0; - maxwell3d.ProcessCBMultiData(zeroes.data(), parameters[2] * 4); - } -}; - -class HLE_ClearMemory final : public HLEMacroImpl { -public: - explicit HLE_ClearMemory(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - - const u32 needed_memory = parameters[2] / sizeof(u32); - if (needed_memory > zero_memory.size()) { - zero_memory.resize(needed_memory, 0); - } - auto& regs = maxwell3d.regs; - regs.upload.line_length_in = parameters[2]; - regs.upload.line_count = 1; - regs.upload.dest.address_high = parameters[0]; - regs.upload.dest.address_low = parameters[1]; - maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); - maxwell3d.CallMultiMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), zero_memory.data(), needed_memory, needed_memory); - } - -private: - std::vector zero_memory; -}; - -class HLE_TransformFeedbackSetup final : public HLEMacroImpl { -public: - explicit HLE_TransformFeedbackSetup(Maxwell3D& maxwell3d_) : HLEMacroImpl(maxwell3d_) {} - - void Execute(const std::vector& parameters, [[maybe_unused]] u32 method) override { - maxwell3d.RefreshParameters(); - - auto& regs = maxwell3d.regs; - regs.transform_feedback_enabled = 1; - regs.transform_feedback.buffers[0].start_offset = 0; - regs.transform_feedback.buffers[1].start_offset = 0; - regs.transform_feedback.buffers[2].start_offset = 0; - regs.transform_feedback.buffers[3].start_offset = 0; - - regs.upload.line_length_in = 4; - regs.upload.line_count = 1; - regs.upload.dest.address_high = parameters[0]; - regs.upload.dest.address_low = parameters[1]; - maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); - maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), regs.transform_feedback.controls[0].stride, true); - - maxwell3d.Rasterizer().RegisterTransformFeedback(regs.upload.dest.Address()); - } -}; - -} // Anonymous namespace - -HLEMacro::HLEMacro(Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {} - -HLEMacro::~HLEMacro() = default; - -std::unique_ptr HLEMacro::GetHLEProgram(u64 hash) const { - // Compiler will make you a GREAT job at making an ad-hoc hash table :) - switch (hash) { - case 0x0D61FC9FAAC9FCADULL: return std::make_unique>(maxwell3d); - case 0x8A4D173EB99A8603ULL: return std::make_unique>(maxwell3d); - case 0x771BB18C62444DA0ULL: return std::make_unique>(maxwell3d); - case 0x0217920100488FF7ULL: return std::make_unique>(maxwell3d); - case 0x3F5E74B9C9A50164ULL: return std::make_unique(maxwell3d); - case 0xEAD26C3E2109B06BULL: return std::make_unique(maxwell3d); - case 0xC713C83D8F63CCF3ULL: return std::make_unique(maxwell3d); - case 0xD7333D26E0A93EDEULL: return std::make_unique(maxwell3d); - case 0xEB29B2A09AA06D38ULL: return std::make_unique(maxwell3d); - case 0xDB1341DBEB4C8AF7ULL: return std::make_unique(maxwell3d); - case 0x6C97861D891EDf7EULL: return std::make_unique>(maxwell3d); - case 0xD246FDDF3A6173D7ULL: return std::make_unique>(maxwell3d); - case 0xEE4D0004BEC8ECF4ULL: return std::make_unique(maxwell3d); - case 0xFC0CF27F5FFAA661ULL: return std::make_unique(maxwell3d); - case 0xB5F74EDB717278ECULL: return std::make_unique(maxwell3d); - default: - return nullptr; + maxwell3d.draw_manager->DrawArray(topology, vertex_first, vertex_count, base_instance, instance_count); + if (extended) { + maxwell3d.regs.global_base_instance_index = 0; + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); } } -namespace { -class MacroInterpreterImpl final : public CachedMacro { -public: - explicit MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d_, const std::vector& code_) - : CachedMacro(maxwell3d_) - , code{code_} - {} +void HLE_DrawIndexedIndirect::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { + auto topology = static_cast(parameters[0]); + if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { + Fallback(maxwell3d, parameters); + return; + } - void Execute(const std::vector& params, u32 method) override; + const u32 estimate = u32(maxwell3d.EstimateIndexBufferSize()); + const u32 element_base = parameters[4]; + const u32 base_instance = parameters[5]; + maxwell3d.regs.vertex_id_base = element_base; + maxwell3d.regs.global_base_vertex_index = element_base; + maxwell3d.regs.global_base_instance_index = base_instance; + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); + maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + } + auto& params = maxwell3d.draw_manager->GetIndirectParams(); + params.is_byte_count = false; + params.is_indexed = true; + params.include_count = false; + params.count_start_address = 0; + params.indirect_start_address = maxwell3d.GetMacroAddress(1); + params.buffer_size = 5 * sizeof(u32); + params.max_draw_counts = 1; + params.stride = 0; + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate); + maxwell3d.regs.vertex_id_base = 0x0; + maxwell3d.regs.global_base_vertex_index = 0x0; + maxwell3d.regs.global_base_instance_index = 0x0; + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } +} +void HLE_DrawIndexedIndirect::Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters) { + maxwell3d.RefreshParameters(); + const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); + const u32 element_base = parameters[4]; + const u32 base_instance = parameters[5]; + maxwell3d.regs.vertex_id_base = element_base; + maxwell3d.regs.global_base_vertex_index = element_base; + maxwell3d.regs.global_base_instance_index = base_instance; + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); + maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + } + maxwell3d.draw_manager->DrawIndex(Tegra::Maxwell3D::Regs::PrimitiveTopology(parameters[0]), parameters[3], parameters[1], element_base, base_instance, instance_count); + maxwell3d.regs.vertex_id_base = 0x0; + maxwell3d.regs.global_base_vertex_index = 0x0; + maxwell3d.regs.global_base_instance_index = 0x0; + if (extended) { + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } +} +void HLE_MultiLayerClear::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { + maxwell3d.RefreshParameters(); + ASSERT(parameters.size() == 1); -private: - /// Resets the execution engine state, zeroing registers, etc. - void Reset(); + const Maxwell3D::Regs::ClearSurface clear_params{parameters[0]}; + const u32 rt_index = clear_params.RT; + const u32 num_layers = maxwell3d.regs.rt[rt_index].depth; + ASSERT(clear_params.layer == 0); - /** - * Executes a single macro instruction located at the current program counter. Returns whether - * the interpreter should keep running. - * - * @param is_delay_slot Whether the current step is being executed due to a delay slot in a - * previous instruction. - */ - bool Step(bool is_delay_slot); + maxwell3d.regs.clear_surface.raw = clear_params.raw; + maxwell3d.draw_manager->Clear(num_layers); +} +void HLE_MultiDrawIndexedIndirectCount::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { + const auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[2]); + if (!IsTopologySafe(topology)) { + Fallback(maxwell3d, parameters); + return; + } - /// Calculates the result of an ALU operation. src_a OP src_b; - u32 GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b); + const u32 start_indirect = parameters[0]; + const u32 end_indirect = parameters[1]; + if (start_indirect >= end_indirect) { + // Nothing to do. + return; + } - /// Performs the result operation on the input result and stores it in the specified register - /// (if necessary). - void ProcessResult(Macro::ResultOperation operation, u32 reg, u32 result); + const u32 padding = parameters[3]; // padding is in words - /// Evaluates the branch condition and returns whether the branch should be taken or not. - bool EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const; + // size of each indirect segment + const u32 indirect_words = 5 + padding; + const u32 stride = indirect_words * sizeof(u32); + const std::size_t draw_count = end_indirect - start_indirect; + const u32 estimate = static_cast(maxwell3d.EstimateIndexBufferSize()); + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + auto& params = maxwell3d.draw_manager->GetIndirectParams(); + params.is_byte_count = false; + params.is_indexed = true; + params.include_count = true; + params.count_start_address = maxwell3d.GetMacroAddress(4); + params.indirect_start_address = maxwell3d.GetMacroAddress(5); + params.buffer_size = stride * draw_count; + params.max_draw_counts = draw_count; + params.stride = stride; + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); + maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + maxwell3d.SetHLEReplacementAttributeType(0, 0x648, Maxwell3D::HLEReplacementAttributeType::DrawID); + maxwell3d.draw_manager->DrawIndexedIndirect(topology, 0, estimate); + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); +} +void HLE_MultiDrawIndexedIndirectCount::Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters) { + SCOPE_EXIT { + // Clean everything. + maxwell3d.regs.vertex_id_base = 0x0; + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + }; + maxwell3d.RefreshParameters(); + const u32 start_indirect = parameters[0]; + const u32 end_indirect = parameters[1]; + if (start_indirect >= end_indirect) { + // Nothing to do. + return; + } + const auto topology = static_cast(parameters[2]); + const u32 padding = parameters[3]; + const std::size_t max_draws = parameters[4]; + const u32 indirect_words = 5 + padding; + const std::size_t first_draw = start_indirect; + const std::size_t effective_draws = end_indirect - start_indirect; + const std::size_t last_draw = start_indirect + (std::min)(effective_draws, max_draws); + for (std::size_t index = first_draw; index < last_draw; index++) { + const std::size_t base = index * indirect_words + 5; + const u32 base_vertex = parameters[base + 3]; + const u32 base_instance = parameters[base + 4]; + maxwell3d.regs.vertex_id_base = base_vertex; + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); + maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + maxwell3d.CallMethod(0x8e3, 0x648, true); + maxwell3d.CallMethod(0x8e4, static_cast(index), true); + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + maxwell3d.draw_manager->DrawIndex(topology, parameters[base + 2], parameters[base], base_vertex, base_instance, parameters[base + 1]); + } +} +void HLE_DrawIndirectByteCount::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { + const bool force = maxwell3d.Rasterizer().HasDrawTransformFeedback(); + auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[0] & 0xFFFFU); + if (!force && (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology))) { + Fallback(maxwell3d, parameters); + return; + } + auto& params = maxwell3d.draw_manager->GetIndirectParams(); + params.is_byte_count = true; + params.is_indexed = false; + params.include_count = false; + params.count_start_address = 0; + params.indirect_start_address = maxwell3d.GetMacroAddress(2); + params.buffer_size = 4; + params.max_draw_counts = 1; + params.stride = parameters[1]; + maxwell3d.regs.draw.begin = parameters[0]; + maxwell3d.regs.draw_auto_stride = parameters[1]; + maxwell3d.regs.draw_auto_byte_count = parameters[2]; + maxwell3d.draw_manager->DrawArrayIndirect(topology); +} +void HLE_DrawIndirectByteCount::Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters) { + maxwell3d.RefreshParameters(); - /// Reads an opcode at the current program counter location. - Macro::Opcode GetOpcode() const; + maxwell3d.regs.draw.begin = parameters[0]; + maxwell3d.regs.draw_auto_stride = parameters[1]; + maxwell3d.regs.draw_auto_byte_count = parameters[2]; - /// Returns the specified register's value. Register 0 is hardcoded to always return 0. - u32 GetRegister(u32 register_id) const; + maxwell3d.draw_manager->DrawArray( + maxwell3d.regs.draw.topology, 0, + maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1); +} +void HLE_C713C83D8F63CCF3::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { + maxwell3d.RefreshParameters(); + const u32 offset = (parameters[0] & 0x3FFFFFFF) << 2; + const u32 address = maxwell3d.regs.shadow_scratch[24]; + auto& const_buffer = maxwell3d.regs.const_buffer; + const_buffer.size = 0x7000; + const_buffer.address_high = (address >> 24) & 0xFF; + const_buffer.address_low = address << 8; + const_buffer.offset = offset; +} +void HLE_D7333D26E0A93EDE::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { + maxwell3d.RefreshParameters(); + const size_t index = parameters[0]; + const u32 address = maxwell3d.regs.shadow_scratch[42 + index]; + const u32 size = maxwell3d.regs.shadow_scratch[47 + index]; + auto& const_buffer = maxwell3d.regs.const_buffer; + const_buffer.size = size; + const_buffer.address_high = (address >> 24) & 0xFF; + const_buffer.address_low = address << 8; +} +void HLE_BindShader::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { + maxwell3d.RefreshParameters(); + auto& regs = maxwell3d.regs; + const u32 index = parameters[0]; + if ((parameters[1] - regs.shadow_scratch[28 + index]) == 0) { + return; + } - /// Sets the register to the input value. - void SetRegister(u32 register_id, u32 value); + regs.pipelines[index & 0xF].offset = parameters[2]; + maxwell3d.dirty.flags[VideoCommon::Dirty::Shaders] = true; + regs.shadow_scratch[28 + index] = parameters[1]; + regs.shadow_scratch[34 + index] = parameters[2]; - /// Sets the method address to use for the next Send instruction. - void SetMethodAddress(u32 address); + const u32 address = parameters[4]; + auto& const_buffer = regs.const_buffer; + const_buffer.size = 0x10000; + const_buffer.address_high = (address >> 24) & 0xFF; + const_buffer.address_low = address << 8; - /// Calls a GPU Engine method with the input parameter. - void Send(u32 value); + const size_t bind_group_id = parameters[3] & 0x7F; + auto& bind_group = regs.bind_groups[bind_group_id]; + bind_group.raw_config = 0x11; + maxwell3d.ProcessCBBind(bind_group_id); +} +void HLE_SetRasterBoundingBox::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { + maxwell3d.RefreshParameters(); + const u32 raster_mode = parameters[0]; + auto& regs = maxwell3d.regs; + const u32 raster_enabled = maxwell3d.regs.conservative_raster_enable; + const u32 scratch_data = maxwell3d.regs.shadow_scratch[52]; + regs.raster_bounding_box.raw = raster_mode & 0xFFFFF00F; + regs.raster_bounding_box.pad.Assign(scratch_data & raster_enabled); +} +void HLE_ClearConstBuffer::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { + static constexpr std::array zeroes{}; //must be bigger than either 7000 or 5F00 + maxwell3d.RefreshParameters(); + auto& regs = maxwell3d.regs; + regs.const_buffer.size = u32(base_size); + regs.const_buffer.address_high = parameters[0]; + regs.const_buffer.address_low = parameters[1]; + regs.const_buffer.offset = 0; + maxwell3d.ProcessCBMultiData(zeroes.data(), parameters[2] * 4); +} +void HLE_ClearMemory::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { + maxwell3d.RefreshParameters(); + const u32 needed_memory = parameters[2] / sizeof(u32); + if (needed_memory > zero_memory.size()) { + zero_memory.resize(needed_memory, 0); + } + auto& regs = maxwell3d.regs; + regs.upload.line_length_in = parameters[2]; + regs.upload.line_count = 1; + regs.upload.dest.address_high = parameters[0]; + regs.upload.dest.address_low = parameters[1]; + maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); + maxwell3d.CallMultiMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), zero_memory.data(), needed_memory, needed_memory); +} +void HLE_TransformFeedbackSetup::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { + maxwell3d.RefreshParameters(); + auto& regs = maxwell3d.regs; + regs.transform_feedback_enabled = 1; + regs.transform_feedback.buffers[0].start_offset = 0; + regs.transform_feedback.buffers[1].start_offset = 0; + regs.transform_feedback.buffers[2].start_offset = 0; + regs.transform_feedback.buffers[3].start_offset = 0; + regs.upload.line_length_in = 4; + regs.upload.line_count = 1; + regs.upload.dest.address_high = parameters[0]; + regs.upload.dest.address_low = parameters[1]; + maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); + maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), regs.transform_feedback.controls[0].stride, true); + maxwell3d.Rasterizer().RegisterTransformFeedback(regs.upload.dest.Address()); +} - /// Reads a GPU register located at the method address. - u32 Read(u32 method) const; +#define HLE_MACRO_LIST \ + HLE_MACRO_ELEM(0x0D61FC9FAAC9FCADULL, HLE_DrawArraysIndirect, (false)) \ + HLE_MACRO_ELEM(0x8A4D173EB99A8603ULL, HLE_DrawArraysIndirect, (true)) \ + HLE_MACRO_ELEM(0x771BB18C62444DA0ULL, HLE_DrawIndexedIndirect, (false)) \ + HLE_MACRO_ELEM(0x0217920100488FF7ULL, HLE_DrawIndexedIndirect, (true)) \ + HLE_MACRO_ELEM(0x3F5E74B9C9A50164ULL, HLE_MultiDrawIndexedIndirectCount, ()) \ + HLE_MACRO_ELEM(0xEAD26C3E2109B06BULL, HLE_MultiLayerClear, ()) \ + HLE_MACRO_ELEM(0xC713C83D8F63CCF3ULL, HLE_C713C83D8F63CCF3, ()) \ + HLE_MACRO_ELEM(0xD7333D26E0A93EDEULL, HLE_D7333D26E0A93EDE, ()) \ + HLE_MACRO_ELEM(0xEB29B2A09AA06D38ULL, HLE_BindShader, ()) \ + HLE_MACRO_ELEM(0xDB1341DBEB4C8AF7ULL, HLE_SetRasterBoundingBox, ()) \ + HLE_MACRO_ELEM(0x6C97861D891EDf7EULL, HLE_ClearConstBuffer, (0x5F00)) \ + HLE_MACRO_ELEM(0xD246FDDF3A6173D7ULL, HLE_ClearConstBuffer, (0x7000)) \ + HLE_MACRO_ELEM(0xEE4D0004BEC8ECF4ULL, HLE_ClearMemory, ()) \ + HLE_MACRO_ELEM(0xFC0CF27F5FFAA661ULL, HLE_TransformFeedbackSetup, ()) \ + HLE_MACRO_ELEM(0xB5F74EDB717278ECULL, HLE_DrawIndirectByteCount, ()) \ - /// Returns the next parameter in the parameter queue. - u32 FetchParameter(); +// Allocates and returns a cached macro if the hash matches a known function. +[[nodiscard]] inline AnyCachedMacro GetHLEProgram(u64 hash) noexcept { + // Compiler will make you a GREAT job at making an ad-hoc hash table :) + switch (hash) { +#define HLE_MACRO_ELEM(HASH, TY, VAL) case HASH: return TY VAL; + HLE_MACRO_LIST +#undef HLE_MACRO_ELEM + default: return std::monostate{}; + } +} +[[nodiscard]] inline bool CanBeHLEProgram(u64 hash) noexcept { + switch (hash) { +#define HLE_MACRO_ELEM(HASH, TY, VAL) case HASH: return true; + HLE_MACRO_LIST +#undef HLE_MACRO_ELEM + default: return false; + } +} - /// Current program counter - u32 pc{}; - /// Program counter to execute at after the delay slot is executed. - std::optional delayed_pc; - - /// General purpose macro registers. - std::array registers = {}; - - /// Method address to use for the next Send instruction. - Macro::MethodAddress method_address = {}; - - /// Input parameters of the current macro. - std::unique_ptr parameters; - std::size_t num_parameters = 0; - std::size_t parameters_capacity = 0; - /// Index of the next parameter that will be fetched by the 'parm' instruction. - u32 next_parameter_index = 0; - - bool carry_flag = false; - const std::vector& code; -}; - -void MacroInterpreterImpl::Execute(const std::vector& params, u32 method) { +void MacroInterpreterImpl::Execute(Engines::Maxwell3D& maxwell3d, std::span params, u32 method) { Reset(); registers[1] = params[0]; - num_parameters = params.size(); - - if (num_parameters > parameters_capacity) { - parameters_capacity = num_parameters; - parameters = std::make_unique(num_parameters); - } - std::memcpy(parameters.get(), params.data(), num_parameters * sizeof(u32)); + parameters.resize(params.size()); + std::memcpy(parameters.data(), params.data(), params.size() * sizeof(u32)); // Execute the code until we hit an exit condition. bool keep_executing = true; while (keep_executing) { - keep_executing = Step(false); + keep_executing = Step(maxwell3d, false); } // Assert the the macro used all the input parameters - ASSERT(next_parameter_index == num_parameters); + ASSERT(next_parameter_index == parameters.size()); } +/// Resets the execution engine state, zeroing registers, etc. void MacroInterpreterImpl::Reset() { registers = {}; pc = 0; delayed_pc = {}; method_address.raw = 0; - num_parameters = 0; + // Vector must hold its last indices otherwise wonky shit will happen // The next parameter index starts at 1, because $r1 already has the value of the first // parameter. next_parameter_index = 1; carry_flag = false; } -bool MacroInterpreterImpl::Step(bool is_delay_slot) { +/// @brief Executes a single macro instruction located at the current program counter. Returns whether +/// the interpreter should keep running. +/// @param is_delay_slot Whether the current step is being executed due to a delay slot in a previous instruction. +bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slot) { u32 base_address = pc; Macro::Opcode opcode = GetOpcode(); @@ -682,14 +493,12 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) { switch (opcode.operation) { case Macro::Operation::ALU: { - u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a), - GetRegister(opcode.src_b)); - ProcessResult(opcode.result_operation, opcode.dst, result); + u32 result = GetALUResult(opcode.alu_operation, GetRegister(opcode.src_a), GetRegister(opcode.src_b)); + ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result); break; } case Macro::Operation::AddImmediate: { - ProcessResult(opcode.result_operation, opcode.dst, - GetRegister(opcode.src_a) + opcode.immediate); + ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, GetRegister(opcode.src_a) + opcode.immediate); break; } case Macro::Operation::ExtractInsert: { @@ -699,7 +508,7 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) { src = (src >> opcode.bf_src_bit) & opcode.GetBitfieldMask(); dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit); dst |= src << opcode.bf_dst_bit; - ProcessResult(opcode.result_operation, opcode.dst, dst); + ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, dst); break; } case Macro::Operation::ExtractShiftLeftImmediate: { @@ -708,7 +517,7 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) { u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit; - ProcessResult(opcode.result_operation, opcode.dst, result); + ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result); break; } case Macro::Operation::ExtractShiftLeftRegister: { @@ -717,12 +526,12 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) { u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst; - ProcessResult(opcode.result_operation, opcode.dst, result); + ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result); break; } case Macro::Operation::Read: { - u32 result = Read(GetRegister(opcode.src_a) + opcode.immediate); - ProcessResult(opcode.result_operation, opcode.dst, result); + u32 result = Read(maxwell3d, GetRegister(opcode.src_a) + opcode.immediate); + ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result); break; } case Macro::Operation::Branch: { @@ -738,7 +547,7 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) { delayed_pc = base_address + opcode.GetBranchTarget(); // Execute one more instruction due to the delay slot. - return Step(true); + return Step(maxwell3d, true); } break; } @@ -751,13 +560,13 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) { // cause an exit if it's executed inside a delay slot. if (opcode.is_exit && !is_delay_slot) { // Exit has a delay slot, execute the next instruction - Step(true); + Step(maxwell3d, true); return false; } - return true; } +/// Calculates the result of an ALU operation. src_a OP src_b; u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b) { switch (operation) { case Macro::ALUOperation::Add: { @@ -797,7 +606,8 @@ u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a, } } -void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 reg, u32 result) { +/// Performs the result operation on the input result and stores it in the specified register (if necessary). +void MacroInterpreterImpl::ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result) { switch (operation) { case Macro::ResultOperation::IgnoreAndFetch: // Fetch parameter and ignore result. @@ -815,12 +625,12 @@ void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 r case Macro::ResultOperation::FetchAndSend: // Fetch parameter and send result. SetRegister(reg, FetchParameter()); - Send(result); + Send(maxwell3d, result); break; case Macro::ResultOperation::MoveAndSend: // Move and send result. SetRegister(reg, result); - Send(result); + Send(maxwell3d, result); break; case Macro::ResultOperation::FetchAndSetMethod: // Fetch parameter and use result as Method Address. @@ -831,13 +641,13 @@ void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 r // Move result and use as Method Address, then fetch and send parameter. SetRegister(reg, result); SetMethodAddress(result); - Send(FetchParameter()); + Send(maxwell3d, FetchParameter()); break; case Macro::ResultOperation::MoveAndSetMethodSend: // Move result and use as Method Address, then send bits 12:17 of result. SetRegister(reg, result); SetMethodAddress(result); - Send((result >> 12) & 0b111111); + Send(maxwell3d, (result >> 12) & 0b111111); break; default: UNIMPLEMENTED_MSG("Unimplemented result operation {}", operation); @@ -845,6 +655,7 @@ void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 r } } +/// Evaluates the branch condition and returns whether the branch should be taken or not. bool MacroInterpreterImpl::EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const { switch (cond) { case Macro::BranchCondition::Zero: @@ -855,46 +666,44 @@ bool MacroInterpreterImpl::EvaluateBranchCondition(Macro::BranchCondition cond, UNREACHABLE(); } +/// Reads an opcode at the current program counter location. Macro::Opcode MacroInterpreterImpl::GetOpcode() const { ASSERT((pc % sizeof(u32)) == 0); ASSERT(pc < code.size() * sizeof(u32)); return {code[pc / sizeof(u32)]}; } +/// Returns the specified register's value. Register 0 is hardcoded to always return 0. u32 MacroInterpreterImpl::GetRegister(u32 register_id) const { - return registers.at(register_id); + return registers[register_id]; } +/// Sets the register to the input value. void MacroInterpreterImpl::SetRegister(u32 register_id, u32 value) { // Register 0 is hardwired as the zero register. // Ensure no writes to it actually occur. - if (register_id == 0) { + if (register_id == 0) return; - } - - registers.at(register_id) = value; + registers[register_id] = value; } -void MacroInterpreterImpl::SetMethodAddress(u32 address) { - method_address.raw = address; -} - -void MacroInterpreterImpl::Send(u32 value) { +/// Calls a GPU Engine method with the input parameter. +void MacroInterpreterImpl::Send(Engines::Maxwell3D& maxwell3d, u32 value) { maxwell3d.CallMethod(method_address.address, value, true); // Increment the method address by the method increment. - method_address.address.Assign(method_address.address.Value() + - method_address.increment.Value()); + method_address.address.Assign(method_address.address.Value() + method_address.increment.Value()); } -u32 MacroInterpreterImpl::Read(u32 method) const { +/// Reads a GPU register located at the method address. +u32 MacroInterpreterImpl::Read(Engines::Maxwell3D& maxwell3d, u32 method) const { return maxwell3d.GetRegisterValue(method); } +/// Returns the next parameter in the parameter queue. u32 MacroInterpreterImpl::FetchParameter() { - ASSERT(next_parameter_index < num_parameters); + ASSERT(next_parameter_index < parameters.size()); return parameters[next_parameter_index++]; } -} // Anonymous namespace #ifdef ARCHITECTURE_x86_64 namespace { @@ -930,17 +739,15 @@ static const auto default_cg_mode = Xbyak::DontSetProtectRWE; static const auto default_cg_mode = nullptr; //Allow RWE #endif -class MacroJITx64Impl final : public Xbyak::CodeGenerator, public CachedMacro { -public: - explicit MacroJITx64Impl(Engines::Maxwell3D& maxwell3d_, const std::vector& code_) +struct MacroJITx64Impl final : public Xbyak::CodeGenerator, public DynamicCachedMacro { + explicit MacroJITx64Impl(std::span code_) : Xbyak::CodeGenerator(MAX_CODE_SIZE, default_cg_mode) - , CachedMacro(maxwell3d_) , code{code_} { Compile(); } - void Execute(const std::vector& parameters, u32 method) override; + void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, u32 method) override; void Compile_ALU(Macro::Opcode opcode); void Compile_AddImmediate(Macro::Opcode opcode); @@ -950,18 +757,13 @@ public: void Compile_Read(Macro::Opcode opcode); void Compile_Branch(Macro::Opcode opcode); -private: void Optimizer_ScanFlags(); - void Compile(); bool Compile_NextInstruction(); - Xbyak::Reg32 Compile_FetchParameter(); Xbyak::Reg32 Compile_GetRegister(u32 index, Xbyak::Reg32 dst); - void Compile_ProcessResult(Macro::ResultOperation operation, u32 reg); void Compile_Send(Xbyak::Reg32 value); - Macro::Opcode GetOpCode() const; struct JITState { @@ -981,21 +783,17 @@ private: bool enable_asserts{}; }; OptimizerState optimizer{}; - std::optional next_opcode{}; ProgramType program{nullptr}; - std::array labels; std::array delay_skip; Xbyak::Label end_of_code{}; - bool is_delay_slot{}; u32 pc{}; - - const std::vector& code; + std::span code; }; -void MacroJITx64Impl::Execute(const std::vector& parameters, u32 method) { +void MacroJITx64Impl::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, u32 method) { ASSERT_OR_EXECUTE(program != nullptr, { return; }); JITState state{}; state.maxwell3d = &maxwell3d; @@ -1231,7 +1029,7 @@ void MacroJITx64Impl::Compile_Read(Macro::Opcode opcode) { Compile_ProcessResult(opcode.result_operation, opcode.dst); } -void Send(Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) { +static void MacroJIT_SendThunk(Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) { maxwell3d->CallMethod(method_address.address, value, true); } @@ -1240,7 +1038,7 @@ void MacroJITx64Impl::Compile_Send(Xbyak::Reg32 value) { mov(Common::X64::ABI_PARAM1, qword[STATE]); mov(Common::X64::ABI_PARAM2.cvt32(), METHOD_ADDRESS); mov(Common::X64::ABI_PARAM3.cvt32(), value); - Common::X64::CallFarFunction(*this, &Send); + Common::X64::CallFarFunction(*this, &MacroJIT_SendThunk); Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); Xbyak::Label dont_process{}; @@ -1452,10 +1250,8 @@ bool MacroJITx64Impl::Compile_NextInstruction() { return true; } -static void WarnInvalidParameter(uintptr_t parameter, uintptr_t max_parameter) { - LOG_CRITICAL(HW_GPU, - "Macro JIT: invalid parameter access 0x{:x} (0x{:x} is the last parameter)", - parameter, max_parameter - sizeof(u32)); +static void MacroJIT_ErrorThunk(uintptr_t parameter, uintptr_t max_parameter) { + LOG_CRITICAL(HW_GPU, "Macro JIT: invalid parameter access 0x{:x} (0x{:x} is the last parameter)", parameter, max_parameter - sizeof(u32)); } Xbyak::Reg32 MacroJITx64Impl::Compile_FetchParameter() { @@ -1465,7 +1261,7 @@ Xbyak::Reg32 MacroJITx64Impl::Compile_FetchParameter() { Common::X64::ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); mov(Common::X64::ABI_PARAM1, PARAMETERS); mov(Common::X64::ABI_PARAM2, MAX_PARAMETER); - Common::X64::CallFarFunction(*this, &WarnInvalidParameter); + Common::X64::CallFarFunction(*this, &MacroJIT_ErrorThunk); Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); L(parameter_ok); mov(eax, dword[PARAMETERS]); @@ -1574,33 +1370,42 @@ static void Dump(u64 hash, std::span code, bool decompiled = false) { macro_file.write(reinterpret_cast(code.data()), code.size_bytes()); } -MacroEngine::MacroEngine(Engines::Maxwell3D& maxwell3d_, bool is_interpreted_) - : hle_macros{std::make_optional(maxwell3d_)} - , maxwell3d{maxwell3d_} - , is_interpreted{is_interpreted_} -{} - -MacroEngine::~MacroEngine() = default; - -void MacroEngine::AddCode(u32 method, u32 data) { - uploaded_macro_code[method].push_back(data); -} - -void MacroEngine::ClearCode(u32 method) { - macro_cache.erase(method); - uploaded_macro_code.erase(method); -} - -void MacroEngine::Execute(u32 method, const std::vector& parameters) { - auto compiled_macro = macro_cache.find(method); - if (compiled_macro != macro_cache.end()) { - const auto& cache_info = compiled_macro->second; - if (cache_info.has_hle_program) { - cache_info.hle_program->Execute(parameters, method); - } else { - maxwell3d.RefreshParameters(); - cache_info.lle_program->Execute(parameters, method); - } +void MacroEngine::Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span parameters) { + auto const execute_variant = [&maxwell3d, ¶meters, method](AnyCachedMacro& acm) { + if (auto a = std::get_if(&acm)) + a->Execute(maxwell3d, parameters, method); + if (auto a = std::get_if(&acm)) + a->Execute(maxwell3d, parameters, method); + if (auto a = std::get_if(&acm)) + a->Execute(maxwell3d, parameters, method); + if (auto a = std::get_if(&acm)) + a->Execute(maxwell3d, parameters, method); + if (auto a = std::get_if(&acm)) + a->Execute(maxwell3d, parameters, method); + if (auto a = std::get_if(&acm)) + a->Execute(maxwell3d, parameters, method); + if (auto a = std::get_if(&acm)) + a->Execute(maxwell3d, parameters, method); + if (auto a = std::get_if(&acm)) + a->Execute(maxwell3d, parameters, method); + if (auto a = std::get_if(&acm)) + a->Execute(maxwell3d, parameters, method); + if (auto a = std::get_if(&acm)) + a->Execute(maxwell3d, parameters, method); + if (auto a = std::get_if(&acm)) + a->Execute(maxwell3d, parameters, method); + if (auto a = std::get_if(&acm)) + a->Execute(maxwell3d, parameters, method); + if (auto a = std::get_if(&acm)) + a->Execute(maxwell3d, parameters, method); + if (auto a = std::get_if>(&acm)) + a->get()->Execute(maxwell3d, parameters, method); + }; + if (auto const it = macro_cache.find(method); it != macro_cache.end()) { + auto& ci = it->second; + if (!CanBeHLEProgram(ci.hash) || Settings::values.disable_macro_hle) + maxwell3d.RefreshParameters(); //LLE must reload parameters + execute_variant(ci.program); } else { // Macro not compiled, check if it's uploaded and if so, compile it std::optional mid_method; @@ -1617,51 +1422,37 @@ void MacroEngine::Execute(u32 method, const std::vector& parameters) { return; } } - auto& cache_info = macro_cache[method]; - - if (!mid_method.has_value()) { - cache_info.lle_program = Compile(macro_code->second); - cache_info.hash = Common::HashValue(macro_code->second); - } else { + auto& ci = macro_cache[method]; + if (mid_method) { const auto& macro_cached = uploaded_macro_code[mid_method.value()]; const auto rebased_method = method - mid_method.value(); auto& code = uploaded_macro_code[method]; code.resize(macro_cached.size() - rebased_method); std::memcpy(code.data(), macro_cached.data() + rebased_method, code.size() * sizeof(u32)); - cache_info.hash = Common::HashValue(code); - cache_info.lle_program = Compile(code); - } - - auto hle_program = hle_macros->GetHLEProgram(cache_info.hash); - if (!hle_program || Settings::values.disable_macro_hle) { - maxwell3d.RefreshParameters(); - cache_info.lle_program->Execute(parameters, method); + ci.hash = Common::HashValue(code); + ci.program = Compile(maxwell3d, code); } else { - cache_info.has_hle_program = true; - cache_info.hle_program = std::move(hle_program); - cache_info.hle_program->Execute(parameters, method); + ci.program = Compile(maxwell3d, macro_code->second); + ci.hash = Common::HashValue(macro_code->second); } - + if (CanBeHLEProgram(ci.hash) && !Settings::values.disable_macro_hle) { + ci.program = GetHLEProgram(ci.hash); + } else { + maxwell3d.RefreshParameters(); + } + execute_variant(ci.program); if (Settings::values.dump_macros) { - Dump(cache_info.hash, macro_code->second, cache_info.has_hle_program); + Dump(ci.hash, macro_code->second, !std::holds_alternative(ci.program)); } } } -std::unique_ptr MacroEngine::Compile(const std::vector& code) { +AnyCachedMacro MacroEngine::Compile(Engines::Maxwell3D& maxwell3d, std::span code) { #ifdef ARCHITECTURE_x86_64 if (!is_interpreted) - return std::make_unique(maxwell3d, code); -#endif - return std::make_unique(maxwell3d, code); -} - -std::optional GetMacroEngine(Engines::Maxwell3D& maxwell3d) { -#ifdef ARCHITECTURE_x86_64 - return std::make_optional(maxwell3d, bool(Settings::values.disable_macro_jit)); -#else - return std::make_optional(maxwell3d, true); + return std::make_unique(code); #endif + return MacroInterpreterImpl(code); } } // namespace Tegra diff --git a/src/video_core/macro.h b/src/video_core/macro.h index 9bdb4219ce..a9a8f2de04 100644 --- a/src/video_core/macro.h +++ b/src/video_core/macro.h @@ -7,8 +7,10 @@ #pragma once #include -#include +#include +#include #include +#include #include "common/bit_field.h" #include "common/common_types.h" @@ -98,62 +100,142 @@ union MethodAddress { } // namespace Macro -class CachedMacro { -public: - CachedMacro(Engines::Maxwell3D& maxwell3d_) - : maxwell3d{maxwell3d_} - {} - virtual ~CachedMacro() = default; +struct HLEMacro { +}; +/// @note: these macros have two versions, a normal and extended version, with the extended version +/// also assigning the base vertex/instance. +struct HLE_DrawArraysIndirect final { + HLE_DrawArraysIndirect(bool extended_) noexcept : extended{extended_} {} + void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters); + bool extended; +}; +/// @note: these macros have two versions, a normal and extended version, with the extended version +/// also assigning the base vertex/instance. +struct HLE_DrawIndexedIndirect final { + explicit HLE_DrawIndexedIndirect(bool extended_) noexcept : extended{extended_} {} + void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters); + bool extended; +}; +struct HLE_MultiLayerClear final { + void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); +}; +struct HLE_MultiDrawIndexedIndirectCount final { + void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters); +}; +struct HLE_DrawIndirectByteCount final { + void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters); +}; +struct HLE_C713C83D8F63CCF3 final { + void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); +}; +struct HLE_D7333D26E0A93EDE final { + void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); +}; +struct HLE_BindShader final { + void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); +}; +struct HLE_SetRasterBoundingBox final { + void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); +}; +struct HLE_ClearConstBuffer final { + HLE_ClearConstBuffer(size_t base_size_) noexcept : base_size{base_size_} {} + void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + size_t base_size; +}; +struct HLE_ClearMemory final { + void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + std::vector zero_memory; +}; +struct HLE_TransformFeedbackSetup final { + void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); +}; +struct MacroInterpreterImpl final { + MacroInterpreterImpl() {} + MacroInterpreterImpl(std::span code_) : code{code_} {} + void Execute(Engines::Maxwell3D& maxwell3d, std::span params, u32 method); + void Reset(); + bool Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slot); + u32 GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b); + void ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result); + bool EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const; + Macro::Opcode GetOpcode() const; + u32 GetRegister(u32 register_id) const; + void SetRegister(u32 register_id, u32 value); + /// Sets the method address to use for the next Send instruction. + [[nodiscard]] inline void SetMethodAddress(u32 address) noexcept { + method_address.raw = address; + } + void Send(Engines::Maxwell3D& maxwell3d, u32 value); + u32 Read(Engines::Maxwell3D& maxwell3d, u32 method) const; + u32 FetchParameter(); + /// General purpose macro registers. + std::array registers = {}; + /// Input parameters of the current macro. + std::vector parameters; + std::span code; + /// Program counter to execute at after the delay slot is executed. + std::optional delayed_pc; + /// Method address to use for the next Send instruction. + Macro::MethodAddress method_address = {}; + /// Current program counter + u32 pc{}; + /// Index of the next parameter that will be fetched by the 'parm' instruction. + u32 next_parameter_index = 0; + bool carry_flag = false; +}; +struct DynamicCachedMacro { + virtual ~DynamicCachedMacro() = default; /// Executes the macro code with the specified input parameters. /// @param parameters The parameters of the macro /// @param method The method to execute - virtual void Execute(const std::vector& parameters, u32 method) = 0; - Engines::Maxwell3D& maxwell3d; + virtual void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, u32 method) = 0; }; -class HLEMacro { -public: - explicit HLEMacro(Engines::Maxwell3D& maxwell3d_); - ~HLEMacro(); - // Allocates and returns a cached macro if the hash matches a known function. - // Returns nullptr otherwise. - [[nodiscard]] std::unique_ptr GetHLEProgram(u64 hash) const; -private: - Engines::Maxwell3D& maxwell3d; -}; - -class MacroEngine { -public: - explicit MacroEngine(Engines::Maxwell3D& maxwell3d, bool is_interpreted); - ~MacroEngine(); +using AnyCachedMacro = std::variant< + std::monostate, + HLEMacro, + HLE_DrawArraysIndirect, + HLE_DrawIndexedIndirect, + HLE_MultiDrawIndexedIndirectCount, + HLE_MultiLayerClear, + HLE_C713C83D8F63CCF3, + HLE_D7333D26E0A93EDE, + HLE_BindShader, + HLE_SetRasterBoundingBox, + HLE_ClearConstBuffer, + HLE_ClearMemory, + HLE_TransformFeedbackSetup, + HLE_DrawIndirectByteCount, + MacroInterpreterImpl, + // Used for JIT x86 macro + std::unique_ptr +>; +struct MacroEngine { + MacroEngine(bool is_interpreted_) noexcept : is_interpreted{is_interpreted_} {} // Store the uploaded macro code to compile them when they're called. - void AddCode(u32 method, u32 data); - + inline void AddCode(u32 method, u32 data) noexcept { + uploaded_macro_code[method].push_back(data); + } // Clear the code associated with a method. - void ClearCode(u32 method); - + inline void ClearCode(u32 method) noexcept { + macro_cache.erase(method); + uploaded_macro_code.erase(method); + } // Compiles the macro if its not in the cache, and executes the compiled macro - void Execute(u32 method, const std::vector& parameters); - -protected: - std::unique_ptr Compile(const std::vector& code); - -private: + void Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span parameters); + AnyCachedMacro Compile(Engines::Maxwell3D& maxwell3d, std::span code); struct CacheInfo { - std::unique_ptr lle_program{}; - std::unique_ptr hle_program{}; + AnyCachedMacro program; u64 hash{}; - bool has_hle_program{}; }; - ankerl::unordered_dense::map macro_cache; ankerl::unordered_dense::map> uploaded_macro_code; - std::optional hle_macros; - Engines::Maxwell3D& maxwell3d; bool is_interpreted; }; -std::optional GetMacroEngine(Engines::Maxwell3D& maxwell3d); - } // namespace Tegra From 2ed1328c93c4739c069e7a284cb82b0a72762dac Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 6 Mar 2026 15:05:05 +0100 Subject: [PATCH 45/74] [vk] use static_vector instead of small_vector for TFB and other bindings (#3641) MK8D is a big offender, taking up lots of time memcpy'ing and memmov'ing small_vector<> AND to add salt to the wound it doesn't even do heap allocations (no game does I think) - so basically useless waste of compute time in hot path for NO reason :^) Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3641 Reviewed-by: CamilleLaVey Reviewed-by: DraVee Co-authored-by: lizzie Co-committed-by: lizzie --- .../buffer_cache/buffer_cache_base.h | 13 +++-- .../renderer_vulkan/vk_buffer_cache.cpp | 50 +++++++++---------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 0596329392..08524bd854 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -14,9 +14,12 @@ #include #include #include -#include #include +#include +#include +#include + #include "common/common_types.h" #include "common/div_ceil.h" #include "common/literals.h" @@ -94,10 +97,10 @@ static constexpr Binding NULL_BINDING{ template struct HostBindings { - boost::container::small_vector buffers; - boost::container::small_vector offsets; - boost::container::small_vector sizes; - boost::container::small_vector strides; + boost::container::static_vector buffers; + boost::container::static_vector offsets; + boost::container::static_vector sizes; + boost::container::static_vector strides; u32 min_index{NUM_VERTEX_BUFFERS}; u32 max_index{0}; }; diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index f4345262fb..c842cce709 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -10,6 +10,7 @@ #include #include +#include "video_core/buffer_cache/buffer_cache_base.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" @@ -583,18 +584,18 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset } void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) { - boost::container::small_vector buffer_handles; - for (u32 index = 0; index < bindings.buffers.size(); ++index) { - auto handle = bindings.buffers[index]->Handle(); + boost::container::static_vector buffer_handles(bindings.buffers.size()); + for (u32 i = 0; i < bindings.buffers.size(); ++i) { + auto handle = bindings.buffers[i]->Handle(); if (handle == VK_NULL_HANDLE) { - bindings.offsets[index] = 0; - bindings.sizes[index] = VK_WHOLE_SIZE; + bindings.offsets[i] = 0; + bindings.sizes[i] = VK_WHOLE_SIZE; if (!device.HasNullDescriptor()) { ReserveNullBuffer(); handle = *null_buffer; } } - buffer_handles.push_back(handle); + buffer_handles[i] = handle; } const u32 device_max = device.GetMaxVertexInputBindings(); const u32 min_binding = (std::min)(bindings.min_index, device_max); @@ -604,19 +605,12 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bi return; } if (device.IsExtExtendedDynamicStateSupported()) { - scheduler.Record([bindings_ = std::move(bindings), - buffer_handles_ = std::move(buffer_handles), - binding_count](vk::CommandBuffer cmdbuf) { - cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, binding_count, buffer_handles_.data(), - bindings_.offsets.data(), bindings_.sizes.data(), - bindings_.strides.data()); + scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles), binding_count](vk::CommandBuffer cmdbuf) { + cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, binding_count, buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data(), bindings_.strides.data()); }); } else { - scheduler.Record([bindings_ = std::move(bindings), - buffer_handles_ = std::move(buffer_handles), - binding_count](vk::CommandBuffer cmdbuf) { - cmdbuf.BindVertexBuffers(bindings_.min_index, binding_count, buffer_handles_.data(), - bindings_.offsets.data()); + scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles), binding_count](vk::CommandBuffer cmdbuf) { + cmdbuf.BindVertexBuffers(bindings_.min_index, binding_count, buffer_handles_.data(), bindings_.offsets.data()); }); } } @@ -647,15 +641,21 @@ void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings< // Already logged in the rasterizer return; } - boost::container::small_vector buffer_handles; - for (u32 index = 0; index < bindings.buffers.size(); ++index) { - buffer_handles.push_back(bindings.buffers[index]->Handle()); + boost::container::static_vector buffer_handles(bindings.buffers.size()); + for (u32 i = 0; i < bindings.buffers.size(); ++i) { + auto handle = bindings.buffers[i]->Handle(); + if (handle == VK_NULL_HANDLE) { + bindings.offsets[i] = 0; + bindings.sizes[i] = VK_WHOLE_SIZE; + if (!device.HasNullDescriptor()) { + ReserveNullBuffer(); + handle = *null_buffer; + } + } + buffer_handles[i] = handle; } - scheduler.Record([bindings_ = std::move(bindings), - buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { - cmdbuf.BindTransformFeedbackBuffersEXT(0, static_cast(buffer_handles_.size()), - buffer_handles_.data(), bindings_.offsets.data(), - bindings_.sizes.data()); + scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { + cmdbuf.BindTransformFeedbackBuffersEXT(0, u32(buffer_handles_.size()), buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data()); }); } From b75e81af5e11cb09eae405d51c40489401794912 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 6 Mar 2026 15:05:39 +0100 Subject: [PATCH 46/74] [video_core/engines] implement stub NV01 timer, inline other channel engines (#3640) Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3640 Reviewed-by: CamilleLaVey Reviewed-by: DraVee Co-authored-by: lizzie Co-committed-by: lizzie --- src/video_core/control/channel_state.cpp | 15 ++++--- src/video_core/control/channel_state.h | 44 +++++++++---------- src/video_core/engines/engine_interface.h | 3 +- src/video_core/engines/maxwell_3d.h | 2 +- src/video_core/engines/nv01_timer.h | 52 +++++++++++++++++++++++ src/video_core/engines/puller.cpp | 42 +++++++++--------- src/video_core/engines/puller.h | 4 ++ 7 files changed, 109 insertions(+), 53 deletions(-) create mode 100644 src/video_core/engines/nv01_timer.h diff --git a/src/video_core/control/channel_state.cpp b/src/video_core/control/channel_state.cpp index 2539997d53..d07c7e2a83 100644 --- a/src/video_core/control/channel_state.cpp +++ b/src/video_core/control/channel_state.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -19,12 +22,12 @@ ChannelState::ChannelState(s32 bind_id_) : bind_id{bind_id_}, initialized{} {} void ChannelState::Init(Core::System& system, GPU& gpu, u64 program_id_) { ASSERT(memory_manager); program_id = program_id_; - dma_pusher = std::make_unique(system, gpu, *memory_manager, *this); - maxwell_3d = std::make_unique(system, *memory_manager); - fermi_2d = std::make_unique(*memory_manager); - kepler_compute = std::make_unique(system, *memory_manager); - maxwell_dma = std::make_unique(system, *memory_manager); - kepler_memory = std::make_unique(system, *memory_manager); + dma_pusher.emplace(system, gpu, *memory_manager, *this); + maxwell_3d.emplace(system, *memory_manager); + fermi_2d.emplace(*memory_manager); + kepler_compute.emplace(system, *memory_manager); + maxwell_dma.emplace(system, *memory_manager); + kepler_memory.emplace(system, *memory_manager); initialized = true; } diff --git a/src/video_core/control/channel_state.h b/src/video_core/control/channel_state.h index b385f4939f..2984d2e09e 100644 --- a/src/video_core/control/channel_state.h +++ b/src/video_core/control/channel_state.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -6,6 +9,12 @@ #include #include "common/common_types.h" +#include "video_core/engines/fermi_2d.h" +#include "video_core/engines/kepler_memory.h" +#include "video_core/engines/kepler_compute.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/engines/maxwell_dma.h" +#include "video_core/dma_pusher.h" namespace Core { class System; @@ -18,49 +27,34 @@ class RasterizerInterface; namespace Tegra { class GPU; - -namespace Engines { -class Puller; -class Fermi2D; -class Maxwell3D; -class MaxwellDMA; -class KeplerCompute; -class KeplerMemory; -} // namespace Engines - class MemoryManager; -class DmaPusher; namespace Control { struct ChannelState { explicit ChannelState(s32 bind_id); - ChannelState(const ChannelState& state) = delete; - ChannelState& operator=(const ChannelState&) = delete; - ChannelState(ChannelState&& other) noexcept = default; - ChannelState& operator=(ChannelState&& other) noexcept = default; void Init(Core::System& system, GPU& gpu, u64 program_id); void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); - s32 bind_id = -1; - u64 program_id = 0; /// 3D engine - std::unique_ptr maxwell_3d; + std::optional maxwell_3d; /// 2D engine - std::unique_ptr fermi_2d; + std::optional fermi_2d; /// Compute engine - std::unique_ptr kepler_compute; + std::optional kepler_compute; /// DMA engine - std::unique_ptr maxwell_dma; + std::optional maxwell_dma; /// Inline memory engine - std::unique_ptr kepler_memory; - + std::optional kepler_memory; + /// NV01 Timer + std::optional nv01_timer; + std::optional dma_pusher; std::shared_ptr memory_manager; - std::unique_ptr dma_pusher; - + s32 bind_id = -1; + u64 program_id = 0; bool initialized{}; }; diff --git a/src/video_core/engines/engine_interface.h b/src/video_core/engines/engine_interface.h index e271ecab59..bf3bd66aca 100644 --- a/src/video_core/engines/engine_interface.h +++ b/src/video_core/engines/engine_interface.h @@ -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 @@ -15,6 +15,7 @@ namespace Tegra::Engines { enum class EngineTypes : u32 { + Nv01Timer, KeplerCompute, Maxwell3D, Fermi2D, diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 52546e4279..b73082b7ef 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -2258,7 +2258,7 @@ public: /// Returns whether the vertex array specified by index is supposed to be /// accessed per instance or not. bool IsInstancingEnabled(std::size_t index) const { - return is_instanced[index]; + return bool(is_instanced[index]); //FUCK YOU MSVC } }; diff --git a/src/video_core/engines/nv01_timer.h b/src/video_core/engines/nv01_timer.h new file mode 100644 index 0000000000..a8e60f9f53 --- /dev/null +++ b/src/video_core/engines/nv01_timer.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "video_core/engines/engine_interface.h" +#include "video_core/engines/engine_upload.h" + +namespace Core { +class System; +} + +namespace Tegra { +class MemoryManager; +} + +namespace Tegra::Engines { +class Nv01Timer final : public EngineInterface { +public: + explicit Nv01Timer(Core::System& system_, MemoryManager& memory_manager) + : system{system_} + {} + ~Nv01Timer() override; + + /// Write the value to the register identified by method. + void CallMethod(u32 method, u32 method_argument, bool is_last_call) override { + LOG_DEBUG(HW_GPU, "method={}, argument={}, is_last_call={}", method, method_argument, is_last_call); + } + + /// Write multiple values to the register identified by method. + void CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) override { + LOG_DEBUG(HW_GPU, "method={}, base_start={}, amount={}, pending={}", method, fmt::ptr(base_start), amount, methods_pending); + } + + struct Regs { + // No fucking idea + INSERT_PADDING_BYTES_NOINIT(0x48); + } regs{}; +private: + void ConsumeSinkImpl() override {} + Core::System& system; +}; +} diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp index 8dd34c04ab..b5b4e5d7fa 100644 --- a/src/video_core/engines/puller.cpp +++ b/src/video_core/engines/puller.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -34,24 +37,22 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) { bound_engines[method_call.subchannel] = engine_id; switch (engine_id) { case EngineID::FERMI_TWOD_A: - dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel, - EngineTypes::Fermi2D); + dma_pusher.BindSubchannel(&*channel_state.fermi_2d, method_call.subchannel, EngineTypes::Fermi2D); break; case EngineID::MAXWELL_B: - dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel, - EngineTypes::Maxwell3D); + dma_pusher.BindSubchannel(&*channel_state.maxwell_3d, method_call.subchannel, EngineTypes::Maxwell3D); break; case EngineID::KEPLER_COMPUTE_B: - dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel, - EngineTypes::KeplerCompute); + dma_pusher.BindSubchannel(&*channel_state.kepler_compute, method_call.subchannel, EngineTypes::KeplerCompute); break; case EngineID::MAXWELL_DMA_COPY_A: - dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel, - EngineTypes::MaxwellDMA); + dma_pusher.BindSubchannel(&*channel_state.maxwell_dma, method_call.subchannel, EngineTypes::MaxwellDMA); break; case EngineID::KEPLER_INLINE_TO_MEMORY_B: - dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel, - EngineTypes::KeplerMemory); + dma_pusher.BindSubchannel(&*channel_state.kepler_memory, method_call.subchannel, EngineTypes::KeplerMemory); + break; + case EngineID::NV01_TIMER: + dma_pusher.BindSubchannel(&*channel_state.nv01_timer, method_call.subchannel, EngineTypes::Nv01Timer); break; default: UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); @@ -209,24 +210,22 @@ void Puller::CallEngineMethod(const MethodCall& method_call) { switch (engine) { case EngineID::FERMI_TWOD_A: - channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument, - method_call.IsLastCall()); + channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::MAXWELL_B: - channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument, - method_call.IsLastCall()); + channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::KEPLER_COMPUTE_B: - channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument, - method_call.IsLastCall()); + channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::MAXWELL_DMA_COPY_A: - channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument, - method_call.IsLastCall()); + channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::KEPLER_INLINE_TO_MEMORY_B: - channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument, - method_call.IsLastCall()); + channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); + break; + case EngineID::NV01_TIMER: + channel_state.nv01_timer->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); break; default: UNIMPLEMENTED_MSG("Unimplemented engine"); @@ -255,6 +254,9 @@ void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_s case EngineID::KEPLER_INLINE_TO_MEMORY_B: channel_state.kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending); break; + case EngineID::NV01_TIMER: + channel_state.nv01_timer->CallMultiMethod(method, base_start, amount, methods_pending); + break; default: UNIMPLEMENTED_MSG("Unimplemented engine"); break; diff --git a/src/video_core/engines/puller.h b/src/video_core/engines/puller.h index d4175ee945..fe5102e3ed 100644 --- a/src/video_core/engines/puller.h +++ b/src/video_core/engines/puller.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -20,6 +23,7 @@ class MemoryManager; class DmaPusher; enum class EngineID { + NV01_TIMER = 0x0004, FERMI_TWOD_A = 0x902D, // 2D Engine MAXWELL_B = 0xB197, // 3D Engine KEPLER_COMPUTE_B = 0xB1C0, From e4122dae1d56a8b358c46e450b3d50ca10ffcc21 Mon Sep 17 00:00:00 2001 From: crueter Date: Fri, 6 Mar 2026 16:38:21 +0100 Subject: [PATCH 47/74] [desktop] addons: open mod folder in rc menu (#3662) also fixed the multiselection being absolutely horrendous Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3662 --- .../configure_per_game_addons.cpp | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index bdff73a040..1d2d358672 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -10,13 +10,14 @@ #include +#include #include #include #include +#include #include #include #include -#include #include "common/common_types.h" #include "common/fs/fs.h" @@ -42,7 +43,7 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p item_model = new QStandardItemModel(tree_view); tree_view->setModel(item_model); tree_view->setAlternatingRowColors(true); - tree_view->setSelectionMode(QHeaderView::MultiSelection); + tree_view->setSelectionMode(QHeaderView::ExtendedSelection); tree_view->setSelectionBehavior(QHeaderView::SelectRows); tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); @@ -248,8 +249,11 @@ void ConfigurePerGameAddons::AddonDeleteRequested(QList selected) { void ConfigurePerGameAddons::showContextMenu(const QPoint& pos) { const QModelIndex index = tree_view->indexAt(pos); - auto selected = tree_view->selectionModel()->selectedIndexes(); - if (index.isValid() && selected.empty()) selected = {index}; + auto selected = tree_view->selectionModel()->selectedRows(); + if (index.isValid() && selected.empty()) { + QModelIndex idx = item_model->index(index.row(), 0); + if (idx.isValid()) selected << idx; + } if (selected.empty()) return; @@ -260,6 +264,15 @@ void ConfigurePerGameAddons::showContextMenu(const QPoint& pos) { AddonDeleteRequested(selected); }); + if (selected.length() == 1) { + auto loc = selected.at(0).data(PATCH_LOCATION).toString(); + if (QFileInfo::exists(loc)) { + QAction* open = menu.addAction(tr("&Open in File Manager")); + connect(open, &QAction::triggered, this, + [selected, loc]() { QDesktopServices::openUrl(QUrl::fromLocalFile(loc)); }); + } + } + menu.exec(tree_view->viewport()->mapToGlobal(pos)); } From c062931c9bef18afd5f0cd74329e8c3e32b4b598 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 6 Mar 2026 16:38:39 +0100 Subject: [PATCH 48/74] [qt] add translation table entry for debug_knobs,serial_battery and serial_unit (#3682) trivial qt change Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3682 Reviewed-by: DraVee Co-authored-by: lizzie Co-committed-by: lizzie --- src/qt_common/config/shared_translation.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index f49c43ee2a..d1ed32134c 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -425,6 +425,9 @@ std::unique_ptr InitializeTranslations(QObject* parent) "their resolution, details and supported controllers and depending on this setting.\n" "Setting to Handheld can help improve performance for low end systems.")); INSERT(Settings, current_user, QString(), QString()); + INSERT(Settings, serial_unit, tr("Unit Serial"), QString()); + INSERT(Settings, serial_battery, tr("Battery Serial"), QString()); + INSERT(Settings, debug_knobs, tr("Debug knobs"), QString()); // Controls From ddac8c8eb500918bd8c89e0c330587c591206c2c Mon Sep 17 00:00:00 2001 From: xbzk Date: Fri, 6 Mar 2026 19:52:17 +0100 Subject: [PATCH 49/74] [vk] fix crash introduced in 9a07bd0570 (#3685) Fix for current crash on master. Just reverted only the necessary stuff so that PresentManager can hold a reference to khr and resist death upon application hold/restore. @Lizzie shall judge. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3685 Co-authored-by: xbzk Co-committed-by: xbzk --- src/video_core/renderer_vulkan/renderer_vulkan.cpp | 2 +- src/video_core/renderer_vulkan/vk_present_manager.cpp | 6 +++--- src/video_core/renderer_vulkan/vk_present_manager.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index cb1b1a5362..1725bc8ccc 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -137,7 +137,7 @@ try memory_allocator, scheduler, swapchain, - *surface) + surface) , blit_swapchain(device_memory, device, memory_allocator, diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp index aa019a4160..80853362ad 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -101,7 +101,7 @@ PresentManager::PresentManager(const vk::Instance& instance_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_, Swapchain& swapchain_, - VkSurfaceKHR_T* surface_) + vk::SurfaceKHR& surface_) : instance{instance_} , render_window{render_window_} , device{device_} @@ -291,7 +291,7 @@ void PresentManager::PresentThread(std::stop_token token) { } void PresentManager::RecreateSwapchain(Frame* frame) { - swapchain.Create(surface, frame->width, frame->height); // Pass raw pointer + swapchain.Create(*surface, frame->width, frame->height); // Pass raw pointer SetImageCount(); } @@ -310,7 +310,7 @@ void PresentManager::CopyToSwapchain(Frame* frame) { // Recreate surface and swapchain if needed. if (requires_recreation) { #ifdef ANDROID - surface = *CreateSurface(instance, render_window.GetWindowInfo()).address(); + surface = CreateSurface(instance, render_window.GetWindowInfo()); #endif RecreateSwapchain(frame); } diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h index 3d5cc32102..c51f8ed77f 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.h +++ b/src/video_core/renderer_vulkan/vk_present_manager.h @@ -44,7 +44,7 @@ public: MemoryAllocator& memory_allocator, Scheduler& scheduler, Swapchain& swapchain, - VkSurfaceKHR_T* surface); + vk::SurfaceKHR& surface); ~PresentManager(); /// Returns the last used presentation frame @@ -78,7 +78,7 @@ private: MemoryAllocator& memory_allocator; Scheduler& scheduler; Swapchain& swapchain; - VkSurfaceKHR_T* surface; + vk::SurfaceKHR& surface; vk::CommandPool cmdpool; std::vector frames; boost::container::deque present_queue; From 11ad71b1e7db5206f074481d642020d1c30dafb1 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 7 Mar 2026 18:16:05 +0100 Subject: [PATCH 50/74] [docs] Obtanium, installing mods, external ES-DE config, section about CFW and settings (#3678) Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3678 Reviewed-by: DraVee Reviewed-by: CamilleLaVey Co-authored-by: lizzie Co-committed-by: lizzie --- README.md | 8 +- docs/README.md | 3 +- docs/user/AddGamesToSRM.md | 100 ---------- docs/user/CFW.md | 11 ++ docs/user/Graphics.md | 4 +- docs/user/Mods.md | 206 +++++++++++++++++++++ docs/user/README.md | 19 +- docs/user/Settings.md | 54 ++++++ docs/user/{AddEdenToSRM.md => SteamROM.md} | 129 +++++++++++-- docs/user/ThirdParty.md | 59 ++++++ 10 files changed, 473 insertions(+), 120 deletions(-) delete mode 100644 docs/user/AddGamesToSRM.md create mode 100644 docs/user/CFW.md create mode 100644 docs/user/Mods.md create mode 100644 docs/user/Settings.md rename docs/user/{AddEdenToSRM.md => SteamROM.md} (50%) diff --git a/README.md b/README.md index 3ae31151f7..44a2b4c28b 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,10 @@ See the [sign-up instructions](docs/SIGNUP.md) for information on registration. Alternatively, if you wish to add translations, go to the [Eden project on Transifex](https://app.transifex.com/edenemu/eden-emulator) and review [the translations README](./dist/languages). +## Documentation + +We have a user manual! See our [User Handbook](./docs/user/README.md). + ## Building See the [General Build Guide](docs/Build.md) @@ -69,7 +73,9 @@ For information on provided development tooling, see the [Tools directory](./too ## Download -You can download the latest releases from [here](https://github.com/eden-emulator/Releases/releases). +You can download the latest releases from [here](https://git.eden-emu.dev/eden-emu/eden/releases). + +Save us some bandwidth! We have [mirrors available](./docs/user/ThirdParty.md#mirrors) as well. ## Support diff --git a/docs/README.md b/docs/README.md index 01727ae4dc..4ea532be8e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,7 @@ # Eden Build Documentation +Are you just a casual user? Take a look at our [User Handbook](./user) then! + This contains documentation created by developers. This contains build instructions, guidelines, instructions/layouts for [cool stuff we made](./CPMUtil), and more. - **[General Build Instructions](Build.md)** @@ -11,7 +13,6 @@ This contains documentation created by developers. This contains build instructi - **[CPM - CMake Package Manager](./CPMUtil)** - **[Platform-Specific Caveats](Caveats.md)** - **[The NVIDIA SM86 (Maxwell) GPU](./NvidiaGpu.md)** -- **[User Handbook](./user)** - **[Dynarmic](./dynarmic)** - **[Cross compilation](./CrossCompile.md)** - **[Driver Bugs](./DriverBugs.md)** diff --git a/docs/user/AddGamesToSRM.md b/docs/user/AddGamesToSRM.md deleted file mode 100644 index 433999c9b6..0000000000 --- a/docs/user/AddGamesToSRM.md +++ /dev/null @@ -1,100 +0,0 @@ -# Importing Games into Steam with Steam Rom Manager - -Use this when you want to import your games inside Eden into Steam to launch with artwork from Steam Game Mode without needing to launch Eden first. - -**Click [Here](https://evilperson1337.notion.site/Importing-Games-into-Steam-with-Steam-Rom-Manager-2b757c2edaf680d7a491c92b138f1fcc) for a version of this guide with images & visual elements.** - ---- - -### Pre-Requisites - -- Steam Deck Set up and Configured -- Eden set up and Configured -- Internet Access - ---- - -## Steps - -1. Press the **STEAM** button and then go to *Power → Switch to Desktop* to enter the Desktop mode. - -1. Install ***Steam ROM Manager***, there are 2 ways you can accomplish this, either manually or through [*EmuDeck*](https://www.emudeck.com/#downloads). - - --- - - ### Manual Installation - - 1. Open the *Discover Store* and search for *Steam ROM Manager.* - 2. Select the **Install** button to install the program. - - --- - - ### Installing Through *EmuDeck* - - - - 1. Open **EmuDeck**, then navigate to *Manage Emulators.* - 2. Scroll down to the bottom of the page to the *Manage your Tools & Frontends* section. Click **Steam ROM Manager**. - - 3. Click the **Install** button on the right hand side to install it. - - --- - -2. Open the Start Menu and Launch ***Steam ROM Manager*** - -1. The program will now launch and show you a window with parsers. - - - -2. Switch off all Parsers by hitting the *Toggle Parsers* switch. -3. Scroll down the list on the left-hand side and look for a parser called *Nintendo Switch - Eden* and switch it on. This parser may not exist depending on how you installed *Steam ROM Manager* (EmuDeck creates it for you). Follow these steps to create it if it is missing. - - --- - ### Creating the Eden Parser - - 1. Select Create Parser and in the *Community Presets* option look for **Nintendo Switch - Yuzu**. - 2. Change the **Parser title** from *Nintendo Switch - Yuzu* to *Nintendo Switch - Eden.* - 3. Hit the **Browse** option under the *ROMs directory* section. Select the directory containing your Switch ROMs. - 4. Under *Steam collections*, you can add a Steam category name. This just organizes the games under a common category in your Steam Library, this is optional but recommended. - 5. Scroll down slightly to the **Executable Configuration → Executable**, select **Browse** and select the Eden AppImage. - 6. Leave everything else the same and hit **Save** to save the parser. - --- - -4. Click the Eden parser to view the options on the right, select **Test** at the bottom of the screen to ensure that *Steam ROM Manager* detects your games correctly. -1. *Steam ROM Manager* will start to scan the specified ROMs directory and match them to games. Look over the results to ensure they are accurate. If you do not see any entries - check your parsers ROMs directory field. -1. When you are happy with the results, click the **Add Games** → **Parse** to start the actual Parsing. -1. The program will now identify the games and pull artwork from [*SteamGridDB*](https://www.steamgriddb.com/). -2. Review the game matches and ensure everything is there. - - --- - - ### Correcting a Mismatch - - If the game is not identified correctly, you may need to tell *Steam ROM Manager* what the game is manually. - - 1. Hover over the game card and click the magnifying glass icon. - 2. Search for the game on the *Search SteamGridDB* section and scroll through the results, selecting the one you want. - 3. Ensure the *Name* and *Game ID* update in the **Per-App Exceptions** and press **Save and close**. The game should now update. - - --- - - ### Excluding Matches - - You may want to tell Steam ROM Manager to ignore some files (updates/DLC/etc.) that it finds in the directory. This is how you do so. - - 1. Hit the **Exclude Games** button in the bottom right. - 2. Deselect the game you want to exclude, the poster artwork should go dim and the **Number Excluded** number should increment up. Repeat with any other exclusions you want to add. - 3. Hit **Save Excludes** when you are happy with your selections. - --- -3. When you are happy with the results, select **Save to Steam** to save the results. -1. The program will now start writing the entries into the Steam Library. You should get pop up notifications of the progress, but you can monitor the progress by selecting the **Log** on the left-hand side if needed. -2. Restart Steam to have the changes take effect. Check your library to ensure that your games are there, in a category if you defined one in the parser. -3. Try to launch a game and ensure everything is working. You are now good to go. \ No newline at end of file diff --git a/docs/user/CFW.md b/docs/user/CFW.md new file mode 100644 index 0000000000..ea224d3d36 --- /dev/null +++ b/docs/user/CFW.md @@ -0,0 +1,11 @@ +# User Handbook - Custom Firmware (CFW) + +At the moment of writing, we do not support CFW such as Atmosphere, due to: + +- Lacking the required LLE emulation capabilities to properly emulate the full firmware. +- Lack of implementation on some of the key internals. +- Nobody has bothered to do it (PRs always welcome!) + +We do however, maintain HLE compatibility with the former mentioned CFW, applications that require Atmosphere to run will run fine in the emulator without any adjustments. + +If they don't run - then that's a bug! diff --git a/docs/user/Graphics.md b/docs/user/Graphics.md index e1e13a777d..ad359b9049 100644 --- a/docs/user/Graphics.md +++ b/docs/user/Graphics.md @@ -1,5 +1,7 @@ # User Handbook - Graphics +Graphical enhancements and visual quality improvments. This doesn't cover texture mods. + ## Visual Enhancements ### Anti-aliasing @@ -89,7 +91,7 @@ The OpenGL backend would invoke behaviour that would result in swarst/LLVMpipe w ### HaikuOS compatibility -HaikuOS bundles a Mesa library that doesn't support full core OpenGL 4.6 (required by the emulator). This leads to HaikuOS being one of the few computer platforms where Vulkan is the only available option for users. If OpenGL is desired, Mesa has to be built manually from source. For debugging purpouses `lavapipe` is recommended over the GPU driver; there is in-kernel support for NVIDIA cards through. +HaikuOS bundles a Mesa library that doesn't support full core OpenGL 4.6 (required by the emulator). This leads to HaikuOS being one of the few computer platforms where Vulkan is the only available option for users. If OpenGL is desired, Mesa has to be built manually from source. For debugging purposes `lavapipe` is recommended over the GPU driver; there is in-kernel support for NVIDIA cards through. ### Fixes for Windows 10 and above having "Device loss" diff --git a/docs/user/Mods.md b/docs/user/Mods.md new file mode 100644 index 0000000000..11361d628c --- /dev/null +++ b/docs/user/Mods.md @@ -0,0 +1,206 @@ +# User Handbook - Installing Mods + +## General Notes + +**Note:** When installing a mod, always read the mod's installation instructions. + +This is especially important if a mod uses a framework such as **ARCropolis**, **Skyline**, or **Atmosphere plugins**. In those cases, follow the framework's instructions instead of using Eden's normal mod folder. + +For example, **Super Smash Bros. Ultimate** uses such a framework. See the related section below for details. + +--- + +# Installing Mods for Most Games + +1. Right click a game in the game list. +2. Click **"Open Mod Data Location"**. +3. Extract the mod into that folder. + +Each mod should be placed inside **its own subfolder**. + +--- + +# Enabling or Disabling Mods + +1. Right click the game in the game list. +2. Click **Configure Game**. +3. In the **Add-Ons** tab, enable or disable mods, updates, and DLC by ticking or unticking their boxes. + +--- + +# Important Note About SD Card Paths + +Some mods are designed for real Nintendo Switch consoles and refer to the **SD card root**. + +The emulated SD card is located at: + +``` +%AppData%\eden\sdmc +``` + +Example: + +``` +Switch instruction: sd:/ultimate/mods +Eden equivalent: sdmc/ultimate/mods +``` + +--- + +# Framework-Based Mods (Super Smash Bros. Ultimate) + +Some games require external mod frameworks instead of the built-in mod loader. + +The most common example is **Super Smash Bros. Ultimate**. + +These mods are installed directly to the **emulated SD card**, not the normal Eden mod folder. + +--- + +# Installing the ARCropolis Modding Framework + +**Note:** Some mod packs bundle ARCropolis with their installer (for example, Smash Ult-S). + +--- + +## 1. Download ARCropolis + +Download the latest release: + +https://github.com/Raytwo/ARCropolis/releases/ + +--- + +## 2. Install ARCropolis + +Extract the **`atmosphere`** folder into: + +``` +%AppData%\eden\sdmc +``` + +This is the **emulated SD card directory**. + +Verify installation by checking that the following file exists: + +``` +sdmc\atmosphere\contents\01006A800016E000\romfs\skyline\plugins\libarcropolis.nro +``` + +--- + +## 3. Download Skyline + +Download the latest Skyline release: + +https://github.com/skyline-dev/skyline/releases + +Skyline used to be bundled with ARCropolis but is now distributed separately to avoid compatibility issues caused by outdated bundled versions. + +--- + +## 4. Install Skyline + +Extract the **`exefs`** folder into: + +``` +sdmc\atmosphere\contents\01006A800016E000 +``` + +The `exefs` folder should be **next to the `romfs` folder**. + +Verify installation by checking that the following file exists: + +``` +%AppData%\eden\sdmc\atmosphere\contents\01006A800016E000\exefs\subsdk9 +``` + +--- + +## 5. Launch the Game Once + +Start the game and make sure you see the **ARCropolis version text on the title screen**. + +This will also create the folders required for installing mods. + +--- + +## 6. Install Smash Ultimate Mods + +Install mods inside: + +``` +sdmc\ultimate\mods +``` + +Each mod must be placed inside **its own subfolder**. + +Example: + +``` +sdmc\ultimate\mods\ExampleMod +``` + +--- + +# Troubleshooting + +## ARCropolis text does not appear on startup + +Check the following: + +- `libarcropolis.nro` exists in: + +``` +sdmc\atmosphere\contents\01006A800016E000\romfs\skyline\plugins +``` + +- `subsdk9` exists in: + +``` +sdmc\atmosphere\contents\01006A800016E000\exefs +``` + +- Files were extracted to: + +``` +%AppData%\eden\sdmc +``` + +--- + +## Mods are not loading + +Make sure mods are installed inside: + +``` +sdmc\ultimate\mods +``` + +Each mod must have its **own subfolder**. + +Correct example: + +``` +sdmc\ultimate\mods\ExampleMod +``` + +Incorrect example: + +``` +sdmc\ultimate\mods\ExampleMod\ExampleMod +``` + +--- + +## Installing mods in the wrong folder + +ARCropolis mods **do not go in Eden's normal mod folder**. + +Do **not** install Smash mods here: + +``` +user\load\01006A800016E000 +``` + +That folder is only used for traditional **RomFS mods**, not ARCropolis. diff --git a/docs/user/README.md b/docs/user/README.md index 5fd3a17e51..9804f4d62f 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -4,10 +4,14 @@ The "FAQ". This handbook is primarily aimed at the end-user - baking useful knowledge for enhancing their emulation experience. +A copy of this handbook is [available online](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/README.md). + ## Basics - **[The Basics](Basics.md)** - **[Quickstart](./QuickStart.md)** +- **[Settings](./Settings.md)** +- **[Installing Mods](./Mods.md)** - **[Run On macOS](./RunOnMacOS.md)** - **[Audio](Audio.md)** - **[Graphics](Graphics.md)** @@ -17,22 +21,29 @@ This handbook is primarily aimed at the end-user - baking useful knowledge for e - **[Using Amiibo](./UsingAmiibo.md)** - **[Using Cheats](./UsingCheats.md)** - **[Importing Saves](./ImportingSaves.md)** -- **[Add Eden to Steam ROM Manager](./AddEdenToSRM.md)** -- **[Add Games to Steam ROM Manager](./AddGamesToSRM.md)** - **[Installing Atmosphere Mods](./InstallingAtmosphereMods.md)** - **[Installing Updates & DLCs](./InstallingUpdatesDLC.md)** - **[Controller Profiles](./ControllerProfiles.md)** - **[Alter Date & Time](./AlterDateTime.md)** +## 3rd-party Integration + +- **[Configuring Steam ROM Manager](./SteamROM.md)** +- **[Server hosting](ServerHosting.md)** +- **[Syncthing Guide](./SyncthingGuide.md)** +- **[Third Party](./ThirdParty.md)** + - **[Obtainium](./ThirdParty.md#configuring-obtainium)** + - **[ES-DE](./ThirdParty.md#configuring-es-de)** + - **[Mirrors](./ThirdParty.md#mirrors)** + ## Advanced +- **[Custom Firmware](./CFW.md)** - **[How To Access Logs](./HowToAccessLogs.md)** - **[Gyro Controls](./GyroControls.md)** - **[Platforms and Architectures](Architectures.md)** -- **[Server hosting](ServerHosting.md)** - **[Command Line](CommandLine.md)** - **[Native Application Development](Native.md)** - **[Adding Boolean Settings Toggles](AddingBooleanToggles.md)** - **[Adding Debug Knobs](./AddingDebugKnobs.md)** -- **[Syncthing Guide](./SyncthingGuide.md)** - **[Testing](Testing.md)** diff --git a/docs/user/Settings.md b/docs/user/Settings.md new file mode 100644 index 0000000000..35fcd0c9ef --- /dev/null +++ b/docs/user/Settings.md @@ -0,0 +1,54 @@ +# User Handbook - Settings + +As the emulator continues to grow, so does the number of settings that come and go. + +Most of the development adds new settings that enhance performance/compatibility, only to be removed later in newer versions due to newfound discoveries or because they were "a hacky workaround". + +As such, this guide will NOT mention those kind of settings, we'd rather mention settings which have a long shelf time (i.e won't get removed in future releases) and are likely to be unchanged. + +Some of the options are self explainatory, and they do exactly what they say they do (i.e "Pause when not in focus"); such options will be also skipped due to triviality. + +## Foreword + +Before touching the settings, please see the game boots with stock options. We try our best to ensure users can boot any game using the default settings. If they don't work, then you may try fiddling with options - but please, first use stock options. + +## General + +- `General/Force X11 as Graphics Backend`: Wayland on *NIX has prominent issues that are unlikely to be resolved; the kind that are "not our fault, it's Wayland issue", this "temporary" hack forces X11 as the backend, regardless of the desktop manager's default. +- `General/Enable Gamemode`: This only does anything when you have Feral Interactive's Gamemode library installed somewhere, if you do, this will help boost FPS by telling the OS to explicitly prioritize *this* application for "gaming" - only for *NIX systems. +- `Hotkeys`: Deceptively to remove a hotkey you must right click and a menu will appear to remove that specific hotkey. +- `UI/Language`: Changes language *of the interface* NOT the emulated program! +- `Debug/Enable Auto Stub`: May help to "fix" some games by just lying and saying that everything they do returns "success" instead of outright crashing for any function/service that is NOT implemented. +- `Debug/Show log in console`: Does as said, note that the program may need to be reopened (Windows) for changes to take effect. +- `Debug/Flush log output`: Classically, every write to the log is "buffered", that is, changes aren't written to the disk UNTIL the program has decided it is time to write, until then it keeps data in a buffer which resides on RAM. If the program crashes, the OS will automatically discard said buffer (any RAM associated with a dead process is automatically discarded/reused for some other purpose); this means critical data may not be logged to the disk on time, which may lead to missing log lines. Use this if you're wanting to remove that factor when debugging, sometimes a hard crash may "eat" some of the log lines IF this option isn't enabled. +- `Debug/Disable Macro HLE:` The emulator has HLE emulation of macro programs for Maxwell, this means that some details are purpousefully skipped; this option forces all macro programs to be ran without skipping anything. + +## System + +- `System/RNG Seed`: Set to 0 (and uncheck) to disable ASLR systemwide (this makes mods like CTGP to stop working); by default it enables ASLR to replicate console behaviour. +- `Network/Enable Airplane Mode`: Enable this if a game is crashing before loading AND the logs mention anything related to "web" or "internet" services. + +## CPU + +- `CPU/Virtual table bouncing`: Some games have the tendency to crash on loading due to an indirect bad jump (Pokemon ZA being the worst offender); this option lies to the game and tells it to just pretend it never executed a given function. This is fine for most casual users, but developers of switch applications **must** disable this. This temporary "hack" should hopefully be gone in 6-7 months from now on. +- `Fastmem`, aka. `CPU/Enable Host MMU`: Enables "fastmem"; a detailed description of fastmem can be found [here](../dynarmic/Design.md#fast-memory-fastmem). +- `CPU/Unsafe FMA`: Enables deliberate innacurate FMA behaviour which may affect how FMA returns any given operation - this may introduce tiny floating point errors which can cascade in sensitive code (i.e FFmpeg). +- `CPU/Faster FRSQRTE and FRECPE`: Introduces accuracy errors on square root and reciprocals in exchange for less checks - this introduces inaccuracies with some cases but it's mostly safe. +- `CPU/Faster ASIMD Instructions`: Skips rounding mode checks for ARM ASIMD instructions - this means some code dpeending on these rounding modes may misbehave. +- `CPU/Disable address space checks`: Before each memory access, the emulator checks the address is in range, if not it faults; this option makes it so the emulator skips the check entirely (which may be expensive for a myriad of reasons). However at the same time this allows the guest program to "break out" of the emulation context by writing to arbitrary addresses. +- `CPU/Ignore global monitor`: This relies on a quirk present on x86 to avoid the ARM global monitor emulation, this may increase performance in mutex-heavy contexts (i.e games waiting for next frames or such); but also can cause deadlocks and fun to debug issues. + +It is important to note the majority of precision-reducing instructions do not benefit cases where they are not used, which means the performance gains will vary per game. + +# Graphics + +See also [an extended breakdown of some options](./Graphics.md). + +- `Extras/Extended Dynamic State` and `Extras/Vertex Input Dynamic State`: These Vulkan extensions essentially allow you to reuse the same pipeline but just change the state between calls (so called "dynamic state"); the "extended" levels signifies how much state can be placed on this "dynamic" range, for example the amount of depth culling to use can be placed on the dynamic state, avoiding costly reloads and flushes. While this by itself is a fine option, SOME vendors (notably PowerVR and Mali) have problems with anything related to EDS3. EDS3 contains EDS2, and EDS2 contains EDS1. Essentially this means more extended data the driver has to keep track of, at the benefit of avoiding costly flushes. +- `Advanced/Use persistent cache`: This saves compiled shaders onto the disk, independent of any driver's own disk saved shaders (yes, some drivers, notably NVIDIA, save a secondary shader cache onto disk) - disable this only if you're debugging or working on the GPU backend. This option is meant to massively help to reduce shader stutters (after playing for one session that compiles them). +- `Advanced/Use Vulkan pipeline cache`: This is NOT the same as `Use persistent cache`; it's a separate flag that tells the Vulkan backend to create pipeline caches, which are a detail that can be used to massively improve performance and remove pipeline creation overhead. This is a Vulkan feature. + +## Controls + +Most of the controls should work out of the box. If not, please use a joystick calibrator to ensure it's not an issue with your own controller, for example: +- https://github.com/dkosmari/calibrate-joystick diff --git a/docs/user/AddEdenToSRM.md b/docs/user/SteamROM.md similarity index 50% rename from docs/user/AddEdenToSRM.md rename to docs/user/SteamROM.md index 4658bcf7e0..a782b51969 100644 --- a/docs/user/AddEdenToSRM.md +++ b/docs/user/SteamROM.md @@ -1,4 +1,6 @@ -# Importing Eden into Steam with Steam Rom Manager +# User Handbook - Configuring Steam ROM Manager + +## Importing Eden into Steam with Steam Rom Manager Use this when you want to import the Eden AppImage into your Steam Library along with artwork using *Steam ROM Manager.* @@ -6,7 +8,7 @@ Use this when you want to import the Eden AppImage into your Steam Library along --- -### Pre-Requisites +#### Pre-Requisites - Eden set up and configured - Internet Connection @@ -14,9 +16,9 @@ Use this when you want to import the Eden AppImage into your Steam Library along --- -## Steps +### Steps -### Initial Setup +#### Initial Setup 1. Press the **STEAM** button and then go to *Power → Switch to Desktop* to enter the Desktop mode. @@ -24,14 +26,14 @@ Use this when you want to import the Eden AppImage into your Steam Library along --- - ### Manual Installation + #### Manual Installation 1. Open the *Discover Store* and search for *Steam ROM Manager.* 2. Select the **Install** button to install the program. --- - ### Installing Through *EmuDeck* + #### Installing Through *EmuDeck*