diff --git a/Engine/CMakeLists.txt b/Engine/CMakeLists.txt index 35c2be93..3e99ba81 100644 --- a/Engine/CMakeLists.txt +++ b/Engine/CMakeLists.txt @@ -159,10 +159,12 @@ if(TEMPEST_BUILD_VULKAN) add_definitions(-DVULKAN_HPP_NO_EXCEPTIONS) add_definitions(-DVULKAN_HPP_NO_SMART_HANDLE) - if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - target_link_directories(${PROJECT_NAME} PRIVATE "$ENV{VULKAN_SDK}/lib") - else() - target_link_directories(${PROJECT_NAME} PRIVATE "$ENV{VULKAN_SDK}/Lib32") + if(NOT ANDROID) + if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + target_link_directories(${PROJECT_NAME} PRIVATE "$ENV{VULKAN_SDK}/lib") + else() + target_link_directories(${PROJECT_NAME} PRIVATE "$ENV{VULKAN_SDK}/Lib32") + endif() endif() target_include_directories(${PROJECT_NAME} PRIVATE "$ENV{VULKAN_SDK}/include") diff --git a/Engine/exceptions/exception.h b/Engine/exceptions/exception.h index cfa9b17f..32ed4d45 100644 --- a/Engine/exceptions/exception.h +++ b/Engine/exceptions/exception.h @@ -28,6 +28,11 @@ class SwapchainSuboptimal : public std::exception { const char* what() const noexcept override { return "swapchain no longer matches the surface properties"; } }; +class SurfaceLost: public std::exception { + public: + const char* what() const noexcept override { return "surface is no longer valid"; } + }; + class IncompleteFboException : public std::exception { public: const char* what() const noexcept override { return "inconsistent framebuffer dimensions"; } diff --git a/Engine/gapi/vulkan/vcommandbuffer.cpp b/Engine/gapi/vulkan/vcommandbuffer.cpp index c13c9434..140a653e 100644 --- a/Engine/gapi/vulkan/vcommandbuffer.cpp +++ b/Engine/gapi/vulkan/vcommandbuffer.cpp @@ -155,7 +155,7 @@ static VkImage toVkResource(const AbstractGraphicsApi::BarrierDesc& b) { } auto& s = *reinterpret_cast(b.swapchain); - return s.images[b.swId]; + return s.image(b.swId); } @@ -300,7 +300,7 @@ void VCommandBuffer::beginRendering(const FrameBufferDesc& fbo, size_t fboSize, for(size_t i=0; i(fbo.sw[i]); - imageView = t.views[fbo.imgId[i]]; + imageView = t.view(fbo.imgId[i]); imageFormat = t.format(); } else { diff --git a/Engine/gapi/vulkan/vdevice.cpp b/Engine/gapi/vulkan/vdevice.cpp index e1b88bf4..3a32b81a 100644 --- a/Engine/gapi/vulkan/vdevice.cpp +++ b/Engine/gapi/vulkan/vdevice.cpp @@ -824,7 +824,7 @@ void VDevice::deviceQueueProps(VkPhysicalDevice device, VkProps& props) { static const VkQueueFlags rqFlag = (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT); const bool graphicsSupport = ((queueFamily.queueFlags & rqFlag)==rqFlag); - const bool presentSupport = VSwapchain::checkPresentSupport(device, i); + const bool presentSupport = VSwapchain::checkPresentationSupport(device, i); if(graphicsSupport) graphics = i; @@ -843,21 +843,34 @@ void VDevice::deviceQueueProps(VkPhysicalDevice device, VkProps& props) { props.presentFamily = present; } -VDevice::SwapChainSupport VDevice::querySwapChainSupport(VkPhysicalDevice device, VkSurfaceKHR surface) { - SwapChainSupport details; - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); +VDevice::SwapChainSupport VDevice::querySwapChainSupport(VkPhysicalDevice device, VkSurfaceKHR surface) const { + SwapChainSupport support; + vkAssert(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &support.capabilities)); - uint32_t formatCount = 0; - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); - details.formats.resize(formatCount); - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); - - uint32_t presentModeCount = 0; - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); - details.presentModes.resize(presentModeCount); - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + // NOTE: SurfaceFormats query either works, or returns error. + // Vulkan spec doesn't allow for semi-errors, such as surface-lost + while(true) { + uint32_t formatCount = 0; + vkAssert(vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr)); + support.formats.resize(formatCount); + auto code = vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, support.formats.data()); + if(code==VK_INCOMPLETE) + continue; + vkAssert(code); + break; + } - return details; + while(true) { + uint32_t presentModeCount = 0; + vkAssert(vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr)); + support.presentModes.resize(presentModeCount); + auto code = vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, support.presentModes.data()); + if(code==VK_INCOMPLETE) + continue; + vkAssert(code); + break; + } + return support; } VDevice::MemIndex VDevice::memoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags props, VkImageTiling tiling) const { diff --git a/Engine/gapi/vulkan/vdevice.h b/Engine/gapi/vulkan/vdevice.h index 43629a60..73272e50 100644 --- a/Engine/gapi/vulkan/vdevice.h +++ b/Engine/gapi/vulkan/vdevice.h @@ -41,6 +41,8 @@ inline void vkAssert(VkResult code){ case VK_ERROR_OUT_OF_HOST_MEMORY: //throw std::system_error(Tempest::GraphicsErrc::OutOfHostMemory); throw std::bad_alloc(); + case VK_ERROR_SURFACE_LOST_KHR: + throw SurfaceLost(); case VK_ERROR_DEVICE_LOST: throw DeviceLostException(); @@ -395,7 +397,7 @@ class VDevice : public AbstractGraphicsApi::Device { static void deviceFormatProps(VkPhysicalDevice device, VkProps& props); static void deviceQueueProps(VkPhysicalDevice device, VkProps& props); - SwapChainSupport querySwapChainSupport(VkSurfaceKHR surface) { return querySwapChainSupport(physicalDevice,surface); } + SwapChainSupport querySwapChainSupport(VkSurfaceKHR surface) const { return querySwapChainSupport(physicalDevice,surface); } MemIndex memoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags props, VkImageTiling tiling) const; using DataMgr = UploadEngine; @@ -480,7 +482,7 @@ class VDevice : public AbstractGraphicsApi::Device { void pickPhysicalDevice(); - SwapChainSupport querySwapChainSupport(VkPhysicalDevice device, VkSurfaceKHR surface); + SwapChainSupport querySwapChainSupport(VkPhysicalDevice device, VkSurfaceKHR surface) const; }; }} diff --git a/Engine/gapi/vulkan/vswapchain.cpp b/Engine/gapi/vulkan/vswapchain.cpp index 658ef4f3..6422d942 100644 --- a/Engine/gapi/vulkan/vswapchain.cpp +++ b/Engine/gapi/vulkan/vswapchain.cpp @@ -16,6 +16,9 @@ # define VK_USE_PLATFORM_WIN32_KHR # include # include +#elif defined(__ANDROID__) +# include +# include #elif defined(__UNIX__) # define VK_USE_PLATFORM_XLIB_KHR # include @@ -103,7 +106,8 @@ VSwapchain::FenceList::~FenceList() { } void VSwapchain::FenceList::waitAll() { - vkWaitForFences(dev, size, data.get(), VK_TRUE,std::numeric_limits::max()); + if(size>0) + vkWaitForFences(dev, size, data.get(), VK_TRUE,std::numeric_limits::max()); } @@ -149,25 +153,93 @@ VSwapchain::SemaphoreList::~SemaphoreList() { vkDestroySemaphore(dev,data[i],nullptr); } +VSwapchain::ImageList::ImageList(VDevice& device, VkSwapchainKHR swapChain, VkFormat format) + : device(&device) { + try { + uint32_t imgCount=0; + vkAssert(vkGetSwapchainImagesKHR(device.device.impl, swapChain, &imgCount, nullptr)); + images.resize(imgCount); + vkAssert(vkGetSwapchainImagesKHR(device.device.impl, swapChain, &imgCount, images.data())); + + views.resize(images.size()); + + for(size_t i=0; ifboMap; + for(auto imageView : views) + if(imageView!=VK_NULL_HANDLE) + map.notifyDestroy(imageView); + cleanup(); + } + +void VSwapchain::ImageList::cleanup() { + for(auto imageView : views) + if(imageView!=VK_NULL_HANDLE) + vkDestroyImageView(device->device.impl,imageView,nullptr); + } + + VSwapchain::VSwapchain(VDevice &device, SystemApi::Window* hwnd) :device(device), hwnd(hwnd) { try { - surface = createSurface(device.instance, hwnd); + createSwapchain(device); } catch(...) { cleanup(); throw; } - createSwapchain(device); } VSwapchain::~VSwapchain() { cleanup(); } -bool VSwapchain::checkPresentSupport(VkPhysicalDevice device, uint32_t queueFamilyIndex) { +bool VSwapchain::checkPresentationSupport(VkPhysicalDevice device, uint32_t queueFamilyIndex) { #if defined(__WINDOWS__) const bool presentSupport = vkGetPhysicalDeviceWin32PresentationSupportKHR(device, queueFamilyIndex)!=VK_FALSE; + return presentSupport; +#elif defined(__ANDROID__) + // All Android graphics queues support presentation. + (void)device; + (void)queueFamilyIndex; + return true; #elif defined(__UNIX__) bool presentSupport = false; if(auto dpy = reinterpret_cast(X11Api::display())){ @@ -175,10 +247,35 @@ bool VSwapchain::checkPresentSupport(VkPhysicalDevice device, uint32_t queueFami auto visualId = XVisualIDFromVisual(DefaultVisual(dpy,screen)); presentSupport = vkGetPhysicalDeviceXlibPresentationSupportKHR(device,queueFamilyIndex,dpy,visualId)!=VK_FALSE; } + return presentSupport; #else -#warning "wsi for vulkan not implemented on this platform" +# warning "wsi for vulkan not implemented on this platform" + return false; +#endif + } + +VkResult VSwapchain::createSurface(VkInstance instance, void* hwnd, VkSurfaceKHR* pSurface) { +#ifdef __WINDOWS__ + VkWin32SurfaceCreateInfoKHR createInfo={}; + createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + createInfo.hinstance = GetModuleHandleA(nullptr); + createInfo.hwnd = HWND(hwnd); + return vkCreateWin32SurfaceKHR(instance,&createInfo,nullptr,pSurface); +#elif defined(__ANDROID__) + VkAndroidSurfaceCreateInfoKHR createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; + createInfo.window = reinterpret_cast(hwnd); + return vkCreateAndroidSurfaceKHR(instance,&createInfo,nullptr,pSurface); +#elif defined(__UNIX__) + VkXlibSurfaceCreateInfoKHR createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; + createInfo.dpy = reinterpret_cast(X11Api::display()); + createInfo.window = ::Window(hwnd); + return vkCreateXlibSurfaceKHR(instance, &createInfo, nullptr, pSurface); +#else +# warning "wsi for vulkan not implemented on this platform" + return VK_ERROR_UNKNOWN; #endif - return presentSupport; } void VSwapchain::cleanupSwapchain() noexcept { @@ -191,24 +288,17 @@ void VSwapchain::cleanupSwapchain() noexcept { presentFence = FenceList(); aquireSem = SemaphoreList(); presentSem = SemaphoreList(); - - for(auto imageView : views) - if(map!=nullptr && imageView!=VK_NULL_HANDLE) - map->notifyDestroy(imageView); - map = nullptr; - for(auto imageView : views) - if(imageView!=VK_NULL_HANDLE) - vkDestroyImageView(device.device.impl,imageView,nullptr); - views.clear(); + imageList = ImageList(); sync.clear(); - images.clear(); if(swapChain!=VK_NULL_HANDLE) vkDestroySwapchainKHR(device.device.impl,swapChain,nullptr); swapChain = VK_NULL_HANDLE; swapChainImageFormat = VK_FORMAT_UNDEFINED; swapChainExtent = {}; + imgIndex = 0; + frameId = 0; } void VSwapchain::cleanupSurface() noexcept { @@ -230,24 +320,11 @@ void VSwapchain::cleanup() noexcept { VkSurfaceKHR VSwapchain::createSurface(VkInstance instance, void* hwnd) { if(hwnd==nullptr) return VK_NULL_HANDLE; + if(surface!=VK_NULL_HANDLE) + return surface; VkSurfaceKHR ret = VK_NULL_HANDLE; -#ifdef __WINDOWS__ - VkWin32SurfaceCreateInfoKHR createInfo={}; - createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; - createInfo.hinstance = GetModuleHandleA(nullptr); - createInfo.hwnd = HWND(hwnd); - if(vkCreateWin32SurfaceKHR(instance,&createInfo,nullptr,&ret)!=VK_SUCCESS) + if(createSurface(instance, hwnd, &ret)!=VK_SUCCESS) throw std::system_error(Tempest::GraphicsErrc::NoDevice); -#elif defined(__UNIX__) - VkXlibSurfaceCreateInfoKHR createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; - createInfo.dpy = reinterpret_cast(X11Api::display()); - createInfo.window = ::Window(hwnd); - if(vkCreateXlibSurfaceKHR(instance, &createInfo, nullptr, &ret)!=VK_SUCCESS) - throw std::system_error(Tempest::GraphicsErrc::NoDevice); -#else -#warning "wsi for vulkan not implemented on this platform" -#endif return ret; } @@ -262,13 +339,14 @@ void VSwapchain::createSwapchain(VDevice& device) { return; try { + surface = createSurface(device.instance, hwnd); auto support = device.querySwapChainSupport(surface); - uint32_t imgCount = findImageCount(support); - auto code = createSwapchain(device,support,rect,imgCount); + VkResult code = createSwapchain(device,support,rect); if(isSwapchainLost(code)) { cleanupSwapchain(); continue; } + vkAssert(code); break; } catch(...) { @@ -278,16 +356,18 @@ void VSwapchain::createSwapchain(VDevice& device) { } } -VkResult VSwapchain::createSwapchain(VDevice& device, const SwapChainSupport& swapChainSupport, - const Rect& rect, uint32_t imgCount) { +VkResult VSwapchain::createSwapchain(VDevice& device, const SwapChainSupport& swapChainSupport, const Rect& rect) { VkBool32 support=false; - vkGetPhysicalDeviceSurfaceSupportKHR(device.physicalDevice,device.presentQueue->family,surface,&support); + auto code = vkGetPhysicalDeviceSurfaceSupportKHR(device.physicalDevice,device.presentQueue->family,surface,&support); + vkAssert(code); if(!support) throw std::system_error(Tempest::GraphicsErrc::NoDevice); VkSurfaceFormatKHR surfaceFormat = findSwapSurfaceFormat(swapChainSupport.formats); VkPresentModeKHR presentMode = findSwapPresentMode (swapChainSupport.presentModes); VkExtent2D extent = findSwapExtent (swapChainSupport.capabilities,uint32_t(rect.w),uint32_t(rect.h)); + auto alphaMode = findAlphaMode(swapChainSupport.capabilities.supportedCompositeAlpha); + uint32_t imgCount = findImageCount (swapChainSupport); VkSwapchainCreateInfoKHR createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; @@ -310,58 +390,30 @@ VkResult VSwapchain::createSwapchain(VDevice& device, const SwapChainSupport& sw } createInfo.preTransform = swapChainSupport.capabilities.currentTransform; - createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.compositeAlpha = alphaMode; createInfo.presentMode = presentMode; createInfo.clipped = VK_FALSE; - if(vkCreateSwapchainKHR(device.device.impl, &createInfo, nullptr, &swapChain) != VK_SUCCESS) - throw std::system_error(Tempest::GraphicsErrc::NoDevice); + code = vkCreateSwapchainKHR(device.device.impl, &createInfo, nullptr, &swapChain); + if(code!=VK_SUCCESS) + return code; swapChainImageFormat = surfaceFormat.format; swapChainExtent = extent; swapChaincurrentCaps = swapChainSupport.capabilities; - createImageViews(device); + imageList = ImageList(device, swapChain, swapChainImageFormat); - sync.resize(views.size()); + sync.resize(imageList.size()); - aquireFence = FenceList(device.device.impl, uint32_t(views.size())); - presentFence = FenceList(device.device.impl, uint32_t(views.size())); - presentSem = SemaphoreList(device.device.impl, uint32_t(views.size())); - aquireSem = SemaphoreList(device.device.impl, uint32_t(views.size())); + aquireFence = FenceList(device.device.impl, imageList.size()); + presentFence = FenceList(device.device.impl, imageList.size()); + presentSem = SemaphoreList(device.device.impl, imageList.size()); + aquireSem = SemaphoreList(device.device.impl, imageList.size()); return implAcquireNextImage(); } -void VSwapchain::createImageViews(VDevice &device) { - uint32_t imgCount=0; - vkGetSwapchainImagesKHR(device.device.impl, swapChain, &imgCount, nullptr); - images.resize(imgCount); - vkGetSwapchainImagesKHR(device.device.impl, swapChain, &imgCount, images.data()); - - views.resize(images.size()); - - for(size_t i=0; i& availableFormats) const { if(availableFormats.size()==1 && availableFormats[0].format==VK_FORMAT_UNDEFINED) return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; @@ -396,6 +448,21 @@ VkPresentModeKHR VSwapchain::findSwapPresentMode(const std::vector modes={ + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR + }; + + for(auto alpha : modes) { + if((supported & alpha)!=0) + return alpha; + } + throw std::system_error(Tempest::GraphicsErrc::NoDevice); + } + VkExtent2D VSwapchain::findSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, uint32_t w, uint32_t h) const { if(capabilities.currentExtent.width!=std::numeric_limits::max()) { return capabilities.currentExtent; @@ -427,7 +494,8 @@ bool VSwapchain::isSwapchainLost(VkResult code) const { // WA for issues on some linux distros (https://github.com/Try/OpenGothic/issues/977) // probably would have to exclude currentTransform soon due to android VkSurfaceCapabilitiesKHR capabilities = {}; - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device.physicalDevice, surface, &capabilities); + if(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device.physicalDevice, surface, &capabilities)!=VK_SUCCESS) + return true; capabilities.currentExtent = findSwapExtent(capabilities, swapChainExtent.width, swapChainExtent.height); @@ -474,17 +542,14 @@ VkResult VSwapchain::implAcquireNextImage() { aquireSem[frameId], aquireFence[frameId], &id); - - if(code==VK_ERROR_OUT_OF_DATE_KHR) { + if(code!=VK_SUCCESS && code!=VK_SUBOPTIMAL_KHR) { + // Failed acquisition leaves the fence unsignaled, including on fatal errors. auto rc = vkxRevertFence(device.device.impl, &aquireFence[frameId]); if(rc!=VK_SUCCESS) std::terminate(); // unrecoverable return code; } - if(code!=VK_SUCCESS && code!=VK_SUBOPTIMAL_KHR) - vkAssert(code); - imgIndex = id; slot.state = S_Pending; slot.imgId = id; @@ -530,7 +595,7 @@ void VSwapchain::present() { presentInfo.pSwapchains = &swapChain; presentInfo.pImageIndices = &imgIndex; - frameId = (frameId + 1)%images.size(); + frameId = (frameId + 1)%imageList.size(); slot.state = S_Idle; slot.imgId = uint32_t(-1); slot.acquire = VK_NULL_HANDLE; @@ -554,4 +619,3 @@ void VSwapchain::present() { } #endif - diff --git a/Engine/gapi/vulkan/vswapchain.h b/Engine/gapi/vulkan/vswapchain.h index faa6bc70..4c36c4bb 100644 --- a/Engine/gapi/vulkan/vswapchain.h +++ b/Engine/gapi/vulkan/vswapchain.h @@ -17,7 +17,7 @@ class VSwapchain : public AbstractGraphicsApi::Swapchain { ~VSwapchain() override; VSwapchain& operator=(VSwapchain&& other) = delete; - static bool checkPresentSupport(VkPhysicalDevice device, uint32_t queueFamilyIndex); + static bool checkPresentationSupport(VkPhysicalDevice device, uint32_t queueFamilyIndex); struct SwapChainSupport final { VkSurfaceCapabilitiesKHR capabilities={}; @@ -30,16 +30,13 @@ class VSwapchain : public AbstractGraphicsApi::Swapchain { uint32_t h() const override { return swapChainExtent.height; } void reset() override; - uint32_t imageCount() const override { return uint32_t(views.size()); } + uint32_t imageCount() const override { return uint32_t(imageList.views.size()); } uint32_t currentBackBufferIndex() override; void present(); - VFramebufferMap* map = nullptr; - - VkSwapchainKHR swapChain=VK_NULL_HANDLE; - std::vector views; - std::vector images; + VkImage image(size_t i) const { return imageList.images[i]; } + VkImageView view (size_t i) const { return imageList.views[i]; } enum SyncState : uint8_t { S_Idle, @@ -89,14 +86,34 @@ class VSwapchain : public AbstractGraphicsApi::Swapchain { uint32_t size = 0; }; + class ImageList { + public: + ImageList() = default; + ImageList(VDevice& dev, VkSwapchainKHR swapChain, VkFormat format); + ImageList(ImageList&& oth); + ImageList& operator = (ImageList&& oth); + ~ImageList(); + + uint32_t size() const { return uint32_t(images.size()); } + + std::vector views; + std::vector images; + + private: + void cleanup(); + VDevice* device = {}; + }; + FenceList aquireFence; SemaphoreList aquireSem; FenceList presentFence; SemaphoreList presentSem; + ImageList imageList; VDevice& device; - SystemApi::Window* hwnd = nullptr; - VkSurfaceKHR surface = VK_NULL_HANDLE; + SystemApi::Window* hwnd = nullptr; + VkSurfaceKHR surface = VK_NULL_HANDLE; + VkSwapchainKHR swapChain = VK_NULL_HANDLE; uint32_t imgIndex = 0; uint32_t frameId = 0; @@ -109,13 +126,14 @@ class VSwapchain : public AbstractGraphicsApi::Swapchain { void cleanupSurface() noexcept; void cleanup() noexcept; + static VkResult createSurface(VkInstance instance, void* hwnd, VkSurfaceKHR* pSurface); VkSurfaceKHR createSurface(VkInstance instance, void* hwnd); void createSwapchain(VDevice& device); - VkResult createSwapchain(VDevice& device, const SwapChainSupport& support, const Rect& rect, uint32_t imgCount); - void createImageViews(VDevice &device); + VkResult createSwapchain(VDevice& device, const SwapChainSupport& support, const Rect& rect); VkSurfaceFormatKHR findSwapSurfaceFormat(const std::vector& availableFormats) const; VkPresentModeKHR findSwapPresentMode(const std::vector& availablePresentModes) const; + VkCompositeAlphaFlagBitsKHR findAlphaMode(VkCompositeAlphaFlagsKHR supported) const; VkExtent2D findSwapExtent(const VkSurfaceCapabilitiesKHR &capabilities, uint32_t w, uint32_t h) const; uint32_t findImageCount(const SwapChainSupport& support) const; diff --git a/Engine/gapi/vulkanapi.cpp b/Engine/gapi/vulkanapi.cpp index 711a04b5..5edcd9d6 100644 --- a/Engine/gapi/vulkanapi.cpp +++ b/Engine/gapi/vulkanapi.cpp @@ -24,9 +24,12 @@ using namespace Tempest::Detail; #define VK_KHR_WIN32_SURFACE_EXTENSION_NAME "VK_KHR_win32_surface" #define VK_KHR_XLIB_SURFACE_EXTENSION_NAME "VK_KHR_xlib_surface" +#define VK_KHR_ANDROID_SURFACE_EXTENSION_NAME "VK_KHR_android_surface" #if defined(__WINDOWS__) #define SURFACE_EXTENSION_NAME VK_KHR_WIN32_SURFACE_EXTENSION_NAME +#elif defined(__ANDROID__) +#define SURFACE_EXTENSION_NAME VK_KHR_ANDROID_SURFACE_EXTENSION_NAME #elif defined(__UNIX__) #define SURFACE_EXTENSION_NAME VK_KHR_XLIB_SURFACE_EXTENSION_NAME #endif diff --git a/Engine/system/api/androidapi.cpp b/Engine/system/api/androidapi.cpp index f9b3996d..fde1e76a 100644 --- a/Engine/system/api/androidapi.cpp +++ b/Engine/system/api/androidapi.cpp @@ -55,16 +55,8 @@ void AndroidApi::updateWindow() { void AndroidApi::onAppCmd(void*, int32_t cmd) { switch(cmd) { case APP_CMD_INIT_WINDOW: - if(mainWindow!=nullptr) { - // TODO: handle native surface recreation in the Vulkan swapchain. - Log::e("Android native window recreation is not implemented"); - std::terminate(); - } updateWindow(); break; - case APP_CMD_TERM_WINDOW: - hasWindow = false; - break; case APP_CMD_WINDOW_RESIZED: case APP_CMD_CONFIG_CHANGED: updateWindow(); @@ -85,7 +77,9 @@ void AndroidApi::onAppCmd(void*, int32_t cmd) { resumed = false; updateFocus(); break; + case APP_CMD_TERM_WINDOW: case APP_CMD_DESTROY: + hasWindow = false; if(mainWindow!=nullptr) { CloseEvent event; AndroidApi::dispatchClose(*mainWindow,event); @@ -103,6 +97,8 @@ static void pollAndroid(android_app* state, int timeout) { while(ALooper_pollOnce(timeout,nullptr,&pending,reinterpret_cast(&source))>=0) { if(source!=nullptr) source->process(state,source); + if(isExit.load()) + break; timeout = 0; } } @@ -116,6 +112,7 @@ SystemApi::Window* AndroidApi::createAndroidWindow(Tempest::Window* owner) { if(isExit.load() || app->destroyRequested!=0) return nullptr; mainWindow = owner; + ANativeWindow_acquire(app->window); return reinterpret_cast(app->window); } @@ -127,7 +124,8 @@ SystemApi::Window* AndroidApi::implCreateWindow(Tempest::Window* owner, ShowMode return createAndroidWindow(owner); } -void AndroidApi::implDestroyWindow(SystemApi::Window*) { +void AndroidApi::implDestroyWindow(SystemApi::Window* w) { + ANativeWindow_release(reinterpret_cast(w)); mainWindow = nullptr; } @@ -234,7 +232,8 @@ extern "C" void android_main(android_app* state) { if(app->destroyRequested==0) ANativeActivity_finish(app->activity); // Re-entering main would reuse application statics from the previous run. - std::exit(result); + // The application has unwound; process-wide destructors can race Android runtime threads. + std::_Exit(result); } #endif diff --git a/Examples/Android/AndroidManifest.xml b/Examples/Android/AndroidManifest.xml index 1915b1bd..bb43ae66 100644 --- a/Examples/Android/AndroidManifest.xml +++ b/Examples/Android/AndroidManifest.xml @@ -1,9 +1,10 @@ + + android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout|smallestScreenSize|density|uiMode"> diff --git a/Examples/Android/CMakeLists.txt b/Examples/Android/CMakeLists.txt index 8fedd4f3..a0b6099d 100644 --- a/Examples/Android/CMakeLists.txt +++ b/Examples/Android/CMakeLists.txt @@ -6,9 +6,8 @@ if(NOT ANDROID) endif() set(TEMPEST_BUILD_SHARED OFF CACHE BOOL "" FORCE) -# Graphics and audio backends are outside this window lifecycle example. set(TEMPEST_BUILD_AUDIO OFF CACHE BOOL "" FORCE) -set(TEMPEST_BUILD_VULKAN OFF CACHE BOOL "" FORCE) +set(TEMPEST_BUILD_VULKAN ON CACHE BOOL "" FORCE) add_subdirectory(../../Engine Engine) add_library(TempestExample SHARED main.cpp) diff --git a/Examples/Android/README.md b/Examples/Android/README.md index ef4977ae..b53aca18 100644 --- a/Examples/Android/README.md +++ b/Examples/Android/README.md @@ -1,10 +1,12 @@ -# Android window +# Android Vulkan window -A minimal NativeActivity application using Tempest's Android window backend. It forwards window creation, resize, focus, pause, resume and destruction through Tempest's normal application lifecycle. +A minimal NativeActivity application using Tempest's Android window and Vulkan swapchain. The blue background animates while rendering. -Native surface recreation and immersive-mode switching are not implemented yet. The example terminates if Android recreates its drawing surface. +Immersive-mode switching is not implemented yet. -With JDK 17, Gradle 8.9, Ninja and the Android SDK configured (`ANDROID_HOME`), install SDK 35, build-tools 35.0.0, NDK 27.0.12077973 and CMake 3.22.1. Replace `/path/to/ndk` below with the NDK installation directory. +The manifest handles screen, keyboard, navigation and UI-mode configuration changes without recreating the Activity. Native-window destruction closes the application, and Vulkan surface loss (`VK_ERROR_SURFACE_LOST_KHR`) is fatal. Android may destroy the window when the application is backgrounded or the screen is locked. + +With JDK 17, Gradle 8.9, Ninja and the Android SDK configured (`ANDROID_HOME`), install SDK 35, build-tools 35.0.0, NDK 27.0.12077973 and CMake 3.22.1. Set `VULKAN_SDK` to a host Vulkan SDK installation for current headers and put its `glslangValidator` on `PATH`. Android links the NDK's Vulkan loader. Replace `/path/to/ndk` below with the NDK installation directory. ```sh cmake -S Examples/Android -B build/android-example -G Ninja -DCMAKE_TOOLCHAIN_FILE=/path/to/ndk/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-24 -DCMAKE_BUILD_TYPE=Release diff --git a/Examples/Android/main.cpp b/Examples/Android/main.cpp index 786fc0ca..f4f792ca 100644 --- a/Examples/Android/main.cpp +++ b/Examples/Android/main.cpp @@ -1,12 +1,42 @@ #include +#include #include +#include #include +#include #include class Example final : public Tempest::Window { + public: + Example(Tempest::Device& device) : device(device), swapchain(device,hwnd()) { + } + + ~Example() override { + device.waitIdle(); + } + private: + void render() override { + try { + fence.wait(); + { + auto enc = commands.startEncoding(device); + const float blue = float(Tempest::Application::tickCount()%2000)/2000.f; + enc.setFramebuffer({{swapchain[swapchain.currentImage()],Tempest::Vec4(0,0,blue,1),Tempest::Preserve}}); + } + fence = device.submit(commands); + device.present(swapchain); + } + catch(const Tempest::SwapchainSuboptimal&) { + device.waitIdle(); + swapchain.reset(); + } + } + void resizeEvent(Tempest::SizeEvent& event) override { Tempest::Log::i("Window resized: ",event.w,"x",event.h); + device.waitIdle(); + swapchain.reset(); Tempest::Window::resizeEvent(event); } @@ -14,10 +44,17 @@ class Example final : public Tempest::Window { Tempest::Log::i(event.in ? "Window resumed" : "Window paused"); Tempest::Window::focusEvent(event); } + + Tempest::Device& device; + Tempest::Swapchain swapchain; + Tempest::CommandBuffer commands; + Tempest::Fence fence; }; int main(int, char**) { Tempest::Application app; - Example window; + Tempest::VulkanApi api; + Tempest::Device device(api); + Example window(device); return app.exec(); }