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/triangle/triangle.cpp b/examples/triangle/triangle.cpp index 7d5905c3..705ca144 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,228 +175,45 @@ 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 @@ -420,158 +237,146 @@ public: 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 +384,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 +463,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 +490,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 +517,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])); } } @@ -807,16 +616,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 +633,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 +662,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 +681,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; + 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 +738,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 +793,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,30 +824,30 @@ 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; @@ -1052,60 +860,42 @@ public: 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,13 +903,137 @@ 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!"; + } + } };