From e9677c1e27044b2e62f713635a5f6913bf262823 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 16 Aug 2025 13:14:32 +0200 Subject: [PATCH 1/3] Next attempt at Android CI --- .github/workflows/build.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ebb9866cf..038eca508 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -85,3 +85,28 @@ jobs: # ln -s ~/VulkanSDK/latest/macOS/lib/MoltenVK.xcframework # xcodebuild -scheme examples-ios build CODE_SIGNING_ALLOWED=NO # xcodebuild -scheme examples-macos build + + build_android: + name: "Build Android" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: "recursive" + + - name: Install JDK + uses: actions/setup-java@v4 + with: + java-version: "21" + distribution: "zulu" + cache: gradle + + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2.9 + with: + key: ${{ github.job }}-android + + - name: Build + working-directory: android + run: NDK_CCACHE=1 NDK_CCACHE_BIN=ccache ./gradlew assembleDebug--info \ No newline at end of file From 11a641b3399aec494c0aa404da0d5355b6cab490 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 16 Aug 2025 13:21:44 +0200 Subject: [PATCH 2/3] Android CI --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 038eca508..1ac028260 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -109,4 +109,4 @@ jobs: - name: Build working-directory: android - run: NDK_CCACHE=1 NDK_CCACHE_BIN=ccache ./gradlew assembleDebug--info \ No newline at end of file + run: NDK_CCACHE=1 NDK_CCACHE_BIN=ccache ./gradlew assembleDebug --info \ No newline at end of file From c896091c29725648cd59924bde1ac3eba22c7619 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 17 Aug 2025 11:43:40 +0200 Subject: [PATCH 3/3] Base class cleanup Use designated initializers --- base/vulkanexamplebase.cpp | 472 ++++++++++++++++--------------------- base/vulkanexamplebase.h | 13 +- 2 files changed, 210 insertions(+), 275 deletions(-) diff --git a/base/vulkanexamplebase.cpp b/base/vulkanexamplebase.cpp index 6e1e747e7..0d8668c24 100644 --- a/base/vulkanexamplebase.cpp +++ b/base/vulkanexamplebase.cpp @@ -55,13 +55,10 @@ VkResult VulkanExampleBase::createInstance() // Get extensions supported by the instance and store for later use uint32_t extCount = 0; vkEnumerateInstanceExtensionProperties(nullptr, &extCount, nullptr); - if (extCount > 0) - { + if (extCount > 0) { std::vector extensions(extCount); - if (vkEnumerateInstanceExtensionProperties(nullptr, &extCount, &extensions.front()) == VK_SUCCESS) - { - for (VkExtensionProperties& extension : extensions) - { + if (vkEnumerateInstanceExtensionProperties(nullptr, &extCount, &extensions.front()) == VK_SUCCESS) { + for (VkExtensionProperties& extension : extensions) { supportedInstanceExtensions.push_back(extension.extensionName); } } @@ -76,13 +73,10 @@ VkResult VulkanExampleBase::createInstance() #endif // Enabled requested instance extensions - if (!enabledInstanceExtensions.empty()) - { - for (const char * enabledExtension : enabledInstanceExtensions) - { + if (!enabledInstanceExtensions.empty()) { + for (const char * enabledExtension : enabledInstanceExtensions) { // Output message if requested extension is not available - if (std::find(supportedInstanceExtensions.begin(), supportedInstanceExtensions.end(), enabledExtension) == supportedInstanceExtensions.end()) - { + if (std::find(supportedInstanceExtensions.begin(), supportedInstanceExtensions.end(), enabledExtension) == supportedInstanceExtensions.end()) { std::cerr << "Enabled instance extension \"" << enabledExtension << "\" is not present at instance level\n"; } instanceExtensions.push_back(enabledExtension); @@ -98,15 +92,15 @@ VkResult VulkanExampleBase::createInstance() enabledDeviceExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); } - VkApplicationInfo appInfo{}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - appInfo.pApplicationName = name.c_str(); - appInfo.pEngineName = name.c_str(); - appInfo.apiVersion = apiVersion; + VkApplicationInfo appInfo{ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = name.c_str(), + .pEngineName = name.c_str(), + .apiVersion = apiVersion }; - VkInstanceCreateInfo instanceCreateInfo{}; - instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - instanceCreateInfo.pApplicationInfo = &appInfo; + VkInstanceCreateInfo instanceCreateInfo{ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &appInfo }; VkDebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCI{}; if (settings.validation) { @@ -117,8 +111,7 @@ VkResult VulkanExampleBase::createInstance() #if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) && defined(VK_KHR_portability_enumeration) // SRS - When running on iOS/macOS with MoltenVK and VK_KHR_portability_enumeration is defined and supported by the instance, enable the extension and the flag - if (std::find(supportedInstanceExtensions.begin(), supportedInstanceExtensions.end(), VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) != supportedInstanceExtensions.end()) - { + if (std::find(supportedInstanceExtensions.begin(), supportedInstanceExtensions.end(), VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) != supportedInstanceExtensions.end()) { instanceExtensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); instanceCreateInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; } @@ -188,40 +181,84 @@ std::string VulkanExampleBase::getWindowTitle() const return windowTitle; } -void VulkanExampleBase::createCommandBuffers() -{ - VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast(drawCmdBuffers.size())); - VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, drawCmdBuffers.data())); -} - -void VulkanExampleBase::destroyCommandBuffers() -{ - vkFreeCommandBuffers(device, cmdPool, static_cast(drawCmdBuffers.size()), drawCmdBuffers.data()); -} - std::string VulkanExampleBase::getShadersPath() const { return getShaderBasePath() + shaderDir + "/"; } -void VulkanExampleBase::createPipelineCache() -{ - VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {}; - pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; - VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache)); -} - void VulkanExampleBase::prepare() { - createSurface(); - createCommandPool(); + // Create a OS specific surface that is used to present onto +#if defined(_WIN32) + swapChain.initSurface(windowInstance, window); +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + swapChain.initSurface(androidApp->window); +#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK)) + swapChain.initSurface(view); +#elif defined(VK_USE_PLATFORM_METAL_EXT) + swapChain.initSurface(metalLayer); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + swapChain.initSurface(dfb, surface); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + swapChain.initSurface(display, surface); +#elif defined(VK_USE_PLATFORM_XCB_KHR) + swapChain.initSurface(connection, window); +#elif (defined(_DIRECT2DISPLAY) || defined(VK_USE_PLATFORM_HEADLESS_EXT)) + swapChain.initSurface(width, height); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + swapChain.initSurface(screen_context, screen_window); +#endif + // Swapchain creation is moved to a separate function as this needs to be recreated at runtime, e.g. when resizing createSwapChain(); - createCommandBuffers(); - createSynchronizationPrimitives(); + + // Create static base Vulkan objects shared by all samples. These never change at runtime. + + // Pool used to allocate command buffers from + VkCommandPoolCreateInfo cmdPoolInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = swapChain.queueNodeIndex }; + VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &cmdPool)); + + // Command buffers used to submit draw commands + VkCommandBufferAllocateInfo cmdBufAllocateInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = cmdPool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = static_cast(drawCmdBuffers.size()) + }; + VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, drawCmdBuffers.data())); + + // Optional pipeline cache + VkPipelineCacheCreateInfo pipelineCacheCreateInfo = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO + }; + VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache)); + + // Synchronization primitives + // Wait fences to sync command buffer access + VkFenceCreateInfo fenceCreateInfo = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = VK_FENCE_CREATE_SIGNALED_BIT }; + for (auto& fence : waitFences) { + VK_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence)); + } + // Used to ensure that image presentation is complete before starting to submit again + for (auto& semaphore : presentCompleteSemaphores) { + VkSemaphoreCreateInfo semaphoreCI{ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; + VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &semaphore)); + } + // Semaphore used to ensure that all commands submitted have been finished before submitting the image to the queue + renderCompleteSemaphores.resize(swapChain.images.size()); + for (auto& semaphore : renderCompleteSemaphores) { + VkSemaphoreCreateInfo semaphoreCI{ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; + VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &semaphore)); + } + + // Create dynamic base Vulkan objects shared by all samples. These can change at runtime. setupDepthStencil(); setupRenderPass(); - createPipelineCache(); setupFrameBuffer(); + + // User interface for samples that make use of it settings.overlay = settings.overlay && (!benchmark.active); if (settings.overlay) { ui.maxConcurrentFrames = maxConcurrentFrames; @@ -238,15 +275,15 @@ void VulkanExampleBase::prepare() VkPipelineShaderStageCreateInfo VulkanExampleBase::loadShader(std::string fileName, VkShaderStageFlagBits stage) { - VkPipelineShaderStageCreateInfo shaderStage = {}; - shaderStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - shaderStage.stage = stage; + VkPipelineShaderStageCreateInfo shaderStage{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = stage, + .pName = "main" }; #if defined(VK_USE_PLATFORM_ANDROID_KHR) shaderStage.module = vks::tools::loadShader(androidApp->activity->assetManager, fileName.c_str(), device); #else shaderStage.module = vks::tools::loadShader(fileName.c_str(), device); #endif - shaderStage.pName = "main"; assert(shaderStage.module != VK_NULL_HANDLE); shaderModules.push_back(shaderStage.module); return shaderStage; @@ -636,17 +673,14 @@ void VulkanExampleBase::updateOverlay() return; ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)width, (float)height); io.DeltaTime = frameTimer; - io.MousePos = ImVec2(mouseState.position.x, mouseState.position.y); io.MouseDown[0] = mouseState.buttons.left && ui.visible; io.MouseDown[1] = mouseState.buttons.right && ui.visible; io.MouseDown[2] = mouseState.buttons.middle && ui.visible; ImGui::NewFrame(); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); ImGui::SetNextWindowPos(ImVec2(10 * ui.scale, 10 * ui.scale)); ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiSetCond_FirstUseEver); @@ -664,7 +698,6 @@ void VulkanExampleBase::updateOverlay() #if defined(VK_USE_PLATFORM_ANDROID_KHR) ImGui::PopStyleVar(); #endif - ImGui::End(); ImGui::PopStyleVar(); ImGui::Render(); @@ -678,7 +711,7 @@ void VulkanExampleBase::updateOverlay() #endif } -void VulkanExampleBase::drawUI(const VkCommandBuffer commandBuffer) +void VulkanExampleBase::drawUI(VkCommandBuffer commandBuffer) { if (settings.overlay && ui.visible) { const VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); @@ -716,23 +749,25 @@ void VulkanExampleBase::submitFrame(bool skipQueueSubmit) { if (!skipQueueSubmit) { const VkPipelineStageFlags waitPipelineStage{ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; - VkSubmitInfo submitInfo{ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO }; - submitInfo.pWaitDstStageMask = &waitPipelineStage; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; - submitInfo.pWaitSemaphores = &presentCompleteSemaphores[currentBuffer]; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pSignalSemaphores = &renderCompleteSemaphores[currentImageIndex]; - submitInfo.signalSemaphoreCount = 1; + VkSubmitInfo submitInfo{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &presentCompleteSemaphores[currentBuffer], + .pWaitDstStageMask = &waitPipelineStage, + .commandBufferCount = 1, + .pCommandBuffers = &drawCmdBuffers[currentBuffer], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &renderCompleteSemaphores[currentImageIndex] }; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, waitFences[currentBuffer])); } - VkPresentInfoKHR presentInfo{ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; - presentInfo.waitSemaphoreCount = 1; - presentInfo.pWaitSemaphores = &renderCompleteSemaphores[currentImageIndex]; - presentInfo.swapchainCount = 1; - presentInfo.pSwapchains = &swapChain.swapChain; - presentInfo.pImageIndices = ¤tImageIndex; + VkPresentInfoKHR presentInfo{ + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &renderCompleteSemaphores[currentImageIndex], + .swapchainCount = 1, + .pSwapchains = &swapChain.swapChain, + .pImageIndices = ¤tImageIndex }; VkResult result = vkQueuePresentKHR(queue, &presentInfo); // Recreate the swapchain if it's no longer compatible with the surface (OUT_OF_DATE) or no longer optimal for presentation (SUBOPTIMAL) if ((result == VK_ERROR_OUT_OF_DATE_KHR) || (result == VK_SUBOPTIMAL_KHR)) { @@ -880,8 +915,7 @@ VulkanExampleBase::VulkanExampleBase() #if defined(_WIN32) // Enable console if validation is active, debug message callback will output to it - if (this->settings.validation) - { + if (this->settings.validation) { setupConsole("Vulkan example"); } setupDPIAwareness(); @@ -892,54 +926,41 @@ VulkanExampleBase::~VulkanExampleBase() { // Clean up Vulkan resources swapChain.cleanup(); - if (descriptorPool != VK_NULL_HANDLE) - { + if (descriptorPool != VK_NULL_HANDLE) { vkDestroyDescriptorPool(device, descriptorPool, nullptr); } - destroyCommandBuffers(); - if (renderPass != VK_NULL_HANDLE) - { + vkFreeCommandBuffers(device, cmdPool, static_cast(drawCmdBuffers.size()), drawCmdBuffers.data()); + if (renderPass != VK_NULL_HANDLE) { vkDestroyRenderPass(device, renderPass, nullptr); } - for (auto& frameBuffer : frameBuffers) - { + for (auto& frameBuffer : frameBuffers) { vkDestroyFramebuffer(device, frameBuffer, nullptr); } - for (auto& shaderModule : shaderModules) - { + for (auto& shaderModule : shaderModules) { vkDestroyShaderModule(device, shaderModule, nullptr); } vkDestroyImageView(device, depthStencil.view, nullptr); vkDestroyImage(device, depthStencil.image, nullptr); vkFreeMemory(device, depthStencil.memory, nullptr); - vkDestroyPipelineCache(device, pipelineCache, nullptr); - vkDestroyCommandPool(device, cmdPool, nullptr); - for (auto& fence : waitFences) { vkDestroyFence(device, fence, nullptr); } - for (auto& semaphore : presentCompleteSemaphores) { vkDestroySemaphore(device, semaphore, nullptr); } for (auto& semaphore : renderCompleteSemaphores) { vkDestroySemaphore(device, semaphore, nullptr); } - if (settings.overlay) { ui.freeResources(); } - delete vulkanDevice; - - if (settings.validation) - { + if (settings.validation) { vks::debug::freeDebugCallback(instance); } - vkDestroyInstance(instance, nullptr); #if defined(_DIRECT2DISPLAY) @@ -1003,8 +1024,7 @@ bool VulkanExampleBase::initVulkan() #endif // If requested, we enable the default validation layers for debugging - if (settings.validation) - { + if (settings.validation) { vks::debug::setupDebugging(instance); } @@ -2931,69 +2951,35 @@ void VulkanExampleBase::keyPressed(uint32_t) {} void VulkanExampleBase::mouseMoved(double x, double y, bool & handled) {} -void VulkanExampleBase::createSynchronizationPrimitives() -{ - // Wait fences to sync command buffer access - VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT); - for (auto& fence : waitFences) { - VK_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence)); - } - // Used to ensure that image presentation is complete before starting to submit again - for (auto& semaphore : presentCompleteSemaphores) { - VkSemaphoreCreateInfo semaphoreCI{ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; - VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &semaphore)); - } - // Semaphore used to ensure that all commands submitted have been finished before submitting the image to the queue - renderCompleteSemaphores.resize(swapChain.images.size()); - for (auto& semaphore : renderCompleteSemaphores) { - VkSemaphoreCreateInfo semaphoreCI{ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; - VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &semaphore)); - } -} - -void VulkanExampleBase::createCommandPool() -{ - VkCommandPoolCreateInfo cmdPoolInfo = {}; - cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - cmdPoolInfo.queueFamilyIndex = swapChain.queueNodeIndex; - cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &cmdPool)); -} - void VulkanExampleBase::setupDepthStencil() { - VkImageCreateInfo imageCI{}; - imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageCI.imageType = VK_IMAGE_TYPE_2D; - imageCI.format = depthFormat; - imageCI.extent = { width, height, 1 }; - imageCI.mipLevels = 1; - imageCI.arrayLayers = 1; - imageCI.samples = VK_SAMPLE_COUNT_1_BIT; - imageCI.tiling = VK_IMAGE_TILING_OPTIMAL; - imageCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; - + VkImageCreateInfo imageCI{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .format = depthFormat, + .extent = { width, height, 1 }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT }; VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &depthStencil.image)); VkMemoryRequirements memReqs{}; vkGetImageMemoryRequirements(device, depthStencil.image, &memReqs); - VkMemoryAllocateInfo memAllloc{}; - memAllloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - memAllloc.allocationSize = memReqs.size; - memAllloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VkMemoryAllocateInfo memAllloc{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memReqs.size, + .memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) }; VK_CHECK_RESULT(vkAllocateMemory(device, &memAllloc, nullptr, &depthStencil.memory)); VK_CHECK_RESULT(vkBindImageMemory(device, depthStencil.image, depthStencil.memory, 0)); - VkImageViewCreateInfo imageViewCI{}; - imageViewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D; - imageViewCI.image = depthStencil.image; - imageViewCI.format = depthFormat; - imageViewCI.subresourceRange.baseMipLevel = 0; - imageViewCI.subresourceRange.levelCount = 1; - imageViewCI.subresourceRange.baseArrayLayer = 0; - imageViewCI.subresourceRange.layerCount = 1; - imageViewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + VkImageViewCreateInfo imageViewCI{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = depthStencil.image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = depthFormat, + .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .levelCount = 1, .layerCount = 1 } }; // Stencil aspect should only be set on depth + stencil formats (VK_FORMAT_D16_UNORM_S8_UINT..VK_FORMAT_D32_SFLOAT_S8_UINT if (depthFormat >= VK_FORMAT_D16_UNORM_S8_UINT) { imageViewCI.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; @@ -3005,94 +2991,82 @@ void VulkanExampleBase::setupFrameBuffer() { // Create frame buffers for every swap chain image frameBuffers.resize(swapChain.images.size()); - for (uint32_t i = 0; i < frameBuffers.size(); i++) - { + for (uint32_t i = 0; i < frameBuffers.size(); i++) { const VkImageView attachments[2] = { swapChain.imageViews[i], // Depth/Stencil attachment is the same for all frame buffers depthStencil.view }; - VkFramebufferCreateInfo frameBufferCreateInfo{}; - frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - frameBufferCreateInfo.renderPass = renderPass; - frameBufferCreateInfo.attachmentCount = 2; - frameBufferCreateInfo.pAttachments = attachments; - frameBufferCreateInfo.width = width; - frameBufferCreateInfo.height = height; - frameBufferCreateInfo.layers = 1; + VkFramebufferCreateInfo frameBufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .renderPass = renderPass, + .attachmentCount = 2, + .pAttachments = attachments, + .width = width, + .height = height, + .layers = 1 }; VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCreateInfo, nullptr, &frameBuffers[i])); } } void VulkanExampleBase::setupRenderPass() { - std::array attachments = {}; - // Color attachment - attachments[0].format = swapChain.colorFormat; - attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; - attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - // Depth attachment - attachments[1].format = depthFormat; - attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE; - attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkAttachmentReference colorReference = {}; - colorReference.attachment = 0; - colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkAttachmentReference depthReference = {}; - depthReference.attachment = 1; - depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpassDescription = {}; - subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpassDescription.colorAttachmentCount = 1; - subpassDescription.pColorAttachments = &colorReference; - subpassDescription.pDepthStencilAttachment = &depthReference; - subpassDescription.inputAttachmentCount = 0; - subpassDescription.pInputAttachments = nullptr; - subpassDescription.preserveAttachmentCount = 0; - subpassDescription.pPreserveAttachments = nullptr; - subpassDescription.pResolveAttachments = nullptr; - - // Subpass dependencies for layout transitions + std::array attachments{}; + attachments[0] = { + .format = swapChain.colorFormat, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR }; + attachments[1] = { + .format = depthFormat, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; + + VkAttachmentReference colorReference{ .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; + VkAttachmentReference depthReference{ .attachment = 1, .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; + + VkSubpassDescription subpassDescription{ + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .colorAttachmentCount = 1, + .pColorAttachments = &colorReference, + .pDepthStencilAttachment = &depthReference }; + + // Subpass dependencies used for implicit layout transitions std::array dependencies{}; - - dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; - dependencies[0].dstSubpass = 0; - dependencies[0].srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - dependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - dependencies[0].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - dependencies[0].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; - dependencies[0].dependencyFlags = 0; - - dependencies[1].srcSubpass = VK_SUBPASS_EXTERNAL; - dependencies[1].dstSubpass = 0; - dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependencies[1].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependencies[1].srcAccessMask = 0; - dependencies[1].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; - dependencies[1].dependencyFlags = 0; - - VkRenderPassCreateInfo renderPassInfo = {}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = static_cast(attachments.size()); - renderPassInfo.pAttachments = attachments.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpassDescription; - renderPassInfo.dependencyCount = static_cast(dependencies.size()); - renderPassInfo.pDependencies = dependencies.data(); - + dependencies[0] = { + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, + .dependencyFlags = 0 }; + dependencies[1] = { + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, + .dependencyFlags = 0 }; + + VkRenderPassCreateInfo renderPassInfo{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .attachmentCount = static_cast(attachments.size()), + .pAttachments = attachments.data(), + .subpassCount = 1, + .pSubpasses = &subpassDescription, + .dependencyCount = static_cast(dependencies.size()), + .pDependencies = dependencies.data() }; VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass)); } @@ -3102,22 +3076,18 @@ void VulkanExampleBase::getEnabledExtensions() {} void VulkanExampleBase::windowResize() { - if (!prepared) - { + if (!prepared) { return; } prepared = false; resized = true; - // Ensure all operations on the device have been finished before destroying resources vkDeviceWaitIdle(device); - // Recreate swap chain width = destWidth; height = destHeight; createSwapChain(); - - // Recreate the frame buffers + // All resources that are atteched to or reference the swap chain need to be recreated vkDestroyImageView(device, depthStencil.view, nullptr); vkDestroyImage(device, depthStencil.image, nullptr); vkFreeMemory(device, depthStencil.memory, nullptr); @@ -3126,33 +3096,28 @@ void VulkanExampleBase::windowResize() vkDestroyFramebuffer(device, frameBuffer, nullptr); } setupFrameBuffer(); - if ((width > 0.0f) && (height > 0.0f)) { if (settings.overlay) { ui.resize(width, height); } } - - for (auto& semaphore : presentCompleteSemaphores) { - vkDestroySemaphore(device, semaphore, nullptr); - } - for (auto& semaphore : renderCompleteSemaphores) { - vkDestroySemaphore(device, semaphore, nullptr); - } - for (auto& fence : waitFences) { - vkDestroyFence(device, fence, nullptr); + // In rare cases, swapchain image count might change at rutime, so we need to recreate attached sync objects + if (swapChain.imageCount != static_cast(presentCompleteSemaphores.size())) { + for (auto& semaphore : renderCompleteSemaphores) { + vkDestroySemaphore(device, semaphore, nullptr); + } + renderCompleteSemaphores.resize(swapChain.images.size()); + for (auto& semaphore : renderCompleteSemaphores) { + VkSemaphoreCreateInfo semaphoreCI{ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; + VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &semaphore)); + } } - createSynchronizationPrimitives(); - vkDeviceWaitIdle(device); - if ((width > 0.0f) && (height > 0.0f)) { camera.updateAspectRatio((float)width / (float)height); } - // Notify derived class windowResized(); - prepared = true; } @@ -3188,29 +3153,6 @@ void VulkanExampleBase::handleMouseMove(int32_t x, int32_t y) void VulkanExampleBase::windowResized() {} -void VulkanExampleBase::createSurface() -{ -#if defined(_WIN32) - swapChain.initSurface(windowInstance, window); -#elif defined(VK_USE_PLATFORM_ANDROID_KHR) - swapChain.initSurface(androidApp->window); -#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK)) - swapChain.initSurface(view); -#elif defined(VK_USE_PLATFORM_METAL_EXT) - swapChain.initSurface(metalLayer); -#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) - swapChain.initSurface(dfb, surface); -#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) - swapChain.initSurface(display, surface); -#elif defined(VK_USE_PLATFORM_XCB_KHR) - swapChain.initSurface(connection, window); -#elif (defined(_DIRECT2DISPLAY) || defined(VK_USE_PLATFORM_HEADLESS_EXT)) - swapChain.initSurface(width, height); -#elif defined(VK_USE_PLATFORM_SCREEN_QNX) - swapChain.initSurface(screen_context, screen_window); -#endif -} - void VulkanExampleBase::createSwapChain() { swapChain.create(width, height, settings.vsync, settings.fullscreen); diff --git a/base/vulkanexamplebase.h b/base/vulkanexamplebase.h index ef24ecd2d..a6c44083f 100644 --- a/base/vulkanexamplebase.h +++ b/base/vulkanexamplebase.h @@ -85,13 +85,8 @@ class VulkanExampleBase void handleMouseMove(int32_t x, int32_t y); void nextFrame(); void updateOverlay(); - void createPipelineCache(); - void createCommandPool(); - void createSynchronizationPrimitives(); - void createSurface(); void createSwapChain(); - void createCommandBuffers(); - void destroyCommandBuffers(); + VkResult createInstance(); std::string shaderDir = "glsl"; protected: // Returns the path to the root of the glsl, hlsl or slang shader directory. @@ -146,7 +141,7 @@ class VulkanExampleBase VulkanSwapChain swapChain; // Synchronization related objects and variables - // These are used to have multiple frame buffers "in flight" to get some CPU/GPU parallelism + // These are used to have multiple frames "in flight" to get some CPU/GPU parallelism uint32_t currentImageIndex{ 0 }; uint32_t currentBuffer{ 0 }; std::array presentCompleteSemaphores{}; @@ -342,8 +337,6 @@ class VulkanExampleBase #else void setupWindow(); #endif - /** @brief (Virtual) Creates the application wide Vulkan instance */ - virtual VkResult createInstance(); /** @brief (Pure virtual) Render function to be implemented by the sample application */ virtual void render() = 0; /** @brief (Virtual) Called after a key was pressed, can be used to do custom key handling */ @@ -375,7 +368,7 @@ class VulkanExampleBase void renderLoop(); /** @brief Adds the drawing commands for the ImGui overlay to the given command buffer */ - void drawUI(const VkCommandBuffer commandBuffer); + void drawUI(VkCommandBuffer commandBuffer); /** Prepare the next frame for workload submission by acquiring the next swap chain image and waiting for the previous command buffer to finish */ void prepareFrame(bool waitForFence = true);