From d4ade8ca26c25a4e694a1416b8cdc01336017d02 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Fri, 20 Feb 2026 04:03:04 +0300 Subject: [PATCH 1/3] Added support for VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES --- examples_tests | 2 +- include/nbl/asset/IPipeline.h | 10 +- include/nbl/asset/IRayTracingPipeline.h | 2 + include/nbl/video/IGPUComputePipeline.h | 2 + include/nbl/video/IGPUGraphicsPipeline.h | 2 + include/nbl/video/ILogicalDevice.h | 22 +++ src/nbl/video/CVulkanLogicalDevice.cpp | 144 ++++++++++++++++++ src/nbl/video/CVulkanLogicalDevice.h | 3 + .../device_capabilities/device_features.json | 1 - 9 files changed, 180 insertions(+), 8 deletions(-) diff --git a/examples_tests b/examples_tests index 6ebb966161..cd8aab95bd 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 6ebb9661618c01e00498c43481f610586200885a +Subproject commit cd8aab95bd93405bdd2a9c24e9aae2670e3f4b19 diff --git a/include/nbl/asset/IPipeline.h b/include/nbl/asset/IPipeline.h index dddfe4b528..ada60fee9f 100644 --- a/include/nbl/asset/IPipeline.h +++ b/include/nbl/asset/IPipeline.h @@ -23,13 +23,13 @@ namespace nbl::asset Vulkan supports multiple types of pipelines: - graphics pipeline - compute pipeline - - TODO: Raytracing + - raytracing pipeline */ class IPipelineBase { public: // TODO: tbf these shouldn't even be laying around in `nbl::asset`, we should probably move to `nbl::video::IGPUPipelineBase` - enum class CreationFlags : uint64_t + enum class CreationFlags : uint64_t { NONE = 0, // disallowed in maintanance5 DISABLE_OPTIMIZATIONS = 1 << 0, @@ -47,10 +47,8 @@ class IPipelineBase // This is for NV-raytracing extension. Now this is done via IDeferredOperation //DEFER_COMPILE_NV = 1<<5, - // We use Renderdoc to take care of this for us, - // we won't be parsing the statistics and internal representation ourselves. - //CAPTURE_STATISTICS = 1<<6, - //CAPTURE_INTERNAL_REPRESENTATIONS = 1<<7, + CAPTURE_STATISTICS = 1<<6, + CAPTURE_INTERNAL_REPRESENTATIONS = 1<<7, // Will soon be deprecated due to // https://github.com/Devsh-Graphics-Programming/Nabla/issues/854 diff --git a/include/nbl/asset/IRayTracingPipeline.h b/include/nbl/asset/IRayTracingPipeline.h index 23d2fac81f..fe8dd92de4 100644 --- a/include/nbl/asset/IRayTracingPipeline.h +++ b/include/nbl/asset/IRayTracingPipeline.h @@ -31,6 +31,8 @@ class IRayTracingPipelineBase : public virtual core::IReferenceCounted NO_NULL_MISS_SHADERS = 1<<16, NO_NULL_INTERSECTION_SHADERS = 1<<17, ALLOW_MOTION = 1<<20, + CAPTURE_STATISTICS = base_flag(CAPTURE_STATISTICS), + CAPTURE_INTERNAL_REPRESENTATIONS = base_flag(CAPTURE_INTERNAL_REPRESENTATIONS), }; #undef base_flag diff --git a/include/nbl/video/IGPUComputePipeline.h b/include/nbl/video/IGPUComputePipeline.h index 9854725cd1..deb0545f62 100644 --- a/include/nbl/video/IGPUComputePipeline.h +++ b/include/nbl/video/IGPUComputePipeline.h @@ -38,6 +38,8 @@ class IGPUComputePipeline : public IGPUPipeline params, core::smart_refctd_ptr* const output); + // Pipeline executable statistics report (VK_KHR_pipeline_executable_properties). + // Pipeline must have been created with CAPTURE_STATISTICS flag. + // If includeInternalRepresentations is true, also includes shader IR/assembly + // (pipeline must have been created with CAPTURE_INTERNAL_REPRESENTATIONS flag). + template + std::string getPipelineExecutableReport(const Pipeline* pipeline, bool includeInternalRepresentations = false) + { + if (!pipeline) + { + NBL_LOG_ERROR("Null pipeline"); + return {}; + } + if (!getEnabledFeatures().pipelineExecutableInfo) + { + NBL_LOG_ERROR("Feature `pipelineExecutableInfo` is not enabled"); + return {}; + } + return getPipelineExecutableReport_impl(pipeline->getNativeHandle(), includeInternalRepresentations); + } + // queries inline core::smart_refctd_ptr createQueryPool(const IQueryPool::SCreationParams& params) { @@ -1296,6 +1316,8 @@ class NBL_API2 ILogicalDevice : public core::IReferenceCounted, public IDeviceMe const SSpecializationValidationResult& validation ) = 0; + virtual std::string getPipelineExecutableReport_impl(const void* nativeHandle, bool includeInternalRepresentations) = 0; + virtual core::smart_refctd_ptr createQueryPool_impl(const IQueryPool::SCreationParams& params) = 0; virtual bool getQueryPoolResults_impl(const IQueryPool* const queryPool, const uint32_t firstQuery, const uint32_t queryCount, void* const pData, const size_t stride, const core::bitflag flags) = 0; diff --git a/src/nbl/video/CVulkanLogicalDevice.cpp b/src/nbl/video/CVulkanLogicalDevice.cpp index 46b79a7094..d23035d011 100644 --- a/src/nbl/video/CVulkanLogicalDevice.cpp +++ b/src/nbl/video/CVulkanLogicalDevice.cpp @@ -1662,6 +1662,150 @@ void CVulkanLogicalDevice::createRayTracingPipelines_impl( std::fill_n(output,vk_createInfos.size(),nullptr); } +std::string CVulkanLogicalDevice::getPipelineExecutableReport_impl(const void* nativeHandle, bool includeInternalRepresentations) +{ + const VkPipeline vkPipeline = *reinterpret_cast(nativeHandle); + + VkPipelineInfoKHR pipelineInfo = {VK_STRUCTURE_TYPE_PIPELINE_INFO_KHR, nullptr}; + pipelineInfo.pipeline = vkPipeline; + + // Enumerate executables + uint32_t executableCount = 0; + m_devf.vk.vkGetPipelineExecutablePropertiesKHR(m_vkdev, &pipelineInfo, &executableCount, nullptr); + + core::vector properties(executableCount); + for (uint32_t i = 0; i < executableCount; ++i) + properties[i] = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_PROPERTIES_KHR, nullptr}; + m_devf.vk.vkGetPipelineExecutablePropertiesKHR(m_vkdev, &pipelineInfo, &executableCount, properties.data()); + + std::string report; + report.reserve(1024); // avoid some reallocations, we can adjust this if needed + for (uint32_t i = 0; i < executableCount; ++i) + { + const auto& prop = properties[i]; + + report += "======== "; + report += prop.name; + report += " ========\n"; + report += prop.description; + report += "\n"; + + VkPipelineExecutableInfoKHR execInfo = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INFO_KHR, nullptr}; + execInfo.pipeline = vkPipeline; + execInfo.executableIndex = i; + + // Statistics + report += "==== Statistics ====\n"; + report += "Subgroup Size: "; + report += std::to_string(prop.subgroupSize); + report += "\n"; + + uint32_t statCount = 0; + m_devf.vk.vkGetPipelineExecutableStatisticsKHR(m_vkdev, &execInfo, &statCount, nullptr); + + if (statCount > 0) + { + core::vector stats(statCount); + for (uint32_t s = 0; s < statCount; ++s) + stats[s] = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_STATISTIC_KHR, nullptr}; + m_devf.vk.vkGetPipelineExecutableStatisticsKHR(m_vkdev, &execInfo, &statCount, stats.data()); + + // First pass: format name:value pairs and find max width for alignment + core::vector nameValues(statCount); + size_t maxNameValueLen = 0; + for (uint32_t s = 0; s < statCount; ++s) + { + const auto& stat = stats[s]; + std::string value; + switch (stat.format) + { + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_BOOL32_KHR: + value = stat.value.b32 ? "true" : "false"; + break; + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_INT64_KHR: + value = std::to_string(stat.value.i64); + break; + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_UINT64_KHR: + value = std::to_string(stat.value.u64); + break; + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_FLOAT64_KHR: + value = std::to_string(stat.value.f64); + break; + default: + value = ""; + break; + } + nameValues[s] = std::string(stat.name) + ": " + value; + maxNameValueLen = std::max(maxNameValueLen, nameValues[s].size()); + } + + // Overkill for now, but it produces nicely aligned output which is easier to read. We can optimize later if needed. + // Second pass: emit with aligned columns + for (uint32_t s = 0; s < statCount; ++s) + { + report += nameValues[s]; + report.append(maxNameValueLen - nameValues[s].size() + 4, ' '); + report += "// "; + report += stats[s].description; + report += "\n"; + } + } + + // Internal representations + if (includeInternalRepresentations) + { + uint32_t irCount = 0; + m_devf.vk.vkGetPipelineExecutableInternalRepresentationsKHR(m_vkdev, &execInfo, &irCount, nullptr); + + if (irCount > 0) + { + core::vector irs(irCount); + for (uint32_t r = 0; r < irCount; ++r) + irs[r] = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INTERNAL_REPRESENTATION_KHR, nullptr}; + + // First call to get sizes + m_devf.vk.vkGetPipelineExecutableInternalRepresentationsKHR(m_vkdev, &execInfo, &irCount, irs.data()); + + // Allocate data buffers and second call to get data + core::vector> irData(irCount); + for (uint32_t r = 0; r < irCount; ++r) + { + irData[r].resize(irs[r].dataSize); + irs[r].pData = irData[r].data(); + } + + m_devf.vk.vkGetPipelineExecutableInternalRepresentationsKHR(m_vkdev, &execInfo, &irCount, irs.data()); + + report += "\n"; + for (uint32_t r = 0; r < irCount; ++r) + { + report += "---- "; + report += irs[r].name; + report += " ----\n"; + report += "; "; + report += irs[r].description; + report += "\n"; + if (irs[r].isText) + { + auto* str = static_cast(irs[r].pData); + report.append(str, irs[r].dataSize > 0 ? irs[r].dataSize - 1 : 0); + report += "\n"; + } + else + { + report += "[binary data, "; + report += std::to_string(irs[r].dataSize); + report += " bytes]\n"; + } + } + } + } + report += "\n"; + } + + return report; +} + core::smart_refctd_ptr CVulkanLogicalDevice::createQueryPool_impl(const IQueryPool::SCreationParams& params) { VkQueryPoolCreateInfo info = {VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, nullptr}; diff --git a/src/nbl/video/CVulkanLogicalDevice.h b/src/nbl/video/CVulkanLogicalDevice.h index 4cc633ec55..209ba84cb0 100644 --- a/src/nbl/video/CVulkanLogicalDevice.h +++ b/src/nbl/video/CVulkanLogicalDevice.h @@ -301,6 +301,9 @@ class CVulkanLogicalDevice final : public ILogicalDevice const SSpecializationValidationResult& validation ) override; + // pipeline executable statistics + std::string getPipelineExecutableReport_impl(const void* nativeHandle, bool includeInternalRepresentations) override; + // queries core::smart_refctd_ptr createQueryPool_impl(const IQueryPool::SCreationParams& params) override; bool getQueryPoolResults_impl(const IQueryPool* const queryPool, const uint32_t firstQuery, const uint32_t queryCount, void* const pData, const size_t stride, const core::bitflag flags) override; diff --git a/src/nbl/video/device_capabilities/device_features.json b/src/nbl/video/device_capabilities/device_features.json index 5e4775e9b4..f3989e26ee 100644 --- a/src/nbl/video/device_capabilities/device_features.json +++ b/src/nbl/video/device_capabilities/device_features.json @@ -2922,7 +2922,6 @@ "name": "pipelineExecutableInfo", "value": false, "comment": [ - "[TODO] need impl", "PipelineExecutablePropertiesFeaturesKHR", "VK_KHR_pipeline_executable_properties" ] From 75ebe75d7bb252173ec83c4d0d658619877bb7f3 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Sun, 22 Feb 2026 23:05:32 +0300 Subject: [PATCH 2/3] Created `SPipelineExecutableInfo` instead of a string, more error checks, `getPipelineExecutableProperties_impl` overloads with one helper function --- examples_tests | 2 +- include/nbl/video/ILogicalDevice.h | 102 ++++++++++++++++++++++--- src/nbl/video/CVulkanLogicalDevice.cpp | 80 ++++++++++--------- src/nbl/video/CVulkanLogicalDevice.h | 7 +- 4 files changed, 144 insertions(+), 47 deletions(-) diff --git a/examples_tests b/examples_tests index cd8aab95bd..97a8695d02 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit cd8aab95bd93405bdd2a9c24e9aae2670e3f4b19 +Subproject commit 97a8695d021e817433f38abcf98fb0294fd7abd4 diff --git a/include/nbl/video/ILogicalDevice.h b/include/nbl/video/ILogicalDevice.h index 1237703204..d3a5a284a8 100644 --- a/include/nbl/video/ILogicalDevice.h +++ b/include/nbl/video/ILogicalDevice.h @@ -14,6 +14,7 @@ #include "nbl/video/IGPUCommandBuffer.h" #include "nbl/video/CThreadSafeQueueAdapter.h" #include "nbl/video/CJITIncludeLoader.h" +#include "nbl/system/to_string.h" #include "git_info.h" #define NBL_LOG_FUNCTION m_logger.log @@ -1033,24 +1034,45 @@ class NBL_API2 ILogicalDevice : public core::IReferenceCounted, public IDeviceMe const std::span params, core::smart_refctd_ptr* const output); - // Pipeline executable statistics report (VK_KHR_pipeline_executable_properties). - // Pipeline must have been created with CAPTURE_STATISTICS flag. - // If includeInternalRepresentations is true, also includes shader IR/assembly - // (pipeline must have been created with CAPTURE_INTERNAL_REPRESENTATIONS flag). + // Per-executable info from VK_KHR_pipeline_executable_properties + struct SPipelineExecutableInfo + { + std::string name; + std::string description; + core::bitflag stages = hlsl::ShaderStage::ESS_UNKNOWN; + uint32_t subgroupSize = 0; + std::string statistics; + std::string internalRepresentations; + }; + + // Query pipeline executable properties (VK_KHR_pipeline_executable_properties). + // Pipeline must have been created with CAPTURE_STATISTICS flag for statistics. + // Pipeline must have been created with CAPTURE_INTERNAL_REPRESENTATIONS flag for IR. template - std::string getPipelineExecutableReport(const Pipeline* pipeline, bool includeInternalRepresentations = false) + core::vector getPipelineExecutableProperties(const Pipeline* pipeline, bool includeInternalRepresentations = false) { if (!pipeline) { NBL_LOG_ERROR("Null pipeline"); return {}; } - if (!getEnabledFeatures().pipelineExecutableInfo) + using flags_t = Pipeline::SCreationParams::FLAGS; + if (!pipeline->getCreationFlags().hasFlags(flags_t::CAPTURE_STATISTICS)) { - NBL_LOG_ERROR("Feature `pipelineExecutableInfo` is not enabled"); + NBL_LOG_ERROR("Pipeline was not created with CAPTURE_STATISTICS flag"); return {}; } - return getPipelineExecutableReport_impl(pipeline->getNativeHandle(), includeInternalRepresentations); + if (includeInternalRepresentations && !pipeline->getCreationFlags().hasFlags(flags_t::CAPTURE_INTERNAL_REPRESENTATIONS)) + { + NBL_LOG_ERROR("Pipeline was not created with CAPTURE_INTERNAL_REPRESENTATIONS flag"); + return {}; + } + auto properties = getPipelineExecutableProperties_impl(pipeline, includeInternalRepresentations); + if (properties.empty()) + { + NBL_LOG_ERROR("Driver returned 0 executables for pipeline created with CAPTURE_STATISTICS flag. This pipeline type may not be supported by the driver's VK_KHR_pipeline_executable_properties implementation."); + } + return properties; } // queries @@ -1293,6 +1315,20 @@ class NBL_API2 ILogicalDevice : public core::IReferenceCounted, public IDeviceMe return {}; } + // CAPTURE_STATISTICS and CAPTURE_INTERNAL_REPRESENTATIONS require pipelineExecutableInfo feature + constexpr auto CaptureStatsFlag = CreationParams::FLAGS::CAPTURE_STATISTICS; + constexpr auto CaptureIRFlag = CreationParams::FLAGS::CAPTURE_INTERNAL_REPRESENTATIONS; + if (ci.getFlags().hasFlags(CaptureStatsFlag) && !getEnabledFeatures().pipelineExecutableInfo) + { + NBL_LOG_ERROR("CAPTURE_STATISTICS flag requires `pipelineExecutableInfo` feature (params[%d])", i); + return {}; + } + if (ci.getFlags().hasFlags(CaptureIRFlag) && !getEnabledFeatures().pipelineExecutableInfo) + { + NBL_LOG_ERROR("CAPTURE_INTERNAL_REPRESENTATIONS flag requires `pipelineExecutableInfo` feature (params[%d])", i); + return {}; + } + retval += validation; } return retval; @@ -1316,7 +1352,9 @@ class NBL_API2 ILogicalDevice : public core::IReferenceCounted, public IDeviceMe const SSpecializationValidationResult& validation ) = 0; - virtual std::string getPipelineExecutableReport_impl(const void* nativeHandle, bool includeInternalRepresentations) = 0; + virtual core::vector getPipelineExecutableProperties_impl(const IGPUComputePipeline* pipeline, bool includeInternalRepresentations) = 0; + virtual core::vector getPipelineExecutableProperties_impl(const IGPUGraphicsPipeline* pipeline, bool includeInternalRepresentations) = 0; + virtual core::vector getPipelineExecutableProperties_impl(const IGPURayTracingPipeline* pipeline, bool includeInternalRepresentations) = 0; virtual core::smart_refctd_ptr createQueryPool_impl(const IQueryPool::SCreationParams& params) = 0; virtual bool getQueryPoolResults_impl(const IQueryPool* const queryPool, const uint32_t firstQuery, const uint32_t queryCount, void* const pData, const size_t stride, const core::bitflag flags) = 0; @@ -1627,5 +1665,51 @@ inline bool ILogicalDevice::validateMemoryBarrier(const uint32_t queueFamilyInde } // namespace nbl::video +namespace nbl::system::impl +{ + +template<> +struct to_string_helper +{ + static std::string __call(const video::ILogicalDevice::SPipelineExecutableInfo& info) + { + std::string result; + result += "======== "; + result += info.name; + result += " ========\n"; + result += info.description; + result += "\nSubgroup Size: "; + result += std::to_string(info.subgroupSize); + if (!info.statistics.empty()) + { + result += "\n"; + result += info.statistics; + } + if (!info.internalRepresentations.empty()) + { + result += "\n"; + result += info.internalRepresentations; + } + return result; + } +}; + +template<> +struct to_string_helper> +{ + static std::string __call(const core::vector& infos) + { + std::string result; + for (const auto& info : infos) + { + result += to_string_helper::__call(info); + result += "\n"; + } + return result; + } +}; + +} + #include "nbl/undef_logging_macros.h" #endif //_NBL_VIDEO_I_LOGICAL_DEVICE_H_INCLUDED_ \ No newline at end of file diff --git a/src/nbl/video/CVulkanLogicalDevice.cpp b/src/nbl/video/CVulkanLogicalDevice.cpp index d23035d011..c77c7ca596 100644 --- a/src/nbl/video/CVulkanLogicalDevice.cpp +++ b/src/nbl/video/CVulkanLogicalDevice.cpp @@ -1662,10 +1662,23 @@ void CVulkanLogicalDevice::createRayTracingPipelines_impl( std::fill_n(output,vk_createInfos.size(),nullptr); } -std::string CVulkanLogicalDevice::getPipelineExecutableReport_impl(const void* nativeHandle, bool includeInternalRepresentations) +core::vector CVulkanLogicalDevice::getPipelineExecutableProperties_impl(const IGPUComputePipeline* pipeline, bool includeInternalRepresentations) { - const VkPipeline vkPipeline = *reinterpret_cast(nativeHandle); + return getPipelineExecutableProperties_helper(static_cast(pipeline)->getInternalObject(), includeInternalRepresentations); +} + +core::vector CVulkanLogicalDevice::getPipelineExecutableProperties_impl(const IGPUGraphicsPipeline* pipeline, bool includeInternalRepresentations) +{ + return getPipelineExecutableProperties_helper(static_cast(pipeline)->getInternalObject(), includeInternalRepresentations); +} + +core::vector CVulkanLogicalDevice::getPipelineExecutableProperties_impl(const IGPURayTracingPipeline* pipeline, bool includeInternalRepresentations) +{ + return getPipelineExecutableProperties_helper(static_cast(pipeline)->getInternalObject(), includeInternalRepresentations); +} +core::vector CVulkanLogicalDevice::getPipelineExecutableProperties_helper(VkPipeline vkPipeline, bool includeInternalRepresentations) +{ VkPipelineInfoKHR pipelineInfo = {VK_STRUCTURE_TYPE_PIPELINE_INFO_KHR, nullptr}; pipelineInfo.pipeline = vkPipeline; @@ -1673,33 +1686,32 @@ std::string CVulkanLogicalDevice::getPipelineExecutableReport_impl(const void* n uint32_t executableCount = 0; m_devf.vk.vkGetPipelineExecutablePropertiesKHR(m_vkdev, &pipelineInfo, &executableCount, nullptr); + if (executableCount == 0) + { + return {}; + } + core::vector properties(executableCount); for (uint32_t i = 0; i < executableCount; ++i) properties[i] = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_PROPERTIES_KHR, nullptr}; m_devf.vk.vkGetPipelineExecutablePropertiesKHR(m_vkdev, &pipelineInfo, &executableCount, properties.data()); - std::string report; - report.reserve(1024); // avoid some reallocations, we can adjust this if needed + core::vector result(executableCount); + for (uint32_t i = 0; i < executableCount; ++i) { const auto& prop = properties[i]; + auto& info = result[i]; - report += "======== "; - report += prop.name; - report += " ========\n"; - report += prop.description; - report += "\n"; + info.name = prop.name; + info.description = prop.description; + info.stages = static_cast(prop.stages); + info.subgroupSize = prop.subgroupSize; VkPipelineExecutableInfoKHR execInfo = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INFO_KHR, nullptr}; execInfo.pipeline = vkPipeline; execInfo.executableIndex = i; - // Statistics - report += "==== Statistics ====\n"; - report += "Subgroup Size: "; - report += std::to_string(prop.subgroupSize); - report += "\n"; - uint32_t statCount = 0; m_devf.vk.vkGetPipelineExecutableStatisticsKHR(m_vkdev, &execInfo, &statCount, nullptr); @@ -1739,15 +1751,15 @@ std::string CVulkanLogicalDevice::getPipelineExecutableReport_impl(const void* n maxNameValueLen = std::max(maxNameValueLen, nameValues[s].size()); } - // Overkill for now, but it produces nicely aligned output which is easier to read. We can optimize later if needed. // Second pass: emit with aligned columns + std::string& statsStr = info.statistics; for (uint32_t s = 0; s < statCount; ++s) { - report += nameValues[s]; - report.append(maxNameValueLen - nameValues[s].size() + 4, ' '); - report += "// "; - report += stats[s].description; - report += "\n"; + statsStr += nameValues[s]; + statsStr.append(maxNameValueLen - nameValues[s].size() + 4, ' '); + statsStr += "// "; + statsStr += stats[s].description; + statsStr += "\n"; } } @@ -1776,34 +1788,32 @@ std::string CVulkanLogicalDevice::getPipelineExecutableReport_impl(const void* n m_devf.vk.vkGetPipelineExecutableInternalRepresentationsKHR(m_vkdev, &execInfo, &irCount, irs.data()); - report += "\n"; + std::string& irStr = info.internalRepresentations; for (uint32_t r = 0; r < irCount; ++r) { - report += "---- "; - report += irs[r].name; - report += " ----\n"; - report += "; "; - report += irs[r].description; - report += "\n"; + irStr += "---- "; + irStr += irs[r].name; + irStr += " ----\n"; + irStr += irs[r].description; + irStr += "\n"; if (irs[r].isText) { auto* str = static_cast(irs[r].pData); - report.append(str, irs[r].dataSize > 0 ? irs[r].dataSize - 1 : 0); - report += "\n"; + irStr.append(str, irs[r].dataSize > 0 ? irs[r].dataSize - 1 : 0); + irStr += "\n"; } else { - report += "[binary data, "; - report += std::to_string(irs[r].dataSize); - report += " bytes]\n"; + irStr += "[binary data, "; + irStr += std::to_string(irs[r].dataSize); + irStr += " bytes]\n"; } } } } - report += "\n"; } - return report; + return result; } core::smart_refctd_ptr CVulkanLogicalDevice::createQueryPool_impl(const IQueryPool::SCreationParams& params) diff --git a/src/nbl/video/CVulkanLogicalDevice.h b/src/nbl/video/CVulkanLogicalDevice.h index 209ba84cb0..01aea09a0a 100644 --- a/src/nbl/video/CVulkanLogicalDevice.h +++ b/src/nbl/video/CVulkanLogicalDevice.h @@ -301,8 +301,11 @@ class CVulkanLogicalDevice final : public ILogicalDevice const SSpecializationValidationResult& validation ) override; - // pipeline executable statistics - std::string getPipelineExecutableReport_impl(const void* nativeHandle, bool includeInternalRepresentations) override; + // pipeline executable properties + core::vector getPipelineExecutableProperties_impl(const IGPUComputePipeline* pipeline, bool includeInternalRepresentations) override; + core::vector getPipelineExecutableProperties_impl(const IGPUGraphicsPipeline* pipeline, bool includeInternalRepresentations) override; + core::vector getPipelineExecutableProperties_impl(const IGPURayTracingPipeline* pipeline, bool includeInternalRepresentations) override; + core::vector getPipelineExecutableProperties_helper(VkPipeline vkPipeline, bool includeInternalRepresentations); // queries core::smart_refctd_ptr createQueryPool_impl(const IQueryPool::SCreationParams& params) override; From c2cf167f2de346c73914b5044aed7b63afb24802 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Wed, 25 Feb 2026 06:10:08 +0300 Subject: [PATCH 3/3] Moved vulkan code to `CVulkanPipelineExecutableInfo.h` and moved data to pipeline base --- examples_tests | 2 +- include/nbl/video/CVulkanRayTracingPipeline.h | 2 + include/nbl/video/IGPUPipeline.h | 63 ++++++ include/nbl/video/ILogicalDevice.h | 91 --------- src/nbl/video/CVulkanComputePipeline.cpp | 7 + src/nbl/video/CVulkanComputePipeline.h | 4 +- src/nbl/video/CVulkanGraphicsPipeline.cpp | 7 + src/nbl/video/CVulkanGraphicsPipeline.h | 2 + src/nbl/video/CVulkanLogicalDevice.cpp | 180 +++--------------- src/nbl/video/CVulkanLogicalDevice.h | 6 - src/nbl/video/CVulkanPipelineExecutableInfo.h | 149 +++++++++++++++ src/nbl/video/CVulkanRayTracingPipeline.cpp | 7 + 12 files changed, 265 insertions(+), 255 deletions(-) create mode 100644 src/nbl/video/CVulkanPipelineExecutableInfo.h diff --git a/examples_tests b/examples_tests index 97a8695d02..0e20b7d86c 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 97a8695d021e817433f38abcf98fb0294fd7abd4 +Subproject commit 0e20b7d86ca3ebad9d49ceefafdc58665a34133b diff --git a/include/nbl/video/CVulkanRayTracingPipeline.h b/include/nbl/video/CVulkanRayTracingPipeline.h index a9bc476f43..791d6a766e 100644 --- a/include/nbl/video/CVulkanRayTracingPipeline.h +++ b/include/nbl/video/CVulkanRayTracingPipeline.h @@ -25,6 +25,8 @@ class CVulkanRayTracingPipeline final : public IGPURayTracingPipeline inline VkPipeline getInternalObject() const { return m_vkPipeline; } + void populateExecutableInfo(bool includeInternalRepresentations); + virtual const SShaderGroupHandle& getRaygen() const override; virtual std::span getMissHandles() const override; virtual std::span getHitHandles() const override; diff --git a/include/nbl/video/IGPUPipeline.h b/include/nbl/video/IGPUPipeline.h index c22ad998db..595f2f2c58 100644 --- a/include/nbl/video/IGPUPipeline.h +++ b/include/nbl/video/IGPUPipeline.h @@ -10,6 +10,7 @@ #include "nbl/video/SPipelineCreationParams.h" #include "nbl/asset/ICPUPipeline.h" #include "nbl/asset/IPipeline.h" +#include "nbl/system/to_string.h" namespace nbl::video { @@ -127,6 +128,21 @@ class IGPUPipelineBase { using SShaderEntryMap = SShaderSpecInfo::entry_map_t; + // Per-executable info from VK_KHR_pipeline_executable_properties + struct SExecutableInfo + { + std::string name; + std::string description; + core::bitflag stages = hlsl::ShaderStage::ESS_UNKNOWN; + uint32_t subgroupSize = 0; + std::string statistics; + std::string internalRepresentations; + }; + + inline std::span getExecutableInfo() const { return m_executableInfo; } + + protected: + core::vector m_executableInfo; }; // Common Base class for pipelines @@ -146,4 +162,51 @@ class IGPUPipeline : public IBackendObject, public PipelineNonBackendObjectBase, } +namespace nbl::system::impl +{ + +template<> +struct to_string_helper +{ + static std::string __call(const video::IGPUPipelineBase::SExecutableInfo& info) + { + std::string result; + result += "======== "; + result += info.name; + result += " ========\n"; + result += info.description; + result += "\nSubgroup Size: "; + result += std::to_string(info.subgroupSize); + if (!info.statistics.empty()) + { + result += "\n"; + result += info.statistics; + } + if (!info.internalRepresentations.empty()) + { + result += "\n"; + result += info.internalRepresentations; + } + return result; + } +}; + +// Another version for core::vector? +template<> +struct to_string_helper> +{ + static std::string __call(const std::span& infos) + { + std::string result; + for (const auto& info : infos) + { + result += to_string_helper::__call(info); + result += "\n"; + } + return result; + } +}; + +} + #endif diff --git a/include/nbl/video/ILogicalDevice.h b/include/nbl/video/ILogicalDevice.h index d3a5a284a8..bf93249400 100644 --- a/include/nbl/video/ILogicalDevice.h +++ b/include/nbl/video/ILogicalDevice.h @@ -1034,47 +1034,6 @@ class NBL_API2 ILogicalDevice : public core::IReferenceCounted, public IDeviceMe const std::span params, core::smart_refctd_ptr* const output); - // Per-executable info from VK_KHR_pipeline_executable_properties - struct SPipelineExecutableInfo - { - std::string name; - std::string description; - core::bitflag stages = hlsl::ShaderStage::ESS_UNKNOWN; - uint32_t subgroupSize = 0; - std::string statistics; - std::string internalRepresentations; - }; - - // Query pipeline executable properties (VK_KHR_pipeline_executable_properties). - // Pipeline must have been created with CAPTURE_STATISTICS flag for statistics. - // Pipeline must have been created with CAPTURE_INTERNAL_REPRESENTATIONS flag for IR. - template - core::vector getPipelineExecutableProperties(const Pipeline* pipeline, bool includeInternalRepresentations = false) - { - if (!pipeline) - { - NBL_LOG_ERROR("Null pipeline"); - return {}; - } - using flags_t = Pipeline::SCreationParams::FLAGS; - if (!pipeline->getCreationFlags().hasFlags(flags_t::CAPTURE_STATISTICS)) - { - NBL_LOG_ERROR("Pipeline was not created with CAPTURE_STATISTICS flag"); - return {}; - } - if (includeInternalRepresentations && !pipeline->getCreationFlags().hasFlags(flags_t::CAPTURE_INTERNAL_REPRESENTATIONS)) - { - NBL_LOG_ERROR("Pipeline was not created with CAPTURE_INTERNAL_REPRESENTATIONS flag"); - return {}; - } - auto properties = getPipelineExecutableProperties_impl(pipeline, includeInternalRepresentations); - if (properties.empty()) - { - NBL_LOG_ERROR("Driver returned 0 executables for pipeline created with CAPTURE_STATISTICS flag. This pipeline type may not be supported by the driver's VK_KHR_pipeline_executable_properties implementation."); - } - return properties; - } - // queries inline core::smart_refctd_ptr createQueryPool(const IQueryPool::SCreationParams& params) { @@ -1352,10 +1311,6 @@ class NBL_API2 ILogicalDevice : public core::IReferenceCounted, public IDeviceMe const SSpecializationValidationResult& validation ) = 0; - virtual core::vector getPipelineExecutableProperties_impl(const IGPUComputePipeline* pipeline, bool includeInternalRepresentations) = 0; - virtual core::vector getPipelineExecutableProperties_impl(const IGPUGraphicsPipeline* pipeline, bool includeInternalRepresentations) = 0; - virtual core::vector getPipelineExecutableProperties_impl(const IGPURayTracingPipeline* pipeline, bool includeInternalRepresentations) = 0; - virtual core::smart_refctd_ptr createQueryPool_impl(const IQueryPool::SCreationParams& params) = 0; virtual bool getQueryPoolResults_impl(const IQueryPool* const queryPool, const uint32_t firstQuery, const uint32_t queryCount, void* const pData, const size_t stride, const core::bitflag flags) = 0; @@ -1665,51 +1620,5 @@ inline bool ILogicalDevice::validateMemoryBarrier(const uint32_t queueFamilyInde } // namespace nbl::video -namespace nbl::system::impl -{ - -template<> -struct to_string_helper -{ - static std::string __call(const video::ILogicalDevice::SPipelineExecutableInfo& info) - { - std::string result; - result += "======== "; - result += info.name; - result += " ========\n"; - result += info.description; - result += "\nSubgroup Size: "; - result += std::to_string(info.subgroupSize); - if (!info.statistics.empty()) - { - result += "\n"; - result += info.statistics; - } - if (!info.internalRepresentations.empty()) - { - result += "\n"; - result += info.internalRepresentations; - } - return result; - } -}; - -template<> -struct to_string_helper> -{ - static std::string __call(const core::vector& infos) - { - std::string result; - for (const auto& info : infos) - { - result += to_string_helper::__call(info); - result += "\n"; - } - return result; - } -}; - -} - #include "nbl/undef_logging_macros.h" #endif //_NBL_VIDEO_I_LOGICAL_DEVICE_H_INCLUDED_ \ No newline at end of file diff --git a/src/nbl/video/CVulkanComputePipeline.cpp b/src/nbl/video/CVulkanComputePipeline.cpp index 8335bae4fc..b5b18d677d 100644 --- a/src/nbl/video/CVulkanComputePipeline.cpp +++ b/src/nbl/video/CVulkanComputePipeline.cpp @@ -1,6 +1,7 @@ #include "nbl/video/CVulkanComputePipeline.h" #include "nbl/video/CVulkanLogicalDevice.h" +#include "nbl/video/CVulkanPipelineExecutableInfo.h" namespace nbl::video { @@ -12,6 +13,12 @@ CVulkanComputePipeline::~CVulkanComputePipeline() vk->vk.vkDestroyPipeline(vulkanDevice->getInternalObject(), m_pipeline, nullptr); } +void CVulkanComputePipeline::populateExecutableInfo(bool includeInternalRepresentations) +{ + const CVulkanLogicalDevice* vulkanDevice = static_cast(getOriginDevice()); + populateExecutableInfoFromVulkan(m_executableInfo, vulkanDevice->getFunctionTable(), vulkanDevice->getInternalObject(), m_pipeline, includeInternalRepresentations); +} + void CVulkanComputePipeline::setObjectDebugName(const char* label) const { IBackendObject::setObjectDebugName(label); diff --git a/src/nbl/video/CVulkanComputePipeline.h b/src/nbl/video/CVulkanComputePipeline.h index 89077f9a9a..e2a56d5450 100644 --- a/src/nbl/video/CVulkanComputePipeline.h +++ b/src/nbl/video/CVulkanComputePipeline.h @@ -22,7 +22,9 @@ class CVulkanComputePipeline final : public IGPUComputePipeline inline const void* getNativeHandle() const override { return &m_pipeline; } inline VkPipeline getInternalObject() const { return m_pipeline; } - + + void populateExecutableInfo(bool includeInternalRepresentations); + void setObjectDebugName(const char* label) const override; private: diff --git a/src/nbl/video/CVulkanGraphicsPipeline.cpp b/src/nbl/video/CVulkanGraphicsPipeline.cpp index a57dbfc83a..9872725bd4 100644 --- a/src/nbl/video/CVulkanGraphicsPipeline.cpp +++ b/src/nbl/video/CVulkanGraphicsPipeline.cpp @@ -1,10 +1,17 @@ #include "nbl/video/CVulkanGraphicsPipeline.h" #include "nbl/video/CVulkanLogicalDevice.h" +#include "CVulkanPipelineExecutableInfo.h" namespace nbl::video { +void CVulkanGraphicsPipeline::populateExecutableInfo(bool includeInternalRepresentations) +{ + const CVulkanLogicalDevice* vulkanDevice = static_cast(getOriginDevice()); + populateExecutableInfoFromVulkan(m_executableInfo, vulkanDevice->getFunctionTable(), vulkanDevice->getInternalObject(), m_vkPipeline, includeInternalRepresentations); +} + CVulkanGraphicsPipeline::~CVulkanGraphicsPipeline() { const CVulkanLogicalDevice* vulkanDevice = static_cast(getOriginDevice()); diff --git a/src/nbl/video/CVulkanGraphicsPipeline.h b/src/nbl/video/CVulkanGraphicsPipeline.h index 1b99e58dd6..4a11610b20 100644 --- a/src/nbl/video/CVulkanGraphicsPipeline.h +++ b/src/nbl/video/CVulkanGraphicsPipeline.h @@ -18,6 +18,8 @@ class CVulkanGraphicsPipeline final : public IGPUGraphicsPipeline inline VkPipeline getInternalObject() const {return m_vkPipeline;} + void populateExecutableInfo(bool includeInternalRepresentations); + private: ~CVulkanGraphicsPipeline(); diff --git a/src/nbl/video/CVulkanLogicalDevice.cpp b/src/nbl/video/CVulkanLogicalDevice.cpp index c77c7ca596..913bfc6318 100644 --- a/src/nbl/video/CVulkanLogicalDevice.cpp +++ b/src/nbl/video/CVulkanLogicalDevice.cpp @@ -1190,9 +1190,16 @@ void CVulkanLogicalDevice::createComputePipelines_impl( const VkPipeline vk_pipeline = vk_pipelines[i]; // break the lifetime cause of the aliasing std::uninitialized_default_construct_n(output+i,1); - output[i] = core::make_smart_refctd_ptr( + auto pipeline = core::make_smart_refctd_ptr( info,vk_pipeline ); + if (info.flags.hasFlags(IGPUComputePipeline::SCreationParams::FLAGS::CAPTURE_STATISTICS)) + { + pipeline->populateExecutableInfo(info.flags.hasFlags(IGPUComputePipeline::SCreationParams::FLAGS::CAPTURE_INTERNAL_REPRESENTATIONS)); + if (pipeline->getExecutableInfo().empty()) + m_logger.log("Driver returned 0 executables for pipeline created with CAPTURE_STATISTICS flag. This pipeline type may not be supported by the driver's VK_KHR_pipeline_executable_properties implementation.", system::ILogger::ELL_WARNING); + } + output[i] = std::move(pipeline); debugNameBuilder.str(""); const auto& specInfo = createInfos[i].shader; debugNameBuilder << specInfo.shader->getFilepathHint() << "(" << specInfo.entryPoint << "," << hlsl::ShaderStage::ESS_COMPUTE << ")\n"; @@ -1446,7 +1453,14 @@ void CVulkanLogicalDevice::createGraphicsPipelines_impl( const VkPipeline vk_pipeline = vk_pipelines[i]; // break the lifetime cause of the aliasing std::uninitialized_default_construct_n(output+i,1); - output[i] = core::make_smart_refctd_ptr(createInfos[i],vk_pipeline); + auto pipeline = core::make_smart_refctd_ptr(createInfos[i],vk_pipeline); + if (createInfo.flags.hasFlags(IGPUGraphicsPipeline::SCreationParams::FLAGS::CAPTURE_STATISTICS)) + { + pipeline->populateExecutableInfo(createInfo.flags.hasFlags(IGPUGraphicsPipeline::SCreationParams::FLAGS::CAPTURE_INTERNAL_REPRESENTATIONS)); + if (pipeline->getExecutableInfo().empty()) + m_logger.log("Driver returned 0 executables for pipeline created with CAPTURE_STATISTICS flag. This pipeline type may not be supported by the driver's VK_KHR_pipeline_executable_properties implementation.", system::ILogger::ELL_WARNING); + } + output[i] = std::move(pipeline); debugNameBuilder.str(""); auto buildDebugName = [&](const IGPUPipelineBase::SShaderSpecInfo& spec, hlsl::ShaderStage stage) { @@ -1651,169 +1665,23 @@ void CVulkanLogicalDevice::createRayTracingPipelines_impl( const auto success = m_devf.vk.vkGetRayTracingShaderGroupHandlesKHR(m_vkdev, vk_pipeline, 0, handleCount, dataSize, shaderGroupHandles->data()) == VK_SUCCESS; assert(success); - output[i] = core::make_smart_refctd_ptr( + auto pipeline = core::make_smart_refctd_ptr( createInfos[i], vk_pipeline, std::move(shaderGroupHandles) ); - } - } - else - std::fill_n(output,vk_createInfos.size(),nullptr); -} - -core::vector CVulkanLogicalDevice::getPipelineExecutableProperties_impl(const IGPUComputePipeline* pipeline, bool includeInternalRepresentations) -{ - return getPipelineExecutableProperties_helper(static_cast(pipeline)->getInternalObject(), includeInternalRepresentations); -} - -core::vector CVulkanLogicalDevice::getPipelineExecutableProperties_impl(const IGPUGraphicsPipeline* pipeline, bool includeInternalRepresentations) -{ - return getPipelineExecutableProperties_helper(static_cast(pipeline)->getInternalObject(), includeInternalRepresentations); -} - -core::vector CVulkanLogicalDevice::getPipelineExecutableProperties_impl(const IGPURayTracingPipeline* pipeline, bool includeInternalRepresentations) -{ - return getPipelineExecutableProperties_helper(static_cast(pipeline)->getInternalObject(), includeInternalRepresentations); -} - -core::vector CVulkanLogicalDevice::getPipelineExecutableProperties_helper(VkPipeline vkPipeline, bool includeInternalRepresentations) -{ - VkPipelineInfoKHR pipelineInfo = {VK_STRUCTURE_TYPE_PIPELINE_INFO_KHR, nullptr}; - pipelineInfo.pipeline = vkPipeline; - - // Enumerate executables - uint32_t executableCount = 0; - m_devf.vk.vkGetPipelineExecutablePropertiesKHR(m_vkdev, &pipelineInfo, &executableCount, nullptr); - - if (executableCount == 0) - { - return {}; - } - - core::vector properties(executableCount); - for (uint32_t i = 0; i < executableCount; ++i) - properties[i] = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_PROPERTIES_KHR, nullptr}; - m_devf.vk.vkGetPipelineExecutablePropertiesKHR(m_vkdev, &pipelineInfo, &executableCount, properties.data()); - - core::vector result(executableCount); - - for (uint32_t i = 0; i < executableCount; ++i) - { - const auto& prop = properties[i]; - auto& info = result[i]; - - info.name = prop.name; - info.description = prop.description; - info.stages = static_cast(prop.stages); - info.subgroupSize = prop.subgroupSize; - VkPipelineExecutableInfoKHR execInfo = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INFO_KHR, nullptr}; - execInfo.pipeline = vkPipeline; - execInfo.executableIndex = i; - - uint32_t statCount = 0; - m_devf.vk.vkGetPipelineExecutableStatisticsKHR(m_vkdev, &execInfo, &statCount, nullptr); - - if (statCount > 0) - { - core::vector stats(statCount); - for (uint32_t s = 0; s < statCount; ++s) - stats[s] = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_STATISTIC_KHR, nullptr}; - m_devf.vk.vkGetPipelineExecutableStatisticsKHR(m_vkdev, &execInfo, &statCount, stats.data()); - - // First pass: format name:value pairs and find max width for alignment - core::vector nameValues(statCount); - size_t maxNameValueLen = 0; - for (uint32_t s = 0; s < statCount; ++s) - { - const auto& stat = stats[s]; - std::string value; - switch (stat.format) - { - case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_BOOL32_KHR: - value = stat.value.b32 ? "true" : "false"; - break; - case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_INT64_KHR: - value = std::to_string(stat.value.i64); - break; - case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_UINT64_KHR: - value = std::to_string(stat.value.u64); - break; - case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_FLOAT64_KHR: - value = std::to_string(stat.value.f64); - break; - default: - value = ""; - break; - } - nameValues[s] = std::string(stat.name) + ": " + value; - maxNameValueLen = std::max(maxNameValueLen, nameValues[s].size()); - } - - // Second pass: emit with aligned columns - std::string& statsStr = info.statistics; - for (uint32_t s = 0; s < statCount; ++s) + if (info.cached.flags.hasFlags(IGPURayTracingPipeline::SCreationParams::FLAGS::CAPTURE_STATISTICS)) { - statsStr += nameValues[s]; - statsStr.append(maxNameValueLen - nameValues[s].size() + 4, ' '); - statsStr += "// "; - statsStr += stats[s].description; - statsStr += "\n"; - } - } - - // Internal representations - if (includeInternalRepresentations) - { - uint32_t irCount = 0; - m_devf.vk.vkGetPipelineExecutableInternalRepresentationsKHR(m_vkdev, &execInfo, &irCount, nullptr); - - if (irCount > 0) - { - core::vector irs(irCount); - for (uint32_t r = 0; r < irCount; ++r) - irs[r] = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INTERNAL_REPRESENTATION_KHR, nullptr}; - - // First call to get sizes - m_devf.vk.vkGetPipelineExecutableInternalRepresentationsKHR(m_vkdev, &execInfo, &irCount, irs.data()); - - // Allocate data buffers and second call to get data - core::vector> irData(irCount); - for (uint32_t r = 0; r < irCount; ++r) - { - irData[r].resize(irs[r].dataSize); - irs[r].pData = irData[r].data(); - } - - m_devf.vk.vkGetPipelineExecutableInternalRepresentationsKHR(m_vkdev, &execInfo, &irCount, irs.data()); - - std::string& irStr = info.internalRepresentations; - for (uint32_t r = 0; r < irCount; ++r) - { - irStr += "---- "; - irStr += irs[r].name; - irStr += " ----\n"; - irStr += irs[r].description; - irStr += "\n"; - if (irs[r].isText) - { - auto* str = static_cast(irs[r].pData); - irStr.append(str, irs[r].dataSize > 0 ? irs[r].dataSize - 1 : 0); - irStr += "\n"; - } - else - { - irStr += "[binary data, "; - irStr += std::to_string(irs[r].dataSize); - irStr += " bytes]\n"; - } - } + pipeline->populateExecutableInfo(info.cached.flags.hasFlags(IGPURayTracingPipeline::SCreationParams::FLAGS::CAPTURE_INTERNAL_REPRESENTATIONS)); + if (pipeline->getExecutableInfo().empty()) + m_logger.log("Driver returned 0 executables for pipeline created with CAPTURE_STATISTICS flag. This pipeline type may not be supported by the driver's VK_KHR_pipeline_executable_properties implementation.", system::ILogger::ELL_WARNING); } + output[i] = std::move(pipeline); } } - - return result; + else + std::fill_n(output,vk_createInfos.size(),nullptr); } core::smart_refctd_ptr CVulkanLogicalDevice::createQueryPool_impl(const IQueryPool::SCreationParams& params) diff --git a/src/nbl/video/CVulkanLogicalDevice.h b/src/nbl/video/CVulkanLogicalDevice.h index 01aea09a0a..4cc633ec55 100644 --- a/src/nbl/video/CVulkanLogicalDevice.h +++ b/src/nbl/video/CVulkanLogicalDevice.h @@ -301,12 +301,6 @@ class CVulkanLogicalDevice final : public ILogicalDevice const SSpecializationValidationResult& validation ) override; - // pipeline executable properties - core::vector getPipelineExecutableProperties_impl(const IGPUComputePipeline* pipeline, bool includeInternalRepresentations) override; - core::vector getPipelineExecutableProperties_impl(const IGPUGraphicsPipeline* pipeline, bool includeInternalRepresentations) override; - core::vector getPipelineExecutableProperties_impl(const IGPURayTracingPipeline* pipeline, bool includeInternalRepresentations) override; - core::vector getPipelineExecutableProperties_helper(VkPipeline vkPipeline, bool includeInternalRepresentations); - // queries core::smart_refctd_ptr createQueryPool_impl(const IQueryPool::SCreationParams& params) override; bool getQueryPoolResults_impl(const IQueryPool* const queryPool, const uint32_t firstQuery, const uint32_t queryCount, void* const pData, const size_t stride, const core::bitflag flags) override; diff --git a/src/nbl/video/CVulkanPipelineExecutableInfo.h b/src/nbl/video/CVulkanPipelineExecutableInfo.h new file mode 100644 index 0000000000..4cdf1f194f --- /dev/null +++ b/src/nbl/video/CVulkanPipelineExecutableInfo.h @@ -0,0 +1,149 @@ +#ifndef _NBL_VIDEO_C_VULKAN_PIPELINE_EXECUTABLE_INFO_H_INCLUDED_ +#define _NBL_VIDEO_C_VULKAN_PIPELINE_EXECUTABLE_INFO_H_INCLUDED_ + +#include "nbl/video/IGPUPipeline.h" +#include "nbl/video/CVulkanDeviceFunctionTable.h" + +#include + +namespace nbl::video +{ + +inline void populateExecutableInfoFromVulkan(core::vector& outInfo, const CVulkanDeviceFunctionTable* vk, VkDevice vkDevice, VkPipeline vkPipeline, bool includeInternalRepresentations) +{ + VkPipelineInfoKHR pipelineInfo = {VK_STRUCTURE_TYPE_PIPELINE_INFO_KHR, nullptr}; + pipelineInfo.pipeline = vkPipeline; + + // Enumerate executables + uint32_t executableCount = 0; + vk->vk.vkGetPipelineExecutablePropertiesKHR(vkDevice, &pipelineInfo, &executableCount, nullptr); + + if (executableCount == 0) + return; + + core::vector properties(executableCount); + for (uint32_t i = 0; i < executableCount; ++i) + properties[i] = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_PROPERTIES_KHR, nullptr}; + vk->vk.vkGetPipelineExecutablePropertiesKHR(vkDevice, &pipelineInfo, &executableCount, properties.data()); + + outInfo.resize(executableCount); + + for (uint32_t i = 0; i < executableCount; ++i) + { + const auto& prop = properties[i]; + auto& info = outInfo[i]; + + info.name = prop.name; + info.description = prop.description; + info.stages = static_cast(prop.stages); + info.subgroupSize = prop.subgroupSize; + + VkPipelineExecutableInfoKHR execInfo = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INFO_KHR, nullptr}; + execInfo.pipeline = vkPipeline; + execInfo.executableIndex = i; + + uint32_t statCount = 0; + vk->vk.vkGetPipelineExecutableStatisticsKHR(vkDevice, &execInfo, &statCount, nullptr); + + if (statCount > 0) + { + core::vector stats(statCount); + for (uint32_t s = 0; s < statCount; ++s) + stats[s] = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_STATISTIC_KHR, nullptr}; + vk->vk.vkGetPipelineExecutableStatisticsKHR(vkDevice, &execInfo, &statCount, stats.data()); + + // First pass: format name:value pairs and find max width for alignment + core::vector nameValues(statCount); + size_t maxNameValueLen = 0; + for (uint32_t s = 0; s < statCount; ++s) + { + const auto& stat = stats[s]; + std::string value; + switch (stat.format) + { + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_BOOL32_KHR: + value = stat.value.b32 ? "true" : "false"; + break; + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_INT64_KHR: + value = std::to_string(stat.value.i64); + break; + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_UINT64_KHR: + value = std::to_string(stat.value.u64); + break; + case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_FLOAT64_KHR: + value = std::to_string(stat.value.f64); + break; + default: + value = ""; + break; + } + nameValues[s] = std::string(stat.name) + ": " + value; + maxNameValueLen = std::max(maxNameValueLen, nameValues[s].size()); + } + + // Second pass: emit with aligned columns + std::string& statsStr = info.statistics; + for (uint32_t s = 0; s < statCount; ++s) + { + statsStr += nameValues[s]; + statsStr.append(maxNameValueLen - nameValues[s].size() + 4, ' '); + statsStr += "// "; + statsStr += stats[s].description; + statsStr += "\n"; + } + } + + // Internal representations + if (includeInternalRepresentations) + { + uint32_t irCount = 0; + vk->vk.vkGetPipelineExecutableInternalRepresentationsKHR(vkDevice, &execInfo, &irCount, nullptr); + + if (irCount > 0) + { + core::vector irs(irCount); + for (uint32_t r = 0; r < irCount; ++r) + irs[r] = {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INTERNAL_REPRESENTATION_KHR, nullptr}; + + // First call to get sizes + vk->vk.vkGetPipelineExecutableInternalRepresentationsKHR(vkDevice, &execInfo, &irCount, irs.data()); + + // Allocate data buffers and second call to get data + core::vector> irData(irCount); + for (uint32_t r = 0; r < irCount; ++r) + { + irData[r].resize(irs[r].dataSize); + irs[r].pData = irData[r].data(); + } + + vk->vk.vkGetPipelineExecutableInternalRepresentationsKHR(vkDevice, &execInfo, &irCount, irs.data()); + + std::string& irStr = info.internalRepresentations; + for (uint32_t r = 0; r < irCount; ++r) + { + irStr += "---- "; + irStr += irs[r].name; + irStr += " ----\n"; + irStr += irs[r].description; + irStr += "\n"; + if (irs[r].isText) + { + auto* str = static_cast(irs[r].pData); + irStr.append(str, irs[r].dataSize > 0 ? irs[r].dataSize - 1 : 0); + irStr += "\n"; + } + else + { + irStr += "[binary data, "; + irStr += std::to_string(irs[r].dataSize); + irStr += " bytes]\n"; + } + } + } + } + } +} + +} + +#endif diff --git a/src/nbl/video/CVulkanRayTracingPipeline.cpp b/src/nbl/video/CVulkanRayTracingPipeline.cpp index 960d78428a..48a6b4a92a 100644 --- a/src/nbl/video/CVulkanRayTracingPipeline.cpp +++ b/src/nbl/video/CVulkanRayTracingPipeline.cpp @@ -3,6 +3,7 @@ #include "nbl/video/CVulkanRayTracingPipeline.h" #include "nbl/video/CVulkanLogicalDevice.h" #include "nbl/video/IGPURayTracingPipeline.h" +#include "nbl/video/CVulkanPipelineExecutableInfo.h" #include @@ -78,6 +79,12 @@ namespace nbl::video vk->vk.vkDestroyPipeline(vulkanDevice->getInternalObject(), m_vkPipeline, nullptr); } + void CVulkanRayTracingPipeline::populateExecutableInfo(bool includeInternalRepresentations) + { + const CVulkanLogicalDevice* vulkanDevice = static_cast(getOriginDevice()); + populateExecutableInfoFromVulkan(m_executableInfo, vulkanDevice->getFunctionTable(), vulkanDevice->getInternalObject(), m_vkPipeline, includeInternalRepresentations); + } + const IGPURayTracingPipeline::SShaderGroupHandle& CVulkanRayTracingPipeline::getRaygen() const { return m_shaderGroupHandles->operator[](getRaygenIndex());