/* * Vulkan Example - Using inline uniform blocks for passing data to shader stages * Note: Requires a device that supports the VK_EXT_inline_uniform_block extension * * Relevant code parts are marked with [POI] * * 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 #include "vulkanexamplebase.h" #include "VulkanTexture.hpp" #include "VulkanModel.hpp" #define ENABLE_VALIDATION false class VulkanExample : public VulkanExampleBase { public: bool animate = true; vks::VertexLayout vertexLayout = vks::VertexLayout({ vks::VERTEX_COMPONENT_POSITION, vks::VERTEX_COMPONENT_NORMAL, vks::VERTEX_COMPONENT_UV, vks::VERTEX_COMPONENT_COLOR, }); /* [POI] This is the data structure that'll be passed using inline uniform blocks */ struct InlineBlockData { glm::vec4 color; }; struct Cube { struct Matrices { glm::mat4 projection; glm::mat4 view; glm::mat4 model; } matrices; InlineBlockData inlineBlockData; VkDescriptorSet descriptorSet; vks::Texture2D texture; vks::Buffer uniformBuffer; glm::vec3 rotation; }; std::array cubes; struct Models { vks::Model cube; } models; VkPipeline pipeline; VkPipelineLayout pipelineLayout; VkDescriptorSetLayout descriptorSetLayout; VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) { title = "Inline uniform blocks"; settings.overlay = true; camera.type = Camera::CameraType::lookat; camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f); camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f)); camera.setTranslation(glm::vec3(0.0f, 0.0f, -5.0f)); /* [POI] Enable extension required for conditional rendering */ enabledDeviceExtensions.push_back(VK_EXT_INLINE_UNIFORM_BLOCK_EXTENSION_NAME); } ~VulkanExample() { vkDestroyPipeline(device, pipeline, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); models.cube.destroy(); for (auto cube : cubes) { cube.uniformBuffer.destroy(); cube.texture.destroy(); } } void buildCommandBuffers() { VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); 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; for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) { renderPassBeginInfo.framebuffer = frameBuffers[i]; VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); VkDeviceSize offsets[1] = { 0 }; vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &models.cube.vertices.buffer, offsets); vkCmdBindIndexBuffer(drawCmdBuffers[i], models.cube.indices.buffer, 0, VK_INDEX_TYPE_UINT32); for (auto cube : cubes) { vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &cube.descriptorSet, 0, nullptr); vkCmdDrawIndexed(drawCmdBuffers[i], models.cube.indexCount, 1, 0, 0, 0); } drawUI(drawCmdBuffers[i]); vkCmdEndRenderPass(drawCmdBuffers[i]); VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); } } void loadAssets() { models.cube.loadFromFile(getAssetPath() + "models/cube.dae", vertexLayout, 1.0f, vulkanDevice, queue); cubes[0].texture.loadFromFile(getAssetPath() + "textures/crate01_color_height_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); cubes[1].texture.loadFromFile(getAssetPath() + "textures/crate02_color_height_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); cubes[0].inlineBlockData.color = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f); cubes[1].inlineBlockData.color = glm::vec4(0.0f, 0.0f, 1.0f, 1.0f); } /* [POI] Set up descriptor sets and set layout */ void setupDescriptors() { const uint32_t cubeCount = static_cast(cubes.size()); /* Descriptor pool */ std::array descriptorPoolSizes{}; // One uniform buffer descriptor per cube descriptorPoolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; descriptorPoolSizes[0].descriptorCount = cubeCount; /* [POI] One inline uniform block descriptor per cube */ descriptorPoolSizes[1].type = VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT; // Descriptor count for inline uniform blocks contains the combined data sizes of all inline uniform blocks used from this pool descriptorPoolSizes[1].descriptorCount = cubeCount * sizeof(InlineBlockData); // One combined image samples per cube descriptorPoolSizes[2].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; descriptorPoolSizes[2].descriptorCount = static_cast(cubes.size()); // Create the global descriptor pool VkDescriptorPoolCreateInfo descriptorPoolCI = {}; descriptorPoolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; descriptorPoolCI.poolSizeCount = static_cast(descriptorPoolSizes.size()); descriptorPoolCI.pPoolSizes = descriptorPoolSizes.data(); descriptorPoolCI.maxSets = static_cast(descriptorPoolSizes.size()); # /* [POI] New structure that has to be chained into the descriptor pool create info if you want to allocate inline uniform blocks */ VkDescriptorPoolInlineUniformBlockCreateInfoEXT descriptorPoolInlineUniformBlockCreateInfo{}; descriptorPoolInlineUniformBlockCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_INLINE_UNIFORM_BLOCK_CREATE_INFO_EXT; descriptorPoolInlineUniformBlockCreateInfo.maxInlineUniformBlockBindings = 1; // Chain into descriptor pool create info descriptorPoolCI.pNext = &descriptorPoolInlineUniformBlockCreateInfo; VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorPool)); /* Descriptor set layout */ std::array setLayoutBindings{}; // Binding 0: Uniform buffers (used to pass matrices) setLayoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; setLayoutBindings[0].binding = 0; setLayoutBindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; setLayoutBindings[0].descriptorCount = 1; /* [POI] Binding 1: Inline uniform block */ setLayoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT; setLayoutBindings[1].binding = 1; setLayoutBindings[1].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; // Descriptor count for an inline uniform block contains data sizes of the block setLayoutBindings[1].descriptorCount = sizeof(InlineBlockData); // Binding 2: Combined image sampler (used to pass per object texture information) setLayoutBindings[2].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; setLayoutBindings[2].binding = 2; setLayoutBindings[2].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; setLayoutBindings[2].descriptorCount = 1; // Create the descriptor set layout VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{}; descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; descriptorLayoutCI.bindingCount = static_cast(setLayoutBindings.size()); descriptorLayoutCI.pBindings = setLayoutBindings.data(); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayout)); /* Descriptor sets */ for (auto &cube: cubes) { // Allocates an empty descriptor set without actual descriptors from the pool using the set layout VkDescriptorSetAllocateInfo allocateInfo{}; allocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocateInfo.descriptorPool = descriptorPool; allocateInfo.descriptorSetCount = 1; allocateInfo.pSetLayouts = &descriptorSetLayout; VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocateInfo, &cube.descriptorSet)); // Update the descriptor set with the actual descriptors matching shader bindings set in the layout std::array writeDescriptorSets{}; // Binding 0: Object matrices Uniform buffer writeDescriptorSets[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptorSets[0].dstSet = cube.descriptorSet; writeDescriptorSets[0].dstBinding = 0; writeDescriptorSets[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; writeDescriptorSets[0].pBufferInfo = &cube.uniformBuffer.descriptor; writeDescriptorSets[0].descriptorCount = 1; /* [POI] Binding 1: Inline uniform block */ writeDescriptorSets[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptorSets[1].dstSet = cube.descriptorSet; writeDescriptorSets[1].dstBinding = 1; writeDescriptorSets[1].descriptorType = VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT; // The dstArrayElement member can be used to define an offset for inline uniform blocks writeDescriptorSets[1].dstArrayElement = 0; // TODO: API-Design from hell writeDescriptorSets[1].descriptorCount = sizeof(glm::vec4); /* [POI] New structure that defines size and data of the inline uniform block */ VkWriteDescriptorSetInlineUniformBlockEXT writeDescriptorSetInlineUniformBlock{}; writeDescriptorSetInlineUniformBlock.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK_EXT; writeDescriptorSetInlineUniformBlock.dataSize = sizeof(InlineBlockData); writeDescriptorSetInlineUniformBlock.pData = &cube.inlineBlockData; // Needs to be chained to an existing write descriptor set structure writeDescriptorSets[1].pNext = &writeDescriptorSetInlineUniformBlock; // Binding 2: Object texture writeDescriptorSets[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptorSets[2].dstSet = cube.descriptorSet; writeDescriptorSets[2].dstBinding = 2; writeDescriptorSets[2].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; writeDescriptorSets[2].pImageInfo = &cube.texture.descriptor; writeDescriptorSets[2].descriptorCount = 1; vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } } void preparePipelines() { VkPipelineLayoutCreateInfo pipelineLayoutCI{}; pipelineLayoutCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutCI.setLayoutCount = 1; pipelineLayoutCI.pSetLayouts = &descriptorSetLayout; VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); const std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; 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); VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast(dynamicStateEnables.size()),0); // Vertex bindings and attributes const std::vector vertexInputBindings = { vks::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX), }; const 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 2: UV vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 8), // Location 3: Color }; 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(); VkGraphicsPipelineCreateInfo pipelineCreateInfoCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0); pipelineCreateInfoCI.pVertexInputState = &vertexInputState; pipelineCreateInfoCI.pInputAssemblyState = &inputAssemblyStateCI; pipelineCreateInfoCI.pRasterizationState = &rasterizationStateCI; pipelineCreateInfoCI.pColorBlendState = &colorBlendStateCI; pipelineCreateInfoCI.pMultisampleState = &multisampleStateCI; pipelineCreateInfoCI.pViewportState = &viewportStateCI; pipelineCreateInfoCI.pDepthStencilState = &depthStencilStateCI; pipelineCreateInfoCI.pDynamicState = &dynamicStateCI; const std::array shaderStages = { loadShader(getAssetPath() + "shaders/inlineuniformblocks/cube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), loadShader(getAssetPath() + "shaders/inlineuniformblocks/cube.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) }; pipelineCreateInfoCI.stageCount = static_cast(shaderStages.size()); pipelineCreateInfoCI.pStages = shaderStages.data(); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfoCI, nullptr, &pipeline)); } void prepareUniformBuffers() { // Vertex shader matrix uniform buffer block for (auto& cube : cubes) { VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &cube.uniformBuffer, sizeof(Cube::Matrices))); VK_CHECK_RESULT(cube.uniformBuffer.map()); } updateUniformBuffers(); } void updateUniformBuffers() { cubes[0].matrices.model = glm::translate(glm::mat4(1.0f), glm::vec3(-2.0f, 0.0f, 0.0f)); cubes[1].matrices.model = glm::translate(glm::mat4(1.0f), glm::vec3( 1.5f, 0.5f, 0.0f)); for (auto& cube : cubes) { cube.matrices.projection = camera.matrices.perspective; cube.matrices.view = camera.matrices.view; cube.matrices.model = glm::rotate(cube.matrices.model, glm::radians(cube.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); cube.matrices.model = glm::rotate(cube.matrices.model, glm::radians(cube.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); cube.matrices.model = glm::rotate(cube.matrices.model, glm::radians(cube.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); memcpy(cube.uniformBuffer.mapped, &cube.matrices, sizeof(cube.matrices)); } } void draw() { VulkanExampleBase::prepareFrame(); submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); VulkanExampleBase::submitFrame(); } void prepare() { VulkanExampleBase::prepare(); loadAssets(); prepareUniformBuffers(); setupDescriptors(); preparePipelines(); buildCommandBuffers(); prepared = true; } virtual void render() { if (!prepared) return; draw(); if (animate) { cubes[0].rotation.x += 2.5f * frameTimer; if (cubes[0].rotation.x > 360.0f) cubes[0].rotation.x -= 360.0f; cubes[1].rotation.y += 2.0f * frameTimer; if (cubes[1].rotation.x > 360.0f) cubes[1].rotation.x -= 360.0f; } if ((camera.updated) || (animate)) { updateUniformBuffers(); } } virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) { if (overlay->header("Settings")) { overlay->checkBox("Animate", &animate); } } }; VULKAN_EXAMPLE_MAIN()