mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-20 01:39:07 +02:00
Merge pull request #21960 from hrydgard/more-emuthread-work
More EmuThread/cleanup work
This commit is contained in:
32 files changed
+242
-256
No files matched your search
@@ -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 ..
|
||||
```
|
||||
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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<bool()> conditionStopped) {
|
||||
_dbg_assert_(NeedsRenderThread());
|
||||
|
||||
bool exitOnEmpty = false;
|
||||
|
||||
INFO_LOG(Log::System, "Executing graphicsContext->ThreadFrame to clear buffers");
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
+56
-34
@@ -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,38 +40,70 @@ bool MainThread_Ready() {
|
||||
return g_inLoop;
|
||||
}
|
||||
|
||||
static void EmuThreadFunc(GraphicsContext *graphicsContext) {
|
||||
static void EmuThreadFunc(GraphicsContext *graphicsContext, Application *application, std::function<void()> 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<void()> 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<void()> 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();
|
||||
|
||||
+9
-1
@@ -16,6 +16,9 @@
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#pragma once
|
||||
#include <functional>
|
||||
|
||||
#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<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, Application *application, std::function<void()> postFrame);
|
||||
void EmuThread_Join(GraphicsContext *graphicsContext, std::thread &emuThread);
|
||||
+1
-1
@@ -676,7 +676,7 @@ void MainUI::EmuThreadFunc() {
|
||||
}
|
||||
emuThreadState = (int)EmuThreadState::STOPPED;
|
||||
|
||||
NativeShutdownGraphics();
|
||||
NativeShutdownGraphics(graphicsContext);
|
||||
}
|
||||
|
||||
void MainUI::EmuThreadStart() {
|
||||
|
||||
@@ -65,6 +65,8 @@ public:
|
||||
renderManager_ = nullptr;
|
||||
}
|
||||
|
||||
bool NeedsRenderThread() const override { return true; }
|
||||
|
||||
void Shutdown() override {}
|
||||
void Resize() override {}
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
+11
-71
@@ -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<int> 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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
+6
-2
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
+2
-1
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
+71
-95
@@ -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<FrameCommand> frameCommands;
|
||||
static std::vector<FrameCommand> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<FrameCommand> frameCommands;
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user