From 4f64e1186f09eed37773161d0e702ed868fdeb92 Mon Sep 17 00:00:00 2001 From: SSimco <37044560+SSimco@users.noreply.github.com> Date: Sat, 9 May 2026 17:01:21 +0300 Subject: [PATCH] Vulkan: implement Android surface & recreation --- src/CMakeLists.txt | 4 ++ .../Latte/Renderer/Vulkan/SwapchainInfoVk.cpp | 56 +++++++++++++-- .../Latte/Renderer/Vulkan/SwapchainInfoVk.h | 14 +++- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h | 6 +- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 69 +++++++++++++++++-- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 13 ++-- src/gui/interface/WindowSystem.h | 1 + 7 files changed, 146 insertions(+), 17 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3af482fb..3b566364 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,10 @@ elseif(UNIX) else() add_compile_definitions(_XOPEN_SOURCE) endif() + elseif(ANDROID) + if(ENABLE_VULKAN) + add_compile_definitions(VK_USE_PLATFORM_ANDROID_KHR) + endif() else() if (ENABLE_VULKAN) add_compile_definitions( diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp index b27aae0e..8f7daa05 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp @@ -15,7 +15,11 @@ SwapchainInfoVk::SwapchainInfoVk(bool mainWindow, Vector2i size) : mainWindow(ma m_logicalDevice = renderer->GetLogicalDevice(); m_physicalDevice = renderer->GetPhysicalDevice(); +#if BOOST_PLAT_ANDROID + m_surface = renderer->CreateFramebufferSurface(m_instance, windowHandleInfo, &m_currentWindow); +#else m_surface = renderer->CreateFramebufferSurface(m_instance, windowHandleInfo); +#endif } @@ -26,8 +30,31 @@ SwapchainInfoVk::~SwapchainInfoVk() vkDestroySurfaceKHR(m_instance, m_surface, nullptr); } +#if BOOST_PLAT_ANDROID +void SwapchainInfoVk::RecreateSurface() +{ + if (!mainWindow) + return; + + auto& windowHandleInfo = WindowSystem::GetWindowInfo().canvas_main; + + windowHandleInfo.surface.wait(m_currentWindow); + + auto newSurface = VulkanRenderer::CreateFramebufferSurface(m_instance, windowHandleInfo, &m_currentWindow); + vkDestroySurfaceKHR(m_instance, m_surface, nullptr); + m_surface = newSurface; + + surfaceWasLost = false; +} +#endif + void SwapchainInfoVk::Create() { +#if BOOST_PLAT_ANDROID + if (surfaceWasLost) + RecreateSurface(); +#endif + const auto details = QuerySwapchainSupport(m_surface, m_physicalDevice); m_surfaceFormat = ChooseSurfaceFormat(details.formats); m_actualExtent = ChooseSwapExtent(details.capabilities); @@ -231,6 +258,15 @@ bool SwapchainInfoVk::AcquireImage() VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, 1'000'000'000, acquireSemaphore, m_imageAvailableFence, &swapchainImageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) m_shouldRecreate = true; + +#if BOOST_PLAT_ANDROID + if (result == VK_ERROR_SURFACE_LOST_KHR) + { + surfaceWasLost = true; + m_shouldRecreate = true; + } +#endif + if (result == VK_TIMEOUT) { swapchainImageIndex = -1; @@ -240,10 +276,17 @@ bool SwapchainInfoVk::AcquireImage() if (result < 0) { swapchainImageIndex = -1; - if (result != VK_ERROR_OUT_OF_DATE_KHR) - throw std::runtime_error(fmt::format("Failed to acquire next image: {}", result)); - return false; + if (result == VK_ERROR_OUT_OF_DATE_KHR) + return false; + +#if BOOST_PLAT_ANDROID + if (result == VK_ERROR_SURFACE_LOST_KHR) + return false; +#endif + + throw std::runtime_error(fmt::format("Failed to acquire next image: {}", result)); } + m_currentSemaphore = acquireSemaphore; m_awaitableFence = m_imageAvailableFence; m_acquireIndex = (m_acquireIndex + 1) % m_swapchainImages.size(); @@ -319,7 +362,7 @@ VkSurfaceFormatKHR SwapchainInfoVk::ChooseSurfaceFormat(const std::vector +#if BOOST_PLAT_ANDROID +#include +#endif + struct SwapchainInfoVk { enum class VSync @@ -58,10 +62,13 @@ struct SwapchainInfoVk bool mainWindow{}; +#if BOOST_PLAT_ANDROID + bool surfaceWasLost = false; +#endif + bool m_shouldRecreate = false; VSync m_vsyncState = VSync::Immediate; bool hasDefinedSwapchainImage{}; // indicates if the swapchain image is in a defined state - VkInstance m_instance{}; VkPhysicalDevice m_physicalDevice{}; VkDevice m_logicalDevice{}; @@ -91,5 +98,10 @@ private: VkFence m_awaitableFence = VK_NULL_HANDLE; VkSemaphore m_currentSemaphore = VK_NULL_HANDLE; +#if BOOST_PLAT_ANDROID + void RecreateSurface(); + ANativeWindow* m_currentWindow = nullptr; +#endif + std::array m_swapchainQueueFamilyIndices; }; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h index f503c108..cde4162e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h @@ -130,12 +130,16 @@ VKFUNC_DEVICE(vkDestroyPipeline); VKFUNC_DEVICE(vkCmdBindPipeline); // swapchain -#if BOOST_OS_LINUX || BOOST_OS_BSD +#if BOOST_PLAT_ANDROID +VKFUNC_INSTANCE(vkCreateAndroidSurfaceKHR); +#elif BOOST_OS_LINUX || BOOST_OS_BSD VKFUNC_INSTANCE(vkCreateXlibSurfaceKHR); VKFUNC_INSTANCE(vkCreateXcbSurfaceKHR); + #ifdef HAS_WAYLAND VKFUNC_INSTANCE(vkCreateWaylandSurfaceKHR); #endif + #endif #if BOOST_OS_WINDOWS diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 230ca6e7..a38a83ab 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -113,6 +113,8 @@ std::vector VulkanRenderer::GetDevices() requiredExtensions.emplace_back(VK_KHR_SURFACE_EXTENSION_NAME); #if BOOST_OS_WINDOWS requiredExtensions.emplace_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); + #elif BOOST_PLAT_ANDROID + requiredExtensions.emplace_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); #elif BOOST_OS_LINUX || BOOST_OS_BSD auto backend = WindowSystem::GetWindowInfo().window_main.backend; if(backend == WindowSystem::WindowHandleInfo::Backend::X11) @@ -1481,6 +1483,8 @@ std::vector VulkanRenderer::CheckInstanceExtensionSupport(FeatureCo requiredInstanceExtensions.emplace_back(VK_KHR_SURFACE_EXTENSION_NAME); #if BOOST_OS_WINDOWS requiredInstanceExtensions.emplace_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); + #elif BOOST_PLAT_ANDROID + requiredInstanceExtensions.emplace_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); #elif BOOST_OS_LINUX || BOOST_OS_BSD auto backend = WindowSystem::GetWindowInfo().window_main.backend; if(backend == WindowSystem::WindowHandleInfo::Backend::X11) @@ -1568,7 +1572,25 @@ VkSurfaceKHR VulkanRenderer::CreateWinSurface(VkInstance instance, HWND hwindow) } #endif -#if BOOST_OS_LINUX || BOOST_OS_BSD +#if BOOST_PLAT_ANDROID +VkSurfaceKHR VulkanRenderer::CreateAndroidSurface(VkInstance instance, ANativeWindow* window) +{ + VkAndroidSurfaceCreateInfoKHR sci{}; + sci.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; + sci.flags = 0; + sci.window = window; + + VkSurfaceKHR result; + VkResult err; + if ((err = vkCreateAndroidSurfaceKHR(instance, &sci, nullptr, &result)) != VK_SUCCESS) + { + cemuLog_log(LogType::Force, "Cannot create an Android Vulkan surface: {}", (sint32)err); + throw std::runtime_error(fmt::format("Cannot create an Android Vulkan surface: {}", err)); + } + + return result; +} +#elif BOOST_OS_LINUX || BOOST_OS_BSD VkSurfaceKHR VulkanRenderer::CreateXlibSurface(VkInstance instance, Display* dpy, Window window) { VkXlibSurfaceCreateInfoKHR sci{}; @@ -1625,9 +1647,23 @@ VkSurfaceKHR VulkanRenderer::CreateWaylandSurface(VkInstance instance, wl_displa return result; } -#endif // HAS_WAYLAND -#endif // BOOST_OS_LINUX +#endif +#endif +#if BOOST_PLAT_ANDROID +VkSurfaceKHR VulkanRenderer::CreateFramebufferSurface(VkInstance instance, struct WindowSystem::WindowHandleInfo& windowInfo, ANativeWindow** nativeWindow) +{ + auto window = static_cast(windowInfo.surface.load()); + VkSurfaceKHR surface = CreateAndroidSurface(instance, window); + + if (nativeWindow != nullptr) + { + *nativeWindow = window; + } + + return surface; +} +#else VkSurfaceKHR VulkanRenderer::CreateFramebufferSurface(VkInstance instance, WindowSystem::WindowHandleInfo& windowInfo) { #if BOOST_OS_WINDOWS @@ -1644,6 +1680,7 @@ VkSurfaceKHR VulkanRenderer::CreateFramebufferSurface(VkInstance instance, Windo return CreateCocoaSurface(instance, windowInfo.surface.load()); #endif } +#endif void VulkanRenderer::CreateCommandPool() { @@ -3029,6 +3066,11 @@ bool VulkanRenderer::UpdateSwapchainProperties(bool mainWindow) if (width != extent.width || height != extent.height) stateChanged = true; +#if BOOST_PLAT_ANDROID + if (chainInfo.surfaceWasLost) + stateChanged = true; +#endif + if(stateChanged) { try @@ -3104,19 +3146,34 @@ void VulkanRenderer::SwapBuffer(bool mainWindow) } VkResult result = vkQueuePresentKHR(m_presentQueue, &presentInfo); - if (result < 0 && result != VK_ERROR_OUT_OF_DATE_KHR) + if (result < 0 && result != VK_ERROR_OUT_OF_DATE_KHR +#if BOOST_PLAT_ANDROID + && result != VK_ERROR_SURFACE_LOST_KHR +#endif + ) { throw std::runtime_error(fmt::format("Failed to present image: {}", result)); } - if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) + + if (result == VK_ERROR_OUT_OF_DATE_KHR) chainInfo.m_shouldRecreate = true; - if(result >= 0) + if (result >= 0) { chainInfo.m_queueDepth++; chainInfo.m_presentId++; } +#if BOOST_PLAT_ANDROID + if (result == VK_ERROR_SURFACE_LOST_KHR) + chainInfo.surfaceWasLost = true; +#endif + +#if !BOOST_PLAT_ANDROID + if (result == VK_SUBOPTIMAL_KHR) + chainInfo.m_shouldRecreate = true; +#endif + chainInfo.hasDefinedSwapchainImage = false; chainInfo.swapchainImageIndex = -1; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 464bff32..160f7d45 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -216,16 +216,21 @@ public: #if BOOST_OS_WINDOWS static VkSurfaceKHR CreateWinSurface(VkInstance instance, HWND hwindow); #endif -#if BOOST_OS_LINUX || BOOST_OS_BSD +#if BOOST_PLAT_ANDROID + static VkSurfaceKHR CreateAndroidSurface(VkInstance instance, ANativeWindow* window); +#elif BOOST_OS_LINUX || BOOST_OS_BSD static VkSurfaceKHR CreateXlibSurface(VkInstance instance, Display* dpy, Window window); static VkSurfaceKHR CreateXcbSurface(VkInstance instance, xcb_connection_t* connection, xcb_window_t window); - #ifdef HAS_WAYLAND +#ifdef HAS_WAYLAND static VkSurfaceKHR CreateWaylandSurface(VkInstance instance, wl_display* display, wl_surface* surface); - #endif +#endif // HAS_WAYLAND #endif +#if BOOST_PLAT_ANDROID + static VkSurfaceKHR CreateFramebufferSurface(VkInstance instance, struct WindowSystem::WindowHandleInfo& windowInfo, ANativeWindow** nativeWindow = nullptr); +#else static VkSurfaceKHR CreateFramebufferSurface(VkInstance instance, struct WindowSystem::WindowHandleInfo& windowInfo); - +#endif void AppendOverlayDebugInfo() override; void ImguiInit(); diff --git a/src/gui/interface/WindowSystem.h b/src/gui/interface/WindowSystem.h index 08790bc2..14be4c59 100644 --- a/src/gui/interface/WindowSystem.h +++ b/src/gui/interface/WindowSystem.h @@ -12,6 +12,7 @@ namespace WindowSystem Wayland, Cocoa, Windows, + Android, } backend; std::atomic display = nullptr; std::atomic surface = nullptr;