Reworked conditional rendering example, using complex glTF model

This commit is contained in:
saschawillems 2018-09-03 21:57:26 +02:00
parent 34148d5dc9
commit 0dd76f4ce3

View file

@ -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 <vulkan/vulkan.h>
#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<int32_t, MODEL_ROWS> conditionalVisibility{};
std::vector<int32_t> 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;
void renderNode(vkglTF::Node *node, VkCommandBuffer commandBuffer) {
if (node->mesh) {
for (vkglTF::Primitive * primitive : node->mesh->primitives) {
const std::vector<VkDescriptorSet> descriptorsets = {
descriptorSet,
node->mesh->uniformBuffer.descriptorSet
};
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, static_cast<uint32_t>(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);
}
else {
vks::tools::exitFatal("Selected GPU does not support geometry shaders!", VK_ERROR_FEATURE_NOT_PRESENT);
};
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<glm::vec3, 3> 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<VkDescriptorSetLayout, 2> 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<VkDynamicState> 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<VkVertexInputBindingDescription> 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<VkVertexInputAttributeDescription> 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<uint32_t>(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()
{
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
[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()
{
/*
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<PFN_vkGetPhysicalDeviceFeatures2KHR>(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;
updateConditionalBuffer();
}
VK_CHECK_RESULT(conditionalBuffer.map());
memcpy(conditionalBuffer.mapped, &conditionalVisibility, sizeof(conditionalVisibility));
/*
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,29 +349,41 @@ public:
if (!prepared)
return;
draw();
}
virtual void viewChanged()
{
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])) {
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();
};
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");
}
}
}
};