From 098e2c4077f62890f83c0785e858080e9e18296c Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Thu, 1 Jun 2023 20:07:18 -0700
Subject: [PATCH] android: renderer_vulkan: Fix crash with surface recreation.

---
 src/android/app/src/main/jni/native.cpp       |  1 +
 src/video_core/renderer_base.h                |  3 +++
 .../renderer_vulkan/renderer_vulkan.h         |  4 ++++
 .../renderer_vulkan/vk_present_manager.cpp    | 24 ++++++++++++++++++-
 .../renderer_vulkan/vk_present_manager.h      |  5 ++++
 5 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index d503ef61d0..b87e04b3d8 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -148,6 +148,7 @@ public:
             return;
         }
         m_window->OnSurfaceChanged(m_native_window);
+        m_system.Renderer().NotifySurfaceChanged();
     }
 
     Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 78ea5208b9..3e12a88138 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -89,6 +89,9 @@ public:
     void RequestScreenshot(void* data, std::function<void(bool)> callback,
                            const Layout::FramebufferLayout& layout);
 
+    /// This is called to notify the rendering backend of a surface change
+    virtual void NotifySurfaceChanged() {}
+
 protected:
     Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
     std::unique_ptr<Core::Frontend::GraphicsContext> context;
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 3c63a2004a..b2e8cbd1bb 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -54,6 +54,10 @@ public:
         return device.GetDriverName();
     }
 
+    void NotifySurfaceChanged() override {
+        present_manager.NotifySurfaceChanged();
+    }
+
 private:
     void Report() const;
 
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp
index 77832720df..dc42982e9f 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp
@@ -291,6 +291,13 @@ void PresentManager::PresentThread(std::stop_token token) {
     }
 }
 
+void PresentManager::NotifySurfaceChanged() {
+#ifdef ANDROID
+    std::scoped_lock lock{recreate_surface_mutex};
+    recreate_surface_cv.notify_one();
+#endif
+}
+
 void PresentManager::CopyToSwapchain(Frame* frame) {
     MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
 
@@ -299,7 +306,22 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
         image_count = swapchain.GetImageCount();
     };
 
+    const auto needs_recreation = [&] {
+        if (last_render_surface != render_window.GetWindowInfo().render_surface) {
+            return true;
+        }
+        if (swapchain.NeedsRecreation(frame->is_srgb)) {
+            return true;
+        }
+        return false;
+    };
+
 #ifdef ANDROID
+    std::unique_lock lock{recreate_surface_mutex};
+
+    recreate_surface_cv.wait_for(lock, std::chrono::milliseconds(400),
+                                 [&]() { return !needs_recreation(); });
+
     // If the frontend recreated the surface, recreate the renderer surface and swapchain.
     if (last_render_surface != render_window.GetWindowInfo().render_surface) {
         last_render_surface = render_window.GetWindowInfo().render_surface;
@@ -450,7 +472,7 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
 
     // Submit the image copy/blit to the swapchain
     {
-        std::scoped_lock lock{scheduler.submit_mutex};
+        std::scoped_lock submit_lock{scheduler.submit_mutex};
         switch (const VkResult result =
                     device.GetGraphicsQueue().Submit(submit_info, *frame->present_done)) {
         case VK_SUCCESS:
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h
index 3cbfce4edc..4ac2e2395f 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.h
+++ b/src/video_core/renderer_vulkan/vk_present_manager.h
@@ -55,6 +55,9 @@ public:
     /// Waits for the present thread to finish presenting all queued frames.
     void WaitPresent();
 
+    /// This is called to notify the rendering backend of a surface change
+    void NotifySurfaceChanged();
+
 private:
     void PresentThread(std::stop_token token);
 
@@ -74,7 +77,9 @@ private:
     std::queue<Frame*> free_queue;
     std::condition_variable_any frame_cv;
     std::condition_variable free_cv;
+    std::condition_variable recreate_surface_cv;
     std::mutex swapchain_mutex;
+    std::mutex recreate_surface_mutex;
     std::mutex queue_mutex;
     std::mutex free_mutex;
     std::jthread present_thread;