diff --git a/app/src/main/cpp/main.cpp b/app/src/main/cpp/main.cpp index 7a0bc36..012d60d 100644 --- a/app/src/main/cpp/main.cpp +++ b/app/src/main/cpp/main.cpp @@ -54,9 +54,13 @@ void handle_cmd(android_app *pApp, int32_t cmd) { case APP_CMD_INIT_WINDOW: aout << "APP_CMD_INIT_WINDOW" << std::endl; DebugLog::log("handle_cmd APP_CMD_INIT_WINDOW: calling onWindowInit()"); - g_Application->onWindowInit(); - DebugLog::log("handle_cmd APP_CMD_INIT_WINDOW: onWindowInit() returned, isInited=%d", - (int)g_Application->isInited()); + if (g_Application != nullptr) { + g_Application->onWindowInit(); + DebugLog::log("handle_cmd APP_CMD_INIT_WINDOW: onWindowInit() returned, isInited=%d", + (int)g_Application->isInited()); + } else { + DebugLog::log("handle_cmd APP_CMD_INIT_WINDOW: g_Application is null, skipping"); + } break; case APP_CMD_TERM_WINDOW: aout << "APP_CMD_TERM_WINDOW" << std::endl; diff --git a/vulkan/Application.cpp b/vulkan/Application.cpp index 61f4bc9..3a94757 100644 --- a/vulkan/Application.cpp +++ b/vulkan/Application.cpp @@ -112,9 +112,12 @@ void Application::initVulkan() createSyncObjects(); // 创建同步对象 _lastDrawFrameTime = getCurrentTimeMillis(); _applicationInited = true; + // The first branch above already built all of the window-dependent + // objects. Mark _sceondInited so we don't fall into the recovery + // branch below and leak a second copy of surface/swapChain/etc. + _sceondInited = true; } - - if (!_sceondInited) { + else if (!_sceondInited) { createSurface(); createSwapChain(); createImageViews(); @@ -155,8 +158,14 @@ void Application::cleanupForWindowLost() (int)_applicationInited, (int)_sceondInited); return; } - FACE_DBG_LOG("cleanupForWindowLost: begin (imageCount=%zu MAX_FRAMES_IN_FLIGHT=%d)", - swapChainImages.size(), MAX_FRAMES_IN_FLIGHT); + FACE_DBG_LOG("cleanupForWindowLost: begin (imageCount=%zu MAX_FRAMES_IN_FLIGHT=%d extent=%ux%u format=%d)", + swapChainImages.size(), MAX_FRAMES_IN_FLIGHT, + swapChainExtent.width, swapChainExtent.height, (int)swapChainImageFormat); + + // Snapshot before we destroy the swapchain so reinitForNewWindow() can + // decide whether the new swapchain needs renderPass / pipelines rebuilt. + _prevSwapChainExtent = swapChainExtent; + _prevSwapChainImageFormat = swapChainImageFormat; // Block until GPU is no longer using any of the resources we are about // to destroy. Anything weaker than this risks hitting VK_ERROR_DEVICE_LOST @@ -222,29 +231,73 @@ void Application::cleanupForWindowLost() FACE_DBG_LOG("cleanupForWindowLost: done"); } -void Application::reinitForNewWindow() +bool Application::reinitForNewWindow() { if (!_applicationInited) { FACE_DBG_LOG("reinitForNewWindow: skip, _applicationInited=0 (must go through initVulkan first)"); - return; + return false; } if (_sceondInited) { FACE_DBG_LOG("reinitForNewWindow: skip, already _sceondInited=1"); - return; + return false; } - FACE_DBG_LOG("reinitForNewWindow: begin"); + FACE_DBG_LOG("reinitForNewWindow: begin (prev extent=%ux%u format=%d)", + _prevSwapChainExtent.width, _prevSwapChainExtent.height, + (int)_prevSwapChainImageFormat); createSurface(); createSwapChain(); + + const bool extentChanged = (swapChainExtent.width != _prevSwapChainExtent.width) || + (swapChainExtent.height != _prevSwapChainExtent.height); + const bool formatChanged = (swapChainImageFormat != _prevSwapChainImageFormat); + const bool swapchainIncompatible = extentChanged || formatChanged; + + if (formatChanged) { + // renderPass bakes in swapChainImageFormat, so it must be rebuilt + // when the surface chose a different format. The old Application + // graphicsPipeline is tied to the old renderPass, so destroy it + // first to make the handle invalidation explicit. + FACE_DBG_LOG("reinitForNewWindow: format changed (%d -> %d), rebuilding renderPass", + (int)_prevSwapChainImageFormat, (int)swapChainImageFormat); + if (graphicsPipeline != VK_NULL_HANDLE) { + vkDestroyPipeline(device, graphicsPipeline, nullptr); + graphicsPipeline = VK_NULL_HANDLE; + } + if (pipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + pipelineLayout = VK_NULL_HANDLE; + } + if (renderPass != VK_NULL_HANDLE) { + vkDestroyRenderPass(device, renderPass, nullptr); + renderPass = VK_NULL_HANDLE; + } + createRenderPass(); + createPipelineLayout(); + createGraphicsPipeline(); + } else if (extentChanged) { + // renderPass is still fine, but the Application pipeline has a static + // viewport baked to the old extent and must be rebuilt. + FACE_DBG_LOG("reinitForNewWindow: extent changed (%ux%u -> %ux%u), rebuilding graphicsPipeline", + _prevSwapChainExtent.width, _prevSwapChainExtent.height, + swapChainExtent.width, swapChainExtent.height); + if (graphicsPipeline != VK_NULL_HANDLE) { + vkDestroyPipeline(device, graphicsPipeline, nullptr); + graphicsPipeline = VK_NULL_HANDLE; + } + createGraphicsPipeline(); + } + createImageViews(); createFramebuffers(); createCommandBuffer(); createSyncObjects(); _sceondInited = true; - FACE_DBG_LOG("reinitForNewWindow: done (imageCount=%zu extent=%ux%u MAX_FRAMES_IN_FLIGHT=%d)", + FACE_DBG_LOG("reinitForNewWindow: done (imageCount=%zu extent=%ux%u format=%d MAX_FRAMES_IN_FLIGHT=%d swapchainIncompatible=%d)", swapChainImages.size(), swapChainExtent.width, swapChainExtent.height, - MAX_FRAMES_IN_FLIGHT); + (int)swapChainImageFormat, MAX_FRAMES_IN_FLIGHT, (int)swapchainIncompatible); + return swapchainIncompatible; } diff --git a/vulkan/Application.h b/vulkan/Application.h index ad7d62e..9b6441a 100644 --- a/vulkan/Application.h +++ b/vulkan/Application.h @@ -90,7 +90,19 @@ public: // Rebuild the window-dependent Vulkan objects torn down above. // Safe to call only after cleanupForWindowLost(). - void reinitForNewWindow(); + // Returns true when the new swapchain's extent or format differs from the + // one in use before cleanupForWindowLost(). When true, any pipeline baked + // with a static viewport/scissor from swapChainExtent -- including the + // FaceApp pipelines -- must be destroyed and recreated against the new + // renderPass / extent. Application's own renderPass + graphicsPipeline are + // already handled internally. + bool reinitForNewWindow(); + + // Extent / format captured by the most recent cleanupForWindowLost(). + // Used by reinitForNewWindow() to decide whether renderPass / pipelines + // must be rebuilt against the new swapchain. + VkExtent2D _prevSwapChainExtent = {0, 0}; + VkFormat _prevSwapChainImageFormat = VK_FORMAT_UNDEFINED; protected: void loadTexture(std::string path, Texture& tex, bool srgb, VkCommandPool pool); diff --git a/vulkan/FaceApp.cpp b/vulkan/FaceApp.cpp index 459d384..6e6a5cd 100644 --- a/vulkan/FaceApp.cpp +++ b/vulkan/FaceApp.cpp @@ -9,8 +9,10 @@ #ifndef _WIN32 #include "../app/src/main/cpp/DebugLog.h" #define FACE_DBG_LOG(...) DebugLog::log(__VA_ARGS__) +#define FACE_DBG_LOG_THROTTLED(key, ...) DebugLog::log_throttled(key, __VA_ARGS__) #else #define FACE_DBG_LOG(...) ((void)0) +#define FACE_DBG_LOG_THROTTLED(key, ...) ((void)0) #endif @@ -906,12 +908,18 @@ void FaceApp::onWindowInit() } else { // Recovery path after an earlier onWindowLost. Rebuild only the // window-dependent Vulkan objects; keep renderPass / pipelines / - // FaceApp GPU resources intact. + // FaceApp GPU resources intact unless the new swapchain is + // incompatible (e.g. rotation changed extent). std::unique_lock lk_point(mtx_point); std::unique_lock lk_motion(changeMotionMtx); std::unique_lock lk_tex(createTextureMtx); - Application::reinitForNewWindow(); + bool swapchainIncompatible = Application::reinitForNewWindow(); + + if (swapchainIncompatible && _faceAppInited) { + FACE_DBG_LOG("FaceApp::onWindowInit: swapchain incompatible, rebuilding FaceApp pipelines"); + recreatePipelinesForSwapchain(); + } if (!_secondfaceAppInited) { _secondfaceAppInited = true; @@ -925,6 +933,29 @@ void FaceApp::onWindowInit() (int)_secondfaceAppInited, (int)_running); } +void FaceApp::recreatePipelinesForSwapchain() +{ + // Destroy the two FaceApp pipelines. Layouts / descriptor set layouts are + // swapchain-independent and kept as-is so existing descriptor sets keep + // pointing at the same texture/uniform resources. + if (m_graphicsPipeline != VK_NULL_HANDLE) { + vkDestroyPipeline(device, m_graphicsPipeline, nullptr); + m_graphicsPipeline = VK_NULL_HANDLE; + } + if (m_graphicsPipeline_bg != VK_NULL_HANDLE) { + vkDestroyPipeline(device, m_graphicsPipeline_bg, nullptr); + m_graphicsPipeline_bg = VK_NULL_HANDLE; + } + + // Recreate against the fresh renderPass (if format changed) and the new + // swapChainExtent (viewport/scissor are baked statically into these). + create_face_pipelines(); + create_pipelines_bg(); + + FACE_DBG_LOG("FaceApp::recreatePipelinesForSwapchain: rebuilt for extent=%ux%u", + swapChainExtent.width, swapChainExtent.height); +} + void FaceApp::update_uniform_buffers() { uint32_t width = 480; @@ -1637,7 +1668,7 @@ Motion FaceApp::getMotionByName(string name) void FaceApp::changeMotionList(vector motions, AnimationFinishedCallback callback, bool loop) { - DebugLog::log_throttled("FaceApp.changeMotionList", + FACE_DBG_LOG_THROTTLED("FaceApp.changeMotionList", "FaceApp::changeMotionList called count=%zu loop=%d _isLoadMotion=%d", motions.size(), (int)loop, (int)_isLoadMotion); if (_isLoadMotion) diff --git a/vulkan/FaceApp.h b/vulkan/FaceApp.h index 244d903..0df7f61 100644 --- a/vulkan/FaceApp.h +++ b/vulkan/FaceApp.h @@ -191,8 +191,17 @@ public: // Called from main thread in response to APP_CMD_INIT_WINDOW. // Does the full first-time initVulkan() on the very first call, and a - // lightweight swapchain/surface rebuild on subsequent calls. + // lightweight swapchain/surface rebuild on subsequent calls. When the + // new swapchain's extent or format differs from the previous one, the + // FaceApp pipelines (baked with static viewport / old renderPass) are + // also destroyed and recreated via recreatePipelinesForSwapchain(). void onWindowInit(); + + // Destroy and recreate m_graphicsPipeline / m_graphicsPipeline_bg so they + // match the current renderPass and swapChainExtent. Descriptor set layouts, + // pipeline layouts, vertex/index buffers, uniform buffers and textures are + // all kept. Must be called with device idle and the FaceApp mutexes held. + void recreatePipelinesForSwapchain(); void loadMotionThread(); std::thread worker_; bool _isLoadMotion = false;