11/*
22* Vulkan Example - glTF skinned animation
33*
4- * Copyright (C) 2020-2023 by Sascha Willems - www.saschawillems.de
4+ * Copyright (C) 2020-2025 by Sascha Willems - www.saschawillems.de
55*
66* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
77*/
@@ -44,16 +44,16 @@ VulkanglTFModel::~VulkanglTFModel()
4444 vkFreeMemory (vulkanDevice->logicalDevice , vertices.memory , nullptr );
4545 vkDestroyBuffer (vulkanDevice->logicalDevice , indices.buffer , nullptr );
4646 vkFreeMemory (vulkanDevice->logicalDevice , indices.memory , nullptr );
47- for (Image image : images)
48- {
47+ for (auto & image : images) {
4948 vkDestroyImageView (vulkanDevice->logicalDevice , image.texture .view , nullptr );
5049 vkDestroyImage (vulkanDevice->logicalDevice , image.texture .image , nullptr );
5150 vkDestroySampler (vulkanDevice->logicalDevice , image.texture .sampler , nullptr );
5251 vkFreeMemory (vulkanDevice->logicalDevice , image.texture .deviceMemory , nullptr );
5352 }
54- for (Skin skin : skins)
55- {
56- skin.ssbo .destroy ();
53+ for (auto & skin : skins) {
54+ for (auto & buffer : skin.storageBuffers ) {
55+ buffer.destroy ();
56+ }
5757 }
5858}
5959
@@ -200,14 +200,15 @@ void VulkanglTFModel::loadSkins(tinygltf::Model &input)
200200 memcpy (skins[i].inverseBindMatrices .data (), &buffer.data [accessor.byteOffset + bufferView.byteOffset ], accessor.count * sizeof (glm::mat4));
201201
202202 // Store inverse bind matrices for this skin in a shader storage buffer object
203- // To keep this sample simple, we create a host visible shader storage buffer
204- VK_CHECK_RESULT (vulkanDevice->createBuffer (
205- VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
206- VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
207- &skins[i].ssbo ,
208- sizeof (glm::mat4) * skins[i].inverseBindMatrices .size (),
209- skins[i].inverseBindMatrices .data ()));
210- VK_CHECK_RESULT (skins[i].ssbo .map ());
203+ // Just like other buffers that can be updated on the CPU while the GPU reads we need to duplicate them per frames in flight
204+ // To keep things simple, we use separate buffers, in a real-world application a better solution would be using one large storage buffer with separate regions per frames in flight
205+ skins[i].storageBuffers .resize (maxConcurrentFrames);
206+ skins[i].descriptorSets .resize (maxConcurrentFrames);
207+
208+ for (auto & buffer : skins[i].storageBuffers ) {
209+ VK_CHECK_RESULT (vulkanDevice->createBuffer (VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &buffer, sizeof (glm::mat4) * skins[i].inverseBindMatrices .size (), skins[i].inverseBindMatrices .data ()));
210+ VK_CHECK_RESULT (buffer.map ());
211+ }
211212 }
212213 }
213214}
@@ -505,7 +506,7 @@ void VulkanglTFModel::updateJoints(VulkanglTFModel::Node *node)
505506 jointMatrices[i] = inverseTransform * jointMatrices[i];
506507 }
507508 // Update ssbo
508- skin.ssbo .copyTo (jointMatrices.data (), jointMatrices.size () * sizeof (glm::mat4));
509+ skin.storageBuffers [currentBuffer] .copyTo (jointMatrices.data (), jointMatrices.size () * sizeof (glm::mat4));
509510 }
510511
511512 for (auto &child : node->children )
@@ -515,8 +516,10 @@ void VulkanglTFModel::updateJoints(VulkanglTFModel::Node *node)
515516}
516517
517518// POI: Update the current animation
518- void VulkanglTFModel::updateAnimation (float deltaTime)
519+ void VulkanglTFModel::updateAnimation (float deltaTime, uint32_t currentBuffer )
519520{
521+ this ->currentBuffer = currentBuffer;
522+
520523 if (activeAnimation > static_cast <uint32_t >(animations.size ()) - 1 )
521524 {
522525 std::cout << " No animation with index " << activeAnimation << std::endl;
@@ -598,7 +601,7 @@ void VulkanglTFModel::drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout p
598601 // Pass the final matrix to the vertex shader using push constants
599602 vkCmdPushConstants (commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0 , sizeof (glm::mat4), &nodeMatrix);
600603 // Bind SSBO with skin data for this node to set 1
601- vkCmdBindDescriptorSets (commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1 , 1 , &skins[node.skin ].descriptorSet , 0 , nullptr );
604+ vkCmdBindDescriptorSets (commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1 , 1 , &skins[node.skin ].descriptorSets [currentBuffer] , 0 , nullptr );
602605 for (VulkanglTFModel::Primitive &primitive : node.mesh .primitives )
603606 {
604607 if (primitive.indexCount > 0 )
@@ -639,28 +642,32 @@ void VulkanglTFModel::draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipel
639642
640643VulkanExample::VulkanExample () : VulkanExampleBase()
641644{
642- title = " glTF vertex skinning" ;
645+ useNewSync = true ;
646+ title = " glTF vertex skinning" ;
643647 camera.type = Camera::CameraType::lookat;
644648 camera.flipY = true ;
645649 camera.setPosition (glm::vec3 (0 .0f , 0 .75f , -2 .0f ));
646650 camera.setRotation (glm::vec3 (0 .0f , 0 .0f , 0 .0f ));
647651 camera.setPerspective (60 .0f , (float ) width / (float ) height, 0 .1f , 256 .0f );
652+ uniformBuffers.resize (maxConcurrentFrames);
653+ descriptorSets.resize (maxConcurrentFrames);
648654}
649655
650656VulkanExample::~VulkanExample ()
651657{
652- vkDestroyPipeline (device, pipelines.solid , nullptr );
653- if (pipelines.wireframe != VK_NULL_HANDLE)
654- {
655- vkDestroyPipeline (device, pipelines.wireframe , nullptr );
658+ if (device) {
659+ vkDestroyPipeline (device, pipelines.solid , nullptr );
660+ if (pipelines.wireframe != VK_NULL_HANDLE) {
661+ vkDestroyPipeline (device, pipelines.wireframe , nullptr );
662+ }
663+ vkDestroyPipelineLayout (device, pipelineLayout, nullptr );
664+ vkDestroyDescriptorSetLayout (device, descriptorSetLayouts.matrices , nullptr );
665+ vkDestroyDescriptorSetLayout (device, descriptorSetLayouts.textures , nullptr );
666+ vkDestroyDescriptorSetLayout (device, descriptorSetLayouts.jointMatrices , nullptr );
667+ for (auto & buffer : uniformBuffers) {
668+ buffer.destroy ();
669+ }
656670 }
657-
658- vkDestroyPipelineLayout (device, pipelineLayout, nullptr );
659- vkDestroyDescriptorSetLayout (device, descriptorSetLayouts.matrices , nullptr );
660- vkDestroyDescriptorSetLayout (device, descriptorSetLayouts.textures , nullptr );
661- vkDestroyDescriptorSetLayout (device, descriptorSetLayouts.jointMatrices , nullptr );
662-
663- shaderData.buffer .destroy ();
664671}
665672
666673void VulkanExample::getEnabledFeatures ()
@@ -672,44 +679,6 @@ void VulkanExample::getEnabledFeatures()
672679 };
673680}
674681
675- void VulkanExample::buildCommandBuffers ()
676- {
677- VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo ();
678-
679- VkClearValue clearValues[2 ];
680- clearValues[0 ].color = {{0 .25f , 0 .25f , 0 .25f , 1 .0f }};
681- ;
682- clearValues[1 ].depthStencil = {1 .0f , 0 };
683-
684- VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo ();
685- renderPassBeginInfo.renderPass = renderPass;
686- renderPassBeginInfo.renderArea .offset .x = 0 ;
687- renderPassBeginInfo.renderArea .offset .y = 0 ;
688- renderPassBeginInfo.renderArea .extent .width = width;
689- renderPassBeginInfo.renderArea .extent .height = height;
690- renderPassBeginInfo.clearValueCount = 2 ;
691- renderPassBeginInfo.pClearValues = clearValues;
692-
693- const VkViewport viewport = vks::initializers::viewport ((float ) width, (float ) height, 0 .0f , 1 .0f );
694- const VkRect2D scissor = vks::initializers::rect2D (width, height, 0 , 0 );
695-
696- for (int32_t i = 0 ; i < drawCmdBuffers.size (); ++i)
697- {
698- renderPassBeginInfo.framebuffer = frameBuffers[i];
699- VK_CHECK_RESULT (vkBeginCommandBuffer (drawCmdBuffers[i], &cmdBufInfo));
700- vkCmdBeginRenderPass (drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
701- vkCmdSetViewport (drawCmdBuffers[i], 0 , 1 , &viewport);
702- vkCmdSetScissor (drawCmdBuffers[i], 0 , 1 , &scissor);
703- // Bind scene matrices descriptor to set 0
704- vkCmdBindDescriptorSets (drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0 , 1 , &descriptorSet, 0 , nullptr );
705- vkCmdBindPipeline (drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : pipelines.solid );
706- glTFModel.draw (drawCmdBuffers[i], pipelineLayout);
707- drawUI (drawCmdBuffers[i]);
708- vkCmdEndRenderPass (drawCmdBuffers[i]);
709- VK_CHECK_RESULT (vkEndCommandBuffer (drawCmdBuffers[i]));
710- }
711- }
712-
713682void VulkanExample::loadglTFFile (std::string filename)
714683{
715684 tinygltf::Model glTFInput;
@@ -762,27 +731,21 @@ void VulkanExample::loadglTFFile(std::string filename)
762731 size_t indexBufferSize = indexBuffer.size () * sizeof (uint32_t );
763732 glTFModel.indices .count = static_cast <uint32_t >(indexBuffer.size ());
764733
765- struct StagingBuffer
766- {
767- VkBuffer buffer;
768- VkDeviceMemory memory;
769- } vertexStaging, indexStaging;
734+ vks::Buffer vertexStaging, indexStaging;
770735
771736 // Create host visible staging buffers (source)
772737 VK_CHECK_RESULT (vulkanDevice->createBuffer (
773738 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
774739 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
740+ &vertexStaging,
775741 vertexBufferSize,
776- &vertexStaging.buffer ,
777- &vertexStaging.memory ,
778742 vertexBuffer.data ()));
779743 // Index data
780744 VK_CHECK_RESULT (vulkanDevice->createBuffer (
781745 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
782746 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
747+ &indexStaging,
783748 indexBufferSize,
784- &indexStaging.buffer ,
785- &indexStaging.memory ,
786749 indexBuffer.data ()));
787750
788751 // Create device local buffers (target)
@@ -808,11 +771,8 @@ void VulkanExample::loadglTFFile(std::string filename)
808771 vkCmdCopyBuffer (copyCmd, indexStaging.buffer , glTFModel.indices .buffer , 1 , ©Region);
809772 vulkanDevice->flushCommandBuffer (copyCmd, queue, true );
810773
811- // Free staging resources
812- vkDestroyBuffer (device, vertexStaging.buffer , nullptr );
813- vkFreeMemory (device, vertexStaging.memory , nullptr );
814- vkDestroyBuffer (device, indexStaging.buffer , nullptr );
815- vkFreeMemory (device, indexStaging.memory , nullptr );
774+ vertexStaging.destroy ();
775+ indexStaging.destroy ();
816776}
817777
818778void VulkanExample::setupDescriptors ()
@@ -822,15 +782,15 @@ void VulkanExample::setupDescriptors()
822782 */
823783
824784 std::vector<VkDescriptorPoolSize> poolSizes = {
825- vks::initializers::descriptorPoolSize (VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 ),
785+ vks::initializers::descriptorPoolSize (VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, maxConcurrentFrames ),
826786 // One combined image sampler per material image/texture
827- vks::initializers::descriptorPoolSize (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast <uint32_t >(glTFModel.images .size ())),
787+ vks::initializers::descriptorPoolSize (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast <uint32_t >(glTFModel.images .size ()) * maxConcurrentFrames ),
828788 // One ssbo per skin
829- vks::initializers::descriptorPoolSize (VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast <uint32_t >(glTFModel.skins .size ())),
789+ vks::initializers::descriptorPoolSize (VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast <uint32_t >(glTFModel.skins .size ()) * maxConcurrentFrames ),
830790 };
831791 // Number of descriptor sets = One for the scene ubo + one per image + one per skin
832792 const uint32_t maxSetCount = static_cast <uint32_t >(glTFModel.images .size ()) + static_cast <uint32_t >(glTFModel.skins .size ()) + 1 ;
833- VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo (poolSizes, maxSetCount);
793+ VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo (poolSizes, maxSetCount * maxConcurrentFrames );
834794 VK_CHECK_RESULT (vkCreateDescriptorPool (device, &descriptorPoolInfo, nullptr , &descriptorPool));
835795
836796 // Descriptor set layouts
@@ -849,22 +809,23 @@ void VulkanExample::setupDescriptors()
849809 setLayoutBinding = vks::initializers::descriptorSetLayoutBinding (VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0 );
850810 VK_CHECK_RESULT (vkCreateDescriptorSetLayout (device, &descriptorSetLayoutCI, nullptr , &descriptorSetLayouts.jointMatrices ));
851811
852- // Descriptor set for scene matrices
853- VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo (descriptorPool, &descriptorSetLayouts.matrices , 1 );
854- VK_CHECK_RESULT (vkAllocateDescriptorSets (device, &allocInfo, &descriptorSet));
855- VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet (descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0 , &shaderData.buffer .descriptor );
856- vkUpdateDescriptorSets (device, 1 , &writeDescriptorSet, 0 , nullptr );
857-
858- // Descriptor set for glTF model skin joint matrices
859- for (auto &skin : glTFModel.skins )
860- {
861- const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo (descriptorPool, &descriptorSetLayouts.jointMatrices , 1 );
862- VK_CHECK_RESULT (vkAllocateDescriptorSets (device, &allocInfo, &skin.descriptorSet ));
863- VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet (skin.descriptorSet , VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0 , &skin.ssbo .descriptor );
812+ // Descriptor set for scene matrices and skin storage buffers are per frame, just like the buffers themselves
813+ for (auto i = 0 ; i < uniformBuffers.size (); i++) {
814+ // Scene matrices
815+ VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo (descriptorPool, &descriptorSetLayouts.matrices , 1 );
816+ VK_CHECK_RESULT (vkAllocateDescriptorSets (device, &allocInfo, &descriptorSets[i]));
817+ VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet (descriptorSets[i], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0 , &uniformBuffers[i].descriptor );
864818 vkUpdateDescriptorSets (device, 1 , &writeDescriptorSet, 0 , nullptr );
819+ // glTF model skin joint matrices
820+ for (auto & skin : glTFModel.skins ) {
821+ const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo (descriptorPool, &descriptorSetLayouts.jointMatrices , 1 );
822+ VK_CHECK_RESULT (vkAllocateDescriptorSets (device, &allocInfo, &skin.descriptorSets [i]));
823+ VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet (skin.descriptorSets [i], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0 , &skin.storageBuffers [i].descriptor );
824+ vkUpdateDescriptorSets (device, 1 , &writeDescriptorSet, 0 , nullptr );
825+ }
865826 }
866827
867- // Descriptor sets for glTF model materials
828+ // Descriptor sets for glTF materials, since they only use static images, no need to duplicate them per frame
868829 for (auto &image : glTFModel.images )
869830 {
870831 const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo (descriptorPool, &descriptorSetLayouts.textures , 1 );
@@ -954,16 +915,17 @@ void VulkanExample::preparePipelines()
954915
955916void VulkanExample::prepareUniformBuffers ()
956917{
957- VK_CHECK_RESULT (vulkanDevice->createBuffer (VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &shaderData.buffer , sizeof (shaderData.values )));
958- VK_CHECK_RESULT (shaderData.buffer .map ());
959- updateUniformBuffers ();
918+ for (auto & buffer : uniformBuffers) {
919+ VK_CHECK_RESULT (vulkanDevice->createBuffer (VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &buffer, sizeof (UniformData), &uniformData));
920+ VK_CHECK_RESULT (buffer.map ());
921+ }
960922}
961923
962924void VulkanExample::updateUniformBuffers ()
963925{
964- shaderData. values .projection = camera.matrices .perspective ;
965- shaderData. values .model = camera.matrices .view ;
966- memcpy (shaderData. buffer . mapped , &shaderData. values , sizeof (shaderData. values ));
926+ uniformData .projection = camera.matrices .perspective ;
927+ uniformData .model = camera.matrices .view ;
928+ memcpy (uniformBuffers[currentBuffer]. mapped , &uniformData , sizeof (UniformData ));
967929}
968930
969931void VulkanExample::loadAssets ()
@@ -978,28 +940,64 @@ void VulkanExample::prepare()
978940 prepareUniformBuffers ();
979941 setupDescriptors ();
980942 preparePipelines ();
981- buildCommandBuffers ();
982943 prepared = true ;
983944}
984945
946+ void VulkanExample::buildCommandBuffer ()
947+ {
948+ VkCommandBuffer cmdBuffer = drawCmdBuffers[currentBuffer];
949+ vkResetCommandBuffer (cmdBuffer, 0 );
950+
951+ VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo ();
952+
953+ VkClearValue clearValues[2 ]{};
954+ clearValues[0 ].color = { {0 .25f , 0 .25f , 0 .25f , 1 .0f } };
955+ clearValues[1 ].depthStencil = { 1 .0f , 0 };
956+
957+ VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo ();
958+ renderPassBeginInfo.renderPass = renderPass;
959+ renderPassBeginInfo.renderArea .offset .x = 0 ;
960+ renderPassBeginInfo.renderArea .offset .y = 0 ;
961+ renderPassBeginInfo.renderArea .extent .width = width;
962+ renderPassBeginInfo.renderArea .extent .height = height;
963+ renderPassBeginInfo.clearValueCount = 2 ;
964+ renderPassBeginInfo.pClearValues = clearValues;
965+ renderPassBeginInfo.framebuffer = frameBuffers[currentImageIndex];
966+
967+ VK_CHECK_RESULT (vkBeginCommandBuffer (cmdBuffer, &cmdBufInfo));
968+ vkCmdBeginRenderPass (cmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
969+ const VkViewport viewport = vks::initializers::viewport ((float )width, (float )height, 0 .0f , 1 .0f );
970+ vkCmdSetViewport (cmdBuffer, 0 , 1 , &viewport);
971+ const VkRect2D scissor = vks::initializers::rect2D (width, height, 0 , 0 );
972+ vkCmdSetScissor (cmdBuffer, 0 , 1 , &scissor);
973+ // Bind scene matrices descriptor to set 0
974+ vkCmdBindDescriptorSets (cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0 , 1 , &descriptorSets[currentBuffer], 0 , nullptr );
975+ vkCmdBindPipeline (cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : pipelines.solid );
976+ glTFModel.draw (cmdBuffer, pipelineLayout);
977+ drawUI (cmdBuffer);
978+ vkCmdEndRenderPass (cmdBuffer);
979+ VK_CHECK_RESULT (vkEndCommandBuffer (cmdBuffer));
980+ }
981+
985982void VulkanExample::render ()
986983{
984+ if (!prepared)
985+ return ;
986+ VulkanExampleBase::prepareFrame ();
987987 updateUniformBuffers ();
988988 // POI: Advance animation
989989 if (!paused) {
990- glTFModel.updateAnimation (frameTimer);
990+ glTFModel.updateAnimation (frameTimer, currentBuffer );
991991 }
992- renderFrame ();
992+ buildCommandBuffer ();
993+ VulkanExampleBase::submitFrame ();
994+ updateUniformBuffers ();
993995}
994996
995997void VulkanExample::OnUpdateUIOverlay (vks::UIOverlay *overlay)
996998{
997- if (overlay->header (" Settings" ))
998- {
999- if (overlay->checkBox (" Wireframe" , &wireframe))
1000- {
1001- buildCommandBuffers ();
1002- }
999+ if (overlay->header (" Settings" )) {
1000+ overlay->checkBox (" Wireframe" , &wireframe);
10031001 }
10041002}
10051003
0 commit comments