/* * Vulkan Example - Using inline uniform blocks for passing data to shader stages at descriptor setup * Note: Requires a device that supports the VK_EXT_inline_uniform_block extension * * Relevant code parts are marked with [POI] * * Copyright (C) 2018-2023 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ #include "vulkanexamplebase.h" #include "VulkanglTFModel.h" class VulkanExample : public VulkanExampleBase { public: VkPhysicalDeviceInlineUniformBlockFeaturesEXT enabledInlineUniformBlockFeatures{}; vkglTF::Model model; struct Object { struct Material { float roughness; float metallic; float r, g, b; float ambient; } material; VkDescriptorSet descriptorSet; void setRandomMaterial() { std::random_device rndDevice; std::default_random_engine rndEngine(rndDevice()); std::uniform_real_distribution rndDist(0.1f, 1.0f); material.r = rndDist(rndEngine); material.g = rndDist(rndEngine); material.b = rndDist(rndEngine); material.ambient = 0.0025f; material.roughness = glm::clamp(rndDist(rndEngine), 0.005f, 1.0f); material.metallic = glm::clamp(rndDist(rndEngine), 0.005f, 1.0f); } }; std::array objects{}; struct UniformData { glm::mat4 projection; glm::mat4 model; glm::mat4 view; glm::vec3 camPos; } uniformData; vks::Buffer uniformBuffer; VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; VkPipeline pipeline{ VK_NULL_HANDLE }; VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; struct DescriptorSetLaysts { VkDescriptorSetLayout scene{ VK_NULL_HANDLE }; VkDescriptorSetLayout object{ VK_NULL_HANDLE }; } descriptorSetLayouts; VulkanExample() : VulkanExampleBase() { title = "Inline uniform blocks"; camera.type = Camera::CameraType::firstperson; camera.setPosition(glm::vec3(0.0f, 0.0f, -10.0f)); camera.setRotation(glm::vec3(0.0, 0.0f, 0.0f)); camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f); camera.movementSpeed = 4.0f; camera.rotationSpeed = 0.25f; /* [POI] Enable extensions required for inline uniform blocks */ enabledDeviceExtensions.push_back(VK_EXT_INLINE_UNIFORM_BLOCK_EXTENSION_NAME); enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE1_EXTENSION_NAME); enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); /* [POI] We also need to enable the inline uniform block feature (using the dedicated physical device structure) */ enabledInlineUniformBlockFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES_EXT; enabledInlineUniformBlockFeatures.inlineUniformBlock = VK_TRUE; deviceCreatepNextChain = &enabledInlineUniformBlockFeatures; } ~VulkanExample() { if (device) { vkDestroyPipeline(device, pipeline, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.scene, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.object, nullptr); uniformBuffer.destroy(); } } void buildCommandBuffers() { VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); VkClearValue clearValues[2]; clearValues[0].color = { { 0.15f, 0.15f, 0.15f, 1.0f } }; 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); 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); // Render objects vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); uint32_t objcount = static_cast(objects.size()); for (uint32_t x = 0; x < objcount; x++) { /* [POI] Bind descriptor sets Set 0 = Scene matrices: Set 1 = Object inline uniform block (In shader pbr.frag: layout (set = 1, binding = 0) uniform UniformInline ... ) */ std::vector descriptorSets = { descriptorSet, objects[x].descriptorSet }; vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 2, descriptorSets.data(), 0, nullptr); glm::vec3 pos = glm::vec3(sin(glm::radians(x * (360.0f / objcount))), cos(glm::radians(x * (360.0f / objcount))), 0.0f) * 3.5f; vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::vec3), &pos); model.draw(drawCmdBuffers[i]); } drawUI(drawCmdBuffers[i]); vkCmdEndRenderPass(drawCmdBuffers[i]); VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); } } void loadAssets() { model.loadFromFile(getAssetPath() + "models/sphere.gltf", vulkanDevice, queue); // Setup random materials for every object in the scene for (uint32_t i = 0; i < objects.size(); i++) { objects[i].setRandomMaterial(); } } void setupDescriptors() { // Pool std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), /* [POI] Allocate inline uniform blocks */ vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT, static_cast(objects.size()) * sizeof(Object::Material)), }; VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, static_cast(objects.size()) + 1); /* [POI] New structure that has to be chained into the descriptor pool's createinfo 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 = static_cast(objects.size()); descriptorPoolCI.pNext = &descriptorPoolInlineUniformBlockCreateInfo; VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorPool)); // Layouts std::vector setLayoutBindings{}; VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{}; // Scene matrices setLayoutBindings = { vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0), }; descriptorLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayouts.scene)); setLayoutBindings = { /* [POI] Setup inline uniform block for set 1 at binding 0 (see fragment shader) Descriptor count for an inline uniform block contains data sizes of the block (last parameter) */ vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(Object::Material)), }; descriptorLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayouts.object)); // Sets // Scene VkDescriptorSetAllocateInfo descriptorAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.scene, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorAllocateInfo, &descriptorSet)); std::vector writeDescriptorSets = { vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), }; vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); // Objects for (auto& object : objects) { VkDescriptorSetAllocateInfo descriptorAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.object, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorAllocateInfo, &object.descriptorSet)); /* [POI] New structure that defines size and data of the inline uniform block needs to be chained into the write descriptor set We will be using this inline uniform block to pass per-object material information to the fragment shader */ VkWriteDescriptorSetInlineUniformBlockEXT writeDescriptorSetInlineUniformBlock{}; writeDescriptorSetInlineUniformBlock.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK_EXT; writeDescriptorSetInlineUniformBlock.dataSize = sizeof(Object::Material); // Uniform data for the inline block writeDescriptorSetInlineUniformBlock.pData = &object.material; /* [POI] Setup the inline uniform block */ VkWriteDescriptorSet writeDescriptorSet{}; writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT; writeDescriptorSet.dstSet = object.descriptorSet; writeDescriptorSet.dstBinding = 0; // Descriptor count for an inline uniform block contains data sizes of the block(last parameter) writeDescriptorSet.descriptorCount = sizeof(Object::Material); // Chain inline uniform block structure writeDescriptorSet.pNext = &writeDescriptorSetInlineUniformBlock; vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); } } void preparePipelines() { /* [POI] Pipeline layout usin two sets, one for the scene matrices and one for the per-object inline uniform blocks */ std::vector setLayouts = { descriptorSetLayouts.scene, // Set 0 = Scene matrices descriptorSetLayouts.object // Set 1 = Object inline uniform block }; VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast(setLayouts.size())); // We use push constants for passing object positions std::vector pushConstantRanges = { vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::vec3), 0), }; pipelineLayoutCI.pushConstantRangeCount = 1; pipelineLayoutCI.pPushConstantRanges = pushConstantRanges.data(); VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); // Pipeline 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_FRONT_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE); 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); VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT); std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); std::array shaderStages; VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass); pipelineCI.pInputAssemblyState = &inputAssemblyStateCI; pipelineCI.pRasterizationState = &rasterizationStateCI; pipelineCI.pColorBlendState = &colorBlendStateCI; pipelineCI.pMultisampleState = &multisampleStateCI; pipelineCI.pViewportState = &viewportStateCI; pipelineCI.pDepthStencilState = &depthStencilStateCI; pipelineCI.pDynamicState = &dynamicStateCI; pipelineCI.stageCount = static_cast(shaderStages.size()); pipelineCI.pStages = shaderStages.data(); pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal }); shaderStages[0] = loadShader(getShadersPath() + "inlineuniformblocks/pbr.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getShadersPath() + "inlineuniformblocks/pbr.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); } void prepareUniformBuffers() { 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(UniformData))); VK_CHECK_RESULT(uniformBuffer.map()); updateUniformBuffers(); } void updateUniformBuffers() { uniformData.projection = camera.matrices.perspective; uniformData.view = camera.matrices.view; uniformData.model = glm::scale(glm::mat4(1.0f), glm::vec3(0.5f)); uniformData.camPos = camera.position * glm::vec3(-1.0f, 1.0f, -1.0f); memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); } void prepare() { VulkanExampleBase::prepare(); loadAssets(); prepareUniformBuffers(); setupDescriptors(); preparePipelines(); buildCommandBuffers(); prepared = true; } void draw() { VulkanExampleBase::prepareFrame(); submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); VulkanExampleBase::submitFrame(); } virtual void render() { if (!prepared) return; updateUniformBuffers(); draw(); } /* [POI] Update descriptor sets at runtime, called from the UI to randomize materials */ void updateMaterials() { // Setup random materials for every object in the scene for (uint32_t i = 0; i < objects.size(); i++) { objects[i].setRandomMaterial(); } for (auto &object : objects) { /* [POI] New structure that defines size and data of the inline uniform block needs to be chained into the write descriptor set We will be using this inline uniform block to pass per-object material information to the fragment shader */ VkWriteDescriptorSetInlineUniformBlockEXT writeDescriptorSetInlineUniformBlock{}; writeDescriptorSetInlineUniformBlock.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK_EXT; writeDescriptorSetInlineUniformBlock.dataSize = sizeof(Object::Material); // Uniform data for the inline block writeDescriptorSetInlineUniformBlock.pData = &object.material; /* [POI] Update the object's inline uniform block */ VkWriteDescriptorSet writeDescriptorSet{}; writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT; writeDescriptorSet.dstSet = object.descriptorSet; writeDescriptorSet.dstBinding = 0; writeDescriptorSet.descriptorCount = sizeof(Object::Material); writeDescriptorSet.pNext = &writeDescriptorSetInlineUniformBlock; vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); } } virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) { if (overlay->button("Randomize")) { updateMaterials(); } } }; VULKAN_EXAMPLE_MAIN()