diff --git a/examples/conditionalrender/conditionalrender.cpp b/examples/conditionalrender/conditionalrender.cpp index 8ca1fc07..3e831fbb 100644 --- a/examples/conditionalrender/conditionalrender.cpp +++ b/examples/conditionalrender/conditionalrender.cpp @@ -3,8 +3,8 @@ * * Note: Requires a device that supports the VK_EXT_conditional_rendering extension * -* With conditional rendering it's possible to execute certain rendering commands based -* on a buffer value instead of having to rebuild the command buffers. +* With conditional rendering it's possible to execute certain rendering commands based on a buffer value instead of having to rebuild the command buffers. +* This example sets up a conditonal buffer with one value per glTF part, that is used to toggle visibility of single model parts. * * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de * @@ -24,26 +24,17 @@ #include #include "vulkanexamplebase.h" -#include "VulkanModel.hpp" +#include "VulkanglTFModel.hpp" #define ENABLE_VALIDATION false -#define MODEL_ROWS 3 class VulkanExample : public VulkanExampleBase { public: PFN_vkCmdBeginConditionalRenderingEXT vkCmdBeginConditionalRenderingEXT; PFN_vkCmdEndConditionalRenderingEXT vkCmdEndConditionalRenderingEXT; - VkPhysicalDeviceConditionalRenderingFeaturesEXT conditionalRenderingFeatures{}; - // Vertex layout for the models - vks::VertexLayout vertexLayout = vks::VertexLayout({ - vks::VERTEX_COMPONENT_POSITION, - vks::VERTEX_COMPONENT_NORMAL, - vks::VERTEX_COMPONENT_COLOR, - }); - - vks::Model model; + vkglTF::Model scene; struct { glm::mat4 projection; @@ -52,7 +43,7 @@ public: vks::Buffer uniformBuffer; - std::array conditionalVisibility{}; + std::vector conditionalVisibility; vks::Buffer conditionalBuffer; VkPipelineLayout pipelineLayout; @@ -65,47 +56,75 @@ public: title = "Conditional rendering"; 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, -15.0f)); - rotationSpeed *= 0.25f; + camera.setPerspective(45.0f, (float)width / (float)height, 0.1f, 512.0f); + camera.setRotation(glm::vec3(-9.0f, -55.0f, 0.0f)); + camera.setTranslation(glm::vec3(3.45f, 3.15f, -22.0f)); + camera.rotationSpeed *= 0.25f; - // Enable extension required for conditional rendering + /* + [POI] Enable extension required for conditional rendering + */ enabledDeviceExtensions.push_back(VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME); - // Enable extension required to get conditional rendering supported features - enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); } ~VulkanExample() { - // Clean up used Vulkan resources - // Note : Inherited destructor cleans up resources stored in base class vkDestroyPipeline(device, pipeline, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - model.destroy(); uniformBuffer.destroy(); conditionalBuffer.destroy(); } - // Enable physical device features required for this example - virtual void getEnabledFeatures() - { - // Geometry shader support is required for this example - if (deviceFeatures.geometryShader) { - enabledFeatures.geometryShader = VK_TRUE; - } - else { - vks::tools::exitFatal("Selected GPU does not support geometry shaders!", VK_ERROR_FEATURE_NOT_PRESENT); + void renderNode(vkglTF::Node *node, VkCommandBuffer commandBuffer) { + if (node->mesh) { + for (vkglTF::Primitive * primitive : node->mesh->primitives) { + const std::vector descriptorsets = { + descriptorSet, + node->mesh->uniformBuffer.descriptorSet + }; + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, static_cast(descriptorsets.size()), descriptorsets.data(), 0, NULL); + + struct PushBlock { + glm::vec4 baseColorFactor; + } pushBlock; + pushBlock.baseColorFactor = primitive->material.baseColorFactor; + + vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushBlock), &pushBlock); + + /* + [POI] Setup the conditional rendering + */ + VkConditionalRenderingBeginInfoEXT conditionalRenderingBeginInfo{}; + conditionalRenderingBeginInfo.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT; + conditionalRenderingBeginInfo.buffer = conditionalBuffer.buffer; + conditionalRenderingBeginInfo.offset = sizeof(int32_t) * node->index; + + /* + [POI] Begin conditionally rendered section + + If the value from the conditional rendering buffer at the given offset is != 0, the draw commands will be executed + */ + vkCmdBeginConditionalRenderingEXT(commandBuffer, &conditionalRenderingBeginInfo); + + vkCmdDrawIndexed(commandBuffer, primitive->indexCount, 1, primitive->firstIndex, 0, 0); + + vkCmdEndConditionalRenderingEXT(commandBuffer); + } + + }; + for (auto child : node->children) { + renderNode(child, commandBuffer); } } + void buildCommandBuffers() { VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); VkClearValue clearValues[2]; - clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } }; + clearValues[0].color = { { 1.0f, 1.0f, 1.0f, 1.0f } }; clearValues[1].depthStencil = { 1.0f, 0 }; VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); @@ -117,9 +136,7 @@ public: renderPassBeginInfo.clearValueCount = 2; renderPassBeginInfo.pClearValues = clearValues; - for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) - { - // Set target frame buffer + for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) { renderPassBeginInfo.framebuffer = frameBuffers[i]; VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); @@ -133,49 +150,13 @@ public: vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); - VkDeviceSize offsets[1] = { 0 }; - vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &model.vertices.buffer, offsets); - vkCmdBindIndexBuffer(drawCmdBuffers[i], model.indices.buffer, 0, VK_INDEX_TYPE_UINT32); - - struct PushBlock { - glm::vec4 offset; - glm::vec4 color; - } pushBlock; - - const std::array colors = { - glm::vec3(1.0f, 0.0f, 0.0f), - glm::vec3(0.0f, 1.0f, 0.0f), - glm::vec3(0.0f, 0.0f, 1.0f), - }; - - /* - [POI] Setup the conditional rendering structure that decides on wether the commands are rendered or discarded - */ - VkConditionalRenderingBeginInfoEXT conditionalRenderingBeginInfo{}; - conditionalRenderingBeginInfo.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT; - // If the value in this buffer at the given offset is zero, commands are discadrd - conditionalRenderingBeginInfo.buffer = conditionalBuffer.buffer; - // Offset will be changed in the loop below to toggle visibility of whole rows - conditionalRenderingBeginInfo.offset = 0; - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - for (int32_t x = -1; x < MODEL_ROWS - 1; x++) { - for (int32_t y = -2; y < 3; y++) { - pushBlock.offset = glm::vec4((float)x * 3.0f, (float)y * 2.5f, 0.0f, 1.0f); - pushBlock.color = glm::vec4(colors[x+1], 1.0f); - - /* - [POI] Start the conditionally rendered part (for this row) - */ - - conditionalRenderingBeginInfo.offset = sizeof(uint32_t) * (x + 1); - vkCmdBeginConditionalRenderingEXT(drawCmdBuffers[i], &conditionalRenderingBeginInfo); - - vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pushBlock), &pushBlock); - vkCmdDrawIndexed(drawCmdBuffers[i], model.indexCount, 1, 0, 0, 0); - - vkCmdEndConditionalRenderingEXT(drawCmdBuffers[i]); - } + + const VkDeviceSize offsets[1] = { 0 }; + vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &scene.vertices.buffer, offsets); + vkCmdBindIndexBuffer(drawCmdBuffers[i], scene.indices.buffer, 0, VK_INDEX_TYPE_UINT32); + for (auto node : scene.nodes) { + renderNode(node, drawCmdBuffers[i]); } vkCmdEndRenderPass(drawCmdBuffers[i]); @@ -186,7 +167,7 @@ public: void loadAssets() { - model.loadFromFile(getAssetPath() + "models/suzanne.obj", vertexLayout, 0.1f, vulkanDevice, queue); + scene.loadFromFile(getAssetPath() + "models/Buggy.gltf", vulkanDevice, queue); } void setupDescriptorSets() @@ -206,7 +187,10 @@ public: descriptorLayoutCI.pBindings = setLayoutBindings.data(); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayout)); - VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); + std::array setLayouts = { + descriptorSetLayout, scene.descriptorSetLayout + }; + VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), 2); VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::vec4) * 2, 0); pipelineLayoutCI.pushConstantRangeCount = 1; pipelineLayoutCI.pPushConstantRanges = &pushConstantRange; @@ -225,7 +209,7 @@ public: 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); + VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_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); @@ -235,12 +219,12 @@ public: // Vertex bindings and attributes const std::vector vertexInputBindings = { - vks::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX), + vks::initializers::vertexInputBindingDescription(0, sizeof(vkglTF::Model::Vertex), 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_R32G32B32_SFLOAT, sizeof(float) * 6), // Location 3: Color + vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6), // Location 2: UV }; VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); vertexInputState.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); @@ -283,41 +267,25 @@ public: void updateUniformBuffers() { uboVS.projection = camera.matrices.perspective; - uboVS.modelview = camera.matrices.view; + uboVS.modelview = glm::scale(camera.matrices.view, glm::vec3(0.1f , -0.1f, 0.1f)); memcpy(uniformBuffer.mapped, &uboVS, sizeof(uboVS)); } void updateConditionalBuffer() { - memcpy(conditionalBuffer.mapped, &conditionalVisibility, sizeof(conditionalVisibility)); + memcpy(conditionalBuffer.mapped, conditionalVisibility.data(), sizeof(int32_t) * conditionalVisibility.size()); } - void draw() + /* + [POI] Extension specific setup + + Gets the function pointers required for conditonal rendering + Sets up a dedicated conditional buffer that is used to determine visibility at draw time + */ + void prepareConditionalRendering() { - VulkanExampleBase::prepareFrame(); - - // Command buffer to be sumitted to the queue - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; - - // Submit to queue - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); - - VulkanExampleBase::submitFrame(); - } - - void prepare() - { - VulkanExampleBase::prepare(); - /* - Extension specific functions - */ - - /* - Get the function pointer - - The conditional rendering functions are part of an extension so they have to be manually loaded + The conditional rendering functions are part of an extension so they have to be loaded manually */ vkCmdBeginConditionalRenderingEXT = (PFN_vkCmdBeginConditionalRenderingEXT)vkGetDeviceProcAddr(device, "vkCmdBeginConditionalRenderingEXT"); if (!vkCmdBeginConditionalRenderingEXT) { @@ -329,45 +297,46 @@ public: vks::tools::exitFatal("Could not get a valid function pointer for vkCmdEndConditionalRenderingEXT", -1); } - /* - Get conditional rendering features - */ - PFN_vkGetPhysicalDeviceFeatures2KHR vkGetPhysicalDeviceFeatures2KHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures2KHR")); - if (!vkGetPhysicalDeviceFeatures2KHR) { - vks::tools::exitFatal("Could not get a valid function pointer for vkGetPhysicalDeviceFeatures2KHR", -1); - } - VkPhysicalDeviceFeatures2KHR deviceFeatures2{}; - conditionalRenderingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONDITIONAL_RENDERING_FEATURES_EXT; - deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR; - deviceFeatures2.pNext = &conditionalRenderingFeatures; - vkGetPhysicalDeviceFeatures2KHR(physicalDevice, &deviceFeatures2); - /* Create the buffer that contains the conditional rendering information - + A single conditional value is 32 bits and if it's zero the rendering commands are discarded This sample renders multiple rows of objects conditionally, so we setup a buffer with one value per row */ + conditionalVisibility.resize(scene.linearNodes.size()); VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &conditionalBuffer, - sizeof(uint32_t) * MODEL_ROWS)); + sizeof(int32_t) *conditionalVisibility.size(), + conditionalVisibility.data())); + VK_CHECK_RESULT(conditionalBuffer.map()); + + // By default, all parts of the glTF are visible + for (auto i = 0; i < conditionalVisibility.size(); i++) { + conditionalVisibility[i] = 1; + } /* Copy visibility data */ - for (auto i = 0; i < conditionalVisibility.size(); i++) { - conditionalVisibility[i] = 1; - } - VK_CHECK_RESULT(conditionalBuffer.map()); - memcpy(conditionalBuffer.mapped, &conditionalVisibility, sizeof(conditionalVisibility)); + updateConditionalBuffer(); + } - /* - End of extension specific functions - */ + 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(); + prepareConditionalRendering(); prepareUniformBuffers(); setupDescriptorSets(); preparePipelines(); @@ -380,28 +349,40 @@ public: if (!prepared) return; draw(); - } - - virtual void viewChanged() - { - updateUniformBuffers(); + if (camera.updated) { + updateUniformBuffers(); + } } virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) { if (overlay->header("Visibility")) { - for (uint32_t i = 0; i < MODEL_ROWS; i++) { - if (overlay->checkBox(std::to_string(i).c_str(), &conditionalVisibility[i])) { - updateConditionalBuffer(); - }; - if (i < MODEL_ROWS - 1) { ImGui::SameLine(); }; - } - } - if (overlay->header("Device properties")) { - overlay->text("conditional rendering: %s", conditionalRenderingFeatures.conditionalRendering ? "true" : "false"); - overlay->text("inherited conditional rendering: %s", conditionalRenderingFeatures.inheritedConditionalRendering ? "true" : "false"); - } + if (overlay->button("All")) { + for (auto i = 0; i < conditionalVisibility.size(); i++) { + conditionalVisibility[i] = 1; + } + updateConditionalBuffer(); + } + ImGui::SameLine(); + if (overlay->button("None")) { + for (auto i = 0; i < conditionalVisibility.size(); i++) { + conditionalVisibility[i] = 0; + } + updateConditionalBuffer(); + } + ImGui::NewLine(); + + for (auto node : scene.linearNodes) { + // Add visibility toggle checkboxes for all model nodes with a mesh + if (node->mesh) { + if (overlay->checkBox(("[" + std::to_string(node->index) + "] " + node->mesh->name).c_str(), &conditionalVisibility[node->index])) { + updateConditionalBuffer(); + } + } + } + + } } };