Break the circular dependency caused by putting EmuThread in core using an interface

This commit is contained in:
Henrik Rydgård committed 2026-07-24 15:31:42 +02:00
1 parent cb1b4067f7
commit 85f8715660
15 files changed
+70 -35

No files matched your search

+1
View File
@@ -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
+1
View File
@@ -536,6 +536,7 @@
<ClInclude Include="StringUtils.h" />
<ClInclude Include="Swap.h" />
<ClInclude Include="SysError.h" />
<ClInclude Include="System\Application.h" />
<ClInclude Include="System\Display.h" />
<ClInclude Include="System\OSD.h" />
<ClInclude Include="System\Request.h" />
+3
View File
@@ -784,6 +784,9 @@
<ClInclude Include="Data\Text\StringWriter.h">
<Filter>Data\Text</Filter>
</ClInclude>
<ClInclude Include="System\Application.h">
<Filter>System</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ABI.cpp" />
+11
View File
@@ -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;
};
+16 -1
View File
@@ -4,6 +4,7 @@
#include <string>
#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);
}
};
+14 -14
View File
@@ -4,9 +4,9 @@
#include <atomic>
#include <thread>
#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,7 +40,7 @@ bool MainThread_Ready() {
return g_inLoop;
}
static void EmuThreadFunc(GraphicsContext *graphicsContext, std::function<void()> postFrame) {
static void EmuThreadFunc(GraphicsContext *graphicsContext, Application *application, std::function<void()> postFrame) {
INFO_LOG(Log::G3D, "Entering emu thread");
SetCurrentThreadName("EmuThread");
@@ -50,7 +50,7 @@ static void EmuThreadFunc(GraphicsContext *graphicsContext, std::function<void()
// We just call the update/render loop here.
g_emuThreadState = EmuThreadState::RUNNING;
if (!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;
@@ -61,7 +61,7 @@ static void EmuThreadFunc(GraphicsContext *graphicsContext, std::function<void()
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.
NativeFrame(graphicsContext);
application->Frame(graphicsContext);
if (postFrame) {
postFrame();
}
@@ -75,13 +75,13 @@ static void EmuThreadFunc(GraphicsContext *graphicsContext, std::function<void()
g_emuThreadState = EmuThreadState::STOPPED;
NativeShutdownGraphics();
application->ShutdownGraphics(graphicsContext);
INFO_LOG(Log::System, "Leaving emu thread");
}
std::thread EmuThread_Start(GraphicsContext *graphicsContext, std::function<void()> postFrame) {
std::thread EmuThread_Start(GraphicsContext *graphicsContext, Application *application, std::function<void()> postFrame) {
g_emuThreadState = EmuThreadState::START_REQUESTED;
std::thread emuThread = std::thread(&EmuThreadFunc, graphicsContext, postFrame);
std::thread emuThread = std::thread(&EmuThreadFunc, graphicsContext, application, postFrame);
return emuThread;
}
@@ -102,7 +102,7 @@ void EmuThread_Join(GraphicsContext *graphicsContext, std::thread &emuThread) {
emuThread = std::thread();
}
void MainThreadFunc(GraphicsContext *graphicsContext, std::function<void()> postFrame) {
void MainThreadFunc(GraphicsContext *graphicsContext, Application *application, std::function<void()> postFrame) {
if (graphicsContext->NeedsRenderThread()) {
SetCurrentThreadName("RenderThread");
// This is now the render thread, and will spawn the emu thread below.
@@ -114,7 +114,7 @@ void MainThreadFunc(GraphicsContext *graphicsContext, std::function<void()> post
g_inLoop = true;
std::thread emuThread = EmuThread_Start(graphicsContext, postFrame);
std::thread emuThread = EmuThread_Start(graphicsContext, application, postFrame);
graphicsContext->ThreadStart();
// This thread becomes the render thread.
@@ -144,8 +144,8 @@ void MainThreadFunc(GraphicsContext *graphicsContext, std::function<void()> post
std::string error_string;
bool success = graphicsContext->InitFromRenderThread(&error_string);
NativeInitGraphics(graphicsContext);
NativeResized();
application->InitGraphics(graphicsContext);
// NativeResized();
DEBUG_LOG(Log::Boot, "Done.");
@@ -155,18 +155,18 @@ void MainThreadFunc(GraphicsContext *graphicsContext, std::function<void()> post
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.
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();
+4 -2
View File
@@ -18,6 +18,8 @@
#pragma once
#include <functional>
#include "Common/System/Application.h"
// Utilities to manage Emu and Render threads.
// TODO: Use across platforms, currently Windows-only.
@@ -28,9 +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, std::function<void()> postFrame);
void MainThreadFunc(GraphicsContext * graphicsContext, Application *application, std::function<void()> 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, std::function<void()> postFrame);
std::thread EmuThread_Start(GraphicsContext *graphicsContext, Application *application, std::function<void()> postFrame);
void EmuThread_Join(GraphicsContext *graphicsContext, std::thread &emuThread);
+1 -1
View File
@@ -676,7 +676,7 @@ void MainUI::EmuThreadFunc() {
}
emuThreadState = (int)EmuThreadState::STOPPED;
NativeShutdownGraphics();
NativeShutdownGraphics(graphicsContext);
}
void MainUI::EmuThreadStart() {
+3 -2
View File
@@ -2096,7 +2096,8 @@ int main(int argc, char *argv[]) {
}
EnableFZ();
std::thread emuThread = EmuThread_Start(graphicsContext, nullptr);
NativeApplication application;
std::thread emuThread = EmuThread_Start(graphicsContext, &application, nullptr);
graphicsContext->ThreadStart();
@@ -2196,7 +2197,7 @@ int main(int argc, char *argv[]) {
return 1;
}
emuThread = EmuThread_Start(graphicsContext, nullptr);
emuThread = EmuThread_Start(graphicsContext, &application, nullptr);
graphicsContext->ThreadStart();
}
}
+2 -2
View File
@@ -953,7 +953,7 @@ bool CreateGlobalPipelines() {
return true;
}
void NativeShutdownGraphics() {
void NativeShutdownGraphics(GraphicsContext *graphicContext) {
INFO_LOG(Log::System, "NativeShutdownGraphics begin");
if (g_screenManager) {
@@ -1032,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;
}
+1 -1
View File
@@ -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();
+2 -1
View File
@@ -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> 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);
});
+8 -8
View File
@@ -203,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;
@@ -268,7 +268,6 @@ static void EmuThreadFunc() {
SetCurrentThreadName("Entering EmuThread");
AndroidJNIThreadContext jniContext;
JNIEnv *env = getEnv(); // TODO: Make it gettable from AndroidJNIThreadContext
INFO_LOG(Log::System, "Entering emu thread");
@@ -286,13 +285,13 @@ static void EmuThreadFunc() {
emuThreadState = (int)EmuThreadState::RUNNING;
while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
NativeFrame(graphicsContext);
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);
INFO_LOG(Log::System, "Leaving EmuThread");
}
@@ -723,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();
@@ -1674,7 +1673,8 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_pushCameraImageAndroid(
}
// Call this under frameCommandLock.
static void ProcessFrameCommands(JNIEnv *env) {
static void ProcessFrameCommands() {
JNIEnv *env = getEnv();
std::vector<FrameCommand> frameCommands;
{
std::lock_guard<std::mutex> guard(frameCommandLock);
@@ -1789,14 +1789,14 @@ static void VulkanEmuThread(ANativeWindow *wnd) {
while (!exitRenderLoop) {
NativeFrame(graphicsContext);
ProcessFrameCommands(env);
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();
+1 -1
View File
@@ -134,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");
+2 -2
View File
@@ -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();