diff --git a/include/inexor/vulkan-renderer/wrapper/query_pool.hpp b/include/inexor/vulkan-renderer/wrapper/query_pool.hpp new file mode 100644 index 000000000..4ecebbe54 --- /dev/null +++ b/include/inexor/vulkan-renderer/wrapper/query_pool.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include + +#include +#include + +namespace inexor::vulkan_renderer::wrapper { + +// Forward declaration of device wrapper +class Device; +class CommandBuffer; + +/// @brief A wrapper for Vulkan query pools +class QueryPool { +private: + const Device &m_device; + VkPhysicalDeviceFeatures m_device_features{}; + VkQueryPool m_query_pool{}; + std::vector m_pipeline_stats; + std::vector m_pipeline_stat_names; + + /// @brief These pipeline statistics are enabled by default if the default constructor is used. + /// @note We are not storing these as VkQueryPipelineStatisticFlags, because we need to perform additional checks + /// for some of these flags in order to use them. For example we need to check if tessellation is enabled in order + /// to query its performance. Please note computer shaders do not require special checks. + /// + /// Vulkan specification: Pipeline Statistics Queries + const std::vector default_pipeline_stats_flag_bits = { + 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, + VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_INVOCATIONS_BIT, // requires geometry shaders + VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_PRIMITIVES_BIT, // requires geometry shaders + VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_CONTROL_SHADER_PATCHES_BIT, // requires tesselation shaders + VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT, // requires tesselation shaders + VK_QUERY_PIPELINE_STATISTIC_COMPUTE_SHADER_INVOCATIONS_BIT}; + + [[nodiscard]] static std::string get_pipeline_stats_flag_bit_name(VkQueryPipelineStatisticFlagBits bit); + + /// @brief Validates every specified VkQueryPipelineStatisticFlagBits into one VkQueryPipelineStatisticFlags. + /// Some VkQueryPipelineStatisticFlagBits values require special checks (tesselation shaders for example). + /// @return A vector which contains the valid Vulkan pipeline query statistics flag bits. + [[nodiscard]] std::vector + validate_pipeline_stats_flag_bits(const std::vector &pipeline_stats_flag_bits); + +public: + /// @brief Construct a Vulkan Query Pool using the default pipeline statistics. + /// @param device The const reference to a device RAII wrapper instance. + /// @param name The internal name. + QueryPool(const Device &device, const std::string &name); + + /// Call + /// vkCreateQueryPool + /// @param device The device wrapper. + /// @param name The internal name of this performance query. + /// @param pipeline_stats_flag_bits The enabled + /// Vulkan pipeline statistics flags. + QueryPool(const Device &device, const std::string &name, + const std::vector &pipeline_stats_flag_bits); + + QueryPool(const QueryPool &) = delete; + QueryPool(QueryPool &&) = delete; + ~QueryPool(); + + QueryPool operator=(const QueryPool &) = delete; + QueryPool operator=(QueryPool &&) = delete; + + [[nodiscard]] const wrapper::Device &device() const noexcept { + return m_device; + } + + /// Call + /// vkCmdResetQueryPool + void reset(const wrapper::CommandBuffer &cmd_buffer) const; + + /// Call + /// vkCmdBeginQuery + void begin(const wrapper::CommandBuffer &cmd_buffer) const; + + /// Call + /// vkCmdEndQuery + void end(const wrapper::CommandBuffer &cmd_buffer) const; + + /// Call + /// vkGetQueryPoolResults + void get_results(); + + /// TODO: Implement a get method for the results which returns a tuple of query name and result? + + /// Print all the captured pipeline statistics. + void print_results() const; +}; + +} // namespace inexor::vulkan_renderer::wrapper diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3f22db5f4..b14beb5ad 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,6 +37,7 @@ set(INEXOR_SOURCE_FILES vulkan-renderer/wrapper/instance.cpp vulkan-renderer/wrapper/make_info.cpp vulkan-renderer/wrapper/once_command_buffer.cpp + vulkan-renderer/wrapper/query_pool.cpp vulkan-renderer/wrapper/renderpass.cpp vulkan-renderer/wrapper/semaphore.cpp vulkan-renderer/wrapper/shader.cpp diff --git a/src/vulkan-renderer/wrapper/make_info.cpp b/src/vulkan-renderer/wrapper/make_info.cpp index c47bffed8..9960f9da4 100644 --- a/src/vulkan-renderer/wrapper/make_info.cpp +++ b/src/vulkan-renderer/wrapper/make_info.cpp @@ -221,6 +221,13 @@ VkPresentInfoKHR make_info() { return ret; } +template <> +VkQueryPoolCreateInfo make_info() { + VkQueryPoolCreateInfo ret{}; + ret.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; + return ret; +} + template <> VkRenderPassBeginInfo make_info() { VkRenderPassBeginInfo ret{}; diff --git a/src/vulkan-renderer/wrapper/query_pool.cpp b/src/vulkan-renderer/wrapper/query_pool.cpp new file mode 100644 index 000000000..7ade5a588 --- /dev/null +++ b/src/vulkan-renderer/wrapper/query_pool.cpp @@ -0,0 +1,178 @@ +#include "inexor/vulkan-renderer/wrapper/query_pool.hpp" + +#include "inexor/vulkan-renderer/exception.hpp" +#include "inexor/vulkan-renderer/wrapper/command_buffer.hpp" +#include "inexor/vulkan-renderer/wrapper/device.hpp" +#include "inexor/vulkan-renderer/wrapper/make_info.hpp" + +#include + +namespace inexor::vulkan_renderer::wrapper { + +std::vector QueryPool::validate_pipeline_stats_flag_bits( + const std::vector &pipeline_stats_flag_bits) { + std::vector ret_val; + + ret_val.reserve(default_pipeline_stats_flag_bits.size()); + + for (const auto flag_bit : pipeline_stats_flag_bits) { + switch (flag_bit) { + case VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_CONTROL_SHADER_PATCHES_BIT: + if (m_device_features.tessellationShader == VK_FALSE) { + spdlog::warn("Can't add VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_CONTROL_SHADER_PATCHES_BIT to " + "pipeline statistics flag bit!"); + spdlog::warn( + "Tesselation shaders are not available on this gpu (device_features.tessellationShader = false)"); + break; + } else { + // Tesselation shaders are available so it's safe to add this flag. + ret_val.emplace_back(VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_CONTROL_SHADER_PATCHES_BIT); + } + break; + case VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT: + if (m_device_features.tessellationShader == VK_FALSE) { + spdlog::warn("Can't add VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT to " + "pipeline statistics flag bit!"); + spdlog::warn( + "Tesselation shaders are not available on this gpu (device_features.tessellationShader = false)"); + break; + } else { + // Tesselation shaders are available so it's safe to add this flag. + ret_val.emplace_back(VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT); + } + break; + case VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_INVOCATIONS_BIT: + if (m_device_features.geometryShader == VK_FALSE) { + spdlog::warn("Can't add VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_INVOCATIONS_BIT to pipeline " + "statistics flag bit!"); + spdlog::warn("Geometry shaders are not available on this gpu (device_features.geometryShader = false)"); + break; + } else { + // Geometry shaders are available so it's safe to add this flag. + ret_val.emplace_back(VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_INVOCATIONS_BIT); + } + break; + case VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_PRIMITIVES_BIT: + if (m_device_features.geometryShader == VK_FALSE) { + spdlog::warn("Can't add VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_PRIMITIVES_BIT to pipeline " + "statistics flag bit!"); + spdlog::warn("Geometry shaders are not available on this gpu (device_features.geometryShader = false)"); + break; + } else { + // Geometry shaders are available so it's safe to add this flag. + ret_val.emplace_back(VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_PRIMITIVES_BIT); + } + break; + default: + // No special check required for this flag. + ret_val.emplace_back(flag_bit); + break; + } + } + + ret_val.shrink_to_fit(); + return ret_val; +} + +// TODO: Make this a as_string method and return a std::string_view from representation.cpp! +std::string QueryPool::get_pipeline_stats_flag_bit_name(const VkQueryPipelineStatisticFlagBits bit) { + switch (bit) { + case VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_VERTICES_BIT: + return "Input assembly vertex count"; + case VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_PRIMITIVES_BIT: + return "Input assembly primitives count"; + case VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT: + return "Vertex shader invocations"; + case VK_QUERY_PIPELINE_STATISTIC_CLIPPING_INVOCATIONS_BIT: + return "Clipping stage primitives processed"; + case VK_QUERY_PIPELINE_STATISTIC_CLIPPING_PRIMITIVES_BIT: + return "Clipping stage primitives output"; + case VK_QUERY_PIPELINE_STATISTIC_FRAGMENT_SHADER_INVOCATIONS_BIT: + return "Fragment shader invocations"; + case VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_INVOCATIONS_BIT: + return "Geometry shader invocations"; + case VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_PRIMITIVES_BIT: + return "Geometry assembly primitives count"; + case VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_CONTROL_SHADER_PATCHES_BIT: + return "Tessellation control shader patch invocations"; + case VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT: + return "Tessellation evaluation shader invocations"; + case VK_QUERY_PIPELINE_STATISTIC_COMPUTE_SHADER_INVOCATIONS_BIT: + return "Compute shader invocations"; + default: + break; + } + return "Unknown"; +} + +QueryPool::QueryPool(const Device &device, const std::string &name) + : QueryPool(device, name, default_pipeline_stats_flag_bits) {} + +QueryPool::QueryPool(const Device &device, const std::string &name, + const std::vector &pipeline_stats_flag_bits) + : m_device(device) { + assert(m_device.device()); + assert(!name.empty()); + + // We must first check if pipeline query statistics are available. + vkGetPhysicalDeviceFeatures(m_device.physical_device(), &m_device_features); + + if (m_device_features.pipelineStatisticsQuery == VK_FALSE) { + throw InexorException("Error: vkGetPhysicalDeviceFeatures shows pipelineStatisticsQuery is not supported"); + } + + // Compose pipeline stats flags from pipeline_stats_flag_bits. + const std::vector valid_pipeline_stats_flag_bits{ + validate_pipeline_stats_flag_bits(pipeline_stats_flag_bits)}; + + m_pipeline_stat_names.reserve(valid_pipeline_stats_flag_bits.size()); + + VkQueryPipelineStatisticFlags pipeline_stats_flags{}; + + for (const auto valid_bit : valid_pipeline_stats_flag_bits) { + pipeline_stats_flags |= valid_bit; + m_pipeline_stat_names.emplace_back(get_pipeline_stats_flag_bit_name(valid_bit)); + } + + auto query_pool_ci = make_info(); + + query_pool_ci.queryType = VK_QUERY_TYPE_PIPELINE_STATISTICS; + query_pool_ci.pipelineStatistics = pipeline_stats_flags; + query_pool_ci.queryCount = static_cast(valid_pipeline_stats_flag_bits.size()); + + if (const auto result = vkCreateQueryPool(m_device.device(), &query_pool_ci, nullptr, &m_query_pool); + result != VK_SUCCESS) { + throw VulkanException("Error: vkCreateQueryPool failed!", result); + } +} + +void QueryPool::reset(const wrapper::CommandBuffer &cmd_buffer) const { + assert(!m_pipeline_stats.empty()); + vkCmdResetQueryPool(cmd_buffer.get(), m_query_pool, 0, static_cast(m_pipeline_stats.size())); +} + +void QueryPool::begin(const wrapper::CommandBuffer &cmd_buffer) const { + vkCmdBeginQuery(cmd_buffer.get(), m_query_pool, 0, 0); +} + +void QueryPool::end(const wrapper::CommandBuffer &cmd_buffer) const { + vkCmdEndQuery(cmd_buffer.get(), m_query_pool, 0); +} + +void QueryPool::get_results() { + vkGetQueryPoolResults(m_device.device(), m_query_pool, 0, 1, + static_cast(m_pipeline_stats.size()) * sizeof(std::uint64_t), + m_pipeline_stats.data(), sizeof(uint64_t), VK_QUERY_RESULT_64_BIT); +} + +void QueryPool::print_results() const { + for (std::size_t i = 0; i < m_pipeline_stats.size(); i++) { + spdlog::info("{}: {}", m_pipeline_stat_names[i], m_pipeline_stats[i]); + } +} + +QueryPool::~QueryPool() { + vkDestroyQueryPool(m_device.device(), m_query_pool, nullptr); +} + +} // namespace inexor::vulkan_renderer::wrapper