diff --git a/src/android/app/src/androidTest/java/org/citra/citra_emu/ExampleInstrumentedTest.java b/src/android/app/src/androidTest/java/org/citra/citra_emu/ExampleInstrumentedTest.java new file mode 100644 index 000000000..671fb4b30 --- /dev/null +++ b/src/android/app/src/androidTest/java/org/citra/citra_emu/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package org.citra.citra_emu; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("org.citra.citra_emu", appContext.getPackageName()); + } +} 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..464336592 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 @@ -172,8 +172,6 @@ public final class NativeLibrary { public static native void SurfaceDestroyed(); - public static native void DoFrame(); - /** * Unpauses emulation from a paused state. */ diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.java b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.java index 445faa047..1057e4c16 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.java @@ -6,7 +6,6 @@ import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; -import android.view.Choreographer; import android.view.LayoutInflater; import android.view.Surface; import android.view.SurfaceHolder; @@ -31,7 +30,7 @@ import org.citra.citra_emu.utils.DirectoryStateReceiver; import org.citra.citra_emu.utils.EmulationMenuSettings; import org.citra.citra_emu.utils.Log; -public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback, Choreographer.FrameCallback { +public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback { private static final String KEY_GAMEPATH = "gamepath"; private static final Handler perfStatsUpdateHandler = new Handler(); @@ -115,7 +114,6 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C @Override public void onResume() { super.onResume(); - Choreographer.getInstance().postFrameCallback(this); if (DirectoryInitialization.areCitraDirectoriesReady()) { mEmulationState.run(activity.isActivityRecreated()); } else { @@ -130,11 +128,8 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C directoryStateReceiver = null; } - if (mEmulationState.isRunning()) { + if (mEmulationState.isRunning()) mEmulationState.pause(); - } - - Choreographer.getInstance().removeFrameCallback(this); super.onPause(); } @@ -232,12 +227,6 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C mEmulationState.clearSurface(); } - @Override - public void doFrame(long frameTimeNanos) { - Choreographer.getInstance().postFrameCallback(this); - NativeLibrary.DoFrame(); - } - public void stopEmulation() { mEmulationState.stop(); } @@ -353,9 +342,9 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C private void runWithValidSurface() { mRunWhenSurfaceIsValid = false; if (state == State.STOPPED) { - NativeLibrary.SurfaceChanged(mSurface); Thread mEmulationThread = new Thread(() -> { + NativeLibrary.SurfaceChanged(mSurface); Log.debug("[EmulationFragment] Starting emulation thread."); NativeLibrary.Run(mGamePath); }, "NativeEmulation"); 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 068018b69..9ba9605ac 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 @@ -79,7 +79,6 @@ static void UpdateLandscapeScreenLayout() { void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { render_window = surface; - StopPresenting(); } bool EmuWindow_Android::OnTouchEvent(int x, int y, bool pressed) { @@ -220,6 +219,7 @@ void EmuWindow_Android::DestroyContext() { } EmuWindow_Android::~EmuWindow_Android() { + StopPresenting(); DestroyWindowSurface(); DestroyContext(); } @@ -228,26 +228,34 @@ std::unique_ptr EmuWindow_Android::CreateSharedContex return std::make_unique(egl_display, egl_config, egl_context); } -void EmuWindow_Android::StopPresenting() { - if (presenting_state == PresentingState::Running) { - eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - presenting_state = PresentingState::Stopped; +void EmuWindow_Android::StartPresenting() { + ASSERT(!presentation_thread); + is_presenting = true; + presentation_thread = + std::make_unique([emu_window{this}] { emu_window->Present(); }); } -void EmuWindow_Android::TryPresenting() { - if (presenting_state != PresentingState::Running) { - if (presenting_state == PresentingState::Initial) { - eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - presenting_state = PresentingState::Running; - } else { - return; - } +void EmuWindow_Android::StopPresenting() { + is_presenting = false; + if (presentation_thread) { + presentation_thread->join(); + presentation_thread.reset(); } +} + +bool EmuWindow_Android::IsPresenting() const { + return is_presenting; +} + +void EmuWindow_Android::Present() { + eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0); - VideoCore::g_renderer->TryPresent(100); - eglSwapBuffers(egl_display, egl_surface); + while (IsPresenting()) { + VideoCore::g_renderer->TryPresent(100); + eglSwapBuffers(egl_display, egl_surface); + } + eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } void EmuWindow_Android::PollEvents() { @@ -258,10 +266,14 @@ void EmuWindow_Android::PollEvents() { host_window = render_window; render_window = nullptr; + if (IsPresenting()) { + StopPresenting(); + } + DestroyWindowSurface(); CreateWindowSurface(); OnFramebufferSizeChanged(); - presenting_state = PresentingState::Initial; + StartPresenting(); } void EmuWindow_Android::MakeCurrent() { diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h index 10a293c96..f27b462ff 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.h +++ b/src/android/app/src/main/jni/emu_window/emu_window.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include @@ -50,8 +51,9 @@ public: void MakeCurrent() override; void DoneCurrent() override; - void TryPresenting(); + void StartPresenting(); void StopPresenting(); + bool IsPresenting() const; std::unique_ptr CreateSharedContext() const override; @@ -74,10 +76,7 @@ private: std::unique_ptr core_context; - enum class PresentingState { - Initial, - Running, - Stopped, - }; - PresentingState presenting_state{}; + std::unique_ptr presentation_thread; + + bool is_presenting{}; }; diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 8875444d7..be513d1ef 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -102,6 +102,7 @@ static void TryShutdown() { return; } + window->StopPresenting(); window->DoneCurrent(); Core::System::GetInstance().Shutdown(); window.reset(); @@ -166,6 +167,8 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { is_running = true; pause_emulation = false; + window->StartPresenting(); + SCOPE_EXIT({ TryShutdown(); }); // Audio stretching on Android is only useful with lower framerates, disable it when fullspeed @@ -193,7 +196,6 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { std::unique_lock pause_lock(paused_mutex); running_cv.wait(pause_lock, [] { return !pause_emulation || !is_running; }); - window->PollEvents(); } } @@ -223,13 +225,6 @@ void Java_org_citra_citra_1emu_NativeLibrary_SurfaceDestroyed(JNIEnv* env, } } -void Java_org_citra_citra_1emu_NativeLibrary_DoFrame(JNIEnv* env, [[maybe_unused]] jclass clazz) { - if (!is_running || pause_emulation) { - return; - } - window->TryPresenting(); -} - void Java_org_citra_citra_1emu_NativeLibrary_NotifyOrientationChange(JNIEnv* env, [[maybe_unused]] jclass clazz, jint layout_option, @@ -307,7 +302,6 @@ void Java_org_citra_citra_1emu_NativeLibrary_StopEmulation(JNIEnv* env, [[maybe_unused]] jclass clazz) { is_running = false; pause_emulation = false; - window->StopPresenting(); running_cv.notify_all(); } diff --git a/src/android/app/src/test/java/org/citra/citra_emu/ExampleUnitTest.java b/src/android/app/src/test/java/org/citra/citra_emu/ExampleUnitTest.java new file mode 100644 index 000000000..4e4bb317f --- /dev/null +++ b/src/android/app/src/test/java/org/citra/citra_emu/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package org.citra.citra_emu; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/src/android/build.gradle b/src/android/build.gradle index 92b0e7081..6f6ecaf8e 100644 --- a/src/android/build.gradle +++ b/src/android/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:3.6.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files