Command buffer update strategy scenarios example (wip)

This commit is contained in:
saschawillems 2018-11-27 21:24:33 +01:00
parent ef68d59a0b
commit c5e16e2ee1

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <vector>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <vulkan/vulkan.h>
#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<glm::vec4, 6> 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<VkDescriptorPoolSize> 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<VkDescriptorSetLayoutBinding> 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<VkDynamicState> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStates);
// Vertex bindings and attributes
std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
vks::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX)
};
std::vector<VkVertexInputAttributeDescription> 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<uint32_t>(vertexInputBindings.size());
vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
// Load shaders
std::array<VkPipelineShaderStageCreateInfo, 2> 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, &currentBuffer);
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()