From c5e16e2ee176b6e8c4cd9c6e5d2888e29746640e Mon Sep 17 00:00:00 2001 From: saschawillems Date: Tue, 27 Nov 2018 21:24:33 +0100 Subject: [PATCH] Command buffer update strategy scenarios example (wip) --- examples/commandbuffers/commandbuffers.cpp | 351 +++++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 examples/commandbuffers/commandbuffers.cpp diff --git a/examples/commandbuffers/commandbuffers.cpp b/examples/commandbuffers/commandbuffers.cpp new file mode 100644 index 00000000..f9b35bd2 --- /dev/null +++ b/examples/commandbuffers/commandbuffers.cpp @@ -0,0 +1,351 @@ +/* +* Vulkan Example - Different command buffer update strategies +* +* While for many basic example workloads command buffers are prebuilt and just reused, +* in a real-life setting command buffers are usually recreated all the time +* This sample will demonstrate different command buffer update scenarios +* +* Copyright (C) 2018 by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +*/ + +#include +#include +#include +#include +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include +#include "vulkanexamplebase.h" +#include "VulkanBuffer.hpp" +#include "VulkanModel.hpp" + +#define ENABLE_VALIDATION false + +class VulkanExample : public VulkanExampleBase +{ +public: + + // Vertex layout for the models + vks::VertexLayout vertexLayout = vks::VertexLayout({ + vks::VERTEX_COMPONENT_POSITION, + vks::VERTEX_COMPONENT_NORMAL, + vks::VERTEX_COMPONENT_UV, + vks::VERTEX_COMPONENT_COLOR, + }); + + struct { + vks::Model scene; + } models; + + struct UBOVS { + glm::mat4 projection; + glm::mat4 model; + } uboVS; + vks::Buffer uniformBuffer; + + VkPipelineLayout pipelineLayout; + VkPipeline pipeline; + VkDescriptorSetLayout descriptorSetLayout; + VkDescriptorSet descriptorSet; + + // Single command buffer scenario + VkFence waitFence; + VkCommandPool commandPool; + VkCommandBuffer commandBuffer; + + /// @todo: Multiple command buffers ("render ahead") + /// @todo: Only update command buffer(s) if scene changed + /// @todo: dynamic scene with frustum culling (maybe terrain + simple trees) + + std::array pushConstants; + + VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) + { + rotationSpeed = 0.5f; + timerSpeed *= 0.5f; + title = "Command buffers"; + settings.overlay = false; + camera.type = Camera::CameraType::lookat; + camera.position = { 0.0f, 0.0f, -30.0f }; + camera.setRotation(glm::vec3(-32.5f, 45.0f, 0.0f)); + camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 64.0f); + } + + ~VulkanExample() + { + vkDestroyPipeline(device, pipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + models.scene.destroy(); + uniformBuffer.destroy(); + } + + void setupDescriptors() + { + // Pool + std::vector poolSizes = { + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), + }; + VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes.size(), poolSizes.data(), 1); + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); + + // Layouts + std::vector setLayoutBindings = { + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), + }; + VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); + + VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); + VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(pushConstants), 0); + pipelineLayoutCI.pushConstantRangeCount = 1; + pipelineLayoutCI.pPushConstantRanges = &pushConstantRange; + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); + + // Descriptors + VkDescriptorSetAllocateInfo descriptorSetAI = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAI, &descriptorSet)); + VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor); + vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); + } + + void preparePipelines() + { + VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); + VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE, 0); + VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); + VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); + VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL); + VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); + VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0); + std::vector dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStates); + + // Vertex bindings and attributes + std::vector vertexInputBindings = { + vks::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX) + }; + + std::vector vertexInputAttributes = { + vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Location 0 : Position + vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Location 1 : Normal + vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6), // Location 3 : UV + vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 8) // Location 3 : Cpöpr + }; + + VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); + vertexInputState.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); + vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); + vertexInputState.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); + vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); + + // Load shaders + std::array shaderStages = { + loadShader(getAssetPath() + "shaders/pushconstants/lights.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), + loadShader(getAssetPath() + "shaders/pushconstants/lights.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) + }; + + VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0); + pipelineCI.pVertexInputState = &vertexInputState; + pipelineCI.pInputAssemblyState = &inputAssemblyStateCI; + pipelineCI.pRasterizationState = &rasterizationStateCI; + pipelineCI.pColorBlendState = &colorBlendStateCI; + pipelineCI.pMultisampleState = &multisampleStateCI; + pipelineCI.pViewportState = &viewportStateCI; + pipelineCI.pDepthStencilState = &depthStencilStateCI; + pipelineCI.pDynamicState = &dynamicStateCI; + pipelineCI.stageCount = shaderStages.size(); + pipelineCI.pStages = shaderStages.data(); + + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); + } + + void prepareUniformBuffers() + { + // Vertex shader uniform buffer block + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &uniformBuffer, + sizeof(uboVS))); + + // Map persistent + VK_CHECK_RESULT(uniformBuffer.map()); + + updateUniformBuffers(); + } + + void updateUniformBuffers() + { + uboVS.projection = camera.matrices.perspective; + uboVS.model = camera.matrices.view; + memcpy(uniformBuffer.mapped, &uboVS, sizeof(uboVS)); + } + + void recordCommandBuffer() + { + // A fence is used to wait until this command buffer has finished execution and is no longer in-flight + // Command buffers can only be re-recorded or destroyed if they are not in-flight + + VK_CHECK_RESULT(vkWaitForFences(device, 1, &waitFence, VK_TRUE, UINT64_MAX)); + VK_CHECK_RESULT(vkResetFences(device, 1, &waitFence)); + + VkClearValue clearValues[2]; + clearValues[0].color = defaultClearColor; + clearValues[1].depthStencil = { 1.0f, 0 }; + + VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); + 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[currentBuffer]; + + VkCommandBufferBeginInfo commandBufferBeginInfo = vks::initializers::commandBufferBeginInfo(); + VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &commandBufferBeginInfo)); + + vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + + VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + // Update light positions + // w component = light radius scale + const float r = 7.5f; + const float sin_t = sin(glm::radians(timer * 360)); + const float cos_t = cos(glm::radians(timer * 360)); + const float y = 4.0f; + pushConstants[0] = glm::vec4(r * 1.1 * sin_t, y, r * 1.1 * cos_t, 1.0f); + pushConstants[1] = glm::vec4(-r * sin_t, y, -r * cos_t, 1.0f); + pushConstants[2] = glm::vec4(r * 0.85f * sin_t, y, -sin_t * 2.5f, 1.5f); + pushConstants[3] = glm::vec4(0.0f, y, r * 1.25f * cos_t, 1.5f); + pushConstants[4] = glm::vec4(r * 2.25f * cos_t, y, 0.0f, 1.25f); + pushConstants[5] = glm::vec4(r * 2.5f * cos_t, y, r * 2.5f * sin_t, 1.25f); + + // Submit via push constant (rather than a UBO) + vkCmdPushConstants( + commandBuffer, + pipelineLayout, + VK_SHADER_STAGE_VERTEX_BIT, + 0, + sizeof(pushConstants), + pushConstants.data()); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); + + VkDeviceSize offsets[1] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, &models.scene.vertices.buffer, offsets); + vkCmdBindIndexBuffer(commandBuffer, models.scene.indices.buffer, 0, VK_INDEX_TYPE_UINT32); + + vkCmdDrawIndexed(commandBuffer, models.scene.indexCount, 1, 0, 0, 0); + + //drawUI(commandBuffer); + + vkCmdEndRenderPass(commandBuffer); + + VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer)); + } + + void draw() + { + // Acquire the next image from the swap chain + { + VkResult acquire = swapChain.acquireNextImage(semaphores.presentComplete, ¤tBuffer); + if ((acquire == VK_ERROR_OUT_OF_DATE_KHR) || (acquire == VK_SUBOPTIMAL_KHR)) { + windowResize(); + } + else { + VK_CHECK_RESULT(acquire); + } + } + + // (Re-)record command buffer + if (!paused) { + recordCommandBuffer(); + } + + // Command buffer to be sumitted to the queue + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + // Submit to queue + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, waitFence)); + + // Present + { + VkResult present = swapChain.queuePresent(queue, currentBuffer, semaphores.renderComplete); + if (!((present == VK_SUCCESS) || (present == VK_SUBOPTIMAL_KHR))) { + if (present == VK_ERROR_OUT_OF_DATE_KHR) { + windowResize(); + return; + } + else { + VK_CHECK_RESULT(present); + } + } + } + } + + void loadAssets() + { + models.scene.loadFromFile(getAssetPath() + "models/samplescene.dae", vertexLayout, 0.35f, vulkanDevice, queue); + } + + void prepare() + { + VulkanExampleBase::prepare(); + + /* + Single command buffer, single thread scenario + */ + + // A fence is need to check for command buffer completion before we can recreate it + VkFenceCreateInfo fenceCI{ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr, VK_FENCE_CREATE_SIGNALED_BIT }; + VK_CHECK_RESULT(vkCreateFence(device, &fenceCI, nullptr, &waitFence)); + + // Create a single command pool for the applications main thread + VkCommandPoolCreateInfo commandPoolCI{}; + commandPoolCI.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + /// @todo: Comment flags + commandPoolCI.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + commandPoolCI.queueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; + VK_CHECK_RESULT(vkCreateCommandPool(device, &commandPoolCI, nullptr, &commandPool)); + + // Create a single command buffer that is recorded every frame + VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1); + VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &commandBuffer)); + + loadAssets(); + prepareUniformBuffers(); + setupDescriptors(); + preparePipelines(); + prepared = true; + } + + virtual void render() + { + if (!prepared) { + return; + } + draw(); + if (camera.updated) { + updateUniformBuffers(); + } + } +}; + +VULKAN_EXAMPLE_MAIN() \ No newline at end of file