diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java index 9baeeb5e4..48555252e 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java @@ -6,8 +6,11 @@ package org.citra.citra_emu; +import android.app.Activity; +import android.app.Dialog; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.os.Bundle; import android.text.Html; import android.text.method.LinkMovementMethod; import android.view.Surface; @@ -16,15 +19,21 @@ import android.widget.EditText; import android.widget.FrameLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; +import androidx.fragment.app.DialogFragment; import org.citra.citra_emu.activities.EmulationActivity; +import org.citra.citra_emu.applets.SoftwareKeyboard; import org.citra.citra_emu.utils.EmulationMenuSettings; import org.citra.citra_emu.utils.Log; import org.citra.citra_emu.utils.PermissionsHandler; import java.lang.ref.WeakReference; +import java.util.Date; +import java.util.Objects; import static android.Manifest.permission.CAMERA; import static android.Manifest.permission.RECORD_AUDIO; @@ -209,6 +218,114 @@ public final class NativeLibrary { */ public static native void SwapScreens(boolean swap_screens, int rotation); + public enum CoreError { + ErrorSystemFiles, + ErrorSavestate, + ErrorUnknown, + } + + private static boolean coreErrorAlertResult = false; + private static final Object coreErrorAlertLock = new Object(); + + public static class CoreErrorDialogFragment extends DialogFragment { + static CoreErrorDialogFragment newInstance(String title, String message) { + CoreErrorDialogFragment frag = new CoreErrorDialogFragment(); + Bundle args = new Bundle(); + args.putString("title", title); + args.putString("message", message); + frag.setArguments(args); + return frag; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Activity emulationActivity = Objects.requireNonNull(getActivity()); + + final String title = Objects.requireNonNull(Objects.requireNonNull(getArguments()).getString("title")); + final String message = Objects.requireNonNull(Objects.requireNonNull(getArguments()).getString("message")); + + return new AlertDialog.Builder(emulationActivity) + .setTitle(title) + .setMessage(message) + .setPositiveButton(R.string.continue_button, (dialog, which) -> { + coreErrorAlertResult = true; + synchronized (coreErrorAlertLock) { + coreErrorAlertLock.notify(); + } + }) + .setNegativeButton(R.string.abort_button, (dialog, which) -> { + coreErrorAlertResult = false; + synchronized (coreErrorAlertLock) { + coreErrorAlertLock.notify(); + } + }).setOnDismissListener(dialog -> { + coreErrorAlertResult = true; + synchronized (coreErrorAlertLock) { + coreErrorAlertLock.notify(); + } + }).create(); + } + } + + private static void OnCoreErrorImpl(String title, String message) { + final EmulationActivity emulationActivity = sEmulationActivity.get(); + if (emulationActivity == null) { + Log.error("[NativeLibrary] EmulationActivity not present"); + return; + } + + CoreErrorDialogFragment fragment = CoreErrorDialogFragment.newInstance(title, message); + fragment.show(emulationActivity.getSupportFragmentManager(), "coreError"); + } + + /** + * Handles a core error. + * @return true: continue; false: abort + */ + public static boolean OnCoreError(CoreError error, String details) { + final EmulationActivity emulationActivity = sEmulationActivity.get(); + if (emulationActivity == null) { + Log.error("[NativeLibrary] EmulationActivity not present"); + return false; + } + + String title, message; + switch (error) { + case ErrorSystemFiles: { + title = emulationActivity.getString(R.string.system_archive_not_found); + message = emulationActivity.getString(R.string.system_archive_not_found_message, details.isEmpty() ? emulationActivity.getString(R.string.system_archive_general) : details); + break; + } + case ErrorSavestate: { + title = emulationActivity.getString(R.string.save_load_error); + message = details; + break; + } + case ErrorUnknown: { + title = emulationActivity.getString(R.string.fatal_error); + message = emulationActivity.getString(R.string.fatal_error_message); + break; + } + default: { + return true; + } + } + + // Show the AlertDialog on the main thread. + emulationActivity.runOnUiThread(() -> OnCoreErrorImpl(title, message)); + + // Wait for the lock to notify that it is complete. + synchronized (coreErrorAlertLock) { + try { + coreErrorAlertLock.wait(); + } catch (Exception ignored) { + } + } + + return coreErrorAlertResult; + } + public static boolean isPortraitMode() { return CitraApplication.getAppContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; @@ -487,6 +604,19 @@ public final class NativeLibrary { public static native void InstallCIAS(String[] path); + public static final int SAVESTATE_SLOT_COUNT = 10; + + public static final class SavestateInfo { + public int slot; + public Date time; + } + + @Nullable + public static native SavestateInfo[] GetSavestateInfo(); + + public static native void SaveState(int slot); + public static native void LoadState(int slot); + /** * Button type for use in onTouchEvent */ diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java index 4f058a76a..b9b4c6b01 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java @@ -14,7 +14,9 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.SubMenu; import android.view.View; +import android.widget.CheckBox; import android.widget.SeekBar; import android.widget.TextView; @@ -25,6 +27,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.NotificationManagerCompat; import androidx.fragment.app.FragmentActivity; +import org.citra.citra_emu.CitraApplication; import org.citra.citra_emu.NativeLibrary; import org.citra.citra_emu.R; import org.citra.citra_emu.features.settings.model.view.InputBindingSetting; @@ -303,6 +306,66 @@ public final class EmulationActivity extends AppCompatActivity { return true; } + private void DisplaySavestateWarning() { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.getAppContext()); + if (preferences.getBoolean("savestateWarningShown", false)) { + return; + } + + LayoutInflater inflater = mEmulationFragment.requireActivity().getLayoutInflater(); + View view = inflater.inflate(R.layout.dialog_checkbox, null); + CheckBox checkBox = view.findViewById(R.id.checkBox); + + new AlertDialog.Builder(this) + .setTitle(R.string.savestate_warning_title) + .setMessage(R.string.savestate_warning_message) + .setView(view) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + preferences.edit().putBoolean("savestateWarningShown", checkBox.isChecked()).apply(); + }) + .show(); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + final NativeLibrary.SavestateInfo[] savestates = NativeLibrary.GetSavestateInfo(); + if (savestates == null) { + menu.findItem(R.id.menu_emulation_save_state).setVisible(false); + menu.findItem(R.id.menu_emulation_load_state).setVisible(false); + return true; + } + menu.findItem(R.id.menu_emulation_save_state).setVisible(true); + menu.findItem(R.id.menu_emulation_load_state).setVisible(true); + + final SubMenu saveStateMenu = menu.findItem(R.id.menu_emulation_save_state).getSubMenu(); + final SubMenu loadStateMenu = menu.findItem(R.id.menu_emulation_load_state).getSubMenu(); + saveStateMenu.clear(); + loadStateMenu.clear(); + + // Update savestates information + for (int i = 0; i < NativeLibrary.SAVESTATE_SLOT_COUNT; ++i) { + final int slot = i + 1; + final String text = getString(R.string.emulation_empty_state_slot, slot); + saveStateMenu.add(text).setEnabled(true).setOnMenuItemClickListener((item) -> { + DisplaySavestateWarning(); + NativeLibrary.SaveState(slot); + return true; + }); + loadStateMenu.add(text).setEnabled(false).setOnMenuItemClickListener((item) -> { + NativeLibrary.LoadState(slot); + return true; + }); + } + for (final NativeLibrary.SavestateInfo info : savestates) { + final String text = getString(R.string.emulation_occupied_state_slot, info.slot, info.time); + saveStateMenu.getItem(info.slot - 1).setTitle(text); + loadStateMenu.getItem(info.slot - 1).setTitle(text).setEnabled(true); + } + return true; + } + @SuppressWarnings("WrongConstant") @Override public boolean onOptionsItemSelected(MenuItem item) { 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 71a5ff633..118e33e4c 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 @@ -246,7 +246,7 @@ void EmuWindow_Android::TryPresenting() { } } eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0); - if (VideoCore::g_renderer->TryPresent()) { + if (VideoCore::g_renderer && VideoCore::g_renderer->TryPresent()) { eglSwapBuffers(egl_display, egl_surface); } } diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index 6c5c31acf..d716640b6 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -19,6 +19,9 @@ static constexpr jint JNI_VERSION = JNI_VERSION_1_6; static JavaVM* s_java_vm; static jclass s_native_library_class; +static jclass s_core_error_class; +static jclass s_savestate_info_class; +static jmethodID s_on_core_error; static jmethodID s_display_alert_msg; static jmethodID s_display_alert_prompt; static jmethodID s_alert_prompt_button; @@ -53,6 +56,18 @@ jclass GetNativeLibraryClass() { return s_native_library_class; } +jclass GetCoreErrorClass() { + return s_core_error_class; +} + +jclass GetSavestateInfoClass() { + return s_savestate_info_class; +} + +jmethodID GetOnCoreError() { + return s_on_core_error; +} + jmethodID GetDisplayAlertMsg() { return s_display_alert_msg; } @@ -111,6 +126,13 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { // Initialize Java methods const jclass native_library_class = env->FindClass("org/citra/citra_emu/NativeLibrary"); s_native_library_class = reinterpret_cast(env->NewGlobalRef(native_library_class)); + s_savestate_info_class = reinterpret_cast( + env->NewGlobalRef(env->FindClass("org/citra/citra_emu/NativeLibrary$SavestateInfo"))); + s_core_error_class = reinterpret_cast( + env->NewGlobalRef(env->FindClass("org/citra/citra_emu/NativeLibrary$CoreError"))); + s_on_core_error = env->GetStaticMethodID( + s_native_library_class, "OnCoreError", + "(Lorg/citra/citra_emu/NativeLibrary$CoreError;Ljava/lang/String;)Z"); s_display_alert_msg = env->GetStaticMethodID(s_native_library_class, "displayAlertMsg", "(Ljava/lang/String;Ljava/lang/String;Z)Z"); s_display_alert_prompt = @@ -142,6 +164,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) { } env->DeleteGlobalRef(s_native_library_class); + env->DeleteGlobalRef(s_savestate_info_class); + env->DeleteGlobalRef(s_core_error_class); MiiSelector::CleanupJNI(env); SoftwareKeyboard::CleanupJNI(env); Camera::StillImage::CleanupJNI(env); diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index 64c185e80..fc4987b46 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h @@ -12,6 +12,9 @@ namespace IDCache { JNIEnv* GetEnvForThread(); jclass GetNativeLibraryClass(); +jclass GetCoreErrorClass(); +jclass GetSavestateInfoClass(); +jmethodID GetOnCoreError(); jmethodID GetDisplayAlertMsg(); jmethodID GetDisplayAlertPrompt(); jmethodID GetAlertPromptButton(); diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 8262b9f38..278ea8439 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -23,6 +23,7 @@ #include "core/frontend/scope_acquire_context.h" #include "core/hle/service/am/am.h" #include "core/hle/service/nfc/nfc.h" +#include "core/savestate.h" #include "core/settings.h" #include "jni/applets/mii_selector.h" #include "jni/applets/swkbd.h" @@ -95,6 +96,29 @@ static int AlertPromptButton() { IDCache::GetAlertPromptButton())); } +static jobject ToJavaCoreError(Core::System::ResultStatus result) { + static const std::map CoreErrorNameMap{ + {Core::System::ResultStatus::ErrorSystemFiles, "ErrorSystemFiles"}, + {Core::System::ResultStatus::ErrorSavestate, "ErrorSavestate"}, + {Core::System::ResultStatus::ErrorUnknown, "ErrorUnknown"}, + }; + + const auto name = CoreErrorNameMap.count(result) ? CoreErrorNameMap.at(result) : "ErrorUnknown"; + + JNIEnv* env = IDCache::GetEnvForThread(); + const jclass core_error_class = IDCache::GetCoreErrorClass(); + return env->GetStaticObjectField( + core_error_class, env->GetStaticFieldID(core_error_class, name, + "Lorg/citra/citra_emu/NativeLibrary$CoreError;")); +} + +static bool HandleCoreError(Core::System::ResultStatus result, const std::string& details) { + JNIEnv* env = IDCache::GetEnvForThread(); + return env->CallStaticBooleanMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnCoreError(), + ToJavaCoreError(result), + env->NewStringUTF(details.c_str())) != JNI_FALSE; +} + static Camera::NDK::Factory* g_ndk_factory{}; static void TryShutdown() { @@ -186,7 +210,20 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { // Start running emulation while (is_running) { if (!pause_emulation) { - system.RunLoop(); + const auto result = system.RunLoop(); + if (result == Core::System::ResultStatus::Success) { + continue; + } + if (result == Core::System::ResultStatus::ShutdownRequested) { + return result; // This also exits the emulation activity + } else { + InputManager::NDKMotionHandler()->DisableSensors(); + if (!HandleCoreError(result, system.GetStatusDetails())) { + // Frontend requests us to abort + return result; + } + InputManager::NDKMotionHandler()->EnableSensors(); + } } else { // Ensure no audio bleeds out while game is paused const float volume = Settings::values.volume; @@ -620,4 +657,46 @@ void Java_org_citra_citra_1emu_NativeLibrary_InstallCIAS(JNIEnv* env, [[maybe_un thread.join(); } +jobjectArray Java_org_citra_citra_1emu_NativeLibrary_GetSavestateInfo( + JNIEnv* env, [[maybe_unused]] jclass clazz) { + const jclass date_class = env->FindClass("java/util/Date"); + const auto date_constructor = env->GetMethodID(date_class, "", "(J)V"); + + const jclass savestate_info_class = IDCache::GetSavestateInfoClass(); + const auto slot_field = env->GetFieldID(savestate_info_class, "slot", "I"); + const auto date_field = env->GetFieldID(savestate_info_class, "time", "Ljava/util/Date;"); + + const Core::System& system{Core::System::GetInstance()}; + if (!system.IsPoweredOn()) { + return nullptr; + } + + u64 title_id; + if (system.GetAppLoader().ReadProgramId(title_id) != Loader::ResultStatus::Success) { + return nullptr; + } + + const auto savestates = Core::ListSaveStates(title_id); + const jobjectArray array = + env->NewObjectArray(static_cast(savestates.size()), savestate_info_class, nullptr); + for (std::size_t i = 0; i < savestates.size(); ++i) { + const jobject object = env->AllocObject(savestate_info_class); + env->SetIntField(object, slot_field, static_cast(savestates[i].slot)); + env->SetObjectField(object, date_field, + env->NewObject(date_class, date_constructor, + static_cast(savestates[i].time * 1000))); + + env->SetObjectArrayElement(array, i, object); + } + return array; +} + +void Java_org_citra_citra_1emu_NativeLibrary_SaveState(JNIEnv* env, jclass clazz, jint slot) { + Core::System::GetInstance().SendSignal(Core::System::Signal::Save, slot); +} + +void Java_org_citra_citra_1emu_NativeLibrary_LoadState(JNIEnv* env, jclass clazz, jint slot) { + Core::System::GetInstance().SendSignal(Core::System::Signal::Load, slot); +} + } // extern "C" diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 11f526215..f02b15b35 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -33,9 +33,9 @@ JNIEXPORT jboolean JNICALL Java_org_citra_citra_1emu_NativeLibrary_onGamePadAxis JNIEnv* env, jclass clazz, jstring j_device, jint axis_id, jfloat axis_val); JNIEXPORT jboolean JNICALL Java_org_citra_citra_1emu_NativeLibrary_onTouchEvent(JNIEnv* env, - jclass clazz, jfloat x, - jfloat y, - jboolean pressed); + jclass clazz, + jfloat x, jfloat y, + jboolean pressed); JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz, jfloat x, @@ -142,7 +142,18 @@ JNIEXPORT jboolean Java_org_citra_citra_1emu_NativeLibrary_LoadAmiibo(JNIEnv* en JNIEXPORT void Java_org_citra_citra_1emu_NativeLibrary_RemoveAmiibo(JNIEnv* env, jclass clazz); -JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_InstallCIAS(JNIEnv* env, jclass clazz, jobjectArray path); +JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_InstallCIAS(JNIEnv* env, + jclass clazz, + jobjectArray path); + +JNIEXPORT jobjectArray JNICALL +Java_org_citra_citra_1emu_NativeLibrary_GetSavestateInfo(JNIEnv* env, jclass clazz); + +JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SaveState(JNIEnv* env, jclass clazz, + jint slot); + +JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_LoadState(JNIEnv* env, jclass clazz, + jint slot); #ifdef __cplusplus } diff --git a/src/android/app/src/main/res/layout/dialog_checkbox.xml b/src/android/app/src/main/res/layout/dialog_checkbox.xml new file mode 100644 index 000000000..c0f307117 --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_checkbox.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/src/android/app/src/main/res/menu/menu_emulation.xml b/src/android/app/src/main/res/menu/menu_emulation.xml index a3ecb80d7..ea3301d37 100644 --- a/src/android/app/src/main/res/menu/menu_emulation.xml +++ b/src/android/app/src/main/res/menu/menu_emulation.xml @@ -3,6 +3,18 @@ xmlns:app="http://schemas.android.com/apk/res-auto" tools:context="org.citra.citra_emu.activities.EmulationActivity"> + + + + + + + + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 0e9799dfe..dac2f6bae 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -143,6 +143,10 @@ Invalid ROM format + Save State + Load State + Slot %1$d + Slot %1$d - %2$tF %2$tR Show FPS Configure Controls Edit Layout @@ -177,6 +181,10 @@ Select This Directory No files were found or no game directory has been selected yet. + Do not show this again + Savestates + Warning: Savestates are NOT a replacement for in-game saves, and are not meant to be reliable.\n\nUse at your own risk! + Software Keyboard I Forgot @@ -197,4 +205,14 @@ Microphone Citra needs to access your microphone to emulate the 3DS\'s microphone.\n\nAlternatively, you can also change \"Audio Input Device\" in Audio Settings. + + + Abort + Continue + System Archive Not Found + %s is missing. Please dump your system archives.\nContinuing emulation may result in crashes and bugs. + A system archive + Save/Load Error + Fatal Error + A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs. diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index b75157b40..d5423c739 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -13,6 +13,8 @@ GPUBackend::GPUBackend(VideoCore::RendererBase& renderer) : renderer{renderer} { GPUBackend::~GPUBackend() = default; +void GPUBackend::WaitForProcessing() {} + GPUSerial::GPUSerial(Core::System& system, VideoCore::RendererBase& renderer) : GPUBackend(renderer), system{system} {} @@ -83,4 +85,8 @@ void GPUParallel::InvalidateRegion(VAddr addr, u64 size) { gpu_thread.InvalidateRegion(addr, size); } +void GPUParallel::WaitForProcessing() { + gpu_thread.WaitForProcessing(); +} + } // namespace VideoCore diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index d239704ba..b47023dc5 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -29,6 +29,7 @@ public: virtual void FlushRegion(VAddr addr, u64 size) = 0; virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0; virtual void InvalidateRegion(VAddr addr, u64 size) = 0; + virtual void WaitForProcessing(); protected: VideoCore::RendererBase& renderer; @@ -65,6 +66,7 @@ public: void FlushRegion(VAddr addr, u64 size) override; void FlushAndInvalidateRegion(VAddr addr, u64 size) override; void InvalidateRegion(VAddr addr, u64 size) override; + void WaitForProcessing() override; private: GPUThread::ThreadManager gpu_thread; diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 5cc22433c..fc62e69ae 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -32,12 +32,12 @@ static void RunThread(VideoCore::RendererBase& renderer, SynchState& state, Core Frontend::ScopeAcquireContext acquire_context{renderer.GetRenderWindow()}; - CommandDataContainer next; while (state.is_running) { state.WaitForCommands(); - CommandDataContainer next; - while (state.queue.Pop(next)) { + while (!state.queue.Empty()) { + CommandDataContainer next = state.queue.Front(); + auto command = &next.data; auto fence = next.fence; if (const auto submit_list = std::get_if(command)) { @@ -62,6 +62,7 @@ static void RunThread(VideoCore::RendererBase& renderer, SynchState& state, Core UNREACHABLE(); } state.signaled_fence = next.fence; + state.queue.Pop(); } } } @@ -211,8 +212,15 @@ u64 ThreadManager::PushCommand(CommandData&& command_data) { return fence; } +void ThreadManager::WaitForProcessing() { + state.WaitForProcessing(); +} + MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192)); void SynchState::WaitForSynchronization(u64 fence) { + if (fence > last_fence) { // We don't want to wait infinitely + return; + } if (signaled_fence >= fence) { return; } diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h index 27e33d7f8..ade649cdb 100644 --- a/src/video_core/gpu_thread.h +++ b/src/video_core/gpu_thread.h @@ -169,9 +169,14 @@ struct SynchState final { // commands_condition.wait(lock, [this] { return !queue.Empty(); }); } + void WaitForProcessing() { + while (!queue.Empty() && is_running) + ; + } + using CommandQueue = Common::SPSCQueue; CommandQueue queue; - u64 last_fence{}; + std::atomic last_fence{}; std::atomic signaled_fence{}; }; @@ -195,6 +200,8 @@ public: void InvalidateRegion(VAddr addr, u64 size); + void WaitForProcessing(); + private: void Synchronize(u64 fence, Settings::GpuTimingMode mode); diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 56718c54a..8c67376f8 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -1193,6 +1193,10 @@ VideoCore::ResultStatus RendererOpenGL::Init() { } #endif + if (Settings::values.use_asynchronous_gpu_emulation) { + render_window.MakeCurrent(); + } + const char* gl_version{reinterpret_cast(glGetString(GL_VERSION))}; const char* gpu_vendor{reinterpret_cast(glGetString(GL_VENDOR))}; const char* gpu_model{reinterpret_cast(glGetString(GL_RENDERER))}; diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 2e32953de..e0e234418 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -104,6 +104,7 @@ u16 GetResolutionScaleFactor() { template void serialize(Archive& ar, const unsigned int) { + g_gpu->WaitForProcessing(); ar& Pica::g_state; }