Skip to content

Commit f600f1f

Browse files
committed
Update additional samples to use new sync
1 parent 9b82e67 commit f600f1f

2 files changed

Lines changed: 128 additions & 131 deletions

File tree

examples/gltfskinning/gltfskinning.cpp

Lines changed: 111 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
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

640643
VulkanExample::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

650656
VulkanExample::~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

666673
void 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-
713682
void 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, &copyRegion);
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

818778
void 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

955916
void 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

962924
void 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

969931
void 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+
985982
void 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

995997
void 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

Comments
 (0)