diff --git a/Common/GPU/OpenGL/GLQueueRunner.h b/Common/GPU/OpenGL/GLQueueRunner.h index 593e00f97b..b91648a1fa 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.h +++ b/Common/GPU/OpenGL/GLQueueRunner.h @@ -2,6 +2,7 @@ #include #include +#include #include #include "Common/GPU/OpenGL/GLCommon.h" diff --git a/Common/GPU/OpenGL/GLRenderManager.cpp b/Common/GPU/OpenGL/GLRenderManager.cpp index 74a306fc01..f0a1cce354 100644 --- a/Common/GPU/OpenGL/GLRenderManager.cpp +++ b/Common/GPU/OpenGL/GLRenderManager.cpp @@ -6,6 +6,7 @@ #include "Common/VR/PPSSPPVR.h" #include "Common/Log.h" +#include "Common/TimeUtil.h" #include "Common/MemoryUtil.h" #include "Common/Math/math_util.h" @@ -112,6 +113,8 @@ void GLDeleter::Perform(GLRenderManager *renderManager, bool skipGLCalls) { } GLRenderManager::~GLRenderManager() { + _dbg_assert_(!run_); + for (int i = 0; i < MAX_INFLIGHT_FRAMES; i++) { _assert_(frameData_[i].deleter.IsEmpty()); _assert_(frameData_[i].deleter_prev.IsEmpty()); @@ -123,7 +126,6 @@ GLRenderManager::~GLRenderManager() { void GLRenderManager::ThreadStart(Draw::DrawContext *draw) { queueRunner_.CreateDeviceObjects(); - threadFrame_ = threadInitFrame_; renderThreadId = std::this_thread::get_id(); if (newInflightFrames_ != -1) { @@ -169,24 +171,16 @@ void GLRenderManager::ThreadStart(Draw::DrawContext *draw) { void GLRenderManager::ThreadEnd() { INFO_LOG(G3D, "ThreadEnd"); - // Wait for any shutdown to complete in StopThread(). - std::unique_lock lock(mutex_); queueRunner_.DestroyDeviceObjects(); - VLOG("PULL: Quitting"); + VLOG(" PULL: Quitting"); - // Good point to run all the deleters to get rid of leftover objects. + // Good time to run all the deleters to get rid of leftover objects. for (int i = 0; i < MAX_INFLIGHT_FRAMES; i++) { // Since we're in shutdown, we should skip the GL calls on Android. frameData_[i].deleter.Perform(this, skipGLCalls_); frameData_[i].deleter_prev.Perform(this, skipGLCalls_); - for (int j = 0; j < (int)frameData_[i].steps.size(); j++) { - delete frameData_[i].steps[j]; - } - frameData_[i].steps.clear(); - frameData_[i].initSteps.clear(); } deleter_.Perform(this, skipGLCalls_); - for (int i = 0; i < (int)steps_.size(); i++) { delete steps_[i]; } @@ -194,104 +188,68 @@ void GLRenderManager::ThreadEnd() { initSteps_.clear(); } +// Unlike in Vulkan, this isn't a full independent function, instead it gets called every frame. +// +// This means that we have to block and run the render queue until we've presented one frame, +// at which point we can leave. +// +// NOTE: If run_ is true, we WILL run a task! bool GLRenderManager::ThreadFrame() { - std::unique_lock lock(mutex_); - if (!run_) + if (!run_) { return false; + } + + GLRRenderThreadTask task; // In case of syncs or other partial completion, we keep going until we complete a frame. - do { - if (nextFrame) { - threadFrame_++; - if (threadFrame_ >= inflightFrames_) - threadFrame_ = 0; - } - FrameData &frameData = frameData_[threadFrame_]; - { - std::unique_lock lock(frameData.pull_mutex); - while (!frameData.readyForRun && run_) { - VLOG("PULL: Waiting for frame[%d].readyForRun", threadFrame_); - frameData.pull_condVar.wait(lock); - } - if (!frameData.readyForRun && !run_) { - // This means we're out of frames to render and run_ is false, so bail. - return false; - } - VLOG("PULL: Setting frame[%d].readyForRun = false", threadFrame_); - frameData.readyForRun = false; - frameData.deleter_prev.Perform(this, skipGLCalls_); - frameData.deleter_prev.Take(frameData.deleter); - // Previously we had a quick exit here that avoided calling Run() if run_ was suddenly false, - // but that created a race condition where frames could end up not finished properly on resize etc. + while (true) { + // Pop a task of the queue and execute it. + // NOTE: We need to actually wait for a task, we can't just bail! - // Only increment next time if we're done. - nextFrame = frameData.type == GLRRunType::END; - _assert_(frameData.type == GLRRunType::END || frameData.type == GLRRunType::SYNC); + { + std::unique_lock lock(pushMutex_); + while (renderThreadQueue_.empty()) { + pushCondVar_.wait(lock); + } + task = renderThreadQueue_.front(); + renderThreadQueue_.pop(); } - VLOG("PULL: Running frame %d", threadFrame_); - if (firstFrame) { - INFO_LOG(G3D, "Running first frame (%d)", threadFrame_); - firstFrame = false; + + // We got a task! We can now have pushMutex_ unlocked, allowing the host to + // push more work when it feels like it, and just start working. + if (task.runType == GLRRunType::EXIT) { + // Oh, host wanted out. Let's leave, and also let's notify the host. + // This is unlike Vulkan too which can just block on the thread existing. + std::unique_lock lock(syncMutex_); + syncCondVar_.notify_one(); + syncDone_ = true; + break; } // Render the scene. - Run(threadFrame_); - - VLOG("PULL: Finished frame %d", threadFrame_); - } while (!nextFrame); + VLOG(" PULL: Frame %d RUN (%0.3f)", task.frame, time_now_d()); + if (Run(task)) { + // Swap requested, so we just bail the loop. + break; + } + }; return true; } void GLRenderManager::StopThread() { - // Since we don't control the thread directly, this will only pause the thread. - + // There's not really a lot to do here anymore. + INFO_LOG(G3D, "GLRenderManager::StopThread()"); if (run_) { run_ = false; - for (int i = 0; i < MAX_INFLIGHT_FRAMES; i++) { - auto &frameData = frameData_[i]; - { - std::unique_lock lock(frameData.push_mutex); - frameData.push_condVar.notify_all(); - } - { - std::unique_lock lock(frameData.pull_mutex); - frameData.pull_condVar.notify_all(); - } - } - // Wait until we've definitely stopped the threadframe. - std::unique_lock lock(mutex_); - - INFO_LOG(G3D, "GL submission thread paused. Frame=%d", curFrame_); - - // Eat whatever has been queued up for this frame if anything. - Wipe(); - - // Wait for any fences to finish and be resignaled, so we don't have sync issues. - // Also clean out any queued data, which might refer to things that might not be valid - // when we restart... - for (int i = 0; i < MAX_INFLIGHT_FRAMES; i++) { - auto &frameData = frameData_[i]; - std::unique_lock lock(frameData.push_mutex); - if (frameData.readyForRun || frameData.steps.size() != 0) { - Crash(); - } - frameData.readyForRun = false; - frameData.readyForSubmit = false; - for (size_t i = 0; i < frameData.steps.size(); i++) { - delete frameData.steps[i]; - } - frameData.steps.clear(); - frameData.initSteps.clear(); - - while (!frameData.readyForFence) { - VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (stop)", i); - frameData.push_condVar.wait(lock); - } - } + std::unique_lock lock(pushMutex_); + GLRRenderThreadTask exitTask{}; + exitTask.runType = GLRRunType::EXIT; + renderThreadQueue_.push(exitTask); + pushCondVar_.notify_one(); } else { - INFO_LOG(G3D, "GL submission thread was already paused."); + WARN_LOG(G3D, "GL submission thread was already paused."); } } @@ -450,60 +408,51 @@ void GLRenderManager::CopyImageToMemorySync(GLRTexture *texture, int mipLevel, i } void GLRenderManager::BeginFrame() { - VLOG("BeginFrame"); - #ifdef _DEBUG curProgram_ = nullptr; #endif int curFrame = GetCurFrame(); - FrameData &frameData = frameData_[curFrame]; - // Make sure the very last command buffer from the frame before the previous has been fully executed. + FrameData &frameData = frameData_[curFrame]; { - std::unique_lock lock(frameData.push_mutex); + VLOG("PUSH: BeginFrame (curFrame = %d, readyForFence = %d, time=%0.3f)", curFrame, (int)frameData.readyForFence, time_now_d()); + std::unique_lock lock(frameData.fenceMutex); while (!frameData.readyForFence) { - VLOG("PUSH: Waiting for frame[%d].readyForFence = 1", curFrame); - frameData.push_condVar.wait(lock); + frameData.fenceCondVar.wait(lock); } frameData.readyForFence = false; - frameData.readyForSubmit = true; } - VLOG("PUSH: Fencing %d", curFrame); - - // glFenceSync(&frameData.fence...) - - // Must be after the fence - this performs deletes. - VLOG("PUSH: BeginFrame %d", curFrame); if (!run_) { WARN_LOG(G3D, "BeginFrame while !run_!"); } - // vulkan_->BeginFrame(); - // In GL, we have to do deletes on the submission thread. - insideFrame_ = true; } void GLRenderManager::Finish() { - curRenderStep_ = nullptr; + curRenderStep_ = nullptr; // EndCurRenderStep is this simple here. + int curFrame = GetCurFrame(); FrameData &frameData = frameData_[curFrame]; - { - std::unique_lock lock(frameData.pull_mutex); - VLOG("PUSH: Frame[%d].readyForRun = true, notifying pull", curFrame); - frameData.steps = std::move(steps_); - steps_.clear(); - frameData.initSteps = std::move(initSteps_); - initSteps_.clear(); - frameData.readyForRun = true; - frameData.type = GLRRunType::END; - frameData_[curFrame_].deleter.Take(deleter_); - } - // Notify calls do not in fact need to be done with the mutex locked. - frameData.pull_condVar.notify_all(); + frameData_[curFrame].deleter.Take(deleter_); + + VLOG("PUSH: Finish, pushing task. curFrame = %d", curFrame); + GLRRenderThreadTask task; + task.frame = curFrame; + task.runType = GLRRunType::PRESENT; + + { + std::unique_lock lock(pushMutex_); + renderThreadQueue_.push(task); + renderThreadQueue_.back().initSteps = std::move(initSteps_); + renderThreadQueue_.back().steps = std::move(steps_); + initSteps_.clear(); + steps_.clear(); + pushCondVar_.notify_one(); + } curFrame_++; if (curFrame_ >= inflightFrames_) @@ -512,18 +461,19 @@ void GLRenderManager::Finish() { insideFrame_ = false; } -// Render thread -void GLRenderManager::Run(int frame) { - FrameData &frameData = frameData_[frame]; +// Render thread. Returns true if the caller should handle a swap. +bool GLRenderManager::Run(GLRRenderThreadTask &task) { + FrameData &frameData = frameData_[task.frame]; + if (!frameData.hasBegun) { frameData.hasBegun = true; + + frameData.deleter_prev.Perform(this, skipGLCalls_); + frameData.deleter_prev.Take(frameData.deleter); } - auto &stepsOnThread = frameData_[frame].steps; - auto &initStepsOnThread = frameData_[frame].initSteps; // queueRunner_.LogSteps(stepsOnThread); - queueRunner_.RunInitSteps(initStepsOnThread, skipGLCalls_); - initStepsOnThread.clear(); + queueRunner_.RunInitSteps(task.initSteps, skipGLCalls_); // Run this after RunInitSteps so any fresh GLRBuffers for the pushbuffers can get created. if (!skipGLCalls_) { @@ -537,13 +487,12 @@ void GLRenderManager::Run(int frame) { int passes = GetVRPassesCount(); for (int i = 0; i < passes; i++) { PreVRFrameRender(i); - queueRunner_.RunSteps(stepsOnThread, skipGLCalls_, i < passes - 1, true); + queueRunner_.RunSteps(task.steps, skipGLCalls_, i < passes - 1, true); PostVRFrameRender(); } } else { - queueRunner_.RunSteps(stepsOnThread, skipGLCalls_, false, false); + queueRunner_.RunSteps(task.steps, skipGLCalls_, false, false); } - stepsOnThread.clear(); if (!skipGLCalls_) { for (auto iter : frameData.activePushBuffers) { @@ -551,20 +500,10 @@ void GLRenderManager::Run(int frame) { } } - switch (frameData.type) { - case GLRRunType::END: - frameData.hasBegun = false; - - VLOG("PULL: Frame %d.readyForFence = true", frame); - - { - std::unique_lock lock(frameData.push_mutex); - _assert_(frameData.readyForSubmit); - frameData.readyForFence = true; - frameData.readyForSubmit = false; - frameData.push_condVar.notify_all(); - } + bool swapRequest = false; + switch (task.runType) { + case GLRRunType::PRESENT: if (!frameData.skipSwap) { if (swapIntervalChanged_) { swapIntervalChanged_ = false; @@ -572,78 +511,77 @@ void GLRenderManager::Run(int frame) { swapIntervalFunction_(swapInterval_); } } + // This is the swapchain framebuffer flip. if (swapFunction_) { + VLOG(" PULL: SwapFunction()"); swapFunction_(); + if (!retainControl_) { + // get out of here. + swapRequest = true; + } + } else { + VLOG(" PULL: SwapRequested"); + swapRequest = true; } } else { frameData.skipSwap = false; } + frameData.hasBegun = false; + + VLOG(" PULL: Frame %d.readyForFence = true", task.frame); + + { + std::lock_guard lock(frameData.fenceMutex); + frameData.readyForFence = true; + frameData.fenceCondVar.notify_one(); + // At this point, we're done with this framedata (for now). + } + break; case GLRRunType::SYNC: + frameData.hasBegun = false; + // glFinish is not actually necessary here, and won't be unless we start using // glBufferStorage. Then we need to use fences. { - std::unique_lock lock(frameData.push_mutex); - frameData.readyForFence = true; - frameData.readyForSubmit = true; - frameData.push_condVar.notify_all(); + std::unique_lock lock(syncMutex_); + syncDone_ = true; + syncCondVar_.notify_one(); } break; default: _assert_(false); } - - VLOG("PULL: Finished running frame %d", frame); + VLOG(" PULL: ::Run(): Done running tasks"); + return swapRequest; } void GLRenderManager::FlushSync() { - int curFrame = curFrame_; - FrameData &frameData = frameData_[curFrame]; { - std::unique_lock lock(frameData.pull_mutex); - VLOG("PUSH: Frame[%d].readyForRun = true (sync)", curFrame); - frameData.initSteps = std::move(initSteps_); - initSteps_.clear(); - frameData.steps = std::move(steps_); + VLOG("PUSH: Frame[%d].readyForRun = true (sync)", curFrame_); + + GLRRenderThreadTask task; + task.frame = curFrame_; + task.runType = GLRRunType::SYNC; + + std::unique_lock lock(pushMutex_); + renderThreadQueue_.push(task); + renderThreadQueue_.back().initSteps = std::move(initSteps_); + renderThreadQueue_.back().steps = std::move(steps_); + pushCondVar_.notify_one(); steps_.clear(); - frameData.readyForRun = true; - _assert_(frameData.readyForFence == false); - frameData.type = GLRRunType::SYNC; - frameData.pull_condVar.notify_all(); } + { - std::unique_lock lock(frameData.push_mutex); + std::unique_lock lock(syncMutex_); // Wait for the flush to be hit, since we're syncing. - while (!frameData.readyForFence) { - VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (sync)", curFrame); - frameData.push_condVar.wait(lock); - } - frameData.readyForFence = false; - frameData.readyForSubmit = true; - } -} - -void GLRenderManager::Wipe() { - initSteps_.clear(); - for (auto step : steps_) { - delete step; - } - steps_.clear(); -} - -void GLRenderManager::WaitUntilQueueIdle() { - // Just wait for all frames to be ready. - for (int i = 0; i < MAX_INFLIGHT_FRAMES; i++) { - FrameData &frameData = frameData_[i]; - - std::unique_lock lock(frameData.push_mutex); - // Ignore unsubmitted frames. - while (!frameData.readyForFence && frameData.readyForRun) { - VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (wait idle)", i); - frameData.push_condVar.wait(lock); + while (!syncDone_) { + VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (sync)", curFrame_); + syncCondVar_.wait(lock); } + syncDone_ = false; } } diff --git a/Common/GPU/OpenGL/GLRenderManager.h b/Common/GPU/OpenGL/GLRenderManager.h index 243a66987c..2d213be8b8 100644 --- a/Common/GPU/OpenGL/GLRenderManager.h +++ b/Common/GPU/OpenGL/GLRenderManager.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "Common/GPU/OpenGL/GLCommon.h" @@ -349,11 +350,29 @@ private: GLBufferStrategy strategy_ = GLBufferStrategy::SUBDATA; }; -enum class GLRRunType { - END, - SYNC, +class GLRInputLayout { +public: + struct Entry { + int location; + int count; + GLenum type; + GLboolean normalized; + int stride; + intptr_t offset; + }; + std::vector entries; + int semanticsMask_ = 0; }; +enum class GLRRunType { + PRESENT, + SYNC, + EXIT, +}; + +class GLRenderManager; +class GLPushBuffer; + class GLDeleter { public: void Perform(GLRenderManager *renderManager, bool skipGLCalls); @@ -373,18 +392,14 @@ public: std::vector pushBuffers; }; -class GLRInputLayout { -public: - struct Entry { - int location; - int count; - GLenum type; - GLboolean normalized; - int stride; - intptr_t offset; - }; - std::vector entries; - int semanticsMask_ = 0; +// These are enqueued from the main thread, +// and the render thread pops them off +struct GLRRenderThreadTask { + std::vector steps; + std::vector initSteps; + + int frame; + GLRRunType runType; }; // Note: The GLRenderManager is created and destroyed on the render thread, and the latter @@ -402,7 +417,7 @@ public: void ThreadStart(Draw::DrawContext *draw); void ThreadEnd(); - bool ThreadFrame(); // Returns false to request exiting the loop. + bool ThreadFrame(); // Returns true if it did anything. False means the queue was empty. void SetErrorCallback(ErrorCallbackFn callback, void *userdata) { queueRunner_.SetErrorCallback(callback, userdata); @@ -415,20 +430,14 @@ public: // Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again. void BeginFrame(); // Can run on a different thread! - void Finish(); - void Run(int frame); - - // Zaps queued up commands. Use if you know there's a risk you've queued up stuff that has already been deleted. Can happen during in-game shutdown. - void Wipe(); - - // Wait until no frames are pending. Use during shutdown before freeing pointers. - void WaitUntilQueueIdle(); + void Finish(); + bool Run(GLRRenderThreadTask &task); // Creation commands. These were not needed in Vulkan since there we can do that on the main thread. // We pass in width/height here even though it's not strictly needed until we support glTextureStorage // and then we'll also need formats and stuff. GLRTexture *CreateTexture(GLenum target, int width, int height, int depth, int numMips) { - GLRInitStep step{ GLRInitStepType::CREATE_TEXTURE }; + GLRInitStep step { GLRInitStepType::CREATE_TEXTURE }; step.create_texture.texture = new GLRTexture(caps_, width, height, depth, numMips); step.create_texture.texture->target = target; initSteps_.push_back(step); @@ -983,8 +992,9 @@ public: _dbg_assert_(foundCount == 1); } - void SetSwapFunction(std::function swapFunction) { + void SetSwapFunction(std::function swapFunction, bool retainControl) { swapFunction_ = swapFunction; + retainControl_ = retainControl; } void SetSwapIntervalFunction(std::function swapIntervalFunction) { @@ -1027,26 +1037,14 @@ private: // Per-frame data, round-robin so we can overlap submission with execution of the previous frame. struct FrameData { - std::mutex push_mutex; - std::condition_variable push_condVar; - - std::mutex pull_mutex; - std::condition_variable pull_condVar; - - bool readyForFence = true; - bool readyForRun = false; - bool readyForSubmit = false; - bool skipSwap = false; - GLRRunType type = GLRRunType::END; - // GLuint fence; For future AZDO stuff? - std::vector steps; - std::vector initSteps; + std::mutex fenceMutex; + std::condition_variable fenceCondVar; + bool readyForFence = true; // Swapchain. bool hasBegun = false; - uint32_t curSwapchainImage = -1; GLDeleter deleter; GLDeleter deleter_prev; @@ -1064,16 +1062,23 @@ private: // Execution time state bool run_ = true; + // Thread is managed elsewhere, and should call ThreadFrame. - std::mutex mutex_; - int threadInitFrame_ = 0; GLQueueRunner queueRunner_; - // Thread state - int threadFrame_ = -1; + // For pushing data on the queue. + std::mutex pushMutex_; + std::condition_variable pushCondVar_; - bool nextFrame = false; - bool firstFrame = true; + std::queue renderThreadQueue_; + + // For readbacks and other reasons we need to sync with the render thread. + std::mutex syncMutex_; + std::condition_variable syncCondVar_; + + bool firstFrame_ = true; + bool vrRenderStarted_ = false; + bool syncDone_ = false; GLDeleter deleter_; bool skipGLCalls_ = false; @@ -1082,6 +1087,7 @@ private: std::function swapFunction_; std::function swapIntervalFunction_; + bool retainControl_ = false; GLBufferStrategy bufferStrategy_ = GLBufferStrategy::SUBDATA; int inflightFrames_ = MAX_INFLIGHT_FRAMES; diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index a251db1e0c..480f0cc3bb 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -212,7 +212,6 @@ GLuint ShaderStageToOpenGL(ShaderStage stage) { class OpenGLShaderModule : public ShaderModule { public: OpenGLShaderModule(GLRenderManager *render, ShaderStage stage, const std::string &tag) : render_(render), stage_(stage), tag_(tag) { - DEBUG_LOG(G3D, "Shader module created (%p)", this); glstage_ = ShaderStageToOpenGL(stage); } diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 63cf2e109a..2863dff087 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -1197,11 +1197,11 @@ void VulkanRenderManager::Finish() { int curFrame = vulkan_->GetCurFrame(); FrameData &frameData = frameData_[curFrame]; + VLOG("PUSH: Frame[%d]", curFrame); + VKRRenderThreadTask task; + task.frame = curFrame; + task.runType = VKRRunType::PRESENT; { - VLOG("PUSH: Frame[%d]", curFrame); - VKRRenderThreadTask task; - task.frame = curFrame; - task.runType = VKRRunType::PRESENT; std::unique_lock lock(pushMutex_); renderThreadQueue_.push(task); renderThreadQueue_.back().steps = std::move(steps_); @@ -1327,7 +1327,7 @@ void VulkanRenderManager::FlushSync() { std::unique_lock lock(syncMutex_); // Wait for the flush to be hit, since we're syncing. while (!frameData.syncDone) { - VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (sync)", curFrame); + VLOG("PUSH: Waiting for frame[%d].syncDone = 1 (sync)", curFrame); syncCondVar_.wait(lock); } frameData.syncDone = false; diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index bba23a25e4..5f08266e3a 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -198,7 +198,6 @@ public: const std::string &GetSource() const { return source_; } ~VKShaderModule() { if (module_) { - DEBUG_LOG(G3D, "Queueing %s (shmodule %p) for release", tag_.c_str(), module_); VkShaderModule shaderModule = module_->BlockUntilReady(); vulkan_->Delete().QueueDeleteShaderModule(shaderModule); vulkan_->Delete().QueueCallback([](VulkanContext *context, void *m) { @@ -268,7 +267,6 @@ public: vkrDesc = new VKRGraphicsPipelineDesc(); } ~VKPipeline() { - DEBUG_LOG(G3D, "Queueing %s (pipeline) for release", tag_.c_str()); if (pipeline) { pipeline->QueueForDeletion(vulkan_); } diff --git a/Core/Core.cpp b/Core/Core.cpp index 7935db6e17..611dad8457 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -327,14 +327,14 @@ void Core_ProcessStepping() { // Many platforms, like Android, do not call this function but handle things on their own. // Instead they simply call NativeRender and NativeUpdate directly. -void Core_Run(GraphicsContext *ctx) { +bool Core_Run(GraphicsContext *ctx) { host->UpdateDisassembly(); while (true) { if (GetUIState() != UISTATE_INGAME) { Core_StateProcessed(); if (GetUIState() == UISTATE_EXIT) { UpdateRunLoop(); - return; + return false; } Core_RunLoop(ctx); continue; @@ -347,7 +347,7 @@ void Core_Run(GraphicsContext *ctx) { Core_RunLoop(ctx); if (coreState == CORE_POWERDOWN) { Core_StateProcessed(); - return; + return true; } break; @@ -358,10 +358,10 @@ void Core_Run(GraphicsContext *ctx) { // Exit loop!! Core_StateProcessed(); - return; + return true; case CORE_NEXTFRAME: - return; + return true; } } } diff --git a/Core/Core.h b/Core/Core.h index 6fb372311c..333dbd1b4a 100644 --- a/Core/Core.h +++ b/Core/Core.h @@ -27,8 +27,10 @@ class GraphicsContext; // called from emu thread void UpdateRunLoop(); -void Core_Run(GraphicsContext *ctx); +// Returns false when an UI exit state is detected. +bool Core_Run(GraphicsContext *ctx); void Core_Stop(); + // For platforms that don't call Core_Run void Core_SetGraphicsContext(GraphicsContext *ctx); diff --git a/Core/CoreTiming.cpp b/Core/CoreTiming.cpp index 4b25032325..d91a55ab79 100644 --- a/Core/CoreTiming.cpp +++ b/Core/CoreTiming.cpp @@ -233,16 +233,14 @@ void Shutdown() ClearPendingEvents(); UnregisterAllEvents(); - while(eventPool) - { + while (eventPool) { Event *ev = eventPool; eventPool = ev->next; delete ev; } std::lock_guard lk(externalEventLock); - while(eventTsPool) - { + while (eventTsPool) { Event *ev = eventTsPool; eventTsPool = ev->next; delete ev; @@ -251,7 +249,12 @@ void Shutdown() u64 GetTicks() { - return (u64)globalTimer + slicelength - currentMIPS->downcount; + if (currentMIPS) { + return (u64)globalTimer + slicelength - currentMIPS->downcount; + } else { + // Reporting can actually end up here during weird task switching sequences on Android + return false; + } } u64 GetIdleTicks() diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index a41dc68ee4..034e8bd660 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -128,11 +128,6 @@ GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw) } GPU_GLES::~GPU_GLES() { - if (draw_) { - GLRenderManager *render = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); - render->Wipe(); - } - // If we're here during app shutdown (exiting the Windows app in-game, for example) // everything should already be cleared since DeviceLost has been run. diff --git a/Qt/QtMain.h b/Qt/QtMain.h index afb97149f4..0177ca9292 100644 --- a/Qt/QtMain.h +++ b/Qt/QtMain.h @@ -90,7 +90,6 @@ public: } void StopThread() override { - renderManager_->WaitUntilQueueIdle(); renderManager_->StopThread(); } diff --git a/SDL/SDLGLGraphicsContext.cpp b/SDL/SDLGLGraphicsContext.cpp index 945c9a950e..62fba14b81 100644 --- a/SDL/SDLGLGraphicsContext.cpp +++ b/SDL/SDLGLGraphicsContext.cpp @@ -429,7 +429,7 @@ int SDLGLGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std: #else SDL_GL_SwapWindow(window_); #endif - }); + }, false); renderManager_->SetSwapIntervalFunction([&](int interval) { INFO_LOG(G3D, "SDL SwapInterval: %d", interval); diff --git a/SDL/SDLGLGraphicsContext.h b/SDL/SDLGLGraphicsContext.h index 1910ce8f56..7982a3fa49 100644 --- a/SDL/SDLGLGraphicsContext.h +++ b/SDL/SDLGLGraphicsContext.h @@ -42,7 +42,6 @@ public: } void StopThread() override { - renderManager_->WaitUntilQueueIdle(); renderManager_->StopThread(); } diff --git a/Windows/EmuThread.cpp b/Windows/EmuThread.cpp index 823208c851..c28b59279e 100644 --- a/Windows/EmuThread.cpp +++ b/Windows/EmuThread.cpp @@ -82,7 +82,9 @@ static void EmuThreadFunc(GraphicsContext *graphicsContext) { // This way they can load a new game. if (!Core_IsActive()) UpdateUIState(UISTATE_MENU); - Core_Run(g_graphicsContext); + if (!Core_Run(g_graphicsContext)) { + emuThreadState = (int)EmuThreadState::QUIT_REQUESTED; + } } emuThreadState = (int)EmuThreadState::STOPPED; @@ -99,12 +101,14 @@ static void EmuThreadStart(GraphicsContext *graphicsContext) { } static void EmuThreadStop() { - emuThreadState = (int)EmuThreadState::QUIT_REQUESTED; + if (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED && + emuThreadState != (int)EmuThreadState::STOPPED) { + emuThreadState = (int)EmuThreadState::QUIT_REQUESTED; + } } static void EmuThreadJoin() { emuThread.join(); - emuThread = std::thread(); INFO_LOG(SYSTEM, "EmuThreadJoin - joined"); } diff --git a/Windows/GPU/WindowsGLContext.cpp b/Windows/GPU/WindowsGLContext.cpp index 05afc4d1f5..02cce76040 100644 --- a/Windows/GPU/WindowsGLContext.cpp +++ b/Windows/GPU/WindowsGLContext.cpp @@ -432,7 +432,7 @@ bool WindowsGLContext::InitFromRenderThread(std::string *error_message) { renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); renderManager_->SetInflightFrames(g_Config.iInflightFrames); SetGPUBackend(GPUBackend::OPENGL); - renderManager_->SetSwapFunction([&]() {::SwapBuffers(hDC); }); + renderManager_->SetSwapFunction([&]() {::SwapBuffers(hDC); }, true); if (wglSwapIntervalEXT) { // glew loads wglSwapIntervalEXT if available renderManager_->SetSwapIntervalFunction([&](int interval) { @@ -500,6 +500,5 @@ void WindowsGLContext::ThreadEnd() { } void WindowsGLContext::StopThread() { - renderManager_->WaitUntilQueueIdle(); renderManager_->StopThread(); } diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index 312983befe..3407dc96f2 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -838,4 +838,4 @@ Other Platforms\SDL - \ No newline at end of file + diff --git a/android/jni/AndroidJavaGLContext.cpp b/android/jni/AndroidJavaGLContext.cpp index 08e52034a5..ebf22b143c 100644 --- a/android/jni/AndroidJavaGLContext.cpp +++ b/android/jni/AndroidJavaGLContext.cpp @@ -38,7 +38,6 @@ bool AndroidJavaEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int void AndroidJavaEGLGraphicsContext::ShutdownFromRenderThread() { INFO_LOG(G3D, "AndroidJavaEGLGraphicsContext::Shutdown"); - renderManager_->WaitUntilQueueIdle(); renderManager_ = nullptr; // owned by draw_. delete draw_; draw_ = nullptr; diff --git a/android/jni/AndroidJavaGLContext.h b/android/jni/AndroidJavaGLContext.h index 919bd8b80f..4b4ff95d14 100644 --- a/android/jni/AndroidJavaGLContext.h +++ b/android/jni/AndroidJavaGLContext.h @@ -40,7 +40,6 @@ public: } void StopThread() override { - renderManager_->WaitUntilQueueIdle(); renderManager_->StopThread(); } diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 65d92cb4b5..b510942036 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -335,7 +335,7 @@ static void EmuThreadFunc() { while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) { UpdateRunLoopAndroid(env); } - INFO_LOG(SYSTEM, "QUIT_REQUESTED found, left loop. Setting state to STOPPED."); + INFO_LOG(SYSTEM, "QUIT_REQUESTED found, left EmuThreadFunc loop. Setting state to STOPPED."); emuThreadState = (int)EmuThreadState::STOPPED; NativeShutdownGraphics(); @@ -868,17 +868,18 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) { EmuThreadStop("shutdown"); INFO_LOG(SYSTEM, "BeginAndroidShutdown"); graphicsContext->BeginAndroidShutdown(); - // Skipping GL calls, the old context is gone. - while (graphicsContext->ThreadFrame()) { - INFO_LOG(SYSTEM, "graphicsContext->ThreadFrame executed to clear buffers"); - } - INFO_LOG(SYSTEM, "Joining emuthread"); - EmuThreadJoin(); - INFO_LOG(SYSTEM, "Joined emuthread"); - + // Now, it could be that we had some frames queued up. Get through them. + // We're on the render thread, so this is synchronous. + do { + INFO_LOG(SYSTEM, "Executing graphicsContext->ThreadFrame to clear buffers"); + } while (graphicsContext->ThreadFrame()); graphicsContext->ThreadEnd(); + INFO_LOG(SYSTEM, "ThreadEnd called."); graphicsContext->ShutdownFromRenderThread(); INFO_LOG(SYSTEM, "Graphics context now shut down from NativeApp_shutdown"); + + INFO_LOG(SYSTEM, "Joining emuthread"); + EmuThreadJoin(); } INFO_LOG(SYSTEM, "NativeApp.shutdown() -- begin"); diff --git a/headless/SDLHeadlessHost.cpp b/headless/SDLHeadlessHost.cpp index 13f9ad33fc..9960b01364 100644 --- a/headless/SDLHeadlessHost.cpp +++ b/headless/SDLHeadlessHost.cpp @@ -83,7 +83,6 @@ public: void StopThread() override { if (renderManager_) { - renderManager_->WaitUntilQueueIdle(); renderManager_->StopThread(); } } @@ -167,7 +166,7 @@ bool GLDummyGraphicsContext::InitFromRenderThread(std::string *errorMessage) { _assert_(success); renderManager_->SetSwapFunction([&]() { SDL_GL_SwapWindow(screen_); - }); + }, false); return success; } diff --git a/ios/ViewController.mm b/ios/ViewController.mm index c90c30a22a..ed34f7391f 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -80,7 +80,6 @@ public: } void StopThread() override { - renderManager_->WaitUntilQueueIdle(); renderManager_->StopThread(); } diff --git a/libretro/LibretroGLContext.h b/libretro/LibretroGLContext.h index 5c6ded1d6e..66d8d641c7 100644 --- a/libretro/LibretroGLContext.h +++ b/libretro/LibretroGLContext.h @@ -28,7 +28,6 @@ public: bool ThreadFrame() override { return renderManager_->ThreadFrame(); } void ThreadEnd() override { renderManager_->ThreadEnd(); } void StopThread() override { - renderManager_->WaitUntilQueueIdle(); renderManager_->StopThread(); } diff --git a/libretro/LibretroGLCoreContext.h b/libretro/LibretroGLCoreContext.h index bb8d5e460f..dbe65dc4ae 100644 --- a/libretro/LibretroGLCoreContext.h +++ b/libretro/LibretroGLCoreContext.h @@ -24,7 +24,6 @@ public: bool ThreadFrame() override { return renderManager_->ThreadFrame(); } void ThreadEnd() override { renderManager_->ThreadEnd(); } void StopThread() override { - renderManager_->WaitUntilQueueIdle(); renderManager_->StopThread(); }