Merge pull request #180 from zhaowenlan1779/savestates
Add savestates feature
This commit is contained in:
commit
3ad8c56f74
@ -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
|
||||
*/
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<jclass>(env->NewGlobalRef(native_library_class));
|
||||
s_savestate_info_class = reinterpret_cast<jclass>(
|
||||
env->NewGlobalRef(env->FindClass("org/citra/citra_emu/NativeLibrary$SavestateInfo")));
|
||||
s_core_error_class = reinterpret_cast<jclass>(
|
||||
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);
|
||||
|
@ -12,6 +12,9 @@ namespace IDCache {
|
||||
|
||||
JNIEnv* GetEnvForThread();
|
||||
jclass GetNativeLibraryClass();
|
||||
jclass GetCoreErrorClass();
|
||||
jclass GetSavestateInfoClass();
|
||||
jmethodID GetOnCoreError();
|
||||
jmethodID GetDisplayAlertMsg();
|
||||
jmethodID GetDisplayAlertPrompt();
|
||||
jmethodID GetAlertPromptButton();
|
||||
|
@ -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<Core::System::ResultStatus, const char*> 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, "<init>", "(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<jsize>(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<jint>(savestates[i].slot));
|
||||
env->SetObjectField(object, date_field,
|
||||
env->NewObject(date_class, date_constructor,
|
||||
static_cast<jlong>(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"
|
||||
|
@ -33,8 +33,8 @@ 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,
|
||||
jclass clazz,
|
||||
jfloat x, jfloat y,
|
||||
jboolean pressed);
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_onTouchMoved(JNIEnv* env,
|
||||
@ -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
|
||||
}
|
||||
|
16
src/android/app/src/main/res/layout/dialog_checkbox.xml
Normal file
16
src/android/app/src/main/res/layout/dialog_checkbox.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/do_not_show_this_again" />
|
||||
</LinearLayout>
|
@ -3,6 +3,18 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context="org.citra.citra_emu.activities.EmulationActivity">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_emulation_save_state"
|
||||
android:title="@string/emulation_save_state">
|
||||
<menu/>
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_emulation_load_state"
|
||||
android:title="@string/emulation_load_state">
|
||||
<menu/>
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_emulation_configure_controls"
|
||||
android:title="@string/emulation_configure_controls">
|
||||
|
@ -143,6 +143,10 @@
|
||||
<string name="loader_error_invalid_format">Invalid ROM format</string>
|
||||
|
||||
<!-- Emulation Menu -->
|
||||
<string name="emulation_save_state">Save State</string>
|
||||
<string name="emulation_load_state">Load State</string>
|
||||
<string name="emulation_empty_state_slot">Slot %1$d</string>
|
||||
<string name="emulation_occupied_state_slot">Slot %1$d - %2$tF %2$tR</string>
|
||||
<string name="emulation_show_fps">Show FPS</string>
|
||||
<string name="emulation_configure_controls">Configure Controls</string>
|
||||
<string name="emulation_edit_layout">Edit Layout</string>
|
||||
@ -177,6 +181,10 @@
|
||||
<string name="select_dir">Select This Directory</string>
|
||||
<string name="empty_gamelist">No files were found or no game directory has been selected yet.</string>
|
||||
|
||||
<string name="do_not_show_this_again">Do not show this again</string>
|
||||
<string name="savestate_warning_title">Savestates</string>
|
||||
<string name="savestate_warning_message">Warning: Savestates are NOT a replacement for in-game saves, and are not meant to be reliable.\n\nUse at your own risk!</string>
|
||||
|
||||
<!-- Software Keyboard -->
|
||||
<string name="software_keyboard">Software Keyboard</string>
|
||||
<string name="i_forgot">I Forgot</string>
|
||||
@ -197,4 +205,14 @@
|
||||
<!-- Microphone -->
|
||||
<string name="microphone">Microphone</string>
|
||||
<string name="microphone_permission_needed">Citra needs to access your microphone to emulate the 3DS\'s microphone.\n\nAlternatively, you can also change \"Audio Input Device\" in Audio Settings.</string>
|
||||
|
||||
<!-- Core Errors -->
|
||||
<string name="abort_button">Abort</string>
|
||||
<string name="continue_button">Continue</string>
|
||||
<string name="system_archive_not_found">System Archive Not Found</string>
|
||||
<string name="system_archive_not_found_message">%s is missing. Please dump your system archives.\nContinuing emulation may result in crashes and bugs.</string>
|
||||
<string name="system_archive_general">A system archive</string>
|
||||
<string name="save_load_error">Save/Load Error</string>
|
||||
<string name="fatal_error">Fatal Error</string>
|
||||
<string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string>
|
||||
</resources>
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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<SubmitListCommand>(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;
|
||||
}
|
||||
|
@ -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<CommandDataContainer>;
|
||||
CommandQueue queue;
|
||||
u64 last_fence{};
|
||||
std::atomic<u64> last_fence{};
|
||||
std::atomic<u64> signaled_fence{};
|
||||
};
|
||||
|
||||
@ -195,6 +200,8 @@ public:
|
||||
|
||||
void InvalidateRegion(VAddr addr, u64 size);
|
||||
|
||||
void WaitForProcessing();
|
||||
|
||||
private:
|
||||
void Synchronize(u64 fence, Settings::GpuTimingMode mode);
|
||||
|
||||
|
@ -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<char const*>(glGetString(GL_VERSION))};
|
||||
const char* gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
|
||||
const char* gpu_model{reinterpret_cast<char const*>(glGetString(GL_RENDERER))};
|
||||
|
@ -104,6 +104,7 @@ u16 GetResolutionScaleFactor() {
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
g_gpu->WaitForProcessing();
|
||||
ar& Pica::g_state;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user