/* * Vulkan Example - Retrieving pipeline statistics * * Copyright (C) 2017-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: // This sample lets you select between different models to display struct Models { std::vector objects; int32_t objectIndex{ 3 }; std::vector names; } models; // Size for the two-dimensional grid of objects (e.g. 3 = draws 3x3 objects) int32_t gridSize{ 3 }; struct UniformData { glm::mat4 projection; glm::mat4 modelview; glm::vec4 lightPos{ -10.0f, -10.0f, 10.0f, 1.0f }; } uniformData; vks::Buffer uniformBuffer; int32_t cullMode{ VK_CULL_MODE_BACK_BIT }; bool blending{ false }; bool discard{ false }; bool wireframe{ false }; bool tessellation{ false }; VkPipeline pipeline{ VK_NULL_HANDLE }; VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; VkQueryPool queryPool{ VK_NULL_HANDLE }; // Vector for storing pipeline statistics results std::vector pipelineStats{}; std::vector pipelineStatNames{}; VulkanExample() : VulkanExampleBase() { title = "Pipeline statistics"; camera.type = Camera::CameraType::firstperson; camera.setPosition(glm::vec3(-3.0f, 1.0f, -2.75f)); camera.setRotation(glm::vec3(-15.25f, -46.5f, 0.0f)); camera.movementSpeed = 4.0f; camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f); camera.rotationSpeed = 0.25f; } ~VulkanExample() { if (device) { vkDestroyPipeline(device, pipeline, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); vkDestroyQueryPool(device, queryPool, nullptr); uniformBuffer.destroy(); } } virtual void getEnabledFeatures() { // Support for pipeline statistics is optional if (deviceFeatures.pipelineStatisticsQuery) { enabledFeatures.pipelineStatisticsQuery = VK_TRUE; } else { vks::tools::exitFatal("Selected GPU does not support pipeline statistics!", VK_ERROR_FEATURE_NOT_PRESENT); } if (deviceFeatures.fillModeNonSolid) { enabledFeatures.fillModeNonSolid = VK_TRUE; } if (deviceFeatures.tessellationShader) { enabledFeatures.tessellationShader = VK_TRUE; } } // Setup a query pool for storing pipeline statistics void setupQueryPool() { pipelineStatNames = { "Input assembly vertex count ", "Input assembly primitives count ", "Vertex shader invocations ", "Clipping stage primitives processed", "Clipping stage primitives output ", "Fragment shader invocations " }; if (deviceFeatures.tessellationShader) { pipelineStatNames.push_back("Tess. control shader patches "); pipelineStatNames.push_back("Tess. eval. shader invocations "); } pipelineStats.resize(pipelineStatNames.size()); VkQueryPoolCreateInfo queryPoolInfo = {}; queryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; // This query pool will store pipeline statistics queryPoolInfo.queryType = VK_QUERY_TYPE_PIPELINE_STATISTICS; // Pipeline counters to be returned for this pool queryPoolInfo.pipelineStatistics = VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_VERTICES_BIT | VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_PRIMITIVES_BIT | VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT | VK_QUERY_PIPELINE_STATISTIC_CLIPPING_INVOCATIONS_BIT | VK_QUERY_PIPELINE_STATISTIC_CLIPPING_PRIMITIVES_BIT | VK_QUERY_PIPELINE_STATISTIC_FRAGMENT_SHADER_INVOCATIONS_BIT; if (deviceFeatures.tessellationShader) { queryPoolInfo.pipelineStatistics |= VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_CONTROL_SHADER_PATCHES_BIT | VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT; } queryPoolInfo.queryCount = 1; VK_CHECK_RESULT(vkCreateQueryPool(device, &queryPoolInfo, NULL, &queryPool)); } // Retrieves the results of the pipeline statistics query submitted to the command buffer void getQueryResults() { // The size of the data we want to fetch ist based on the count of statistics values uint32_t dataSize = static_cast(pipelineStats.size()) * sizeof(uint64_t); // The stride between queries is the no. of unique value entries uint32_t stride = static_cast(pipelineStatNames.size()) * sizeof(uint64_t); // Note: for one query both values have the same size, but to make it easier to expand this sample these are properly calculated vkGetQueryPoolResults( device, queryPool, 0, 1, dataSize, pipelineStats.data(), stride, VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); } void buildCommandBuffers() { VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); VkClearValue clearValues[2]; clearValues[0].color = defaultClearColor; 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)); // Reset timestamp query pool vkCmdResetQueryPool(drawCmdBuffers[i], queryPool, 0, 1); 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); VkDeviceSize offsets[1] = { 0 }; // Start capture of pipeline statistics vkCmdBeginQuery(drawCmdBuffers[i], queryPool, 0, 0); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &models.objects[models.objectIndex].vertices.buffer, offsets); vkCmdBindIndexBuffer(drawCmdBuffers[i], models.objects[models.objectIndex].indices.buffer, 0, VK_INDEX_TYPE_UINT32); for (int32_t y = 0; y < gridSize; y++) { for (int32_t x = 0; x < gridSize; x++) { glm::vec3 pos = glm::vec3(float(x - (gridSize / 2.0f)) * 2.5f, 0.0f, float(y - (gridSize / 2.0f)) * 2.5f); vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::vec3), &pos); models.objects[models.objectIndex].draw(drawCmdBuffers[i]); } } // End capture of pipeline statistics vkCmdEndQuery(drawCmdBuffers[i], queryPool, 0); drawUI(drawCmdBuffers[i]); vkCmdEndRenderPass(drawCmdBuffers[i]); VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); } } void loadAssets() { // Objects std::vector filenames = { "sphere.gltf", "teapot.gltf", "torusknot.gltf", "venus.gltf" }; models.names = { "Sphere", "Teapot", "Torusknot", "Venus" }; models.objects.resize(filenames.size()); for (size_t i = 0; i < filenames.size(); i++) { models.objects[i].loadFromFile(getAssetPath() + "models/" + filenames[i], vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY); } } void setupDescriptors() { // Pool std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3) }; VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 3); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); // Layout std::vector setLayoutBindings = { vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0) }; VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); // Set VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &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, NULL); } void preparePipelines() { // Layout if (pipelineLayout == VK_NULL_HANDLE) { VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::vec3), 0); pipelineLayoutCreateInfo.pushConstantRangeCount = 1; pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange; VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); } // Pipeline if (pipeline != VK_NULL_HANDLE) { // Destroy old pipeline if we're going to recreate it vkDestroyPipeline(device, pipeline, nullptr); } VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, cullMode, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0); VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL); VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0); std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast(dynamicStateEnables.size()), 0); VkPipelineTessellationStateCreateInfo tessellationState = vks::initializers::pipelineTessellationStateCreateInfo(3); VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0); pipelineCI.pInputAssemblyState = &inputAssemblyState; pipelineCI.pRasterizationState = &rasterizationState; pipelineCI.pColorBlendState = &colorBlendState; pipelineCI.pMultisampleState = &multisampleState; pipelineCI.pViewportState = &viewportState; pipelineCI.pDepthStencilState = &depthStencilState; pipelineCI.pDynamicState = &dynamicState; pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color }); if (blending) { blendAttachmentState.blendEnable = VK_TRUE; blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD; blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD; depthStencilState.depthWriteEnable = VK_FALSE; } if (discard) { rasterizationState.rasterizerDiscardEnable = VK_TRUE; } if (wireframe) { rasterizationState.polygonMode = VK_POLYGON_MODE_LINE; } std::vector shaderStages; shaderStages.resize(tessellation ? 4 : 2); shaderStages[0] = loadShader(getShadersPath() + "pipelinestatistics/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getShadersPath() + "pipelinestatistics/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); if (tessellation) { inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; pipelineCI.pTessellationState = &tessellationState; shaderStages[2] = loadShader(getShadersPath() + "pipelinestatistics/scene.tesc.spv", VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT); shaderStages[3] = loadShader(getShadersPath() + "pipelinestatistics/scene.tese.spv", VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT); } pipelineCI.stageCount = static_cast(shaderStages.size()); pipelineCI.pStages = shaderStages.data(); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); } // Prepare and initialize uniform buffer containing shader uniforms 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()); } void updateUniformBuffers() { uniformData.projection = camera.matrices.perspective; uniformData.modelview = camera.matrices.view; memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); } void prepare() { VulkanExampleBase::prepare(); loadAssets(); setupQueryPool(); 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(); // Read query results for displaying in next frame getQueryResults(); } virtual void render() { if (!prepared) return; updateUniformBuffers(); draw(); } virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) { if (overlay->header("Settings")) { if (overlay->comboBox("Object type", &models.objectIndex, models.names)) { updateUniformBuffers(); buildCommandBuffers(); } if (overlay->sliderInt("Grid size", &gridSize, 1, 10)) { buildCommandBuffers(); } // To avoid having to create pipelines for all the settings up front, we recreate a single pipelin with different settings instead bool recreatePipeline{ false }; std::vector cullModeNames = { "None", "Front", "Back", "Back and front" }; recreatePipeline |= overlay->comboBox("Cull mode", &cullMode, cullModeNames); recreatePipeline |= overlay->checkBox("Blending", &blending); recreatePipeline |= overlay->checkBox("Discard", &discard); // These features may not be supported by all implementations if (deviceFeatures.fillModeNonSolid) { recreatePipeline |= overlay->checkBox("Wireframe", &wireframe); } if (deviceFeatures.tessellationShader) { recreatePipeline |= overlay->checkBox("Tessellation", &tessellation); } if (recreatePipeline) { preparePipelines(); buildCommandBuffers(); } } if (!pipelineStats.empty()) { if (overlay->header("Pipeline statistics")) { for (auto i = 0; i < pipelineStats.size(); i++) { std::string caption = pipelineStatNames[i] + ": %d"; overlay->text(caption.c_str(), pipelineStats[i]); } } } } }; VULKAN_EXAMPLE_MAIN()