diff --git a/AGENTS.md b/AGENTS.md
index 90a909ec5d..4c53e5d3fb 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -44,3 +44,11 @@ when making cross platform changes.
New unit tests are added by listing them in availableTests in unittest.cpp. If they are large, put them in
separate files in the unittest subdirectory. Remember to update both CMakeLists.txt and the visual studio project.
+
+## Quick rebuild on Linux
+
+You don't need to do ./b.sh --debug to verify every single little change, instead use this shortcut:
+
+```bash
+cd build ; make -j32; cd ..
+```
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000000..43c994c2d3
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+@AGENTS.md
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 82f33b6abe..3251ad12f6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -822,6 +822,7 @@ add_library(Common STATIC
Common/Audio/AudioBackend.h
Common/System/Display.cpp
Common/System/Display.h
+ Common/System/Application.h
Common/System/System.h
Common/System/NativeApp.h
Common/System/Request.cpp
@@ -1549,6 +1550,7 @@ if(WIN32)
target_link_libraries(Common winmm dsound dxguid d2d1 dwrite d3d11 Version)
endif()
+set(UISource)
if(NOT LIBRETRO)
list(APPEND UISource
UI/AudioCommon.h
diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj
index 41021a051c..6569f886d8 100644
--- a/Common/Common.vcxproj
+++ b/Common/Common.vcxproj
@@ -536,6 +536,7 @@
+
diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters
index 048031eb65..3b111d99f1 100644
--- a/Common/Common.vcxproj.filters
+++ b/Common/Common.vcxproj.filters
@@ -784,6 +784,9 @@
Data\Text
+
+ System
+
diff --git a/Common/GraphicsContext.h b/Common/GraphicsContext.h
index 59b33ae169..4f7008cad9 100644
--- a/Common/GraphicsContext.h
+++ b/Common/GraphicsContext.h
@@ -29,6 +29,9 @@ public:
// Needs casting to the appropriate type, unfortunately. Should find a better solution..
virtual void *GetAPIContext() { return nullptr; }
+ // OpenGL returns true here.
+ virtual bool NeedsRenderThread() const { return false; }
+
// Called from the render thread from threaded backends.
virtual void ThreadStart() {}
virtual bool ThreadFrame(bool waitIfEmpty) { return true; } // waitIfEmpty should normally be true, except in exit scenarios.
@@ -41,6 +44,8 @@ public:
virtual Draw::DrawContext *GetDrawContext() = 0;
void ThreadFrameUntilCondition(std::function conditionStopped) {
+ _dbg_assert_(NeedsRenderThread());
+
bool exitOnEmpty = false;
INFO_LOG(Log::System, "Executing graphicsContext->ThreadFrame to clear buffers");
diff --git a/Common/System/Application.h b/Common/System/Application.h
new file mode 100644
index 0000000000..75afe29a5f
--- /dev/null
+++ b/Common/System/Application.h
@@ -0,0 +1,11 @@
+#pragma once
+
+class GraphicsContext;
+
+class Application {
+public:
+ virtual ~Application() = default;
+ virtual bool InitGraphics(GraphicsContext *graphicsContext) = 0;
+ virtual void ShutdownGraphics(GraphicsContext *graphicsContext) = 0;
+ virtual void Frame(GraphicsContext *graphicsContext) = 0;
+};
diff --git a/Common/System/NativeApp.h b/Common/System/NativeApp.h
index 513ab571df..6124978766 100644
--- a/Common/System/NativeApp.h
+++ b/Common/System/NativeApp.h
@@ -4,6 +4,7 @@
#include
#include "Common/Common.h"
+#include "Common/System/Application.h"
// The Native App API.
//
@@ -91,7 +92,7 @@ void NativeMix(short *audio, int num_samples, int sampleRateHz, void *userdata);
// The graphics context should still be active when calling this, as freeing
// of graphics resources happens here.
// Main thread.
-void NativeShutdownGraphics();
+void NativeShutdownGraphics(GraphicsContext *graphicsContext);
void NativeShutdown();
void PostLoadConfig();
@@ -112,3 +113,17 @@ bool Native_IsWindowHidden();
bool Native_UpdateScreenScale(int width, int height, float customScale);
AudioBackend *System_CreateAudioBackend();
+
+class NativeApplication : public Application {
+public:
+ NativeApplication() {}
+ bool InitGraphics(GraphicsContext *graphicsContext) override {
+ return NativeInitGraphics(graphicsContext);
+ }
+ void ShutdownGraphics(GraphicsContext *graphicsContext) override {
+ return NativeShutdownGraphics(graphicsContext);
+ }
+ void Frame(GraphicsContext *graphicsContext) override {
+ return NativeFrame(graphicsContext);
+ }
+};
diff --git a/Core/EmuThread.cpp b/Core/EmuThread.cpp
index a4170b249a..6511340810 100644
--- a/Core/EmuThread.cpp
+++ b/Core/EmuThread.cpp
@@ -4,9 +4,9 @@
#include
#include
-#include "Common/System/NativeApp.h"
#include "Common/System/System.h"
#include "Common/System/Request.h"
+#include "Common/System/Application.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Input/InputState.h"
#include "Common/Data/Encoding/Utf8.h"
@@ -40,38 +40,70 @@ bool MainThread_Ready() {
return g_inLoop;
}
-static void EmuThreadFunc(GraphicsContext *graphicsContext) {
+static void EmuThreadFunc(GraphicsContext *graphicsContext, Application *application, std::function postFrame) {
+ INFO_LOG(Log::G3D, "Entering emu thread");
SetCurrentThreadName("EmuThread");
+ AndroidJNIThreadContext context;
+
// There's no real requirement that NativeInit happen on this thread.
// We just call the update/render loop here.
g_emuThreadState = EmuThreadState::RUNNING;
- NativeInitGraphics(graphicsContext);
+ if (!application->InitGraphics(graphicsContext)) {
+ _assert_msg_(false, "NativeInitGraphics failed, might as well bail");
+ // If this fails, which it normally shouldn't, let's bail.
+ g_emuThreadState = EmuThreadState::QUIT_REQUESTED;
+ } else {
+ INFO_LOG(Log::G3D, "EmuThread: Entering loop");
+ }
while (g_emuThreadState != EmuThreadState::QUIT_REQUESTED) {
// We're here again, so the game quit. Restart Run() which controls the UI.
// This way they can load a new game.
- if (!Core_IsActive()) {
- UpdateUIState(UISTATE_MENU);
+ application->Frame(graphicsContext);
+ if (postFrame) {
+ postFrame();
}
- Core_StateProcessed();
- NativeFrame(graphicsContext);
-
if (GetUIState() == UISTATE_EXIT) {
g_emuThreadState = EmuThreadState::QUIT_REQUESTED;
}
}
+ INFO_LOG(Log::System, "emuThreadState was set to QUIT_REQUESTED, left EmuThreadFunc loop. Setting state to STOPPED.");
+
g_emuThreadState = EmuThreadState::STOPPED;
- NativeShutdownGraphics();
+ application->ShutdownGraphics(graphicsContext);
+ INFO_LOG(Log::System, "Leaving emu thread");
}
-void MainThreadFunc(GraphicsContext *graphicsContext) {
- const bool useEmuThread = g_Config.iGPUBackend == (int)GPUBackend::OPENGL;
- if (useEmuThread) {
+std::thread EmuThread_Start(GraphicsContext *graphicsContext, Application *application, std::function postFrame) {
+ g_emuThreadState = EmuThreadState::START_REQUESTED;
+ std::thread emuThread = std::thread(&EmuThreadFunc, graphicsContext, application, postFrame);
+ return emuThread;
+}
+
+void EmuThread_Join(GraphicsContext *graphicsContext, std::thread &emuThread) {
+ const EmuThreadState state = g_emuThreadState;
+ if (state != EmuThreadState::QUIT_REQUESTED &&
+ state != EmuThreadState::STOPPED) {
+ g_emuThreadState = EmuThreadState::QUIT_REQUESTED;
+ }
+ _dbg_assert_(emuThread.joinable());
+ if (graphicsContext->NeedsRenderThread()) {
+ graphicsContext->ThreadFrameUntilCondition([] {
+ // Need to keep eating frames to allow the EmuThread to exit correctly.
+ return g_emuThreadState == EmuThreadState::STOPPED;
+ });
+ }
+ emuThread.join();
+ emuThread = std::thread();
+}
+
+void MainThreadFunc(GraphicsContext *graphicsContext, Application *application, std::function postFrame) {
+ if (graphicsContext->NeedsRenderThread()) {
SetCurrentThreadName("RenderThread");
// This is now the render thread, and will spawn the emu thread below.
@@ -82,8 +114,7 @@ void MainThreadFunc(GraphicsContext *graphicsContext) {
g_inLoop = true;
- g_emuThreadState = EmuThreadState::START_REQUESTED;
- std::thread emuThread = std::thread(&EmuThreadFunc, graphicsContext);
+ std::thread emuThread = EmuThread_Start(graphicsContext, application, postFrame);
graphicsContext->ThreadStart();
// This thread becomes the render thread.
@@ -99,19 +130,13 @@ void MainThreadFunc(GraphicsContext *graphicsContext) {
Core_Stop();
g_inLoop = false;
- const EmuThreadState state = g_emuThreadState;
- if (state != EmuThreadState::QUIT_REQUESTED &&
- state != EmuThreadState::STOPPED) {
- g_emuThreadState = EmuThreadState::QUIT_REQUESTED;
- }
- graphicsContext->ThreadFrameUntilCondition([] {
- // Need to keep eating frames to allow the EmuThread to exit correctly.
- return g_emuThreadState == EmuThreadState::STOPPED;
- });
- emuThread.join();
- INFO_LOG(Log::System, "EmuThreadJoin - joined");
+ EmuThread_Join(graphicsContext, emuThread);
graphicsContext->ThreadEnd();
+ graphicsContext->ShutdownFromRenderThread();
+
+ INFO_LOG(Log::System, "EmuThreadJoin - joined");
+
} else {
SetCurrentThreadName("EmuThread");
// This is the emu thread. the graphics contexts will spawn and handle its own threads if needed.
@@ -119,32 +144,29 @@ void MainThreadFunc(GraphicsContext *graphicsContext) {
std::string error_string;
bool success = graphicsContext->InitFromRenderThread(&error_string);
- NativeInitGraphics(graphicsContext);
- NativeResized();
+ application->InitGraphics(graphicsContext);
+ // NativeResized();
DEBUG_LOG(Log::Boot, "Done.");
g_inLoop = true;
graphicsContext->ThreadStart();
-
while (GetUIState() != UISTATE_EXIT) {
// We're here again, so the game quit. Restart Run() which controls the UI.
// This way they can load a new game.
- if (!(Core_IsActive() || Core_IsStepping()))
- UpdateUIState(UISTATE_MENU);
- Core_StateProcessed();
- NativeFrame(graphicsContext);
+ application->Frame(graphicsContext);
+ postFrame();
}
Core_Stop();
// Process the shutdown. Without this, non-GL delays 800ms on shutdown.
Core_StateProcessed();
- NativeFrame(graphicsContext);
+ application->Frame(graphicsContext);
g_inLoop = false;
- NativeShutdownGraphics();
+ application->ShutdownGraphics(graphicsContext);
graphicsContext->ThreadEnd();
graphicsContext->ShutdownFromRenderThread();
diff --git a/Core/EmuThread.h b/Core/EmuThread.h
index 470ea7366c..c34a11de23 100644
--- a/Core/EmuThread.h
+++ b/Core/EmuThread.h
@@ -16,6 +16,9 @@
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
+#include
+
+#include "Common/System/Application.h"
// Utilities to manage Emu and Render threads.
// TODO: Use across platforms, currently Windows-only.
@@ -27,4 +30,9 @@ class GraphicsContext;
// Doesn't take ownership of the graphicsContext, you have to delete it.
// This should be used by platforms that launch a separate thread and doesn't
// need to run a polling loop in it.
-void MainThreadFunc(GraphicsContext *graphicsContext);
+void MainThreadFunc(GraphicsContext * graphicsContext, Application *application, std::function postFrame);
+
+// If you're not using MainThreadFunc, you can at least use these to manage a spinning EmuThread (that calls NativeFrame),
+// whether your graphics context requires multithreading or not.
+std::thread EmuThread_Start(GraphicsContext *graphicsContext, Application *application, std::function postFrame);
+void EmuThread_Join(GraphicsContext *graphicsContext, std::thread &emuThread);
diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp
index 82ea66e4d3..0fa59d8e75 100644
--- a/Qt/QtMain.cpp
+++ b/Qt/QtMain.cpp
@@ -676,7 +676,7 @@ void MainUI::EmuThreadFunc() {
}
emuThreadState = (int)EmuThreadState::STOPPED;
- NativeShutdownGraphics();
+ NativeShutdownGraphics(graphicsContext);
}
void MainUI::EmuThreadStart() {
diff --git a/Qt/QtMain.h b/Qt/QtMain.h
index f5d89608f8..3f80b5c82d 100644
--- a/Qt/QtMain.h
+++ b/Qt/QtMain.h
@@ -65,6 +65,8 @@ public:
renderManager_ = nullptr;
}
+ bool NeedsRenderThread() const override { return true; }
+
void Shutdown() override {}
void Resize() override {}
diff --git a/SDL/SDLGLGraphicsContext.h b/SDL/SDLGLGraphicsContext.h
index 362aad00eb..b789976c3d 100644
--- a/SDL/SDLGLGraphicsContext.h
+++ b/SDL/SDLGLGraphicsContext.h
@@ -12,6 +12,8 @@ public:
// Returns 0 on success.
int Init(SDL_Window *&window, int x, int y, int w, int h, int mode, std::string *error_message, int force_gl_version);
+ bool NeedsRenderThread() const override { return true; }
+
bool InitFromRenderThread(std::string *errorMessage) override;
void Shutdown() override {}
diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp
index 9883262a6e..6362cca242 100644
--- a/SDL/SDLMain.cpp
+++ b/SDL/SDLMain.cpp
@@ -29,6 +29,7 @@ SDLJoystick *joystick = NULL;
#include "Common/System/NativeApp.h"
#include "Common/Audio/AudioBackend.h"
#include "Core/CmdLine.h"
+#include "Core/EmuThread.h"
#include "ext/glslang/glslang/Public/ShaderLang.h"
#include "Common/Data/Format/PNGLoad.h"
#include "Common/Net/Resolve.h"
@@ -1276,48 +1277,6 @@ void UpdateWindowState(SDL_Window *window) {
g_windowState.update = false;
}
-enum class EmuThreadState {
- DISABLED,
- START_REQUESTED,
- RUNNING,
- QUIT_REQUESTED,
- STOPPED,
-};
-
-static std::thread emuThread;
-static std::atomic emuThreadState((int)EmuThreadState::DISABLED);
-
-static void EmuThreadFunc(GraphicsContext *graphicsContext) {
- SetCurrentThreadName("EmuThread");
-
- // There's no real requirement that NativeInit happen on this thread.
- // We just call the update/render loop here.
- emuThreadState = (int)EmuThreadState::RUNNING;
-
- NativeInitGraphics(graphicsContext);
-
- while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
- NativeFrame(graphicsContext);
- }
- emuThreadState = (int)EmuThreadState::STOPPED;
-
- NativeShutdownGraphics();
-}
-
-static void EmuThreadStart(GraphicsContext *context) {
- emuThreadState = (int)EmuThreadState::START_REQUESTED;
- emuThread = std::thread(&EmuThreadFunc, context);
-}
-
-static void EmuThreadStop(const char *reason) {
- emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
-}
-
-static void EmuThreadJoin() {
- emuThread.join();
- emuThread = std::thread();
-}
-
struct InputStateTracker {
void MouseCaptureControl(SDL_Window *window) {
bool captureMouseCondition = g_Config.bMouseControl && ((GetUIState() == UISTATE_INGAME && g_Config.bMouseConfine) || g_IsMappingMouseInput);
@@ -2080,8 +2039,6 @@ int main(int argc, char *argv[]) {
Native_UpdateScreenScale(w * g_DesktopDPI, h * g_DesktopDPI, UIScaleFactorToMultiplier(g_Config.iUIScaleFactor));
- bool mainThreadIsRender = g_Config.iGPUBackend == (int)GPUBackend::OPENGL;
-
SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str());
char iconPath[PATH_MAX];
@@ -2139,7 +2096,8 @@ int main(int argc, char *argv[]) {
}
EnableFZ();
- EmuThreadStart(graphicsContext);
+ NativeApplication application;
+ std::thread emuThread = EmuThread_Start(graphicsContext, &application, nullptr);
graphicsContext->ThreadStart();
@@ -2150,21 +2108,19 @@ int main(int argc, char *argv[]) {
initializeOSXExtras();
#endif
- bool waitOnExit = g_Config.iGPUBackend == (int)GPUBackend::OPENGL;
-
// Check if the path to a directory containing an unpacked ISO is passed as a command line argument
for (int i = 1; i < argc; i++) {
if (File::IsDirectory(Path(argv[i]))) {
// Display the toast warning
- System_Toast("Warning: Playing unpacked games may cause issues.");
break;
}
}
+ const bool mainThreadIsRender = graphicsContext->NeedsRenderThread();
if (!mainThreadIsRender) {
// Vulkan mode uses this.
// We should only be a message pump. This allows for lower latency
- // input events, and so on.
+ // input events, and so on. The spawned EmuThread runs emulation and rendering.
while (true) {
SDL_Event event;
if (SDL_WaitEventTimeout(&event, 100)) {
@@ -2192,17 +2148,13 @@ int main(int argc, char *argv[]) {
}
}
} else while (true) {
+ // OpenGL mode uses this.
{
SDL_Event event;
while (SDL_PollEvent(&event)) {
ProcessSDLEvent(window, event, &inputTracker);
}
}
- if (g_QuitRequested || g_RestartRequested)
- break;
- if (emuThreadState == (int)EmuThreadState::DISABLED) {
- NativeFrame(graphicsContext);
- }
if (g_QuitRequested || g_RestartRequested)
break;
@@ -2211,8 +2163,8 @@ int main(int argc, char *argv[]) {
inputTracker.MouseCaptureControl(window);
- bool renderThreadPaused = Native_IsWindowHidden() && g_Config.bPauseWhenMinimized && emuThreadState != (int)EmuThreadState::DISABLED;
- if (emuThreadState != (int)EmuThreadState::DISABLED && !renderThreadPaused) {
+ bool renderThreadPaused = Native_IsWindowHidden() && g_Config.bPauseWhenMinimized;
+ if (graphicsContext->NeedsRenderThread() && !renderThreadPaused) {
if (!graphicsContext->ThreadFrame(true))
break;
}
@@ -2227,11 +2179,7 @@ int main(int argc, char *argv[]) {
if (g_rebootEmuThread) {
fprintf(stderr, "rebooting emu thread");
g_rebootEmuThread = false;
- EmuThreadStop("shutdown");
- graphicsContext->ThreadFrameUntilCondition([]() {
- return emuThreadState == (int)EmuThreadState::STOPPED || emuThreadState == (int)EmuThreadState::DISABLED;
- });
- EmuThreadJoin();
+ EmuThread_Join(graphicsContext, emuThread);
graphicsContext->ThreadEnd();
graphicsContext->ShutdownFromRenderThread();
@@ -2249,20 +2197,12 @@ int main(int argc, char *argv[]) {
return 1;
}
- EmuThreadStart(graphicsContext);
+ emuThread = EmuThread_Start(graphicsContext, &application, nullptr);
graphicsContext->ThreadStart();
}
}
- EmuThreadStop("shutdown");
-
- if (waitOnExit) {
- graphicsContext->ThreadFrameUntilCondition([]() {
- return emuThreadState == (int)EmuThreadState::STOPPED || emuThreadState == (int)EmuThreadState::DISABLED;
- });
- }
-
- EmuThreadJoin();
+ EmuThread_Join(graphicsContext, emuThread);
delete joystick;
diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h
index 82bf1d4eb2..68fb0b13eb 100644
--- a/UI/EmuScreen.h
+++ b/UI/EmuScreen.h
@@ -143,14 +143,6 @@ private:
ImCommand imCmd_{}; // needed to buffer commands in case imgui wasn't created yet.
bool imguiInited_ = false;
- // For ImGui modifier tracking. It works a bit weirdly and it's easier to track it here
- // than to use our global modifier tracking that attaches modifiers to each key event.
- bool keyCtrlLeft_ = false;
- bool keyCtrlRight_ = false;
- bool keyShiftLeft_ = false;
- bool keyShiftRight_ = false;
- bool keyAltLeft_ = false;
- bool keyAltRight_ = false;
bool lastImguiEnabled_ = false;
diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp
index 785646dfb3..3cadee899b 100644
--- a/UI/MainScreen.cpp
+++ b/UI/MainScreen.cpp
@@ -320,7 +320,6 @@ void MainScreen::CreateMainButtons(UI::ViewGroup *parent, bool portrait) {
if (!g_Config.Save("MainScreen::OnExit")) {
System_Toast("Failed to save settings!\nCheck permissions, or try to restart the device.");
}
-
UpdateUIState(UISTATE_EXIT);
// Request the framework to exit cleanly.
System_ExitApp();
diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp
index f17d882845..b81442a543 100644
--- a/UI/NativeApp.cpp
+++ b/UI/NativeApp.cpp
@@ -953,7 +953,7 @@ bool CreateGlobalPipelines() {
return true;
}
-void NativeShutdownGraphics() {
+void NativeShutdownGraphics(GraphicsContext *graphicContext) {
INFO_LOG(Log::System, "NativeShutdownGraphics begin");
if (g_screenManager) {
@@ -1018,6 +1018,10 @@ static void SendMouseDeltaAxis();
void NativeFrame(GraphicsContext *graphicsContext) {
PROFILE_END_FRAME();
+ if (!(Core_IsActive() || Core_IsStepping()))
+ UpdateUIState(UISTATE_MENU);
+ Core_StateProcessed();
+
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_DESKTOP) {
if (g_windowHidden && g_Config.bPauseWhenMinimized) {
sleep_ms(16, "window-hidden");
@@ -1028,7 +1032,7 @@ void NativeFrame(GraphicsContext *graphicsContext) {
// This can only be accessed from Windows currently, and causes linking errors with headless etc.
if (g_restartGraphics == 1) {
// Used for debugging only.
- NativeShutdownGraphics();
+ NativeShutdownGraphics(graphicsContext);
g_restartGraphics++;
return;
}
diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp
index 37efeb8c28..9efbcb0080 100644
--- a/UWP/PPSSPP_UWPMain.cpp
+++ b/UWP/PPSSPP_UWPMain.cpp
@@ -101,7 +101,7 @@ PPSSPP_UWPMain::~PPSSPP_UWPMain() {
g_InputManager.Shutdown();
ctx_->GetDrawContext()->HandleEvent(Draw::Event::LOST_BACKBUFFER, 0, 0, nullptr);
- NativeShutdownGraphics();
+ NativeShutdownGraphics(ctx_.get());
NativeShutdown();
g_VFS.Clear();
diff --git a/Windows/GPU/WindowsGLContext.h b/Windows/GPU/WindowsGLContext.h
index 0d5482995f..9c473a74bb 100644
--- a/Windows/GPU/WindowsGLContext.h
+++ b/Windows/GPU/WindowsGLContext.h
@@ -13,6 +13,8 @@ class WindowsGLContext : public WindowsGraphicsContext {
public:
bool Init(HINSTANCE hInst, HWND window, std::string *error_message) override;
+ bool NeedsRenderThread() const override { return true; }
+
bool InitFromRenderThread(std::string *errorMessage) override;
void ShutdownFromRenderThread() override;
diff --git a/Windows/main.cpp b/Windows/main.cpp
index 4eb5ba79cc..21994b52a8 100644
--- a/Windows/main.cpp
+++ b/Windows/main.cpp
@@ -1261,13 +1261,14 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
System_SetWindowTitle("");
std::thread mainThread = std::thread([]() {
+ NativeApplication application;
std::unique_ptr graphicsContext(CreateGraphicsContextOrExit());
if (!graphicsContext) {
// We're screwed. Should never get here.
_dbg_assert_(false);
return;
}
- MainThreadFunc(graphicsContext.get());
+ MainThreadFunc(graphicsContext.get(), &application, []() {});
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_UPDATE_UI, 0, 0);
});
diff --git a/android/jni/AndroidGraphicsContext.h b/android/jni/AndroidGraphicsContext.h
index f51b7e96c7..f9191aadbd 100644
--- a/android/jni/AndroidGraphicsContext.h
+++ b/android/jni/AndroidGraphicsContext.h
@@ -16,25 +16,10 @@ enum {
ANDROID_VERSION_NOUGAT_1 = 25,
};
-enum class GraphicsContextState {
- PENDING,
- INITIALIZED,
- FAILED_INIT,
- SHUTDOWN,
-};
-
class AndroidGraphicsContext : public GraphicsContext {
public:
// This is different than the base class function since on
// Android (EGL, Vulkan) we do have all this info on the render thread.
- virtual bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) = 0;
+ virtual bool Init(ANativeWindow *wnd) { return true; }
virtual void BeginAndroidShutdown() {}
-
- virtual GraphicsContextState GetState() const { return state_; }
-
-protected:
- GraphicsContextState state_ = GraphicsContextState::PENDING;
-
-private:
- using GraphicsContext::InitFromRenderThread;
};
diff --git a/android/jni/AndroidJavaGLContext.cpp b/android/jni/AndroidJavaGLContext.cpp
index 12e4862592..a23ed5c666 100644
--- a/android/jni/AndroidJavaGLContext.cpp
+++ b/android/jni/AndroidJavaGLContext.cpp
@@ -10,11 +10,11 @@ AndroidJavaEGLGraphicsContext::AndroidJavaEGLGraphicsContext() {
SetGPUBackend(GPUBackend::OPENGL);
}
-bool AndroidJavaEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) {
+bool AndroidJavaEGLGraphicsContext::InitFromRenderThread(std::string *errorMessage) {
INFO_LOG(Log::G3D, "AndroidJavaEGLGraphicsContext::InitFromRenderThread");
if (!CheckGLExtensions()) {
+ *errorMessage = "CheckExtensions failed";
ERROR_LOG(Log::G3D, "CheckGLExtensions failed - not gonna attempt starting up.");
- state_ = GraphicsContextState::FAILED_INIT;
return false;
}
@@ -29,10 +29,8 @@ bool AndroidJavaEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int
if (!draw_->CreatePresets()) {
// This can't really happen now that compilation is async - they're only really queued for compile here.
_assert_msg_(false, "Failed to compile preset shaders");
- state_ = GraphicsContextState::FAILED_INIT;
return false;
}
- state_ = GraphicsContextState::INITIALIZED;
return true;
}
@@ -41,5 +39,4 @@ void AndroidJavaEGLGraphicsContext::ShutdownFromRenderThread() {
renderManager_ = nullptr; // owned by draw_.
delete draw_;
draw_ = nullptr;
- state_ = GraphicsContextState::SHUTDOWN;
}
diff --git a/android/jni/AndroidJavaGLContext.h b/android/jni/AndroidJavaGLContext.h
index 611df72dba..b24ed480e0 100644
--- a/android/jni/AndroidJavaGLContext.h
+++ b/android/jni/AndroidJavaGLContext.h
@@ -9,9 +9,10 @@ public:
AndroidJavaEGLGraphicsContext();
~AndroidJavaEGLGraphicsContext() override { delete draw_; }
- // This performs the actual initialization,
- bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override;
+ bool NeedsRenderThread() const override { return true; }
+ // This performs the actual initialization,
+ bool InitFromRenderThread(std::string *errorMessage) override;
void ShutdownFromRenderThread() override;
void Shutdown() override {}
diff --git a/android/jni/AndroidVulkanContext.cpp b/android/jni/AndroidVulkanContext.cpp
index 65773bb08a..2b4763db5a 100644
--- a/android/jni/AndroidVulkanContext.cpp
+++ b/android/jni/AndroidVulkanContext.cpp
@@ -22,6 +22,7 @@ AndroidVulkanContext::~AndroidVulkanContext() {
}
bool AndroidVulkanContext::InitAPI() {
+ _dbg_assert_(!g_Vulkan);
INFO_LOG(Log::G3D, "AndroidVulkanContext::Init");
init_glslang();
@@ -32,7 +33,6 @@ bool AndroidVulkanContext::InitAPI() {
std::string errorStr;
if (!VulkanLoad(&errorStr)) {
ERROR_LOG(Log::G3D, "Failed to load Vulkan driver library: %s", errorStr.c_str());
- state_ = GraphicsContextState::FAILED_INIT;
return false;
}
@@ -49,17 +49,15 @@ bool AndroidVulkanContext::InitAPI() {
if (!g_Vulkan->CreateInstanceAndDevice(info)) {
delete g_Vulkan;
g_Vulkan = nullptr;
- state_ = GraphicsContextState::FAILED_INIT;
return false;
}
INFO_LOG(Log::G3D, "Vulkan device created!");
- state_ = GraphicsContextState::INITIALIZED;
return true;
}
-bool AndroidVulkanContext::InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) {
- INFO_LOG(Log::G3D, "AndroidVulkanContext::InitFromRenderThread: desiredwidth=%d desiredheight=%d", desiredBackbufferSizeX, desiredBackbufferSizeY);
+bool AndroidVulkanContext::Init(ANativeWindow *wnd) {
+ INFO_LOG(Log::G3D, "AndroidVulkanContext::InitFromRenderThread");
if (!g_Vulkan) {
ERROR_LOG(Log::G3D, "AndroidVulkanContext::InitFromRenderThread: No Vulkan context");
return false;
@@ -104,7 +102,7 @@ bool AndroidVulkanContext::InitFromRenderThread(ANativeWindow *wnd, int desiredB
}
void AndroidVulkanContext::ShutdownFromRenderThread() {
- INFO_LOG(Log::G3D, "AndroidVulkanContext::Shutdown");
+ INFO_LOG(Log::G3D, "AndroidVulkanContext::ShutdownFromRenderThread");
draw_->HandleEvent(Draw::Event::LOST_BACKBUFFER, g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight());
delete draw_;
draw_ = nullptr;
@@ -116,10 +114,11 @@ void AndroidVulkanContext::ShutdownFromRenderThread() {
}
void AndroidVulkanContext::Shutdown() {
- INFO_LOG(Log::G3D, "Calling NativeShutdownGraphics");
+ INFO_LOG(Log::G3D, "AndroidVulkanContext::Shutdown");
g_Vulkan->DestroyDevice();
g_Vulkan->DestroyInstance();
// We keep the g_Vulkan context around to avoid invalidating a ton of pointers around the app.
+ // TODO: Actually the above is inaccurate..
finalize_glslang();
INFO_LOG(Log::G3D, "AndroidVulkanContext::Shutdown completed");
}
@@ -127,14 +126,22 @@ void AndroidVulkanContext::Shutdown() {
void AndroidVulkanContext::Resize() {
INFO_LOG(Log::G3D, "AndroidVulkanContext::Resize begin (oldsize: %dx%d)", g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight());
draw_->HandleEvent(Draw::Event::LOST_BACKBUFFER, g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight());
+
+ // This stuff does not seem to be needed, as the window doesn't change here.
+#if 0
g_Vulkan->DestroySwapchain();
// TODO: We should only destroy the surface here if the window changed. We can track this inside g_Vulkan.
+ // Actually, the window can't really change, can it?
g_Vulkan->DestroySurface();
+
+ // ==========================================
+
g_Vulkan->ReinitSurface();
+#endif
VkPresentModeKHR presentMode = ConfigPresentModeToVulkan(draw_);
- g_Vulkan->InitSwapchain(presentMode);
+ g_Vulkan->InitSwapchain(presentMode); // This also destroys the old swapchain and makes a nice transition.
draw_->HandleEvent(Draw::Event::GOT_BACKBUFFER, g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight());
INFO_LOG(Log::G3D, "AndroidVulkanContext::Resize end (final size: %dx%d)", g_Vulkan->GetBackbufferWidth(), g_Vulkan->GetBackbufferHeight());
}
diff --git a/android/jni/AndroidVulkanContext.h b/android/jni/AndroidVulkanContext.h
index 7da96fbd92..f83daf9fa7 100644
--- a/android/jni/AndroidVulkanContext.h
+++ b/android/jni/AndroidVulkanContext.h
@@ -11,7 +11,7 @@ public:
bool InitAPI();
- bool InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) override;
+ bool Init(ANativeWindow *wnd) override;
void ShutdownFromRenderThread() override; // Inverses InitFromRenderThread.
void Shutdown() override;
diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp
index cea0d300bb..512ff66b0b 100644
--- a/android/jni/app-android.cpp
+++ b/android/jni/app-android.cpp
@@ -108,8 +108,6 @@ struct JNIEnv {};
#include "app-android.h"
-bool useCPUThread = true;
-
enum class EmuThreadState {
DISABLED,
START_REQUESTED,
@@ -132,7 +130,7 @@ struct FrameCommand {
};
static std::mutex frameCommandLock;
-static std::queue frameCommands;
+static std::vector g_frameCommands;
static std::string systemName;
static std::string langRegion;
@@ -205,7 +203,7 @@ int utimensat(int fd, const char *path, const struct timespec times[2]) {
}
#endif
-static void ProcessFrameCommands(JNIEnv *env);
+static void ProcessFrameCommands();
JNIEnv* getEnv() {
JNIEnv *env;
@@ -269,34 +267,14 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
static void EmuThreadFunc() {
SetCurrentThreadName("Entering EmuThread");
- // Name the thread in the JVM, because why not (might result in better debug output in Play Console).
- // TODO: Do something clever with getEnv() and stored names from SetCurrentThreadName?
- JNIEnv *env;
- JavaVMAttachArgs args{};
- args.version = JNI_VERSION_1_6;
- args.name = "EmuThread";
- gJvm->AttachCurrentThread(&env, &args);
+ AndroidJNIThreadContext jniContext;
INFO_LOG(Log::System, "Entering emu thread");
- // Wait for render loop to get started.
- INFO_LOG(Log::System, "Runloop: Waiting for displayInit...");
- while (!graphicsContext || graphicsContext->GetState() == GraphicsContextState::PENDING) {
- sleep_ms(5, "graphics-poll");
- }
-
- // Check the state of the graphics context before we try to feed it into NativeInitGraphics.
- if (graphicsContext->GetState() != GraphicsContextState::INITIALIZED) {
- ERROR_LOG(Log::G3D, "Failed to initialize the graphics context! %d", (int)graphicsContext->GetState());
- emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
- gJvm->DetachCurrentThread();
- return;
- }
-
+ _assert_(graphicsContext);
if (!NativeInitGraphics(graphicsContext)) {
_assert_msg_(false, "NativeInitGraphics failed, might as well bail");
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
- gJvm->DetachCurrentThread();
return;
}
@@ -307,24 +285,14 @@ static void EmuThreadFunc() {
emuThreadState = (int)EmuThreadState::RUNNING;
while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
NativeFrame(graphicsContext);
-
- std::lock_guard guard(frameCommandLock);
- if (!ppssppActivity) {
- ERROR_LOG(Log::System, "No activity, clearing commands");
- while (!frameCommands.empty())
- frameCommands.pop();
- break;
- }
- // Still under lock here.
- ProcessFrameCommands(env);
+ ProcessFrameCommands();
}
INFO_LOG(Log::System, "emuThreadState was set to QUIT_REQUESTED, left EmuThreadFunc loop. Setting state to STOPPED.");
emuThreadState = (int)EmuThreadState::STOPPED;
- NativeShutdownGraphics();
+ NativeShutdownGraphics(graphicsContext);
- gJvm->DetachCurrentThread();
INFO_LOG(Log::System, "Leaving EmuThread");
}
@@ -350,7 +318,7 @@ static void EmuThreadJoin() {
static void PushCommand(std::string_view cmd, std::string_view param) {
std::lock_guard guard(frameCommandLock);
- frameCommands.emplace(std::string(cmd), std::string(param));
+ g_frameCommands.emplace_back(std::string(cmd), std::string(param));
}
// Android implementation of callbacks to the Java part of the app
@@ -754,7 +722,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init
SetCurrentThreadName("androidInit");
// Makes sure we get early permission grants.
- ProcessFrameCommands(env);
+ ProcessFrameCommands();
EARLY_LOG("NativeApp.init() -- begin");
PROFILE_INIT();
@@ -864,16 +832,13 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init
retry:
switch (g_Config.iGPUBackend) {
case (int)GPUBackend::OPENGL:
- useCPUThread = true;
INFO_LOG(Log::System, "NativeApp.init() -- creating OpenGL context (JavaGL)");
graphicsContext = new AndroidJavaEGLGraphicsContext();
- INFO_LOG(Log::System, "NativeApp.init() - launching emu thread");
- EmuThreadStart();
+ INFO_LOG(Log::System, "NativeApp.init() - not yet launching emuthread, waiting to displayInit");
break;
case (int)GPUBackend::VULKAN:
{
INFO_LOG(Log::System, "NativeApp.init() -- creating Vulkan context");
- useCPUThread = false;
// The Vulkan render manager manages its own thread.
// We create and destroy the Vulkan graphics context in the app main thread though.
AndroidVulkanContext *ctx = new AndroidVulkanContext();
@@ -989,9 +954,8 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_pause(JNIEnv *, jclass) {
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) {
INFO_LOG(Log::System, "NativeApp.shutdown() -- begin");
- if (renderer_inited && useCPUThread && graphicsContext) {
+ if (renderer_inited && graphicsContext && graphicsContext->NeedsRenderThread()) {
// Only used in Java EGL path.
-
EmuThreadStop("shutdown");
// NOTE: We know that the GLSurfaceView render thread is stopped here, since we now
// correctly call GLSurfaceView.onPause/onResume. However, there may still be queued frames.
@@ -1033,8 +997,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) {
{
std::lock_guard guard(frameCommandLock);
- while (!frameCommands.empty())
- frameCommands.pop();
+ g_frameCommands.clear();
}
INFO_LOG(Log::System, "NativeApp.shutdown() -- end");
}
@@ -1042,14 +1005,34 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) {
// JavaEGL. This doesn't get called on the Vulkan path.
// This gets called from onSurfaceCreated.
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, jobject obj) {
- _assert_(useCPUThread);
+ if (!graphicsContext) {
+ ERROR_LOG(Log::G3D, "NativeApp.displayInit() - graphicsContext is null!");
+ return false;
+ }
+
+ _assert_(graphicsContext->NeedsRenderThread());
INFO_LOG(Log::G3D, "NativeApp.displayInit()");
bool firstStart = !renderer_inited;
// We should be running on the render thread here.
std::string errorMessage;
- if (renderer_inited) {
+ if (!renderer_inited) {
+ INFO_LOG(Log::G3D, "NativeApp.displayInit() first time");
+ if (!graphicsContext->InitFromRenderThread(&errorMessage)) {
+ System_Toast("Graphics initialization failed. Quitting.");
+ return false;
+ }
+ // This is where we start the emuthread now - after InitFromRenderThread. This eliminates a race condition.
+ EmuThreadStart();
+
+ graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) {
+ g_OSD.Show(OSDType::MESSAGE_ERROR, details, 5.0);
+ }, nullptr);
+
+ graphicsContext->ThreadStart();
+ renderer_inited = true;
+ } else {
// Would be really nice if we could get something on the GL thread immediately when shutting down,
// but the only mechanism for handling lost devices seems to be that onSurfaceCreated is called again,
// which ends up calling displayInit.
@@ -1070,8 +1053,9 @@ extern "C" jboolean Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * e
INFO_LOG(Log::G3D, "Shut down both threads. Now let's bring it up again!");
- if (!graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0)) {
- System_Toast("Graphics initialization failed. Quitting.");
+ std::string errorMessage;
+ if (!graphicsContext->InitFromRenderThread(&errorMessage)) {
+ System_Toast(("Graphics initialization failed: Quitting: " + errorMessage).c_str());
return false;
}
@@ -1084,19 +1068,6 @@ extern "C" jboolean Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * e
graphicsContext->ThreadStart();
INFO_LOG(Log::G3D, "Restored.");
- } else {
- INFO_LOG(Log::G3D, "NativeApp.displayInit() first time");
- if (!graphicsContext || !graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0)) {
- System_Toast("Graphics initialization failed. Quitting.");
- return false;
- }
-
- graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) {
- g_OSD.Show(OSDType::MESSAGE_ERROR, details, 5.0);
- }, nullptr);
-
- graphicsContext->ThreadStart();
- renderer_inited = true;
}
System_PostUIMessage(UIMessage::RECREATE_VIEWS);
@@ -1246,21 +1217,28 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendRequestResult(JNIEn
}
}
+// This doesn't get called on the Vulkan path.
+// We don't need a render thread "loop" as this gets called repeatedly by the system, by a system
+// render thread.
extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, jobject obj) {
- // This doesn't get called on the Vulkan path.
- _assert_(useCPUThread);
-
static bool hasSetThreadName = false;
if (!hasSetThreadName) {
hasSetThreadName = true;
SetCurrentThreadName("AndroidRender");
}
- if (IsVREnabled() && !StartVRRender())
+ if (IsVREnabled() && !StartVRRender()) {
return;
+ }
// This is the "GPU thread". Call ThreadFrame.
- if (!graphicsContext || !graphicsContext->ThreadFrame(true)) {
+ if (!graphicsContext) {
+ return;
+ }
+ _assert_(graphicsContext->NeedsRenderThread());
+
+ if (!graphicsContext->ThreadFrame(true)) {
+ INFO_LOG(Log::G3D, "ThreadFrame returned false");
return;
}
@@ -1695,10 +1673,20 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_pushCameraImageAndroid(
}
// Call this under frameCommandLock.
-static void ProcessFrameCommands(JNIEnv *env) {
- while (!frameCommands.empty()) {
- const FrameCommand &frameCmd = frameCommands.front();
-
+static void ProcessFrameCommands() {
+ JNIEnv *env = getEnv();
+ std::vector frameCommands;
+ {
+ std::lock_guard guard(frameCommandLock);
+ if (!ppssppActivity) {
+ ERROR_LOG(Log::System, "No activity, clearing commands");
+ } else {
+ frameCommands = std::move(g_frameCommands);
+ INFO_LOG(Log::System, "Processing %zu frame commands", g_frameCommands.size());
+ }
+ g_frameCommands.clear();
+ }
+ for (const FrameCommand &frameCmd : frameCommands) {
DEBUG_LOG(Log::System, "frameCommand '%s' '%s'", frameCmd.command.c_str(), frameCmd.params.c_str());
jstring cmd = env->NewStringUTF(frameCmd.command.c_str());
@@ -1706,8 +1694,6 @@ static void ProcessFrameCommands(JNIEnv *env) {
env->CallVoidMethod(ppssppActivity, postCommand, cmd, param);
env->DeleteLocalRef(cmd);
env->DeleteLocalRef(param);
-
- frameCommands.pop();
}
}
@@ -1718,18 +1704,20 @@ static void VulkanEmuThread(ANativeWindow *wnd);
// This runs in Vulkan mode only.
// This handles the entire lifecycle of the Vulkan context, init and exit.
extern "C" jboolean JNICALL Java_org_ppsspp_ppsspp_PpssppActivity_runVulkanRenderLoop(JNIEnv * env, jobject obj, jobject _surf) {
- _assert_(!useCPUThread);
-
if (!graphicsContext) {
ERROR_LOG(Log::G3D, "runVulkanRenderLoop: Tried to enter without a created graphics context.");
return false;
}
+ _assert_(!graphicsContext->NeedsRenderThread());
+
if (g_renderLoopThread.joinable()) {
ERROR_LOG(Log::G3D, "runVulkanRenderLoop: Already running");
return false;
}
+ _assert_(!exitRenderLoop);
+
ANativeWindow *wnd = _surf ? ANativeWindow_fromSurface(env, _surf) : nullptr;
if (!wnd) {
@@ -1761,14 +1749,7 @@ static void VulkanEmuThread(ANativeWindow *wnd) {
AndroidJNIThreadContext ctx;
JNIEnv *env = getEnv();
-
- if (!graphicsContext) {
- ERROR_LOG(Log::G3D, "runVulkanRenderLoop: Tried to enter without a created graphics context.");
- renderLoopRunning = false;
- exitRenderLoop = false;
- ANativeWindow_release(wnd);
- return;
- }
+ _assert_(graphicsContext);
if (exitRenderLoop) {
WARN_LOG(Log::G3D, "runVulkanRenderLoop: ExitRenderLoop requested at start, skipping the whole thing.");
@@ -1784,7 +1765,7 @@ static void VulkanEmuThread(ANativeWindow *wnd) {
WARN_LOG(Log::G3D, "runVulkanRenderLoop. display_xres=%d display_yres=%d desiredBackbufferSizeX=%d desiredBackbufferSizeY=%d",
display_xres, display_yres, desiredBackbufferSizeX, desiredBackbufferSizeY);
- if (!graphicsContext->InitFromRenderThread(wnd, desiredBackbufferSizeX, desiredBackbufferSizeY, backbuffer_format, androidVersion)) {
+ if (!graphicsContext->Init(wnd)) {
// On Android, if we get here, really no point in continuing.
// The UI is supposed to render on any device both on OpenGL and Vulkan. If either of those don't work
// on a device, we blacklist it. Hopefully we should have already failed in InitAPI anyway and reverted to GL back then.
@@ -1807,20 +1788,15 @@ static void VulkanEmuThread(ANativeWindow *wnd) {
renderer_inited = true;
while (!exitRenderLoop) {
- {
- NativeFrame(graphicsContext);
- }
- {
- std::lock_guard guard(frameCommandLock);
- ProcessFrameCommands(env);
- }
+ NativeFrame(graphicsContext);
+ ProcessFrameCommands();
}
INFO_LOG(Log::G3D, "Leaving Vulkan main loop.");
} else {
INFO_LOG(Log::G3D, "Not entering main loop.");
}
- NativeShutdownGraphics();
+ NativeShutdownGraphics(graphicsContext);
renderer_inited = false;
graphicsContext->ThreadEnd();
diff --git a/android/src/org/ppsspp/ppsspp/NativeRenderer.java b/android/src/org/ppsspp/ppsspp/NativeRenderer.java
index 853514787d..c7837d807e 100644
--- a/android/src/org/ppsspp/ppsspp/NativeRenderer.java
+++ b/android/src/org/ppsspp/ppsspp/NativeRenderer.java
@@ -16,7 +16,6 @@ public class NativeRenderer implements GLSurfaceView.Renderer {
public void onDrawFrame(GL10 unused /*use GLES20*/) {
displayRender();
}
-
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.i(TAG, "NativeRenderer (OpenGL): onSurfaceCreated");
diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini
index cc20ffebf5..b0a2f513ef 100644
--- a/assets/lang/en_US.ini
+++ b/assets/lang/en_US.ini
@@ -1149,8 +1149,8 @@ tools = Free tools used:
translators1 = MrYadro, mgaver, VIRGINKLM, raven02
translators2 = sum2012, vnctdj, unknownbrackets, ufdada
translators3 = The Dax, wuspring, hrydgard, adrian17
-translators4 = papel
-translators5 =
+translators4 = papel, Richard Ali, Zie Nozie, Eve Ellora
+translators5 = Eli Ezekiel
translators6 =
website = Check out the website:
written = Written in C++ for speed and portability
diff --git a/ios/ViewController.mm b/ios/ViewController.mm
index ec3b1698cc..09bde9e981 100644
--- a/ios/ViewController.mm
+++ b/ios/ViewController.mm
@@ -55,6 +55,7 @@ public:
~IOSGLESContext() {
delete draw_;
}
+ bool NeedsRenderThread() const override { return true; }
Draw::DrawContext *GetDrawContext() override {
return draw_;
}
@@ -133,7 +134,7 @@ void GLRenderLoop(IOSGLESContext *graphicsContext) {
}
INFO_LOG(Log::System, "Emulation thread shutting down\n");
- NativeShutdownGraphics();
+ NativeShutdownGraphics(graphicsContext);
// Also ask the main thread to stop, so it doesn't hang waiting for a new frame.
INFO_LOG(Log::System, "Emulation thread stopping\n");
diff --git a/ios/ViewControllerMetal.mm b/ios/ViewControllerMetal.mm
index 70382a2a62..dfccad3aa2 100644
--- a/ios/ViewControllerMetal.mm
+++ b/ios/ViewControllerMetal.mm
@@ -117,7 +117,7 @@ void IOSVulkanContext::ShutdownFromRenderThread() {
}
void IOSVulkanContext::Shutdown() {
- INFO_LOG(Log::G3D, "Calling NativeShutdownGraphics");
+ INFO_LOG(Log::G3D, "IOSVulkanContext::Shutdown");
g_Vulkan->DestroyDevice();
g_Vulkan->DestroyInstance();
// We keep the g_Vulkan context around to avoid invalidating a ton of pointers around the app.
@@ -243,7 +243,7 @@ void VulkanRenderLoop(IOSVulkanContext *graphicsContext, CAMetalLayer *metalLaye
INFO_LOG(Log::G3D, "Not entering main loop.");
}
- NativeShutdownGraphics();
+ NativeShutdownGraphics(graphicsContext);
graphicsContext->ThreadEnd();
diff --git a/libretro/LibretroGLContext.h b/libretro/LibretroGLContext.h
index f02d847928..6ca8691a28 100644
--- a/libretro/LibretroGLContext.h
+++ b/libretro/LibretroGLContext.h
@@ -16,6 +16,8 @@ public:
hw_render_.bottom_left_origin = true;
}
+ bool NeedsRenderThread() const override { return true; }
+
bool Init() override;
void CreateDrawContext() override;
void DestroyDrawContext() override;
diff --git a/libretro/LibretroGLCoreContext.h b/libretro/LibretroGLCoreContext.h
index d9ee56820f..c84e741aa9 100644
--- a/libretro/LibretroGLCoreContext.h
+++ b/libretro/LibretroGLCoreContext.h
@@ -7,10 +7,10 @@
class LibretroGLCoreContext : public LibretroHWRenderContext {
public:
LibretroGLCoreContext()
- : LibretroHWRenderContext(RETRO_HW_CONTEXT_OPENGL_CORE, 3, 1)
- {
+ : LibretroHWRenderContext(RETRO_HW_CONTEXT_OPENGL_CORE, 3, 1) {
hw_render_.bottom_left_origin = true;
}
+ bool NeedsRenderThread() const override { return true; }
bool Init() override;
void CreateDrawContext() override;