diff --git a/README.md b/README.md index 33f3f913a..16e676b17 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ yuzu emulator early access ============= -This is the source code for early-access 4093. +This is the source code for early-access 4094. ## Legal Notice 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 55abba093..53137b2e2 100755 --- 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 @@ -261,7 +261,7 @@ object NativeLibrary { /** * Begins emulation. */ - external fun run(path: String?) + external fun run(path: String?, programIndex: Int = 0) // Surface Handling external fun surfaceChanged(surf: Surface?) @@ -489,6 +489,12 @@ object NativeLibrary { sEmulationActivity.get()!!.onEmulationStopped(status) } + @Keep + @JvmStatic + fun onProgramChanged(programIndex: Int) { + sEmulationActivity.get()!!.onProgramChanged(programIndex) + } + /** * Logs the Yuzu version, Android version and, CPU. */ 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 26cddecf4..564aaf305 100755 --- 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 @@ -76,7 +76,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { override fun onDestroy() { stopForegroundService(this) - emulationViewModel.clear() super.onDestroy() } @@ -446,9 +445,14 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { } fun onEmulationStopped(status: Int) { - if (status == 0) { + if (status == 0 && emulationViewModel.programChanged.value == -1) { finish() } + emulationViewModel.setEmulationStopped(true) + } + + fun onProgramChanged(programIndex: Int) { + emulationViewModel.setProgramChanged(programIndex) } private fun startMotionSensorListener() { 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 ef393c4be..1f591ced1 100755 --- 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 @@ -424,10 +424,38 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } } + launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + emulationViewModel.programChanged.collect { + if (it != 0) { + emulationViewModel.setEmulationStarted(false) + binding.drawerLayout.close() + binding.drawerLayout + .setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) + ViewUtils.hideView(binding.surfaceInputOverlay) + ViewUtils.showView(binding.loadingIndicator) + } + } + } + } + launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + emulationViewModel.emulationStopped.collect { + if (it && emulationViewModel.programChanged.value != -1) { + if (perfStatsUpdater != null) { + perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!) + } + emulationState.changeProgram(emulationViewModel.programChanged.value) + emulationViewModel.setProgramChanged(-1) + emulationViewModel.setEmulationStopped(false) + } + } + } + } } } - private fun startEmulation() { + private fun startEmulation(programIndex: Int = 0) { if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) { if (!DirectoryInitialization.areDirectoriesReady) { DirectoryInitialization.start() @@ -435,7 +463,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { updateScreenLayout() - emulationState.run(emulationActivity!!.isActivityRecreated) + emulationState.run(emulationActivity!!.isActivityRecreated, programIndex) } } @@ -833,6 +861,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { ) { private var state: State private var surface: Surface? = null + lateinit var emulationThread: Thread init { // Starting state is stopped. @@ -878,7 +907,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } @Synchronized - fun run(isActivityRecreated: Boolean) { + fun run(isActivityRecreated: Boolean, programIndex: Int = 0) { if (isActivityRecreated) { if (NativeLibrary.isRunning()) { state = State.PAUSED @@ -889,10 +918,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { // If the surface is set, run now. Otherwise, wait for it to get set. if (surface != null) { - runWithValidSurface() + runWithValidSurface(programIndex) } } + @Synchronized + fun changeProgram(programIndex: Int) { + emulationThread.join() + emulationThread = Thread({ + Log.debug("[EmulationFragment] Starting emulation thread.") + NativeLibrary.run(gamePath, programIndex) + }, "NativeEmulation") + emulationThread.start() + } + // Surface callbacks @Synchronized fun newSurface(surface: Surface?) { @@ -932,7 +971,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } - private fun runWithValidSurface() { + private fun runWithValidSurface(programIndex: Int = 0) { NativeLibrary.surfaceChanged(surface) if (!emulationCanStart.invoke()) { return @@ -940,9 +979,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { when (state) { State.STOPPED -> { - val emulationThread = Thread({ + emulationThread = Thread({ Log.debug("[EmulationFragment] Starting emulation thread.") - NativeLibrary.run(gamePath) + NativeLibrary.run(gamePath, programIndex) }, "NativeEmulation") emulationThread.start() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt index b66f47fe7..d024493cd 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt @@ -15,6 +15,12 @@ class EmulationViewModel : ViewModel() { val isEmulationStopping: StateFlow get() = _isEmulationStopping private val _isEmulationStopping = MutableStateFlow(false) + private val _emulationStopped = MutableStateFlow(false) + val emulationStopped = _emulationStopped.asStateFlow() + + private val _programChanged = MutableStateFlow(-1) + val programChanged = _programChanged.asStateFlow() + val shaderProgress: StateFlow get() = _shaderProgress private val _shaderProgress = MutableStateFlow(0) @@ -35,6 +41,17 @@ class EmulationViewModel : ViewModel() { _isEmulationStopping.value = value } + fun setEmulationStopped(value: Boolean) { + if (value) { + _emulationStarted.value = false + } + _emulationStopped.value = value + } + + fun setProgramChanged(programIndex: Int) { + _programChanged.value = programIndex + } + fun setShaderProgress(progress: Int) { _shaderProgress.value = progress } @@ -56,20 +73,4 @@ class EmulationViewModel : ViewModel() { fun setDrawerOpen(value: Boolean) { _drawerOpen.value = value } - - fun clear() { - setEmulationStarted(false) - setIsEmulationStopping(false) - setShaderProgress(0) - setTotalShaders(0) - setShaderMessage("") - } - - companion object { - const val KEY_EMULATION_STARTED = "EmulationStarted" - const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting" - const val KEY_SHADER_PROGRESS = "ShaderProgress" - const val KEY_TOTAL_SHADERS = "TotalShaders" - const val KEY_SHADER_MESSAGE = "ShaderMessage" - } } diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index 96f2ad3d4..f30100bd8 100755 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -19,6 +19,7 @@ static jmethodID s_exit_emulation_activity; static jmethodID s_disk_cache_load_progress; static jmethodID s_on_emulation_started; static jmethodID s_on_emulation_stopped; +static jmethodID s_on_program_changed; static jclass s_game_class; static jmethodID s_game_constructor; @@ -123,6 +124,10 @@ jmethodID GetOnEmulationStopped() { return s_on_emulation_stopped; } +jmethodID GetOnProgramChanged() { + return s_on_program_changed; +} + jclass GetGameClass() { return s_game_class; } @@ -306,6 +311,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V"); s_on_emulation_stopped = env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V"); + s_on_program_changed = + env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V"); const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game"); s_game_class = reinterpret_cast(env->NewGlobalRef(game_class)); diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index a002e705d..00e48afc0 100755 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h @@ -19,6 +19,7 @@ jmethodID GetExitEmulationActivity(); jmethodID GetDiskCacheLoadProgress(); jmethodID GetOnEmulationStarted(); jmethodID GetOnEmulationStopped(); +jmethodID GetOnProgramChanged(); jclass GetGameClass(); jmethodID GetGameConstructor(); diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index be2c7be86..36d83c1a8 100755 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -215,7 +215,8 @@ void EmulationSession::SetAppletId(int applet_id) { static_cast(m_applet_id)); } -Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath) { +Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath, + const std::size_t program_index) { std::scoped_lock lock(m_mutex); // Create the render window. @@ -247,6 +248,7 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string // Load the ROM. Service::AM::FrontendAppletParameters params{ .applet_id = static_cast(m_applet_id), + .program_index = static_cast(program_index), }; m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath, params); if (m_load_result != Core::SystemResultStatus::Success) { @@ -258,6 +260,12 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string m_system.GetCpuManager().OnGpuReady(); m_system.RegisterExitCallback([&] { HaltEmulation(); }); + // 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; } @@ -265,6 +273,11 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string 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. @@ -415,6 +428,12 @@ void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) { static_cast(result)); } +void EmulationSession::ChangeProgram(std::size_t program_index) { + JNIEnv* env = IDCache::GetEnvForThread(); + env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnProgramChanged(), + static_cast(program_index)); +} + u64 EmulationSession::GetProgramId(JNIEnv* env, jstring jprogramId) { auto program_id_string = GetJString(env, jprogramId); try { @@ -424,7 +443,8 @@ u64 EmulationSession::GetProgramId(JNIEnv* env, jstring jprogramId) { } } -static Core::SystemResultStatus RunEmulation(const std::string& filepath) { +static Core::SystemResultStatus RunEmulation(const std::string& filepath, + const size_t program_index = 0) { MicroProfileOnThreadCreate("EmuThread"); SCOPE_EXIT({ MicroProfileShutdown(); }); @@ -437,7 +457,7 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) { SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); }); - jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath); + jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath, program_index); if (result != Core::SystemResultStatus::Success) { return result; } @@ -702,11 +722,11 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj Settings::LogSettings(); } -void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* env, jclass clazz, - jstring j_path) { +void Java_org_yuzu_yuzu_1emu_NativeLibrary_run(JNIEnv* env, jobject jobj, jstring j_path, + jint j_program_index) { const std::string path = GetJString(env, j_path); - const Core::SystemResultStatus result{RunEmulation(path)}; + const Core::SystemResultStatus result{RunEmulation(path, j_program_index)}; if (result != Core::SystemResultStatus::Success) { env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetExitEmulationActivity(), static_cast(result)); diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index feb386a4a..59342468b 100755 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -46,7 +46,8 @@ public: void ConfigureFilesystemProvider(const std::string& filepath); void InitializeSystem(bool reload); void SetAppletId(int applet_id); - Core::SystemResultStatus InitializeEmulation(const std::string& filepath); + Core::SystemResultStatus InitializeEmulation(const std::string& filepath, + const std::size_t program_index = 0); bool IsHandheldOnly(); void SetDeviceType([[maybe_unused]] int index, int type); @@ -61,6 +62,7 @@ public: 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 @@ -86,4 +88,7 @@ private: // Synchronization std::condition_variable_any m_cv; mutable std::mutex m_mutex; + + // Program index for next boot + std::atomic m_next_program_index = -1; }; 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 c01117d14..0d2bfe8d6 100755 --- a/src/android/app/src/main/res/layout/fragment_emulation.xml +++ b/src/android/app/src/main/res/layout/fragment_emulation.xml @@ -34,8 +34,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" + android:focusable="false" android:defaultFocusHighlightEnabled="false" - android:clickable="false"> + android:clickable="false" + app:rippleColor="@android:color/transparent"> +[[nodiscard]] inline bool AtomicCompareAndSwap(T* pointer, T value, T expected); +template +[[nodiscard]] inline bool AtomicCompareAndSwap(T* pointer, T value, T expected, T& actual); + +template <> +[[nodiscard]] inline bool AtomicCompareAndSwap(u8* pointer, u8 value, u8 expected) { const u8 result = _InterlockedCompareExchange8(reinterpret_cast(pointer), value, expected); return result == expected; } -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) { +template <> +[[nodiscard]] inline bool AtomicCompareAndSwap(u16* pointer, u16 value, u16 expected) { const u16 result = _InterlockedCompareExchange16(reinterpret_cast(pointer), value, expected); return result == expected; } -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) { +template <> +[[nodiscard]] inline bool AtomicCompareAndSwap(u32* pointer, u32 value, u32 expected) { const u32 result = _InterlockedCompareExchange(reinterpret_cast(pointer), value, expected); return result == expected; } -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) { +template <> +[[nodiscard]] inline bool AtomicCompareAndSwap(u64* pointer, u64 value, u64 expected) { const u64 result = _InterlockedCompareExchange64(reinterpret_cast(pointer), value, expected); return result == expected; @@ -45,29 +54,32 @@ namespace Common { reinterpret_cast<__int64*>(expected.data())) != 0; } -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected, - u8& actual) { +template <> +[[nodiscard]] inline bool AtomicCompareAndSwap(u8* pointer, u8 value, u8 expected, u8& actual) { actual = _InterlockedCompareExchange8(reinterpret_cast(pointer), value, expected); return actual == expected; } -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected, - u16& actual) { +template <> +[[nodiscard]] inline bool AtomicCompareAndSwap(u16* pointer, u16 value, u16 expected, + u16& actual) { actual = _InterlockedCompareExchange16(reinterpret_cast(pointer), value, expected); return actual == expected; } -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected, - u32& actual) { +template <> +[[nodiscard]] inline bool AtomicCompareAndSwap(u32* pointer, u32 value, u32 expected, + u32& actual) { actual = _InterlockedCompareExchange(reinterpret_cast(pointer), value, expected); return actual == expected; } -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected, - u64& actual) { +template <> +[[nodiscard]] inline bool AtomicCompareAndSwap(u64* pointer, u64 value, u64 expected, + u64& actual) { actual = _InterlockedCompareExchange64(reinterpret_cast(pointer), value, expected); return actual == expected; @@ -91,23 +103,12 @@ namespace Common { #else -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) { +template +[[nodiscard]] inline bool AtomicCompareAndSwap(T* pointer, T value, T expected) { return __sync_bool_compare_and_swap(pointer, expected, value); } -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) { - return __sync_bool_compare_and_swap(pointer, expected, value); -} - -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) { - return __sync_bool_compare_and_swap(pointer, expected, value); -} - -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) { - return __sync_bool_compare_and_swap(pointer, expected, value); -} - -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) { +[[nodiscard]] inline bool AtomicCompareAndSwap(u64* pointer, u128 value, u128 expected) { unsigned __int128 value_a; unsigned __int128 expected_a; std::memcpy(&value_a, value.data(), sizeof(u128)); @@ -115,31 +116,13 @@ namespace Common { return __sync_bool_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a); } -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected, - u8& actual) { +template +[[nodiscard]] inline bool AtomicCompareAndSwap(T* pointer, T value, T expected, T& actual) { actual = __sync_val_compare_and_swap(pointer, expected, value); return actual == expected; } -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected, - u16& actual) { - actual = __sync_val_compare_and_swap(pointer, expected, value); - return actual == expected; -} - -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected, - u32& actual) { - actual = __sync_val_compare_and_swap(pointer, expected, value); - return actual == expected; -} - -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected, - u64& actual) { - actual = __sync_val_compare_and_swap(pointer, expected, value); - return actual == expected; -} - -[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected, +[[nodiscard]] inline bool AtomicCompareAndSwap(u64* pointer, u128 value, u128 expected, u128& actual) { unsigned __int128 value_a; unsigned __int128 expected_a; @@ -151,7 +134,7 @@ namespace Common { return actual_a == expected_a; } -[[nodiscard]] inline u128 AtomicLoad128(volatile u64* pointer) { +[[nodiscard]] inline u128 AtomicLoad128(u64* pointer) { unsigned __int128 zeros_a = 0; unsigned __int128 result_a = __sync_val_compare_and_swap((unsigned __int128*)pointer, zeros_a, zeros_a); diff --git a/src/core/hle/service/am/applet_manager.cpp b/src/core/hle/service/am/applet_manager.cpp index 527d78b3c..430bae235 100755 --- a/src/core/hle/service/am/applet_manager.cpp +++ b/src/core/hle/service/am/applet_manager.cpp @@ -221,6 +221,7 @@ void AppletManager::InsertApplet(std::shared_ptr applet) { void AppletManager::TerminateAndRemoveApplet(AppletResourceUserId aruid) { std::shared_ptr applet; + bool should_stop = false; { std::scoped_lock lk{m_lock}; @@ -231,10 +232,17 @@ void AppletManager::TerminateAndRemoveApplet(AppletResourceUserId aruid) { applet = it->second; m_applets.erase(it); + + should_stop = m_applets.empty(); } // Terminate process. applet->process->Terminate(); + + // If there were no applets left, stop emulation. + if (should_stop) { + m_system.Exit(); + } } void AppletManager::CreateAndInsertByFrontendAppletParameters( diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 4b046a1f8..7a2a2ba32 100755 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -781,8 +781,7 @@ struct Memory::Impl { }, [&]() { HandleRasterizerWrite(GetInteger(vaddr), sizeof(T)); }); if (ptr) { - const auto volatile_pointer = reinterpret_cast(ptr); - return Common::AtomicCompareAndSwap(volatile_pointer, data, expected); + return Common::AtomicCompareAndSwap(reinterpret_cast(ptr), data, expected); } return true; } @@ -796,8 +795,7 @@ struct Memory::Impl { }, [&]() { HandleRasterizerWrite(GetInteger(vaddr), sizeof(u128)); }); if (ptr) { - const auto volatile_pointer = reinterpret_cast(ptr); - return Common::AtomicCompareAndSwap(volatile_pointer, data, expected); + return Common::AtomicCompareAndSwap(reinterpret_cast(ptr), data, expected); } return true; }