diff --git a/README.md b/README.md index b25d671a..ea7f6541 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ Advanced example that uses sub passes and input attachments to write and read ba Basic offscreen rendering in two passes. First pass renders the mirrored scene to a separate framebuffer with color and depth attachments, second pass samples from that color attachment for rendering a mirror surface. -#### [CPU particle system](examples/particlefire/) +#### [CPU particle system](examples/particlesystem/) Implements a simple CPU based particle system. Particle data is stored in host memory, updated on the CPU per-frame and synchronized with the device before it's rendered using pre-multiplied alpha. diff --git a/android/examples/particlefire/CMakeLists.txt b/android/examples/particlesystem/CMakeLists.txt similarity index 97% rename from android/examples/particlefire/CMakeLists.txt rename to android/examples/particlesystem/CMakeLists.txt index f107918e..6f02a2f8 100644 --- a/android/examples/particlefire/CMakeLists.txt +++ b/android/examples/particlesystem/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR) -set(NAME particlefire) +set(NAME particlesystem) set(SRC_DIR ../../../examples/${NAME}) set(BASE_DIR ../../../base) diff --git a/android/examples/particlefire/build.gradle b/android/examples/particlesystem/build.gradle similarity index 91% rename from android/examples/particlefire/build.gradle rename to android/examples/particlesystem/build.gradle index 4b264e5c..054da9c9 100644 --- a/android/examples/particlefire/build.gradle +++ b/android/examples/particlesystem/build.gradle @@ -4,7 +4,7 @@ apply from: '../gradle/outputfilename.gradle' android { compileSdkVersion rootProject.ext.compileSdkVersion defaultConfig { - applicationId "de.saschawillems.vulkanParticlefire" + applicationId "de.saschawillems.vulkanParticlesystem" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 @@ -49,8 +49,8 @@ task copyTask { } copy { - from rootProject.ext.shaderPath + 'glsl/particlefire' - into 'assets/shaders/glsl/particlefire' + from rootProject.ext.shaderPath + 'glsl/particlesystem' + into 'assets/shaders/glsl/particlesystem' include '*.*' } diff --git a/android/examples/particlefire/src/main/AndroidManifest.xml b/android/examples/particlesystem/src/main/AndroidManifest.xml similarity index 94% rename from android/examples/particlefire/src/main/AndroidManifest.xml rename to android/examples/particlesystem/src/main/AndroidManifest.xml index c8f97268..4c7d04e4 100644 --- a/android/examples/particlefire/src/main/AndroidManifest.xml +++ b/android/examples/particlesystem/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + package="de.saschawillems.vulkanParticlesystem"> (vkGetInstanceProcAddr(instance, "vkDestroySurfaceKHR")); vkCmdFillBuffer = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdFillBuffer")); + + vkGetPhysicalDeviceSurfaceSupportKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceSupportKHR")); + vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR")); + vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceFormatsKHR")); + vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfacePresentModesKHR")); + vkCreateSwapchainKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateSwapchainKHR")); + vkDestroySwapchainKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroySwapchainKHR")); + vkGetSwapchainImagesKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetSwapchainImagesKHR")); + vkAcquireNextImageKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkAcquireNextImageKHR")); + vkQueuePresentKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkQueuePresentKHR")); + + vkResetCommandBuffer = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkResetCommandBuffer")); } void freeVulkanLibrary() diff --git a/base/VulkanAndroid.h b/base/VulkanAndroid.h index 966f2845..f5e8b799 100644 --- a/base/VulkanAndroid.h +++ b/base/VulkanAndroid.h @@ -1,7 +1,7 @@ /* * Android Vulkan function pointer prototypes * -* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ @@ -12,6 +12,7 @@ #define VULKANANDROID_H // Vulkan needs to be loaded dynamically on android +// While SDK 26 (and up) come with a loader, we also want to support older devices, so we manually load function pointers #pragma once @@ -158,6 +159,18 @@ extern PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR; extern PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR; extern PFN_vkCmdFillBuffer vkCmdFillBuffer; +extern PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR; +extern PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR; +extern PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR; +extern PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR; +extern PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR; +extern PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR; +extern PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR; +extern PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR; +extern PFN_vkQueuePresentKHR vkQueuePresentKHR; + +extern PFN_vkResetCommandBuffer vkResetCommandBuffer; + namespace vks { namespace android diff --git a/base/VulkanHeightmap.hpp b/base/VulkanHeightmap.hpp deleted file mode 100644 index 08f66e3b..00000000 --- a/base/VulkanHeightmap.hpp +++ /dev/null @@ -1,259 +0,0 @@ -/* -* Heightmap terrain generator -* -* Copyright (C) by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include -#include - -#include "vulkan/vulkan.h" -#include "VulkanDevice.h" -#include "VulkanBuffer.h" -#include -#include - -namespace vks -{ - class HeightMap - { - private: - uint16_t *heightdata; - uint32_t dim; - uint32_t scale; - - vks::VulkanDevice *device = nullptr; - VkQueue copyQueue = VK_NULL_HANDLE; - public: - enum Topology { topologyTriangles, topologyQuads }; - - float heightScale = 1.0f; - float uvScale = 1.0f; - - vks::Buffer vertexBuffer; - vks::Buffer indexBuffer; - - struct Vertex { - glm::vec3 pos; - glm::vec3 normal; - glm::vec2 uv; - }; - - size_t vertexBufferSize = 0; - size_t indexBufferSize = 0; - uint32_t indexCount = 0; - - HeightMap(vks::VulkanDevice *device, VkQueue copyQueue) - { - this->device = device; - this->copyQueue = copyQueue; - }; - - ~HeightMap() - { - vertexBuffer.destroy(); - indexBuffer.destroy(); - delete[] heightdata; - } - - float getHeight(uint32_t x, uint32_t y) - { - glm::ivec2 rpos = glm::ivec2(x, y) * glm::ivec2(scale); - rpos.x = std::max(0, std::min(rpos.x, (int)dim - 1)); - rpos.y = std::max(0, std::min(rpos.y, (int)dim - 1)); - rpos /= glm::ivec2(scale); - return *(heightdata + (rpos.x + rpos.y * dim) * scale) / 65535.0f * heightScale; - } - -#if defined(__ANDROID__) - void loadFromFile(const std::string filename, uint32_t patchsize, glm::vec3 scale, Topology topology, AAssetManager* assetManager) -#else - void loadFromFile(const std::string filename, uint32_t patchsize, glm::vec3 scale, Topology topology) -#endif - { - assert(device); - assert(copyQueue != VK_NULL_HANDLE); - - ktxResult result; - ktxTexture* ktxTexture; -#if defined(__ANDROID__) - AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING); - assert(asset); - size_t size = AAsset_getLength(asset); - assert(size > 0); - void *textureData = malloc(size); - AAsset_read(asset, textureData, size); - AAsset_close(asset); - result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, target); - free(textureData); -#else - result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture); -#endif - assert(result == KTX_SUCCESS); - ktx_size_t ktxSize = ktxTexture_GetImageSize(ktxTexture, 0); - ktx_uint8_t* ktxImage = ktxTexture_GetData(ktxTexture); - dim = ktxTexture->baseWidth; - heightdata = new uint16_t[dim * dim]; - memcpy(heightdata, ktxImage, ktxSize); - this->scale = dim / patchsize; - ktxTexture_Destroy(ktxTexture); - - // Generate vertices - Vertex * vertices = new Vertex[patchsize * patchsize * 4]; - - const float wx = 2.0f; - const float wy = 2.0f; - - for (uint32_t x = 0; x < patchsize; x++) - { - for (uint32_t y = 0; y < patchsize; y++) - { - uint32_t index = (x + y * patchsize); - vertices[index].pos[0] = (x * wx + wx / 2.0f - (float)patchsize * wx / 2.0f) * scale.x; - vertices[index].pos[1] = -getHeight(x, y); - vertices[index].pos[2] = (y * wy + wy / 2.0f - (float)patchsize * wy / 2.0f) * scale.z; - vertices[index].uv = glm::vec2((float)x / patchsize, (float)y / patchsize) * uvScale; - } - } - - for (uint32_t y = 0; y < patchsize; y++) - { - for (uint32_t x = 0; x < patchsize; x++) - { - float dx = getHeight(x < patchsize - 1 ? x + 1 : x, y) - getHeight(x > 0 ? x - 1 : x, y); - if (x == 0 || x == patchsize - 1) - dx *= 2.0f; - - float dy = getHeight(x, y < patchsize - 1 ? y + 1 : y) - getHeight(x, y > 0 ? y - 1 : y); - if (y == 0 || y == patchsize - 1) - dy *= 2.0f; - - glm::vec3 A = glm::vec3(1.0f, 0.0f, dx); - glm::vec3 B = glm::vec3(0.0f, 1.0f, dy); - - glm::vec3 normal = (glm::normalize(glm::cross(A, B)) + 1.0f) * 0.5f; - - vertices[x + y * patchsize].normal = glm::vec3(normal.x, normal.z, normal.y); - } - } - - // Generate indices - - const uint32_t w = (patchsize - 1); - uint32_t *indices; - - switch (topology) - { - // Indices for triangles - case topologyTriangles: - { - indices = new uint32_t[w * w * 6]; - for (uint32_t x = 0; x < w; x++) - { - for (uint32_t y = 0; y < w; y++) - { - uint32_t index = (x + y * w) * 6; - indices[index] = (x + y * patchsize); - indices[index + 1] = indices[index] + patchsize; - indices[index + 2] = indices[index + 1] + 1; - - indices[index + 3] = indices[index + 1] + 1; - indices[index + 4] = indices[index] + 1; - indices[index + 5] = indices[index]; - } - } - indexCount = (patchsize - 1) * (patchsize - 1) * 6; - indexBufferSize = (w * w * 6) * sizeof(uint32_t); - break; - } - // Indices for quad patches (tessellation) - case topologyQuads: - { - - indices = new uint32_t[w * w * 4]; - for (uint32_t x = 0; x < w; x++) - { - for (uint32_t y = 0; y < w; y++) - { - uint32_t index = (x + y * w) * 4; - indices[index] = (x + y * patchsize); - indices[index + 1] = indices[index] + patchsize; - indices[index + 2] = indices[index + 1] + 1; - indices[index + 3] = indices[index] + 1; - } - } - indexCount = (patchsize - 1) * (patchsize - 1) * 4; - indexBufferSize = (w * w * 4) * sizeof(uint32_t); - break; - } - - } - - assert(indexBufferSize > 0); - - vertexBufferSize = (patchsize * patchsize * 4) * sizeof(Vertex); - - // Generate Vulkan buffers - - vks::Buffer vertexStaging, indexStaging; - - // Create staging buffers - device->createBuffer( - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &vertexStaging, - vertexBufferSize, - vertices); - - device->createBuffer( - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &indexStaging, - indexBufferSize, - indices); - - // Device local (target) buffer - device->createBuffer( - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - &vertexBuffer, - vertexBufferSize); - - device->createBuffer( - VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - &indexBuffer, - indexBufferSize); - - // Copy from staging buffers - VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - - VkBufferCopy copyRegion = {}; - - copyRegion.size = vertexBufferSize; - vkCmdCopyBuffer( - copyCmd, - vertexStaging.buffer, - vertexBuffer.buffer, - 1, - ©Region); - - copyRegion.size = indexBufferSize; - vkCmdCopyBuffer( - copyCmd, - indexStaging.buffer, - indexBuffer.buffer, - 1, - ©Region); - - device->flushCommandBuffer(copyCmd, copyQueue, true); - - vkDestroyBuffer(device->logicalDevice, vertexStaging.buffer, nullptr); - vkFreeMemory(device->logicalDevice, vertexStaging.memory, nullptr); - vkDestroyBuffer(device->logicalDevice, indexStaging.buffer, nullptr); - vkFreeMemory(device->logicalDevice, indexStaging.memory, nullptr); - } - }; -} diff --git a/base/VulkanSwapChain.cpp b/base/VulkanSwapChain.cpp index e12405dd..fcc17be4 100644 --- a/base/VulkanSwapChain.cpp +++ b/base/VulkanSwapChain.cpp @@ -3,7 +3,7 @@ * * A swap chain is a collection of framebuffers used for rendering and presentation to the windowing system * -* Copyright (C) 2016-2021 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ @@ -103,7 +103,7 @@ void VulkanSwapChain::initSurface(uint32_t width, uint32_t height) std::vector supportsPresent(queueCount); for (uint32_t i = 0; i < queueCount; i++) { - fpGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface, &supportsPresent[i]); + vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface, &supportsPresent[i]); } // Search for a graphics and a present queue in the array of queue @@ -157,44 +157,30 @@ void VulkanSwapChain::initSurface(uint32_t width, uint32_t height) // Get list of supported surface formats uint32_t formatCount; - VK_CHECK_RESULT(fpGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, NULL)); + VK_CHECK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, NULL)); assert(formatCount > 0); std::vector surfaceFormats(formatCount); - VK_CHECK_RESULT(fpGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, surfaceFormats.data())); + VK_CHECK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, surfaceFormats.data())); - // If the surface format list only includes one entry with VK_FORMAT_UNDEFINED, - // there is no preferred format, so we assume VK_FORMAT_B8G8R8A8_UNORM - if ((formatCount == 1) && (surfaceFormats[0].format == VK_FORMAT_UNDEFINED)) - { - colorFormat = VK_FORMAT_B8G8R8A8_UNORM; - colorSpace = surfaceFormats[0].colorSpace; - } - else - { - // iterate over the list of available surface format and - // check for the presence of VK_FORMAT_B8G8R8A8_UNORM - bool found_B8G8R8A8_UNORM = false; - for (auto&& surfaceFormat : surfaceFormats) - { - if (surfaceFormat.format == VK_FORMAT_B8G8R8A8_UNORM) - { - colorFormat = surfaceFormat.format; - colorSpace = surfaceFormat.colorSpace; - found_B8G8R8A8_UNORM = true; - break; - } - } + // We want to get a format that best suits our needs, so we try to get one from a set of preferred formats + // Initialize the format to the first one returned by the implementation in case we can't find one of the preffered formats + VkSurfaceFormatKHR selectedFormat = surfaceFormats[0]; + std::vector preferredImageFormats = { + VK_FORMAT_B8G8R8A8_UNORM, + VK_FORMAT_R8G8B8A8_UNORM, + VK_FORMAT_A8B8G8R8_UNORM_PACK32 + }; - // in case VK_FORMAT_B8G8R8A8_UNORM is not available - // select the first available color format - if (!found_B8G8R8A8_UNORM) - { - colorFormat = surfaceFormats[0].format; - colorSpace = surfaceFormats[0].colorSpace; + for (auto& availableFormat : surfaceFormats) { + if (std::find(preferredImageFormats.begin(), preferredImageFormats.end(), availableFormat.format) != preferredImageFormats.end()) { + selectedFormat = availableFormat; + break; } } + colorFormat = selectedFormat.format; + colorSpace = selectedFormat.colorSpace; } /** @@ -210,16 +196,6 @@ void VulkanSwapChain::connect(VkInstance instance, VkPhysicalDevice physicalDevi this->instance = instance; this->physicalDevice = physicalDevice; this->device = device; - fpGetPhysicalDeviceSurfaceSupportKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceSupportKHR")); - fpGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR")); - fpGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceFormatsKHR")); - fpGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfacePresentModesKHR")); - - fpCreateSwapchainKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCreateSwapchainKHR")); - fpDestroySwapchainKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkDestroySwapchainKHR")); - fpGetSwapchainImagesKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetSwapchainImagesKHR")); - fpAcquireNextImageKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkAcquireNextImageKHR")); - fpQueuePresentKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkQueuePresentKHR")); } /** @@ -236,15 +212,15 @@ void VulkanSwapChain::create(uint32_t *width, uint32_t *height, bool vsync, bool // Get physical device surface properties and formats VkSurfaceCapabilitiesKHR surfCaps; - VK_CHECK_RESULT(fpGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &surfCaps)); + VK_CHECK_RESULT(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &surfCaps)); // Get available present modes uint32_t presentModeCount; - VK_CHECK_RESULT(fpGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, NULL)); + VK_CHECK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, NULL)); assert(presentModeCount > 0); std::vector presentModes(presentModeCount); - VK_CHECK_RESULT(fpGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, presentModes.data())); + VK_CHECK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, presentModes.data())); VkExtent2D swapchainExtent = {}; // If width (and height) equals the special value 0xFFFFFFFF, the size of the surface will be set by the swapchain @@ -363,7 +339,7 @@ void VulkanSwapChain::create(uint32_t *width, uint32_t *height, bool vsync, bool swapchainCI.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; } - VK_CHECK_RESULT(fpCreateSwapchainKHR(device, &swapchainCI, nullptr, &swapChain)); + VK_CHECK_RESULT(vkCreateSwapchainKHR(device, &swapchainCI, nullptr, &swapChain)); // If an existing swap chain is re-created, destroy the old swap chain // This also cleans up all the presentable images @@ -373,13 +349,13 @@ void VulkanSwapChain::create(uint32_t *width, uint32_t *height, bool vsync, bool { vkDestroyImageView(device, buffers[i].view, nullptr); } - fpDestroySwapchainKHR(device, oldSwapchain, nullptr); + vkDestroySwapchainKHR(device, oldSwapchain, nullptr); } - VK_CHECK_RESULT(fpGetSwapchainImagesKHR(device, swapChain, &imageCount, NULL)); + VK_CHECK_RESULT(vkGetSwapchainImagesKHR(device, swapChain, &imageCount, NULL)); // Get the swap chain images images.resize(imageCount); - VK_CHECK_RESULT(fpGetSwapchainImagesKHR(device, swapChain, &imageCount, images.data())); + VK_CHECK_RESULT(vkGetSwapchainImagesKHR(device, swapChain, &imageCount, images.data())); // Get the swap chain buffers containing the image and imageview buffers.resize(imageCount); @@ -425,7 +401,7 @@ VkResult VulkanSwapChain::acquireNextImage(VkSemaphore presentCompleteSemaphore, { // By setting timeout to UINT64_MAX we will always wait until the next image has been acquired or an actual error is thrown // With that we don't have to handle VK_NOT_READY - return fpAcquireNextImageKHR(device, swapChain, UINT64_MAX, presentCompleteSemaphore, (VkFence)nullptr, imageIndex); + return vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, presentCompleteSemaphore, (VkFence)nullptr, imageIndex); } /** @@ -451,7 +427,7 @@ VkResult VulkanSwapChain::queuePresent(VkQueue queue, uint32_t imageIndex, VkSem presentInfo.pWaitSemaphores = &waitSemaphore; presentInfo.waitSemaphoreCount = 1; } - return fpQueuePresentKHR(queue, &presentInfo); + return vkQueuePresentKHR(queue, &presentInfo); } @@ -469,7 +445,7 @@ void VulkanSwapChain::cleanup() } if (surface != VK_NULL_HANDLE) { - fpDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroySwapchainKHR(device, swapChain, nullptr); vkDestroySurfaceKHR(instance, surface, nullptr); } surface = VK_NULL_HANDLE; diff --git a/base/VulkanSwapChain.h b/base/VulkanSwapChain.h index 8d591100..8163ad4c 100644 --- a/base/VulkanSwapChain.h +++ b/base/VulkanSwapChain.h @@ -3,7 +3,7 @@ * * A swap chain is a collection of framebuffers used for rendering and presentation to the windowing system * -* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ @@ -39,16 +39,6 @@ private: VkDevice device; VkPhysicalDevice physicalDevice; VkSurfaceKHR surface; - // Function pointers - PFN_vkGetPhysicalDeviceSurfaceSupportKHR fpGetPhysicalDeviceSurfaceSupportKHR; - PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR fpGetPhysicalDeviceSurfaceCapabilitiesKHR; - PFN_vkGetPhysicalDeviceSurfaceFormatsKHR fpGetPhysicalDeviceSurfaceFormatsKHR; - PFN_vkGetPhysicalDeviceSurfacePresentModesKHR fpGetPhysicalDeviceSurfacePresentModesKHR; - PFN_vkCreateSwapchainKHR fpCreateSwapchainKHR; - PFN_vkDestroySwapchainKHR fpDestroySwapchainKHR; - PFN_vkGetSwapchainImagesKHR fpGetSwapchainImagesKHR; - PFN_vkAcquireNextImageKHR fpAcquireNextImageKHR; - PFN_vkQueuePresentKHR fpQueuePresentKHR; public: VkFormat colorFormat; VkColorSpaceKHR colorSpace; diff --git a/base/vulkanexamplebase.cpp b/base/vulkanexamplebase.cpp index e2b81c66..36d64499 100644 --- a/base/vulkanexamplebase.cpp +++ b/base/vulkanexamplebase.cpp @@ -821,7 +821,7 @@ VulkanExampleBase::VulkanExampleBase(bool enableValidation) settings.vsync = true; } if (commandLineParser.isSet("height")) { - height = commandLineParser.getValueAsInt("height", width); + height = commandLineParser.getValueAsInt("height", height); } if (commandLineParser.isSet("width")) { width = commandLineParser.getValueAsInt("width", width); diff --git a/base/vulkanexamplebase.h b/base/vulkanexamplebase.h index 91100f73..19fa6569 100644 --- a/base/vulkanexamplebase.h +++ b/base/vulkanexamplebase.h @@ -80,7 +80,6 @@ private: uint32_t destWidth; uint32_t destHeight; bool resizing = false; - void windowResize(); void handleMouseMove(int32_t x, int32_t y); void nextFrame(); void updateOverlay(); @@ -377,6 +376,8 @@ public: /** @brief Loads a SPIR-V shader file for the given shader stage */ VkPipelineShaderStageCreateInfo loadShader(std::string fileName, VkShaderStageFlagBits stage); + void windowResize(); + /** @brief Entry point for the main render loop */ void renderLoop(); diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index a36c5389..d9c8ec1c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -121,7 +121,7 @@ set(EXAMPLES offscreen oit parallaxmapping - particlefire + particlesystem pbrbasic pbribl pbrtexture diff --git a/examples/graphicspipelinelibrary/graphicspipelinelibrary.cpp b/examples/graphicspipelinelibrary/graphicspipelinelibrary.cpp index 79922a4f..3705451f 100644 --- a/examples/graphicspipelinelibrary/graphicspipelinelibrary.cpp +++ b/examples/graphicspipelinelibrary/graphicspipelinelibrary.cpp @@ -1,7 +1,7 @@ /* * Vulkan Example - Using VK_EXT_graphics_pipeline_library * -* Copyright (C) 2022 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2022-2023 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ @@ -207,7 +207,6 @@ public: { #if defined(__ANDROID__) // Load shader from compressed asset - // @todo AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, fileName, AASSET_MODE_STREAMING); assert(asset); size_t size = AAsset_getLength(asset); @@ -280,7 +279,6 @@ public: VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0); - // @todo: we can skip the pipeline shader module info and directly consume the shader module ShaderInfo shaderInfo{}; loadShaderFile(getShadersPath() + "graphicspipelinelibrary/shared.vert.spv", shaderInfo); @@ -307,6 +305,8 @@ public: pipelineLibraryCI.pViewportState = &viewportState; pipelineLibraryCI.pRasterizationState = &rasterizationState; VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineLibraryCI, nullptr, &pipelineLibrary.preRasterizationShaders)); + + delete[] shaderInfo.code; } // Create a pipeline library for the fragment output interface @@ -442,6 +442,8 @@ public: pipelines.push_back(executable); // Push fragment shader to list for deletion in the sample's destructor pipelineLibrary.fragmentShaders.push_back(fragmentShader); + + delete[] shaderInfo.code; } // Prepare and initialize uniform buffer containing shader uniforms diff --git a/examples/particlefire/particlefire.cpp b/examples/particlesystem/particlesystem.cpp similarity index 96% rename from examples/particlefire/particlefire.cpp rename to examples/particlesystem/particlesystem.cpp index e966d545..3493a5db 100644 --- a/examples/particlefire/particlefire.cpp +++ b/examples/particlesystem/particlesystem.cpp @@ -1,7 +1,7 @@ /* -* Vulkan Example - CPU based fire particle system +* Vulkan Example - CPU based particle system * -* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ @@ -402,7 +402,7 @@ public: // Binding 1: Fire texture array vks::initializers::writeDescriptorSet(descriptorSets.particles, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &texDescriptorFire) }; - vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); // Environment VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.environment)); @@ -415,7 +415,7 @@ public: // Binding 2: Normal map vks::initializers::writeDescriptorSet(descriptorSets.environment, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.floor.normalMap.descriptor), }; - vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } void preparePipelines() @@ -439,7 +439,7 @@ public: pipelineCI.pViewportState = &viewportState; pipelineCI.pDepthStencilState = &depthStencilState; pipelineCI.pDynamicState = &dynamicState; - pipelineCI.stageCount = shaderStages.size(); + pipelineCI.stageCount = static_cast(shaderStages.size()); pipelineCI.pStages = shaderStages.data(); // Particle rendering pipeline @@ -478,8 +478,8 @@ public: blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD; blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - shaderStages[0] = loadShader(getShadersPath() + "particlefire/particle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); - shaderStages[1] = loadShader(getShadersPath() + "particlefire/particle.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + shaderStages[0] = loadShader(getShadersPath() + "particlesystem/particle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(getShadersPath() + "particlesystem/particle.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.particles)); } @@ -492,8 +492,8 @@ public: depthStencilState.depthWriteEnable = VK_TRUE; inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - shaderStages[0] = loadShader(getShadersPath() + "particlefire/normalmap.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); - shaderStages[1] = loadShader(getShadersPath() + "particlefire/normalmap.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + shaderStages[0] = loadShader(getShadersPath() + "particlesystem/normalmap.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(getShadersPath() + "particlesystem/normalmap.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.environment)); } } diff --git a/examples/pipelines/pipelines.cpp b/examples/pipelines/pipelines.cpp index ee135ca6..47c39157 100644 --- a/examples/pipelines/pipelines.cpp +++ b/examples/pipelines/pipelines.cpp @@ -1,7 +1,7 @@ /* * Vulkan Example - Using different pipelines in one single renderpass * -* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ @@ -112,14 +112,14 @@ public: scene.bindBuffers(drawCmdBuffers[i]); // Left : Solid colored - viewport.width = (float)width / 3.0; + viewport.width = (float)width / 3.0f; vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.phong); vkCmdSetLineWidth(drawCmdBuffers[i], 1.0f); scene.draw(drawCmdBuffers[i]); // Center : Toon - viewport.x = (float)width / 3.0; + viewport.x = (float)width / 3.0f; vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.toon); // Line width > 1.0f only if wide lines feature is supported @@ -131,7 +131,7 @@ public: if (enabledFeatures.fillModeNonSolid) { // Right : Wireframe - viewport.x = (float)width / 3.0 + (float)width / 3.0; + viewport.x = (float)width / 3.0f + (float)width / 3.0f; vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.wireframe); scene.draw(drawCmdBuffers[i]); @@ -159,10 +159,7 @@ public: }; VkDescriptorPoolCreateInfo descriptorPoolInfo = - vks::initializers::descriptorPoolCreateInfo( - poolSizes.size(), - poolSizes.data(), - 2); + vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); } @@ -213,7 +210,7 @@ public: &uniformBuffer.descriptor) }; - vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } void preparePipelines() @@ -237,7 +234,7 @@ public: pipelineCI.pViewportState = &viewportState; pipelineCI.pDepthStencilState = &depthStencilState; pipelineCI.pDynamicState = &dynamicState; - pipelineCI.stageCount = shaderStages.size(); + pipelineCI.stageCount = static_cast(shaderStages.size()); pipelineCI.pStages = shaderStages.data(); pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color}); diff --git a/examples/raytracingintersection/raytracingintersection.cpp b/examples/raytracingintersection/raytracingintersection.cpp index a2dd4b44..30b8d253 100644 --- a/examples/raytracingintersection/raytracingintersection.cpp +++ b/examples/raytracingintersection/raytracingintersection.cpp @@ -119,13 +119,13 @@ public: // Spheres VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, sizeof(Sphere)* spheres.size(), spheres.data())); - VK_CHECK_RESULT(vulkanDevice->createBuffer(usageFlags, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &spheresBuffer, sizeof(Sphere)* spheres.size())); + VK_CHECK_RESULT(vulkanDevice->createBuffer(usageFlags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &spheresBuffer, sizeof(Sphere)* spheres.size())); vulkanDevice->copyBuffer(&stagingBuffer, &spheresBuffer, queue); stagingBuffer.destroy(); // AABBs VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, sizeof(AABB)* aabbs.size(), aabbs.data())); - VK_CHECK_RESULT(vulkanDevice->createBuffer(usageFlags, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &aabbsBuffer, sizeof(AABB)* aabbs.size())); + VK_CHECK_RESULT(vulkanDevice->createBuffer(usageFlags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &aabbsBuffer, sizeof(AABB)* aabbs.size())); vulkanDevice->copyBuffer(&stagingBuffer, &aabbsBuffer, queue); stagingBuffer.destroy(); } diff --git a/examples/screenshot/screenshot.cpp b/examples/screenshot/screenshot.cpp index 5e34c303..b48c326f 100644 --- a/examples/screenshot/screenshot.cpp +++ b/examples/screenshot/screenshot.cpp @@ -1,7 +1,7 @@ /* * Vulkan Example - Taking screenshots * -* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ @@ -125,7 +125,7 @@ public: std::vector writeDescriptorSets = { vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), // Binding 0: Vertex shader uniform buffer }; - vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr); + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } void preparePipelines() @@ -153,7 +153,7 @@ public: pipelineCI.pViewportState = &viewportState; pipelineCI.pDepthStencilState = &depthStencilState; pipelineCI.pDynamicState = &dynamicState; - pipelineCI.stageCount = shaderStages.size(); + pipelineCI.stageCount = static_cast(shaderStages.size()); pipelineCI.pStages = shaderStages.data(); pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color}); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); diff --git a/examples/sphericalenvmapping/sphericalenvmapping.cpp b/examples/sphericalenvmapping/sphericalenvmapping.cpp index 9bf8ce3e..dd4c9a6e 100644 --- a/examples/sphericalenvmapping/sphericalenvmapping.cpp +++ b/examples/sphericalenvmapping/sphericalenvmapping.cpp @@ -5,7 +5,7 @@ * * Based on https://www.clicktorelease.com/blog/creating-spherical-environment-mapping-shader * -* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ @@ -120,10 +120,7 @@ public: }; VkDescriptorPoolCreateInfo descriptorPoolInfo = - vks::initializers::descriptorPoolCreateInfo( - poolSizes.size(), - poolSizes.data(), - 2); + vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); } @@ -145,9 +142,7 @@ public: }; VkDescriptorSetLayoutCreateInfo descriptorLayout = - vks::initializers::descriptorSetLayoutCreateInfo( - setLayoutBindings.data(), - setLayoutBindings.size()); + vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); @@ -185,7 +180,7 @@ public: &matCapTextureArray.descriptor) }; - vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } void preparePipelines() @@ -209,7 +204,7 @@ public: pipelineCI.pViewportState = &viewportState; pipelineCI.pDepthStencilState = &depthStencilState; pipelineCI.pDynamicState = &dynamicState; - pipelineCI.stageCount = shaderStages.size(); + pipelineCI.stageCount = static_cast(shaderStages.size()); pipelineCI.pStages = shaderStages.data(); pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color}); diff --git a/examples/texturearray/texturearray.cpp b/examples/texturearray/texturearray.cpp index 7acc5512..fd56c20c 100644 --- a/examples/texturearray/texturearray.cpp +++ b/examples/texturearray/texturearray.cpp @@ -1,7 +1,7 @@ /* * Vulkan Example - Texture arrays and instanced rendering * -* Copyright (C) Sascha Willems - www.saschawillems.de +* Copyright (C) 2016-2023 Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ @@ -226,9 +226,8 @@ public: stagingBuffer, textureArray.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - bufferCopyRegions.size(), - bufferCopyRegions.data() - ); + static_cast(bufferCopyRegions.size()), + bufferCopyRegions.data()); // Change texture image layout to shader read after all faces have been copied textureArray.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; @@ -388,7 +387,7 @@ public: vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) }; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes.size(), poolSizes.data(), 2); + VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); } @@ -400,7 +399,7 @@ public: // Binding 1 : Fragment shader image sampler (texture array) vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1) }; - VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), setLayoutBindings.size()); + VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); @@ -425,7 +424,7 @@ public: // Binding 1 : Fragment shader cubemap sampler vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureDescriptor) }; - vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } void preparePipelines() @@ -438,7 +437,7 @@ public: VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0); std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), dynamicStateEnables.size(), 0); + VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables, 0); // Vertex bindings and attributes VkVertexInputBindingDescription vertexInputBinding = { 0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX }; @@ -467,7 +466,7 @@ public: pipelineCI.pViewportState = &viewportStateCI; pipelineCI.pDepthStencilState = &depthStencilStateCI; pipelineCI.pDynamicState = &dynamicStateCI; - pipelineCI.stageCount = shaderStages.size(); + pipelineCI.stageCount = static_cast(shaderStages.size()); pipelineCI.pStages = shaderStages.data(); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); diff --git a/examples/texturecubemap/texturecubemap.cpp b/examples/texturecubemap/texturecubemap.cpp index 222adc5c..7b1839ce 100644 --- a/examples/texturecubemap/texturecubemap.cpp +++ b/examples/texturecubemap/texturecubemap.cpp @@ -1,7 +1,7 @@ /* * Vulkan Example - Cube map texture loading and displaying * -* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ @@ -256,7 +256,7 @@ public: sampler.mipLodBias = 0.0f; sampler.compareOp = VK_COMPARE_OP_NEVER; sampler.minLod = 0.0f; - sampler.maxLod = cubeMap.mipLevels; + sampler.maxLod = static_cast(cubeMap.mipLevels); sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; sampler.maxAnisotropy = 1.0f; if (vulkanDevice->features.samplerAnisotropy) @@ -364,10 +364,7 @@ public: }; VkDescriptorPoolCreateInfo descriptorPoolInfo = - vks::initializers::descriptorPoolCreateInfo( - poolSizes.size(), - poolSizes.data(), - 2); + vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); } @@ -436,7 +433,7 @@ public: 1, &textureDescriptor) }; - vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); // Sky box descriptor set VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.skybox)); @@ -456,7 +453,7 @@ public: 1, &textureDescriptor) }; - vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } void preparePipelines() @@ -480,7 +477,7 @@ public: pipelineCI.pViewportState = &viewportState; pipelineCI.pDepthStencilState = &depthStencilState; pipelineCI.pDynamicState = &dynamicState; - pipelineCI.stageCount = shaderStages.size(); + pipelineCI.stageCount = static_cast(shaderStages.size()); pipelineCI.pStages = shaderStages.data(); pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal }); diff --git a/examples/texturemipmapgen/texturemipmapgen.cpp b/examples/texturemipmapgen/texturemipmapgen.cpp index d5091e20..61f81486 100644 --- a/examples/texturemipmapgen/texturemipmapgen.cpp +++ b/examples/texturemipmapgen/texturemipmapgen.cpp @@ -1,7 +1,7 @@ /* * Vulkan Example - Runtime mip map generation * -* Copyright (C) by Sascha Willems - www.saschawillems.de +* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ @@ -224,7 +224,7 @@ public: VkCommandBuffer blitCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); // Copy down mips from n-1 to n - for (int32_t i = 1; i < texture.mipLevels; i++) + for (uint32_t i = 1; i < texture.mipLevels; i++) { VkImageBlit imageBlit{}; diff --git a/examples/triangle/triangle.cpp b/examples/triangle/triangle.cpp index 7d5905c3..4e37d517 100644 --- a/examples/triangle/triangle.cpp +++ b/examples/triangle/triangle.cpp @@ -6,7 +6,7 @@ * Contrary to the other examples, this one won't make use of helper functions or initializers * Except in a few cases (swap chain setup e.g.) * -* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ @@ -29,9 +29,9 @@ // Set to "true" to enable Vulkan's validation layers (see vulkandebug.cpp for details) #define ENABLE_VALIDATION false -// Set to "true" to use staging buffers for uploading vertex and index data to device local memory -// See "prepareVertices" for details on what's staging and on why to use it -#define USE_STAGING true +// We want to keep GPU and CPU busy. To do that we may start building a new command buffer while the previous one is still being executed +// This number defines how many frames may be worked on simultaneously at once +#define MAX_CONCURRENT_FRAMES 2 class VulkanExample : public VulkanExampleBase { @@ -56,11 +56,17 @@ public: } indices; // Uniform buffer block object - struct { + struct UniformBuffer { VkDeviceMemory memory; VkBuffer buffer; - VkDescriptorBufferInfo descriptor; - } uniformBufferVS; + // The descriptor set stores the resources bound to the binding points in a shader + // It connects the binding points of the different shaders with the buffers and images used for those bindings + VkDescriptorSet descriptorSet; + // We keep a pointer to the mapped buffer, so we can easily update it's contents via a memcpy + uint8_t* mapped{ nullptr }; + }; + // We use one UBO per frame, so we can have a frame overlap and make sure that uniforms aren't updated while still in use + std::array uniformBuffers; // For simplicity we use the same uniform block layout as in the shader: // @@ -73,11 +79,11 @@ public: // // This way we can just memcopy the ubo data to the ubo // Note: You should use data types that align with the GPU in order to avoid manual padding (vec4, mat4) - struct { + struct ShaderData { glm::mat4 projectionMatrix; glm::mat4 modelMatrix; glm::mat4 viewMatrix; - } uboVS; + }; // The pipeline layout is used by a pipeline to access the descriptor sets // It defines interface (without binding any actual data) between the shader stages used by the pipeline and the shader resources @@ -94,27 +100,23 @@ public: // Like the pipeline layout it's pretty much a blueprint and can be used with different descriptor sets as long as their layout matches VkDescriptorSetLayout descriptorSetLayout; - // The descriptor set stores the resources bound to the binding points in a shader - // It connects the binding points of the different shaders with the buffers and images used for those bindings - VkDescriptorSet descriptorSet; - - // Synchronization primitives // Synchronization is an important concept of Vulkan that OpenGL mostly hid away. Getting this right is crucial to using Vulkan. - // Semaphores - // Used to coordinate operations within the graphics queue and ensure correct command ordering - VkSemaphore presentCompleteSemaphore; - VkSemaphore renderCompleteSemaphore; + // Semaphores are used to coordinate operations within the graphics queue and ensure correct command ordering + std::array presentCompleteSemaphores; + std::array renderCompleteSemaphores; - // Fences - // Used to check the completion of queue operations (e.g. command buffer execution) - std::vector queueCompleteFences; + VkCommandPool commandPool; + std::array commandBuffers; + std::array waitFences; + + uint32_t currentFrame = 0; VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) { title = "Vulkan Example - Basic indexed triangle"; - // To keep things simple, we don't use the UI overlay + // To keep things simple, we don't use the UI overlay from the framework settings.overlay = false; // Setup a default look-at camera camera.type = Camera::CameraType::lookat; @@ -139,15 +141,13 @@ public: vkDestroyBuffer(device, indices.buffer, nullptr); vkFreeMemory(device, indices.memory, nullptr); - vkDestroyBuffer(device, uniformBufferVS.buffer, nullptr); - vkFreeMemory(device, uniformBufferVS.memory, nullptr); - - vkDestroySemaphore(device, presentCompleteSemaphore, nullptr); - vkDestroySemaphore(device, renderCompleteSemaphore, nullptr); - - for (auto& fence : queueCompleteFences) + for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) { - vkDestroyFence(device, fence, nullptr); + vkDestroyFence(device, waitFences[i], nullptr); + vkDestroySemaphore(device, presentCompleteSemaphores[i], nullptr); + vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr); + vkDestroyBuffer(device, uniformBuffers[i].buffer, nullptr); + vkFreeMemory(device, uniformBuffers[i].memory, nullptr); } } @@ -155,7 +155,7 @@ public: // Upon success it will return the index of the memory type that fits our requested memory properties // This is necessary as implementations can offer an arbitrary number of memory types with different // memory properties. - // You can check http://vulkan.gpuinfo.org/ for details on different memory configurations + // You can check https://vulkan.gpuinfo.org/ for details on different memory configurations uint32_t getMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties) { // Iterate over all memory types available for the device used in this example @@ -175,236 +175,52 @@ public: } // Create the Vulkan synchronization primitives used in this example - void prepareSynchronizationPrimitives() + void createSynchronizationPrimitives() { - // Semaphores (Used for correct command ordering) - VkSemaphoreCreateInfo semaphoreCreateInfo = {}; - semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - semaphoreCreateInfo.pNext = nullptr; + for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) { + + // Semaphores (Used for correct command ordering) + VkSemaphoreCreateInfo semaphoreCI{}; + semaphoreCI.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + // Semaphore used to ensure that image presentation is complete before starting to submit again + VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &presentCompleteSemaphores[i])); + // Semaphore used to ensure that all commands submitted have been finished before submitting the image to the queue + VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &renderCompleteSemaphores[i])); - // Semaphore used to ensure that image presentation is complete before starting to submit again - VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &presentCompleteSemaphore)); + // Fences (Used to check draw command buffer completion) + VkFenceCreateInfo fenceCI{}; + fenceCI.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + // Create in signaled state so we don't wait on first render of each command buffer + fenceCI.flags = VK_FENCE_CREATE_SIGNALED_BIT; + VK_CHECK_RESULT(vkCreateFence(device, &fenceCI, nullptr, &waitFences[i])); - // Semaphore used to ensure that all commands submitted have been finished before submitting the image to the queue - VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &renderCompleteSemaphore)); - - // Fences (Used to check draw command buffer completion) - VkFenceCreateInfo fenceCreateInfo = {}; - fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - // Create in signaled state so we don't wait on first render of each command buffer - fenceCreateInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - queueCompleteFences.resize(drawCmdBuffers.size()); - for (auto& fence : queueCompleteFences) - { - VK_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence)); } } - // Get a new command buffer from the command pool - // If begin is true, the command buffer is also started so we can start adding commands - VkCommandBuffer getCommandBuffer(bool begin) + void createCommandBuffers() { - VkCommandBuffer cmdBuffer; + // All command buffers are allocated from a command pool + VkCommandPoolCreateInfo commandPoolCI{}; + commandPoolCI.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + commandPoolCI.queueFamilyIndex = swapChain.queueNodeIndex; + commandPoolCI.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + VK_CHECK_RESULT(vkCreateCommandPool(device, &commandPoolCI, nullptr, &commandPool)); - VkCommandBufferAllocateInfo cmdBufAllocateInfo = {}; - cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - cmdBufAllocateInfo.commandPool = cmdPool; - cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - cmdBufAllocateInfo.commandBufferCount = 1; - - VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &cmdBuffer)); - - // If requested, also start the new command buffer - if (begin) - { - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffer, &cmdBufInfo)); - } - - return cmdBuffer; - } - - // End the command buffer and submit it to the queue - // Uses a fence to ensure command buffer has finished executing before deleting it - void flushCommandBuffer(VkCommandBuffer commandBuffer) - { - assert(commandBuffer != VK_NULL_HANDLE); - - VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer)); - - VkSubmitInfo submitInfo = {}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffer; - - // Create fence to ensure that the command buffer has finished executing - VkFenceCreateInfo fenceCreateInfo = {}; - fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fenceCreateInfo.flags = 0; - VkFence fence; - VK_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence)); - - // Submit to the queue - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence)); - // Wait for the fence to signal that command buffer has finished executing - VK_CHECK_RESULT(vkWaitForFences(device, 1, &fence, VK_TRUE, DEFAULT_FENCE_TIMEOUT)); - - vkDestroyFence(device, fence, nullptr); - vkFreeCommandBuffers(device, cmdPool, 1, &commandBuffer); - } - - // Build separate command buffers for every framebuffer image - // Unlike in OpenGL all rendering commands are recorded once into command buffers that are then resubmitted to the queue - // This allows to generate work upfront and from multiple threads, one of the biggest advantages of Vulkan - void buildCommandBuffers() - { - VkCommandBufferBeginInfo cmdBufInfo = {}; - cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - cmdBufInfo.pNext = nullptr; - - // Set clear values for all framebuffer attachments with loadOp set to clear - // We use two attachments (color and depth) that are cleared at the start of the subpass and as such we need to set clear values for both - VkClearValue clearValues[2]; - clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 1.0f } }; - clearValues[1].depthStencil = { 1.0f, 0 }; - - VkRenderPassBeginInfo renderPassBeginInfo = {}; - renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - renderPassBeginInfo.pNext = nullptr; - renderPassBeginInfo.renderPass = renderPass; - renderPassBeginInfo.renderArea.offset.x = 0; - renderPassBeginInfo.renderArea.offset.y = 0; - renderPassBeginInfo.renderArea.extent.width = width; - renderPassBeginInfo.renderArea.extent.height = height; - renderPassBeginInfo.clearValueCount = 2; - renderPassBeginInfo.pClearValues = clearValues; - - for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) - { - // Set target frame buffer - renderPassBeginInfo.framebuffer = frameBuffers[i]; - - VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); - - // Start the first sub pass specified in our default render pass setup by the base class - // This will clear the color and depth attachment - vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); - - // Update dynamic viewport state - VkViewport viewport = {}; - viewport.height = (float)height; - viewport.width = (float)width; - viewport.minDepth = (float) 0.0f; - viewport.maxDepth = (float) 1.0f; - vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); - - // Update dynamic scissor state - VkRect2D scissor = {}; - scissor.extent.width = width; - scissor.extent.height = height; - scissor.offset.x = 0; - scissor.offset.y = 0; - vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); - - // Bind descriptor sets describing shader binding points - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); - - // Bind the rendering pipeline - // The pipeline (state object) contains all states of the rendering pipeline, binding it will set all the states specified at pipeline creation time - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - - // Bind triangle vertex buffer (contains position and colors) - VkDeviceSize offsets[1] = { 0 }; - vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &vertices.buffer, offsets); - - // Bind triangle index buffer - vkCmdBindIndexBuffer(drawCmdBuffers[i], indices.buffer, 0, VK_INDEX_TYPE_UINT32); - - // Draw indexed triangle - vkCmdDrawIndexed(drawCmdBuffers[i], indices.count, 1, 0, 0, 1); - - vkCmdEndRenderPass(drawCmdBuffers[i]); - - // Ending the render pass will add an implicit barrier transitioning the frame buffer color attachment to - // VK_IMAGE_LAYOUT_PRESENT_SRC_KHR for presenting it to the windowing system - - VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); - } - } - - void draw() - { -#if defined(VK_USE_PLATFORM_MACOS_MVK) - // SRS - on macOS use swapchain helper function with common semaphores/fences for proper resize handling - // Get next image in the swap chain (back/front buffer) - prepareFrame(); - - // Use a fence to wait until the command buffer has finished execution before using it again - VK_CHECK_RESULT(vkWaitForFences(device, 1, &waitFences[currentBuffer], VK_TRUE, UINT64_MAX)); - VK_CHECK_RESULT(vkResetFences(device, 1, &waitFences[currentBuffer])); -#else - // SRS - on other platforms use original bare code with local semaphores/fences for illustrative purposes - // Get next image in the swap chain (back/front buffer) - VkResult acquire = swapChain.acquireNextImage(presentCompleteSemaphore, ¤tBuffer); - if (!((acquire == VK_SUCCESS) || (acquire == VK_SUBOPTIMAL_KHR))) { - VK_CHECK_RESULT(acquire); - } - - // Use a fence to wait until the command buffer has finished execution before using it again - VK_CHECK_RESULT(vkWaitForFences(device, 1, &queueCompleteFences[currentBuffer], VK_TRUE, UINT64_MAX)); - VK_CHECK_RESULT(vkResetFences(device, 1, &queueCompleteFences[currentBuffer])); -#endif - - // Pipeline stage at which the queue submission will wait (via pWaitSemaphores) - VkPipelineStageFlags waitStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - // The submit info structure specifies a command buffer queue submission batch - VkSubmitInfo submitInfo = {}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.pWaitDstStageMask = &waitStageMask; // Pointer to the list of pipeline stages that the semaphore waits will occur at - submitInfo.waitSemaphoreCount = 1; // One wait semaphore - submitInfo.signalSemaphoreCount = 1; // One signal semaphore - submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; // Command buffers(s) to execute in this batch (submission) - submitInfo.commandBufferCount = 1; // One command buffer - -#if defined(VK_USE_PLATFORM_MACOS_MVK) - // SRS - on macOS use swapchain helper function with common semaphores/fences for proper resize handling - submitInfo.pWaitSemaphores = &semaphores.presentComplete; // Semaphore(s) to wait upon before the submitted command buffer starts executing - submitInfo.pSignalSemaphores = &semaphores.renderComplete; // Semaphore(s) to be signaled when command buffers have completed - - // Submit to the graphics queue passing a wait fence - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, waitFences[currentBuffer])); - - // Present the current buffer to the swap chain - submitFrame(); -#else - // SRS - on other platforms use original bare code with local semaphores/fences for illustrative purposes - submitInfo.pWaitSemaphores = &presentCompleteSemaphore; // Semaphore(s) to wait upon before the submitted command buffer starts executing - submitInfo.pSignalSemaphores = &renderCompleteSemaphore; // Semaphore(s) to be signaled when command buffers have completed - - // Submit to the graphics queue passing a wait fence - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, queueCompleteFences[currentBuffer])); - - // Present the current buffer to the swap chain - // Pass the semaphore signaled by the command buffer submission from the submit info as the wait semaphore for swap chain presentation - // This ensures that the image is not presented to the windowing system until all commands have been submitted - VkResult present = swapChain.queuePresent(queue, currentBuffer, renderCompleteSemaphore); - if (!((present == VK_SUCCESS) || (present == VK_SUBOPTIMAL_KHR))) { - VK_CHECK_RESULT(present); - } -#endif + // Allocate one command buffer per max. concurrent frame from above pool + VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, MAX_CONCURRENT_FRAMES); + VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, commandBuffers.data())); } // Prepare vertex and index buffers for an indexed triangle // Also uploads them to device local memory using staging and initializes vertex input and attribute binding to match the vertex shader - void prepareVertices(bool useStagingBuffers) + void createVertexBuffer() { // A note on memory management in Vulkan in general: // This is a very complex topic and while it's fine for an example application to small individual memory allocations that is not // what should be done a real-world application, where you should allocate large chunks of memory at once instead. // Setup vertices - std::vector vertexBuffer = - { + std::vector vertexBuffer{ { { 1.0f, 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, { { -1.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f } }, { { 0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } } @@ -412,166 +228,154 @@ public: uint32_t vertexBufferSize = static_cast(vertexBuffer.size()) * sizeof(Vertex); // Setup indices - std::vector indexBuffer = { 0, 1, 2 }; + std::vector indexBuffer{ 0, 1, 2 }; indices.count = static_cast(indexBuffer.size()); uint32_t indexBufferSize = indices.count * sizeof(uint32_t); - VkMemoryAllocateInfo memAlloc = {}; + VkMemoryAllocateInfo memAlloc{}; memAlloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; VkMemoryRequirements memReqs; - void *data; + // Static data like vertex and index buffer should be stored on the device memory for optimal (and fastest) access by the GPU + // + // To achieve this we use so-called "staging buffers" : + // - Create a buffer that's visible to the host (and can be mapped) + // - Copy the data to this buffer + // - Create another buffer that's local on the device (VRAM) with the same size + // - Copy the data from the host to the device using a command buffer + // - Delete the host visible (staging) buffer + // - Use the device local buffers for rendering + // + // Note: On unified memory architectures where host (CPU) and GPU share the same memory, staging is not necessary + // To keep this sample easy to follow, there is no check for that in place - if (useStagingBuffers) - { - // Static data like vertex and index buffer should be stored on the device memory - // for optimal (and fastest) access by the GPU - // - // To achieve this we use so-called "staging buffers" : - // - Create a buffer that's visible to the host (and can be mapped) - // - Copy the data to this buffer - // - Create another buffer that's local on the device (VRAM) with the same size - // - Copy the data from the host to the device using a command buffer - // - Delete the host visible (staging) buffer - // - Use the device local buffers for rendering + struct StagingBuffer { + VkDeviceMemory memory; + VkBuffer buffer; + }; - struct StagingBuffer { - VkDeviceMemory memory; - VkBuffer buffer; - }; + struct { + StagingBuffer vertices; + StagingBuffer indices; + } stagingBuffers; - struct { - StagingBuffer vertices; - StagingBuffer indices; - } stagingBuffers; + void* data; - // Vertex buffer - VkBufferCreateInfo vertexBufferInfo = {}; - vertexBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - vertexBufferInfo.size = vertexBufferSize; - // Buffer is used as the copy source - vertexBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - // Create a host-visible buffer to copy the vertex data to (staging buffer) - VK_CHECK_RESULT(vkCreateBuffer(device, &vertexBufferInfo, nullptr, &stagingBuffers.vertices.buffer)); - vkGetBufferMemoryRequirements(device, stagingBuffers.vertices.buffer, &memReqs); - memAlloc.allocationSize = memReqs.size; - // Request a host visible memory type that can be used to copy our data do - // Also request it to be coherent, so that writes are visible to the GPU right after unmapping the buffer - memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &stagingBuffers.vertices.memory)); - // Map and copy - VK_CHECK_RESULT(vkMapMemory(device, stagingBuffers.vertices.memory, 0, memAlloc.allocationSize, 0, &data)); - memcpy(data, vertexBuffer.data(), vertexBufferSize); - vkUnmapMemory(device, stagingBuffers.vertices.memory); - VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffers.vertices.buffer, stagingBuffers.vertices.memory, 0)); + // Vertex buffer + VkBufferCreateInfo vertexBufferInfoCI{}; + vertexBufferInfoCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + vertexBufferInfoCI.size = vertexBufferSize; + // Buffer is used as the copy source + vertexBufferInfoCI.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + // Create a host-visible buffer to copy the vertex data to (staging buffer) + VK_CHECK_RESULT(vkCreateBuffer(device, &vertexBufferInfoCI, nullptr, &stagingBuffers.vertices.buffer)); + vkGetBufferMemoryRequirements(device, stagingBuffers.vertices.buffer, &memReqs); + memAlloc.allocationSize = memReqs.size; + // Request a host visible memory type that can be used to copy our data do + // Also request it to be coherent, so that writes are visible to the GPU right after unmapping the buffer + memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &stagingBuffers.vertices.memory)); + // Map and copy + VK_CHECK_RESULT(vkMapMemory(device, stagingBuffers.vertices.memory, 0, memAlloc.allocationSize, 0, &data)); + memcpy(data, vertexBuffer.data(), vertexBufferSize); + vkUnmapMemory(device, stagingBuffers.vertices.memory); + VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffers.vertices.buffer, stagingBuffers.vertices.memory, 0)); - // Create a device local buffer to which the (host local) vertex data will be copied and which will be used for rendering - vertexBufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - VK_CHECK_RESULT(vkCreateBuffer(device, &vertexBufferInfo, nullptr, &vertices.buffer)); - vkGetBufferMemoryRequirements(device, vertices.buffer, &memReqs); - memAlloc.allocationSize = memReqs.size; - memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &vertices.memory)); - VK_CHECK_RESULT(vkBindBufferMemory(device, vertices.buffer, vertices.memory, 0)); + // Create a device local buffer to which the (host local) vertex data will be copied and which will be used for rendering + vertexBufferInfoCI.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + VK_CHECK_RESULT(vkCreateBuffer(device, &vertexBufferInfoCI, nullptr, &vertices.buffer)); + vkGetBufferMemoryRequirements(device, vertices.buffer, &memReqs); + memAlloc.allocationSize = memReqs.size; + memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &vertices.memory)); + VK_CHECK_RESULT(vkBindBufferMemory(device, vertices.buffer, vertices.memory, 0)); - // Index buffer - VkBufferCreateInfo indexbufferInfo = {}; - indexbufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - indexbufferInfo.size = indexBufferSize; - indexbufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - // Copy index data to a buffer visible to the host (staging buffer) - VK_CHECK_RESULT(vkCreateBuffer(device, &indexbufferInfo, nullptr, &stagingBuffers.indices.buffer)); - vkGetBufferMemoryRequirements(device, stagingBuffers.indices.buffer, &memReqs); - memAlloc.allocationSize = memReqs.size; - memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &stagingBuffers.indices.memory)); - VK_CHECK_RESULT(vkMapMemory(device, stagingBuffers.indices.memory, 0, indexBufferSize, 0, &data)); - memcpy(data, indexBuffer.data(), indexBufferSize); - vkUnmapMemory(device, stagingBuffers.indices.memory); - VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffers.indices.buffer, stagingBuffers.indices.memory, 0)); + // Index buffer + VkBufferCreateInfo indexbufferCI{}; + indexbufferCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + indexbufferCI.size = indexBufferSize; + indexbufferCI.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + // Copy index data to a buffer visible to the host (staging buffer) + VK_CHECK_RESULT(vkCreateBuffer(device, &indexbufferCI, nullptr, &stagingBuffers.indices.buffer)); + vkGetBufferMemoryRequirements(device, stagingBuffers.indices.buffer, &memReqs); + memAlloc.allocationSize = memReqs.size; + memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &stagingBuffers.indices.memory)); + VK_CHECK_RESULT(vkMapMemory(device, stagingBuffers.indices.memory, 0, indexBufferSize, 0, &data)); + memcpy(data, indexBuffer.data(), indexBufferSize); + vkUnmapMemory(device, stagingBuffers.indices.memory); + VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffers.indices.buffer, stagingBuffers.indices.memory, 0)); - // Create destination buffer with device only visibility - indexbufferInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - VK_CHECK_RESULT(vkCreateBuffer(device, &indexbufferInfo, nullptr, &indices.buffer)); - vkGetBufferMemoryRequirements(device, indices.buffer, &memReqs); - memAlloc.allocationSize = memReqs.size; - memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &indices.memory)); - VK_CHECK_RESULT(vkBindBufferMemory(device, indices.buffer, indices.memory, 0)); + // Create destination buffer with device only visibility + indexbufferCI.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + VK_CHECK_RESULT(vkCreateBuffer(device, &indexbufferCI, nullptr, &indices.buffer)); + vkGetBufferMemoryRequirements(device, indices.buffer, &memReqs); + memAlloc.allocationSize = memReqs.size; + memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &indices.memory)); + VK_CHECK_RESULT(vkBindBufferMemory(device, indices.buffer, indices.memory, 0)); - // Buffer copies have to be submitted to a queue, so we need a command buffer for them - // Note: Some devices offer a dedicated transfer queue (with only the transfer bit set) that may be faster when doing lots of copies - VkCommandBuffer copyCmd = getCommandBuffer(true); + // Buffer copies have to be submitted to a queue, so we need a command buffer for them + // Note: Some devices offer a dedicated transfer queue (with only the transfer bit set) that may be faster when doing lots of copies + VkCommandBuffer copyCmd; - // Put buffer region copies into command buffer - VkBufferCopy copyRegion = {}; + VkCommandBufferAllocateInfo cmdBufAllocateInfo{}; + cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + cmdBufAllocateInfo.commandPool = commandPool; + cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + cmdBufAllocateInfo.commandBufferCount = 1; + VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, ©Cmd)); - // Vertex buffer - copyRegion.size = vertexBufferSize; - vkCmdCopyBuffer(copyCmd, stagingBuffers.vertices.buffer, vertices.buffer, 1, ©Region); - // Index buffer - copyRegion.size = indexBufferSize; - vkCmdCopyBuffer(copyCmd, stagingBuffers.indices.buffer, indices.buffer, 1, ©Region); + VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); + VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo)); + // Put buffer region copies into command buffer + VkBufferCopy copyRegion{}; + // Vertex buffer + copyRegion.size = vertexBufferSize; + vkCmdCopyBuffer(copyCmd, stagingBuffers.vertices.buffer, vertices.buffer, 1, ©Region); + // Index buffer + copyRegion.size = indexBufferSize; + vkCmdCopyBuffer(copyCmd, stagingBuffers.indices.buffer, indices.buffer, 1, ©Region); + VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd)); - // Flushing the command buffer will also submit it to the queue and uses a fence to ensure that all commands have been executed before returning - flushCommandBuffer(copyCmd); + // Submit the command buffer to the queue to finish the copy + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = ©Cmd; - // Destroy staging buffers - // Note: Staging buffer must not be deleted before the copies have been submitted and executed - vkDestroyBuffer(device, stagingBuffers.vertices.buffer, nullptr); - vkFreeMemory(device, stagingBuffers.vertices.memory, nullptr); - vkDestroyBuffer(device, stagingBuffers.indices.buffer, nullptr); - vkFreeMemory(device, stagingBuffers.indices.memory, nullptr); - } - else - { - // Don't use staging - // Create host-visible buffers only and use these for rendering. This is not advised and will usually result in lower rendering performance + // Create fence to ensure that the command buffer has finished executing + VkFenceCreateInfo fenceCI{}; + fenceCI.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceCI.flags = 0; + VkFence fence; + VK_CHECK_RESULT(vkCreateFence(device, &fenceCI, nullptr, &fence)); - // Vertex buffer - VkBufferCreateInfo vertexBufferInfo = {}; - vertexBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - vertexBufferInfo.size = vertexBufferSize; - vertexBufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + // Submit to the queue + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence)); + // Wait for the fence to signal that command buffer has finished executing + VK_CHECK_RESULT(vkWaitForFences(device, 1, &fence, VK_TRUE, DEFAULT_FENCE_TIMEOUT)); - // Copy vertex data to a buffer visible to the host - VK_CHECK_RESULT(vkCreateBuffer(device, &vertexBufferInfo, nullptr, &vertices.buffer)); - vkGetBufferMemoryRequirements(device, vertices.buffer, &memReqs); - memAlloc.allocationSize = memReqs.size; - // VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT is host visible memory, and VK_MEMORY_PROPERTY_HOST_COHERENT_BIT makes sure writes are directly visible - memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &vertices.memory)); - VK_CHECK_RESULT(vkMapMemory(device, vertices.memory, 0, memAlloc.allocationSize, 0, &data)); - memcpy(data, vertexBuffer.data(), vertexBufferSize); - vkUnmapMemory(device, vertices.memory); - VK_CHECK_RESULT(vkBindBufferMemory(device, vertices.buffer, vertices.memory, 0)); + vkDestroyFence(device, fence, nullptr); + vkFreeCommandBuffers(device, commandPool, 1, ©Cmd); - // Index buffer - VkBufferCreateInfo indexbufferInfo = {}; - indexbufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - indexbufferInfo.size = indexBufferSize; - indexbufferInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT; - - // Copy index data to a buffer visible to the host - VK_CHECK_RESULT(vkCreateBuffer(device, &indexbufferInfo, nullptr, &indices.buffer)); - vkGetBufferMemoryRequirements(device, indices.buffer, &memReqs); - memAlloc.allocationSize = memReqs.size; - memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &indices.memory)); - VK_CHECK_RESULT(vkMapMemory(device, indices.memory, 0, indexBufferSize, 0, &data)); - memcpy(data, indexBuffer.data(), indexBufferSize); - vkUnmapMemory(device, indices.memory); - VK_CHECK_RESULT(vkBindBufferMemory(device, indices.buffer, indices.memory, 0)); - } + // Destroy staging buffers + // Note: Staging buffer must not be deleted before the copies have been submitted and executed + vkDestroyBuffer(device, stagingBuffers.vertices.buffer, nullptr); + vkFreeMemory(device, stagingBuffers.vertices.memory, nullptr); + vkDestroyBuffer(device, stagingBuffers.indices.buffer, nullptr); + vkFreeMemory(device, stagingBuffers.indices.memory, nullptr); } - void setupDescriptorPool() + // Descriptors are allocated from a pool, that tells the implementation how many and what types of descriptors we are going to use (at maximum) + void createDescriptorPool() { // We need to tell the API the number of max. requested descriptors per type - VkDescriptorPoolSize typeCounts[1]; - // This example only uses one descriptor type (uniform buffer) and only requests one descriptor of this type - typeCounts[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - typeCounts[0].descriptorCount = 1; + VkDescriptorPoolSize descriptorTypeCounts[1]; + // This example only one descriptor type (uniform buffer) + descriptorTypeCounts[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + // We have one buffer (and as such descriptor) per frame + descriptorTypeCounts[0].descriptorCount = MAX_CONCURRENT_FRAMES; // For additional types you need to add new entries in the type count list // E.g. for two combined image samplers : // typeCounts[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; @@ -579,76 +383,78 @@ public: // Create the global descriptor pool // All descriptors used in this example are allocated from this pool - VkDescriptorPoolCreateInfo descriptorPoolInfo = {}; - descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - descriptorPoolInfo.pNext = nullptr; - descriptorPoolInfo.poolSizeCount = 1; - descriptorPoolInfo.pPoolSizes = typeCounts; + VkDescriptorPoolCreateInfo descriptorPoolCI{}; + descriptorPoolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + descriptorPoolCI.pNext = nullptr; + descriptorPoolCI.poolSizeCount = 1; + descriptorPoolCI.pPoolSizes = descriptorTypeCounts; // Set the max. number of descriptor sets that can be requested from this pool (requesting beyond this limit will result in an error) - descriptorPoolInfo.maxSets = 1; - - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); + // Our sample will create one set per uniform buffer per frame + descriptorPoolCI.maxSets = MAX_CONCURRENT_FRAMES; + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorPool)); } - void setupDescriptorSetLayout() + // Descriptor set layouts define the interface between our application and the shader + // Basically connects the different shader stages to descriptors for binding uniform buffers, image samplers, etc. + // So every shader binding should map to one descriptor set layout binding + void createDescriptorSetLayout() { - // Setup layout of descriptors used in this example - // Basically connects the different shader stages to descriptors for binding uniform buffers, image samplers, etc. - // So every shader binding should map to one descriptor set layout binding - // Binding 0: Uniform buffer (Vertex shader) - VkDescriptorSetLayoutBinding layoutBinding = {}; + VkDescriptorSetLayoutBinding layoutBinding{}; layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; layoutBinding.descriptorCount = 1; layoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; layoutBinding.pImmutableSamplers = nullptr; - VkDescriptorSetLayoutCreateInfo descriptorLayout = {}; - descriptorLayout.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - descriptorLayout.pNext = nullptr; - descriptorLayout.bindingCount = 1; - descriptorLayout.pBindings = &layoutBinding; - - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); + VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{}; + descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + descriptorLayoutCI.pNext = nullptr; + descriptorLayoutCI.bindingCount = 1; + descriptorLayoutCI.pBindings = &layoutBinding; + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayout)); // Create the pipeline layout that is used to generate the rendering pipelines that are based on this descriptor set layout // In a more complex scenario you would have different pipeline layouts for different descriptor set layouts that could be reused - VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = {}; - pPipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pPipelineLayoutCreateInfo.pNext = nullptr; - pPipelineLayoutCreateInfo.setLayoutCount = 1; - pPipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayout; - - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout)); + VkPipelineLayoutCreateInfo pipelineLayoutCI{}; + pipelineLayoutCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutCI.pNext = nullptr; + pipelineLayoutCI.setLayoutCount = 1; + pipelineLayoutCI.pSetLayouts = &descriptorSetLayout; + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); } - void setupDescriptorSet() + // Shaders access data using descriptor sets that "point" at our uniform buffers + // The descriptor sets make use of the descriptor set layouts created above + void createDescriptorSets() { - // Allocate a new descriptor set from the global descriptor pool - VkDescriptorSetAllocateInfo allocInfo = {}; - allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; - allocInfo.descriptorPool = descriptorPool; - allocInfo.descriptorSetCount = 1; - allocInfo.pSetLayouts = &descriptorSetLayout; + // Allocate one descriptor set per frame from the global descriptor pool + for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) { + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = 1; + allocInfo.pSetLayouts = &descriptorSetLayout; + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &uniformBuffers[i].descriptorSet)); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); + // Update the descriptor set determining the shader binding points + // For every binding point used in a shader there needs to be one + // descriptor set matching that binding point + VkWriteDescriptorSet writeDescriptorSet{}; + + // The buffer's information is passed using a descriptor info structure + VkDescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = uniformBuffers[i].buffer; + bufferInfo.range = sizeof(ShaderData); - // Update the descriptor set determining the shader binding points - // For every binding point used in a shader there needs to be one - // descriptor set matching that binding point - - VkWriteDescriptorSet writeDescriptorSet = {}; - - // Binding 0 : Uniform buffer - writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - writeDescriptorSet.dstSet = descriptorSet; - writeDescriptorSet.descriptorCount = 1; - writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - writeDescriptorSet.pBufferInfo = &uniformBufferVS.descriptor; - // Binds this uniform buffer to binding point 0 - writeDescriptorSet.dstBinding = 0; - - vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); + // Binding 0 : Uniform buffer + writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writeDescriptorSet.dstSet = uniformBuffers[i].descriptorSet; + writeDescriptorSet.descriptorCount = 1; + writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + writeDescriptorSet.pBufferInfo = &bufferInfo; + writeDescriptorSet.dstBinding = 0; + vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); + } } // Create the depth (and stencil) buffer attachments used by our framebuffers @@ -656,22 +462,22 @@ public: void setupDepthStencil() { // Create an optimal image used as the depth stencil attachment - VkImageCreateInfo image = {}; - image.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - image.imageType = VK_IMAGE_TYPE_2D; - image.format = depthFormat; + VkImageCreateInfo imageCI{}; + imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageCI.imageType = VK_IMAGE_TYPE_2D; + imageCI.format = depthFormat; // Use example's height and width - image.extent = { width, height, 1 }; - image.mipLevels = 1; - image.arrayLayers = 1; - image.samples = VK_SAMPLE_COUNT_1_BIT; - image.tiling = VK_IMAGE_TILING_OPTIMAL; - image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; - image.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &depthStencil.image)); + 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; + imageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &depthStencil.image)); // Allocate memory for the image (device local) and bind it to our image - VkMemoryAllocateInfo memAlloc = {}; + VkMemoryAllocateInfo memAlloc{}; memAlloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; VkMemoryRequirements memReqs; vkGetImageMemoryRequirements(device, depthStencil.image, &memReqs); @@ -683,22 +489,22 @@ public: // Create a view for the depth stencil image // Images aren't directly accessed in Vulkan, but rather through views described by a subresource range // This allows for multiple views of one image with differing ranges (e.g. for different layers) - VkImageViewCreateInfo depthStencilView = {}; - depthStencilView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D; - depthStencilView.format = depthFormat; - depthStencilView.subresourceRange = {}; - depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + VkImageViewCreateInfo depthStencilViewCI{}; + depthStencilViewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + depthStencilViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D; + depthStencilViewCI.format = depthFormat; + depthStencilViewCI.subresourceRange = {}; + depthStencilViewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; // 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) - depthStencilView.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; - - depthStencilView.subresourceRange.baseMipLevel = 0; - depthStencilView.subresourceRange.levelCount = 1; - depthStencilView.subresourceRange.baseArrayLayer = 0; - depthStencilView.subresourceRange.layerCount = 1; - depthStencilView.image = depthStencil.image; - VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &depthStencil.view)); + if (depthFormat >= VK_FORMAT_D16_UNORM_S8_UINT) { + depthStencilViewCI.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + depthStencilViewCI.subresourceRange.baseMipLevel = 0; + depthStencilViewCI.subresourceRange.levelCount = 1; + depthStencilViewCI.subresourceRange.baseArrayLayer = 0; + depthStencilViewCI.subresourceRange.layerCount = 1; + depthStencilViewCI.image = depthStencil.image; + VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilViewCI, nullptr, &depthStencil.view)); } // Create a frame buffer for each swap chain image @@ -710,20 +516,22 @@ public: for (size_t i = 0; i < frameBuffers.size(); i++) { std::array attachments; - attachments[0] = swapChain.buffers[i].view; // Color attachment is the view of the swapchain image - attachments[1] = depthStencil.view; // Depth/Stencil attachment is the same for all frame buffers + // Color attachment is the view of the swapchain image + attachments[0] = swapChain.buffers[i].view; + // Depth/Stencil attachment is the same for all frame buffers due to how depth works with current GPUs + attachments[1] = depthStencil.view; - VkFramebufferCreateInfo frameBufferCreateInfo = {}; - frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + VkFramebufferCreateInfo frameBufferCI{}; + frameBufferCI.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; // All frame buffers use the same renderpass setup - frameBufferCreateInfo.renderPass = renderPass; - frameBufferCreateInfo.attachmentCount = static_cast(attachments.size()); - frameBufferCreateInfo.pAttachments = attachments.data(); - frameBufferCreateInfo.width = width; - frameBufferCreateInfo.height = height; - frameBufferCreateInfo.layers = 1; + frameBufferCI.renderPass = renderPass; + frameBufferCI.attachmentCount = static_cast(attachments.size()); + frameBufferCI.pAttachments = attachments.data(); + frameBufferCI.width = width; + frameBufferCI.height = height; + frameBufferCI.layers = 1; // Create the framebuffer - VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCreateInfo, nullptr, &frameBuffers[i])); + VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCI, nullptr, &frameBuffers[i])); } } @@ -737,7 +545,7 @@ public: // This example will use a single render pass with one subpass // Descriptors for the attachments used by this renderpass - std::array attachments = {}; + std::array attachments{}; // Color attachment attachments[0].format = swapChain.colorFormat; // Use the color format selected by the swapchain @@ -760,16 +568,16 @@ public: attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; // Transition to depth/stencil attachment // Setup attachment references - VkAttachmentReference colorReference = {}; + VkAttachmentReference colorReference{}; colorReference.attachment = 0; // Attachment 0 is color colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // Attachment layout used as color during the subpass - VkAttachmentReference depthReference = {}; + VkAttachmentReference depthReference{}; depthReference.attachment = 1; // Attachment 1 is color depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; // Attachment used as depth/stencil used during the subpass // Setup a single subpass reference - VkSubpassDescription subpassDescription = {}; + VkSubpassDescription subpassDescription{}; subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpassDescription.colorAttachmentCount = 1; // Subpass uses one color attachment subpassDescription.pColorAttachments = &colorReference; // Reference to the color attachment in slot 0 @@ -807,16 +615,15 @@ public: dependencies[1].dependencyFlags = 0; // Create the actual renderpass - VkRenderPassCreateInfo renderPassInfo = {}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = static_cast(attachments.size()); // Number of attachments used by this render pass - renderPassInfo.pAttachments = attachments.data(); // Descriptions of the attachments used by the render pass - renderPassInfo.subpassCount = 1; // We only use one subpass in this example - renderPassInfo.pSubpasses = &subpassDescription; // Description of that subpass - renderPassInfo.dependencyCount = static_cast(dependencies.size()); // Number of subpass dependencies - renderPassInfo.pDependencies = dependencies.data(); // Subpass dependencies used by the render pass - - VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass)); + VkRenderPassCreateInfo renderPassCI{}; + renderPassCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassCI.attachmentCount = static_cast(attachments.size()); // Number of attachments used by this render pass + renderPassCI.pAttachments = attachments.data(); // Descriptions of the attachments used by the render pass + renderPassCI.subpassCount = 1; // We only use one subpass in this example + renderPassCI.pSubpasses = &subpassDescription; // Description of that subpass + renderPassCI.dependencyCount = static_cast(dependencies.size()); // Number of subpass dependencies + renderPassCI.pDependencies = dependencies.data(); // Subpass dependencies used by the render pass + VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, nullptr, &renderPass)); } // Vulkan loads its shaders from an immediate binary representation called SPIR-V @@ -825,7 +632,7 @@ public: VkShaderModule loadSPIRVShader(std::string filename) { size_t shaderSize; - char* shaderCode = NULL; + char* shaderCode{ nullptr }; #if defined(__ANDROID__) // Load shader from compressed asset @@ -854,13 +661,13 @@ public: if (shaderCode) { // Create a new shader module that will be used for pipeline creation - VkShaderModuleCreateInfo moduleCreateInfo{}; - moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - moduleCreateInfo.codeSize = shaderSize; - moduleCreateInfo.pCode = (uint32_t*)shaderCode; + VkShaderModuleCreateInfo shaderModuleCI{}; + shaderModuleCI.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + shaderModuleCI.codeSize = shaderSize; + shaderModuleCI.pCode = (uint32_t*)shaderCode; VkShaderModule shaderModule; - VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule)); + VK_CHECK_RESULT(vkCreateShaderModule(device, &shaderModuleCI, nullptr, &shaderModule)); delete[] shaderCode; @@ -873,55 +680,55 @@ public: } } - void preparePipelines() + void createPipelines() { // Create the graphics pipeline used in this example // Vulkan uses the concept of rendering pipelines to encapsulate fixed states, replacing OpenGL's complex state machine // A pipeline is then stored and hashed on the GPU making pipeline changes very fast // Note: There are still a few dynamic states that are not directly part of the pipeline (but the info that they are used is) - VkGraphicsPipelineCreateInfo pipelineCreateInfo = {}; - pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + VkGraphicsPipelineCreateInfo pipelineCI{}; + pipelineCI.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; // The layout used for this pipeline (can be shared among multiple pipelines using the same layout) - pipelineCreateInfo.layout = pipelineLayout; + pipelineCI.layout = pipelineLayout; // Renderpass this pipeline is attached to - pipelineCreateInfo.renderPass = renderPass; + pipelineCI.renderPass = renderPass; // Construct the different states making up the pipeline // Input assembly state describes how primitives are assembled // This pipeline will assemble vertex data as a triangle lists (though we only use one triangle) - VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = {}; - inputAssemblyState.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI{}; + inputAssemblyStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssemblyStateCI.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; // Rasterization state - VkPipelineRasterizationStateCreateInfo rasterizationState = {}; - rasterizationState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizationState.polygonMode = VK_POLYGON_MODE_FILL; - rasterizationState.cullMode = VK_CULL_MODE_NONE; - rasterizationState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; - rasterizationState.depthClampEnable = VK_FALSE; - rasterizationState.rasterizerDiscardEnable = VK_FALSE; - rasterizationState.depthBiasEnable = VK_FALSE; - rasterizationState.lineWidth = 1.0f; + VkPipelineRasterizationStateCreateInfo rasterizationStateCI{}; + rasterizationStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizationStateCI.polygonMode = VK_POLYGON_MODE_FILL; + rasterizationStateCI.cullMode = VK_CULL_MODE_NONE; + rasterizationStateCI.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizationStateCI.depthClampEnable = VK_FALSE; + rasterizationStateCI.rasterizerDiscardEnable = VK_FALSE; + rasterizationStateCI.depthBiasEnable = VK_FALSE; + rasterizationStateCI.lineWidth = 1.0f; // Color blend state describes how blend factors are calculated (if used) // We need one blend attachment state per color attachment (even if blending is not used) - VkPipelineColorBlendAttachmentState blendAttachmentState[1] = {}; - blendAttachmentState[0].colorWriteMask = 0xf; - blendAttachmentState[0].blendEnable = VK_FALSE; - VkPipelineColorBlendStateCreateInfo colorBlendState = {}; - colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlendState.attachmentCount = 1; - colorBlendState.pAttachments = blendAttachmentState; + VkPipelineColorBlendAttachmentState blendAttachmentState{}; + blendAttachmentState.colorWriteMask = 0xf; + blendAttachmentState.blendEnable = VK_FALSE; + VkPipelineColorBlendStateCreateInfo colorBlendStateCI{}; + colorBlendStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlendStateCI.attachmentCount = 1; + colorBlendStateCI.pAttachments = &blendAttachmentState; // Viewport state sets the number of viewports and scissor used in this pipeline // Note: This is actually overridden by the dynamic states (see below) - VkPipelineViewportStateCreateInfo viewportState = {}; - viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportState.viewportCount = 1; - viewportState.scissorCount = 1; + VkPipelineViewportStateCreateInfo viewportStateCI{}; + viewportStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportStateCI.viewportCount = 1; + viewportStateCI.scissorCount = 1; // Enable dynamic states // Most states are baked into the pipeline, but there are still a few dynamic states that can be changed within a command buffer @@ -930,38 +737,38 @@ public: std::vector dynamicStateEnables; dynamicStateEnables.push_back(VK_DYNAMIC_STATE_VIEWPORT); dynamicStateEnables.push_back(VK_DYNAMIC_STATE_SCISSOR); - VkPipelineDynamicStateCreateInfo dynamicState = {}; - dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamicState.pDynamicStates = dynamicStateEnables.data(); - dynamicState.dynamicStateCount = static_cast(dynamicStateEnables.size()); + VkPipelineDynamicStateCreateInfo dynamicStateCI{}; + dynamicStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicStateCI.pDynamicStates = dynamicStateEnables.data(); + dynamicStateCI.dynamicStateCount = static_cast(dynamicStateEnables.size()); // Depth and stencil state containing depth and stencil compare and test operations // We only use depth tests and want depth tests and writes to be enabled and compare with less or equal - VkPipelineDepthStencilStateCreateInfo depthStencilState = {}; - depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; - depthStencilState.depthTestEnable = VK_TRUE; - depthStencilState.depthWriteEnable = VK_TRUE; - depthStencilState.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; - depthStencilState.depthBoundsTestEnable = VK_FALSE; - depthStencilState.back.failOp = VK_STENCIL_OP_KEEP; - depthStencilState.back.passOp = VK_STENCIL_OP_KEEP; - depthStencilState.back.compareOp = VK_COMPARE_OP_ALWAYS; - depthStencilState.stencilTestEnable = VK_FALSE; - depthStencilState.front = depthStencilState.back; + VkPipelineDepthStencilStateCreateInfo depthStencilStateCI{}; + depthStencilStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencilStateCI.depthTestEnable = VK_TRUE; + depthStencilStateCI.depthWriteEnable = VK_TRUE; + depthStencilStateCI.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; + depthStencilStateCI.depthBoundsTestEnable = VK_FALSE; + depthStencilStateCI.back.failOp = VK_STENCIL_OP_KEEP; + depthStencilStateCI.back.passOp = VK_STENCIL_OP_KEEP; + depthStencilStateCI.back.compareOp = VK_COMPARE_OP_ALWAYS; + depthStencilStateCI.stencilTestEnable = VK_FALSE; + depthStencilStateCI.front = depthStencilStateCI.back; // Multi sampling state // This example does not make use of multi sampling (for anti-aliasing), the state must still be set and passed to the pipeline - VkPipelineMultisampleStateCreateInfo multisampleState = {}; - multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampleState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - multisampleState.pSampleMask = nullptr; + VkPipelineMultisampleStateCreateInfo multisampleStateCI{}; + multisampleStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampleStateCI.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampleStateCI.pSampleMask = nullptr; // Vertex input descriptions // Specifies the vertex input parameters for a pipeline // Vertex input binding // This example uses a single vertex input binding at binding point 0 (see vkCmdBindVertexBuffers) - VkVertexInputBindingDescription vertexInputBinding = {}; + VkVertexInputBindingDescription vertexInputBinding{}; vertexInputBinding.binding = 0; vertexInputBinding.stride = sizeof(Vertex); vertexInputBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; @@ -985,12 +792,12 @@ public: vertexInputAttributs[1].offset = offsetof(Vertex, color); // Vertex input state used for pipeline creation - VkPipelineVertexInputStateCreateInfo vertexInputState = {}; - vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertexInputState.vertexBindingDescriptionCount = 1; - vertexInputState.pVertexBindingDescriptions = &vertexInputBinding; - vertexInputState.vertexAttributeDescriptionCount = 2; - vertexInputState.pVertexAttributeDescriptions = vertexInputAttributs.data(); + VkPipelineVertexInputStateCreateInfo vertexInputStateCI{}; + vertexInputStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputStateCI.vertexBindingDescriptionCount = 1; + vertexInputStateCI.pVertexBindingDescriptions = &vertexInputBinding; + vertexInputStateCI.vertexAttributeDescriptionCount = 2; + vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributs.data(); // Shaders std::array shaderStages{}; @@ -1016,96 +823,78 @@ public: assert(shaderStages[1].module != VK_NULL_HANDLE); // Set pipeline shader stage info - pipelineCreateInfo.stageCount = static_cast(shaderStages.size()); - pipelineCreateInfo.pStages = shaderStages.data(); + pipelineCI.stageCount = static_cast(shaderStages.size()); + pipelineCI.pStages = shaderStages.data(); // Assign the pipeline states to the pipeline creation info structure - pipelineCreateInfo.pVertexInputState = &vertexInputState; - pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; - pipelineCreateInfo.pRasterizationState = &rasterizationState; - pipelineCreateInfo.pColorBlendState = &colorBlendState; - pipelineCreateInfo.pMultisampleState = &multisampleState; - pipelineCreateInfo.pViewportState = &viewportState; - pipelineCreateInfo.pDepthStencilState = &depthStencilState; - pipelineCreateInfo.pDynamicState = &dynamicState; + pipelineCI.pVertexInputState = &vertexInputStateCI; + pipelineCI.pInputAssemblyState = &inputAssemblyStateCI; + pipelineCI.pRasterizationState = &rasterizationStateCI; + pipelineCI.pColorBlendState = &colorBlendStateCI; + pipelineCI.pMultisampleState = &multisampleStateCI; + pipelineCI.pViewportState = &viewportStateCI; + pipelineCI.pDepthStencilState = &depthStencilStateCI; + pipelineCI.pDynamicState = &dynamicStateCI; // Create rendering pipeline using the specified states - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline)); + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); // Shader modules are no longer needed once the graphics pipeline has been created vkDestroyShaderModule(device, shaderStages[0].module, nullptr); vkDestroyShaderModule(device, shaderStages[1].module, nullptr); } - void prepareUniformBuffers() + void createUniformBuffers() { - // Prepare and initialize a uniform buffer block containing shader uniforms + // Prepare and initialize the per-frame uniform buffer blocks containing shader uniforms // Single uniforms like in OpenGL are no longer present in Vulkan. All Shader uniforms are passed via uniform buffer blocks VkMemoryRequirements memReqs; // Vertex shader uniform buffer block - VkBufferCreateInfo bufferInfo = {}; - VkMemoryAllocateInfo allocInfo = {}; + VkBufferCreateInfo bufferInfo{}; + VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.pNext = nullptr; allocInfo.allocationSize = 0; allocInfo.memoryTypeIndex = 0; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = sizeof(uboVS); + bufferInfo.size = sizeof(ShaderData); // This buffer will be used as a uniform buffer bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; - // Create a new buffer - VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &uniformBufferVS.buffer)); - // Get memory requirements including size, alignment and memory type - vkGetBufferMemoryRequirements(device, uniformBufferVS.buffer, &memReqs); - allocInfo.allocationSize = memReqs.size; - // Get the memory type index that supports host visible memory access - // Most implementations offer multiple memory types and selecting the correct one to allocate memory from is crucial - // We also want the buffer to be host coherent so we don't have to flush (or sync after every update. - // Note: This may affect performance so you might not want to do this in a real world application that updates buffers on a regular base - allocInfo.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - // Allocate memory for the uniform buffer - VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &(uniformBufferVS.memory))); - // Bind memory to buffer - VK_CHECK_RESULT(vkBindBufferMemory(device, uniformBufferVS.buffer, uniformBufferVS.memory, 0)); + // Create the buffers + for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) { + VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &uniformBuffers[i].buffer)); + // Get memory requirements including size, alignment and memory type + vkGetBufferMemoryRequirements(device, uniformBuffers[i].buffer, &memReqs); + allocInfo.allocationSize = memReqs.size; + // Get the memory type index that supports host visible memory access + // Most implementations offer multiple memory types and selecting the correct one to allocate memory from is crucial + // We also want the buffer to be host coherent so we don't have to flush (or sync after every update. + // Note: This may affect performance so you might not want to do this in a real world application that updates buffers on a regular base + allocInfo.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + // Allocate memory for the uniform buffer + VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &(uniformBuffers[i].memory))); + // Bind memory to buffer + VK_CHECK_RESULT(vkBindBufferMemory(device, uniformBuffers[i].buffer, uniformBuffers[i].memory, 0)); + // We map the buffer once, so we can update it without having to map it again + VK_CHECK_RESULT(vkMapMemory(device, uniformBuffers[i].memory, 0, sizeof(ShaderData), 0, (void**)&uniformBuffers[i].mapped)); + } - // Store information in the uniform's descriptor that is used by the descriptor set - uniformBufferVS.descriptor.buffer = uniformBufferVS.buffer; - uniformBufferVS.descriptor.offset = 0; - uniformBufferVS.descriptor.range = sizeof(uboVS); - - updateUniformBuffers(); - } - - void updateUniformBuffers() - { - // Pass matrices to the shaders - uboVS.projectionMatrix = camera.matrices.perspective; - uboVS.viewMatrix = camera.matrices.view; - uboVS.modelMatrix = glm::mat4(1.0f); - - // Map uniform buffer and update it - uint8_t *pData; - VK_CHECK_RESULT(vkMapMemory(device, uniformBufferVS.memory, 0, sizeof(uboVS), 0, (void **)&pData)); - memcpy(pData, &uboVS, sizeof(uboVS)); - // Unmap after data has been copied - // Note: Since we requested a host coherent memory type for the uniform buffer, the write is instantly visible to the GPU - vkUnmapMemory(device, uniformBufferVS.memory); } void prepare() { VulkanExampleBase::prepare(); - prepareSynchronizationPrimitives(); - prepareVertices(USE_STAGING); - prepareUniformBuffers(); - setupDescriptorSetLayout(); - preparePipelines(); - setupDescriptorPool(); - setupDescriptorSet(); - buildCommandBuffers(); + createSynchronizationPrimitives(); + createCommandBuffers(); + createVertexBuffer(); + createUniformBuffers(); + createDescriptorSetLayout(); + createDescriptorPool(); + createDescriptorSets(); + createPipelines(); prepared = true; } @@ -1113,17 +902,141 @@ public: { if (!prepared) return; - draw(); - } - virtual void viewChanged() - { - // This function is called by the base example class each time the view is changed by user input - updateUniformBuffers(); + // Use a fence to wait until the command buffer has finished execution before using it again + vkWaitForFences(device, 1, &waitFences[currentFrame], VK_TRUE, UINT64_MAX); + + // Get the next swap chain image from the implementation + // Note that the implementation is free to return the images in any order, so we must use the acquire function and can't just cycle through the images + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(device, swapChain.swapChain, UINT64_MAX, presentCompleteSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + windowResize(); + return; + } else if ((result != VK_SUCCESS) && (result != VK_SUBOPTIMAL_KHR)) { + throw "Could not acquire the next swap chain image!"; + } + + // Update the uniform buffer for the next frame + ShaderData shaderData{}; + shaderData.projectionMatrix = camera.matrices.perspective; + shaderData.viewMatrix = camera.matrices.view; + shaderData.modelMatrix = glm::mat4(1.0f); + + // Copy the current matrices to the current frame's uniform buffer + // Note: Since we requested a host coherent memory type for the uniform buffer, the write is instantly visible to the GPU + memcpy(uniformBuffers[currentBuffer].mapped, &shaderData, sizeof(ShaderData)); + + VK_CHECK_RESULT(vkResetFences(device, 1, &waitFences[currentFrame])); + + // Build the command buffer + // Unlike in OpenGL all rendering commands are recorded into command buffers that are then submitted to the queue + // This allows to generate work upfront in a separate thread + // For basic command buffers (like in this sample), recording is so fast that there is no need to offload this + + vkResetCommandBuffer(commandBuffers[currentBuffer], 0); + + VkCommandBufferBeginInfo cmdBufInfo{}; + cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + // Set clear values for all framebuffer attachments with loadOp set to clear + // We use two attachments (color and depth) that are cleared at the start of the subpass and as such we need to set clear values for both + VkClearValue clearValues[2]; + clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 1.0f } }; + clearValues[1].depthStencil = { 1.0f, 0 }; + + VkRenderPassBeginInfo renderPassBeginInfo{}; + renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassBeginInfo.pNext = nullptr; + renderPassBeginInfo.renderPass = renderPass; + renderPassBeginInfo.renderArea.offset.x = 0; + renderPassBeginInfo.renderArea.offset.y = 0; + renderPassBeginInfo.renderArea.extent.width = width; + renderPassBeginInfo.renderArea.extent.height = height; + renderPassBeginInfo.clearValueCount = 2; + renderPassBeginInfo.pClearValues = clearValues; + renderPassBeginInfo.framebuffer = frameBuffers[imageIndex]; + VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers[currentBuffer], &cmdBufInfo)); + + // Start the first sub pass specified in our default render pass setup by the base class + // This will clear the color and depth attachment + vkCmdBeginRenderPass(commandBuffers[currentBuffer], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + // Update dynamic viewport state + VkViewport viewport{}; + viewport.height = (float)height; + viewport.width = (float)width; + viewport.minDepth = (float)0.0f; + viewport.maxDepth = (float)1.0f; + vkCmdSetViewport(commandBuffers[currentBuffer], 0, 1, &viewport); + // Update dynamic scissor state + VkRect2D scissor{}; + scissor.extent.width = width; + scissor.extent.height = height; + scissor.offset.x = 0; + scissor.offset.y = 0; + vkCmdSetScissor(commandBuffers[currentBuffer], 0, 1, &scissor); + // Bind descriptor set for the currrent frame's uniform buffer, so the shader uses the data from that buffer for this draw + vkCmdBindDescriptorSets(commandBuffers[currentBuffer], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &uniformBuffers[currentBuffer].descriptorSet, 0, nullptr); + // Bind the rendering pipeline + // The pipeline (state object) contains all states of the rendering pipeline, binding it will set all the states specified at pipeline creation time + vkCmdBindPipeline(commandBuffers[currentBuffer], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + // Bind triangle vertex buffer (contains position and colors) + VkDeviceSize offsets[1]{ 0 }; + vkCmdBindVertexBuffers(commandBuffers[currentBuffer], 0, 1, &vertices.buffer, offsets); + // Bind triangle index buffer + vkCmdBindIndexBuffer(commandBuffers[currentBuffer], indices.buffer, 0, VK_INDEX_TYPE_UINT32); + // Draw indexed triangle + vkCmdDrawIndexed(commandBuffers[currentBuffer], indices.count, 1, 0, 0, 1); + vkCmdEndRenderPass(commandBuffers[currentBuffer]); + // Ending the render pass will add an implicit barrier transitioning the frame buffer color attachment to + // VK_IMAGE_LAYOUT_PRESENT_SRC_KHR for presenting it to the windowing system + VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffers[currentBuffer])); + + // Submit the command buffer to the graphics queue + + // Pipeline stage at which the queue submission will wait (via pWaitSemaphores) + VkPipelineStageFlags waitStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + // The submit info structure specifies a command buffer queue submission batch + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.pWaitDstStageMask = &waitStageMask; // Pointer to the list of pipeline stages that the semaphore waits will occur at + submitInfo.waitSemaphoreCount = 1; // One wait semaphore + submitInfo.signalSemaphoreCount = 1; // One signal semaphore + submitInfo.pCommandBuffers = &commandBuffers[currentBuffer]; // Command buffers(s) to execute in this batch (submission) + submitInfo.commandBufferCount = 1; // One command buffer + + // Semaphore to wait upon before the submitted command buffer starts executing + submitInfo.pWaitSemaphores = &presentCompleteSemaphores[currentFrame]; + // Semaphore to be signaled when command buffers have completed + submitInfo.pSignalSemaphores = &renderCompleteSemaphores[currentFrame]; + + // Submit to the graphics queue passing a wait fence + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, waitFences[currentFrame])); + + // Present the current frame buffer to the swap chain + // Pass the semaphore signaled by the command buffer submission from the submit info as the wait semaphore for swap chain presentation + // This ensures that the image is not presented to the windowing system until all commands have been submitted + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &renderCompleteSemaphores[currentFrame]; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &swapChain.swapChain; + presentInfo.pImageIndices = &imageIndex; + result = vkQueuePresentKHR(queue, &presentInfo); + + if ((result == VK_ERROR_OUT_OF_DATE_KHR) || (result == VK_SUBOPTIMAL_KHR)) { + windowResize(); + } + else if (result != VK_SUCCESS) { + throw "Could not present the image to the swap chain!"; + } + } }; -// OS specific macros for the example main entry points +// OS specific main entry points // Most of the code base is shared for the different supported operating systems, but stuff like message handling differs #if defined(_WIN32) diff --git a/examples/vulkanscene/vulkanscene.cpp b/examples/vulkanscene/vulkanscene.cpp index ce1d4417..839cb5a9 100644 --- a/examples/vulkanscene/vulkanscene.cpp +++ b/examples/vulkanscene/vulkanscene.cpp @@ -3,11 +3,9 @@ * * Don't take this a an example, it's more of a personal playground * -* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * -* Note : Different license than the other examples! -* -* This code is licensed under the Mozilla Public License Version 2.0 (http://opensource.org/licenses/MPL-2.0) +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ #include "vulkanexamplebase.h" @@ -157,10 +155,7 @@ public: }; VkDescriptorPoolCreateInfo descriptorPoolInfo = - vks::initializers::descriptorPoolCreateInfo( - poolSizes.size(), - poolSizes.data(), - 2); + vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); } @@ -182,9 +177,7 @@ public: }; VkDescriptorSetLayoutCreateInfo descriptorLayout = - vks::initializers::descriptorSetLayoutCreateInfo( - setLayoutBindings.data(), - setLayoutBindings.size()); + vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); @@ -229,7 +222,7 @@ public: &texDescriptorCubeMap) }; - vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } void preparePipelines() @@ -252,7 +245,7 @@ public: pipelineCI.pViewportState = &viewportState; pipelineCI.pDepthStencilState = &depthStencilState; pipelineCI.pDynamicState = &dynamicState; - pipelineCI.stageCount = shaderStages.size(); + pipelineCI.stageCount = static_cast(shaderStages.size()); pipelineCI.pStages = shaderStages.data(); pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color });; diff --git a/shaders/glsl/particlefire/normalmap.frag b/shaders/glsl/particlesystem/normalmap.frag similarity index 100% rename from shaders/glsl/particlefire/normalmap.frag rename to shaders/glsl/particlesystem/normalmap.frag diff --git a/shaders/glsl/particlefire/normalmap.frag.spv b/shaders/glsl/particlesystem/normalmap.frag.spv similarity index 100% rename from shaders/glsl/particlefire/normalmap.frag.spv rename to shaders/glsl/particlesystem/normalmap.frag.spv diff --git a/shaders/glsl/particlefire/normalmap.vert b/shaders/glsl/particlesystem/normalmap.vert similarity index 100% rename from shaders/glsl/particlefire/normalmap.vert rename to shaders/glsl/particlesystem/normalmap.vert diff --git a/shaders/glsl/particlefire/normalmap.vert.spv b/shaders/glsl/particlesystem/normalmap.vert.spv similarity index 100% rename from shaders/glsl/particlefire/normalmap.vert.spv rename to shaders/glsl/particlesystem/normalmap.vert.spv diff --git a/shaders/glsl/particlefire/particle.frag b/shaders/glsl/particlesystem/particle.frag similarity index 100% rename from shaders/glsl/particlefire/particle.frag rename to shaders/glsl/particlesystem/particle.frag diff --git a/shaders/glsl/particlefire/particle.frag.spv b/shaders/glsl/particlesystem/particle.frag.spv similarity index 100% rename from shaders/glsl/particlefire/particle.frag.spv rename to shaders/glsl/particlesystem/particle.frag.spv diff --git a/shaders/glsl/particlefire/particle.vert b/shaders/glsl/particlesystem/particle.vert similarity index 100% rename from shaders/glsl/particlefire/particle.vert rename to shaders/glsl/particlesystem/particle.vert diff --git a/shaders/glsl/particlefire/particle.vert.spv b/shaders/glsl/particlesystem/particle.vert.spv similarity index 100% rename from shaders/glsl/particlefire/particle.vert.spv rename to shaders/glsl/particlesystem/particle.vert.spv diff --git a/shaders/hlsl/README.md b/shaders/hlsl/README.md index 20a3de5b..8aec80f1 100644 --- a/shaders/hlsl/README.md +++ b/shaders/hlsl/README.md @@ -6,7 +6,7 @@ These can be compiled with [DXC](https://github.com/microsoft/DirectXShaderCompi ### Known issues - specialization constants can't be used to specify array size. -- `gl_PointCoord` not supported. HLSL has no equivalent. We changed the shaders to calulate the PointCoord manually in the shader. (`computenbody`, `computeparticles`, `particlefire` examples). +- `gl_PointCoord` not supported. HLSL has no equivalent. We changed the shaders to calulate the PointCoord manually in the shader. (`computenbody`, `computeparticles`, `particlesystem` examples). - HLSL doesn't have inverse operation (`deferred`, `hdr`, `instancing`, `skeletalanimation` & `texturecubemap` examples). - `modf` causes compilation to fail without errors or warnings. (`modf` not used by any examples, easily confused with fmod) - In `specializationconstants` example, shader compilation fails with error: diff --git a/shaders/hlsl/particlefire/normalmap.frag b/shaders/hlsl/particlesystem/normalmap.frag similarity index 100% rename from shaders/hlsl/particlefire/normalmap.frag rename to shaders/hlsl/particlesystem/normalmap.frag diff --git a/shaders/hlsl/particlefire/normalmap.frag.spv b/shaders/hlsl/particlesystem/normalmap.frag.spv similarity index 100% rename from shaders/hlsl/particlefire/normalmap.frag.spv rename to shaders/hlsl/particlesystem/normalmap.frag.spv diff --git a/shaders/hlsl/particlefire/normalmap.vert b/shaders/hlsl/particlesystem/normalmap.vert similarity index 100% rename from shaders/hlsl/particlefire/normalmap.vert rename to shaders/hlsl/particlesystem/normalmap.vert diff --git a/shaders/hlsl/particlefire/normalmap.vert.spv b/shaders/hlsl/particlesystem/normalmap.vert.spv similarity index 100% rename from shaders/hlsl/particlefire/normalmap.vert.spv rename to shaders/hlsl/particlesystem/normalmap.vert.spv diff --git a/shaders/hlsl/particlefire/particle.frag b/shaders/hlsl/particlesystem/particle.frag similarity index 100% rename from shaders/hlsl/particlefire/particle.frag rename to shaders/hlsl/particlesystem/particle.frag diff --git a/shaders/hlsl/particlefire/particle.frag.spv b/shaders/hlsl/particlesystem/particle.frag.spv similarity index 100% rename from shaders/hlsl/particlefire/particle.frag.spv rename to shaders/hlsl/particlesystem/particle.frag.spv diff --git a/shaders/hlsl/particlefire/particle.vert b/shaders/hlsl/particlesystem/particle.vert similarity index 100% rename from shaders/hlsl/particlefire/particle.vert rename to shaders/hlsl/particlesystem/particle.vert diff --git a/shaders/hlsl/particlefire/particle.vert.spv b/shaders/hlsl/particlesystem/particle.vert.spv similarity index 100% rename from shaders/hlsl/particlefire/particle.vert.spv rename to shaders/hlsl/particlesystem/particle.vert.spv