From 4262852a35905a62e08577d28bc39a6be897084f Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 26 Jun 2023 14:58:00 -0700 Subject: [PATCH] [Impeller] Report pipeline creation feedback to logs and traces. Piping the feedback to logs is disabled by default but can be enabled by patching the source for now. If reading from logs gets to be useful, we can move it behind a flag. In traces, enabled by default, pipeline cache hits and misses will be shown via counters. The time taken to create a pipeline variant is already covered by existing traces. This patch also sets up infrastructure in the impeller::CapabilitiesVK to quickly enable optional device extensions. Pipeline feedback will only be reported if the device supports `VK_EXT_pipeline_creation_feedback`. Example of logs: ``` E/flutter ( 2011): >>>>>> E/flutter ( 2011): Pipeline 'GaussianBlurAlphaDecal Pipeline' Time: 48.60ms Cache Hit: 0 Base Accel: 0 Thread: 481449901232 E/flutter ( 2011): Stage 1: Time: 12.91ms Cache Hit: 0 Base Accel: 0 Thread: 481449901232 E/flutter ( 2011): Stage 2: Time: 15.10ms Cache Hit: 0 Base Accel: 0 Thread: 481449901232 E/flutter ( 2011): <<<<<< ``` --- impeller/base/timing.h | 1 + .../backend/vulkan/capabilities_vk.cc | 92 +++++++++++--- .../renderer/backend/vulkan/capabilities_vk.h | 20 ++- .../renderer/backend/vulkan/context_vk.cc | 18 +-- .../backend/vulkan/pipeline_cache_vk.cc | 4 + .../backend/vulkan/pipeline_cache_vk.h | 2 + .../backend/vulkan/pipeline_library_vk.cc | 115 +++++++++++++++++- 7 files changed, 222 insertions(+), 30 deletions(-) diff --git a/impeller/base/timing.h b/impeller/base/timing.h index b60104337e861..1496fd9eb9503 100644 --- a/impeller/base/timing.h +++ b/impeller/base/timing.h @@ -8,6 +8,7 @@ namespace impeller { +using MillisecondsF = std::chrono::duration; using SecondsF = std::chrono::duration; using Clock = std::chrono::high_resolution_clock; using TimePoint = std::chrono::time_point; diff --git a/impeller/renderer/backend/vulkan/capabilities_vk.cc b/impeller/renderer/backend/vulkan/capabilities_vk.cc index c9cb8722cb652..84585b84ec33f 100644 --- a/impeller/renderer/backend/vulkan/capabilities_vk.cc +++ b/impeller/renderer/backend/vulkan/capabilities_vk.cc @@ -54,7 +54,7 @@ bool CapabilitiesVK::AreValidationsEnabled() const { return enable_validations_; } -std::optional> CapabilitiesVK::GetRequiredLayers() +std::optional> CapabilitiesVK::GetEnabledLayers() const { std::vector required; @@ -71,7 +71,7 @@ std::optional> CapabilitiesVK::GetRequiredLayers() } std::optional> -CapabilitiesVK::GetRequiredInstanceExtensions() const { +CapabilitiesVK::GetEnabledInstanceExtensions() const { std::vector required; if (!HasExtension("VK_KHR_surface")) { @@ -150,9 +150,29 @@ CapabilitiesVK::GetRequiredInstanceExtensions() const { return required; } -std::optional> -CapabilitiesVK::GetRequiredDeviceExtensions( - const vk::PhysicalDevice& physical_device) const { +static const char* GetDeviceExtensionName(OptionalDeviceExtensionVK ext) { + switch (ext) { + case OptionalDeviceExtensionVK::kEXTPipelineCreationFeedback: + return VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME; + case OptionalDeviceExtensionVK::kLast: + return "Unknown"; + } + return "Unknown"; +} + +static void IterateOptionalDeviceExtensions( + const std::function& it) { + if (!it) { + return; + } + for (size_t i = 0; + i < static_cast(OptionalDeviceExtensionVK::kLast); i++) { + it(static_cast(i)); + } +} + +static std::optional> GetSupportedDeviceExtensions( + const vk::PhysicalDevice& physical_device) { auto device_extensions = physical_device.enumerateDeviceExtensionProperties(); if (device_extensions.result != vk::Result::eSuccess) { return std::nullopt; @@ -161,21 +181,42 @@ CapabilitiesVK::GetRequiredDeviceExtensions( std::set exts; for (const auto& device_extension : device_extensions.value) { exts.insert(device_extension.extensionName); + }; + + return exts; +} + +std::optional> +CapabilitiesVK::GetEnabledDeviceExtensions( + const vk::PhysicalDevice& physical_device) const { + auto exts = GetSupportedDeviceExtensions(physical_device); + + if (!exts.has_value()) { + return std::nullopt; } - std::vector required; + std::vector enabled; - if (exts.find("VK_KHR_swapchain") == exts.end()) { + if (exts->find("VK_KHR_swapchain") == exts->end()) { VALIDATION_LOG << "Device does not support the swapchain extension."; return std::nullopt; } - required.push_back("VK_KHR_swapchain"); + enabled.push_back("VK_KHR_swapchain"); // Required for non-conformant implementations like MoltenVK. - if (exts.find("VK_KHR_portability_subset") != exts.end()) { - required.push_back("VK_KHR_portability_subset"); + if (exts->find("VK_KHR_portability_subset") != exts->end()) { + enabled.push_back("VK_KHR_portability_subset"); } - return required; + + // Enable all optional extensions if the device supports it. + IterateOptionalDeviceExtensions([&](auto ext) { + auto ext_name = GetDeviceExtensionName(ext); + if (exts->find(ext_name) != exts->end()) { + enabled.push_back(ext_name); + } + }); + + return enabled; } static bool HasSuitableColorFormat(const vk::PhysicalDevice& device, @@ -227,7 +268,7 @@ static bool HasRequiredQueues(const vk::PhysicalDevice& physical_device) { } std::optional -CapabilitiesVK::GetRequiredDeviceFeatures( +CapabilitiesVK::GetEnabledDeviceFeatures( const vk::PhysicalDevice& device) const { if (!PhysicalDeviceSupportsRequiredFormats(device)) { VALIDATION_LOG << "Device doesn't support the required formats."; @@ -244,7 +285,7 @@ CapabilitiesVK::GetRequiredDeviceFeatures( return std::nullopt; } - if (!GetRequiredDeviceExtensions(device).has_value()) { + if (!GetEnabledDeviceExtensions(device).has_value()) { VALIDATION_LOG << "Device doesn't support the required queues."; return std::nullopt; } @@ -282,7 +323,7 @@ void CapabilitiesVK::SetOffscreenFormat(PixelFormat pixel_format) const { color_format_ = pixel_format; } -bool CapabilitiesVK::SetDevice(const vk::PhysicalDevice& device) { +bool CapabilitiesVK::SetPhysicalDevice(const vk::PhysicalDevice& device) { if (HasSuitableDepthStencilFormat(device, vk::Format::eS8Uint)) { depth_stencil_format_ = PixelFormat::kS8UInt; } else if (HasSuitableDepthStencilFormat(device, @@ -307,6 +348,21 @@ bool CapabilitiesVK::SetDevice(const vk::PhysicalDevice& device) { .supportedOperations & vk::SubgroupFeatureFlagBits::eArithmetic); + // Determine the optional device extensions this physical device supports. + { + optional_device_extensions_.clear(); + auto exts = GetSupportedDeviceExtensions(device); + if (!exts.has_value()) { + return false; + } + IterateOptionalDeviceExtensions([&](auto ext) { + auto ext_name = GetDeviceExtensionName(ext); + if (exts->find(ext_name) != exts->end()) { + optional_device_extensions_.insert(ext); + } + }); + } + return true; } @@ -348,7 +404,7 @@ bool CapabilitiesVK::SupportsCompute() const { // |Capabilities| bool CapabilitiesVK::SupportsComputeSubgroups() const { - // Set by |SetDevice|. + // Set by |SetPhysicalDevice|. return supports_compute_subgroups_; } @@ -381,4 +437,10 @@ CapabilitiesVK::GetPhysicalDeviceProperties() const { return device_properties_; } +bool CapabilitiesVK::HasOptionalDeviceExtension( + OptionalDeviceExtensionVK extension) const { + return optional_device_extensions_.find(extension) != + optional_device_extensions_.end(); +} + } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/capabilities_vk.h b/impeller/renderer/backend/vulkan/capabilities_vk.h index 46a6a895de322..98614011206ff 100644 --- a/impeller/renderer/backend/vulkan/capabilities_vk.h +++ b/impeller/renderer/backend/vulkan/capabilities_vk.h @@ -18,6 +18,12 @@ namespace impeller { class ContextVK; +enum class OptionalDeviceExtensionVK : uint32_t { + // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_pipeline_creation_feedback.html + kEXTPipelineCreationFeedback, + kLast, +}; + //------------------------------------------------------------------------------ /// @brief The Vulkan layers and extensions wrangler. /// @@ -32,17 +38,20 @@ class CapabilitiesVK final : public Capabilities, bool AreValidationsEnabled() const; - std::optional> GetRequiredLayers() const; + bool HasOptionalDeviceExtension(OptionalDeviceExtensionVK extension) const; + + std::optional> GetEnabledLayers() const; - std::optional> GetRequiredInstanceExtensions() const; + std::optional> GetEnabledInstanceExtensions() const; - std::optional> GetRequiredDeviceExtensions( + std::optional> GetEnabledDeviceExtensions( const vk::PhysicalDevice& physical_device) const; - std::optional GetRequiredDeviceFeatures( + std::optional GetEnabledDeviceFeatures( const vk::PhysicalDevice& physical_device) const; - [[nodiscard]] bool SetDevice(const vk::PhysicalDevice& physical_device); + [[nodiscard]] bool SetPhysicalDevice( + const vk::PhysicalDevice& physical_device); const vk::PhysicalDeviceProperties& GetPhysicalDeviceProperties() const; @@ -90,6 +99,7 @@ class CapabilitiesVK final : public Capabilities, private: const bool enable_validations_; std::map> exts_; + std::set optional_device_extensions_; mutable PixelFormat color_format_ = PixelFormat::kUnknown; PixelFormat depth_stencil_format_ = PixelFormat::kUnknown; vk::PhysicalDeviceProperties device_properties_; diff --git a/impeller/renderer/backend/vulkan/context_vk.cc b/impeller/renderer/backend/vulkan/context_vk.cc index 74ba94804a0f4..6f6978147fc06 100644 --- a/impeller/renderer/backend/vulkan/context_vk.cc +++ b/impeller/renderer/backend/vulkan/context_vk.cc @@ -42,7 +42,7 @@ static std::optional PickPhysicalDevice( const CapabilitiesVK& caps, const vk::Instance& instance) { for (const auto& device : instance.enumeratePhysicalDevices().value) { - if (caps.GetRequiredDeviceFeatures(device).has_value()) { + if (caps.GetEnabledDeviceFeatures(device).has_value()) { return device; } } @@ -140,8 +140,8 @@ void ContextVK::Setup(Settings settings) { gHasValidationLayers = caps->AreValidationsEnabled(); - auto enabled_layers = caps->GetRequiredLayers(); - auto enabled_extensions = caps->GetRequiredInstanceExtensions(); + auto enabled_layers = caps->GetEnabledLayers(); + auto enabled_extensions = caps->GetEnabledInstanceExtensions(); if (!enabled_layers.has_value() || !enabled_extensions.has_value()) { VALIDATION_LOG << "Device has insufficient capabilities."; @@ -268,7 +268,7 @@ void ContextVK::Setup(Settings settings) { /// Create the logical device. /// auto enabled_device_extensions = - caps->GetRequiredDeviceExtensions(device_holder->physical_device); + caps->GetEnabledDeviceExtensions(device_holder->physical_device); if (!enabled_device_extensions.has_value()) { // This shouldn't happen since we already did device selection. But doesn't // hurt to check again. @@ -283,9 +283,9 @@ void ContextVK::Setup(Settings settings) { const auto queue_create_infos = GetQueueCreateInfos( {graphics_queue.value(), compute_queue.value(), transfer_queue.value()}); - const auto required_features = - caps->GetRequiredDeviceFeatures(device_holder->physical_device); - if (!required_features.has_value()) { + const auto enabled_features = + caps->GetEnabledDeviceFeatures(device_holder->physical_device); + if (!enabled_features.has_value()) { // This shouldn't happen since the device can't be picked if this was not // true. But doesn't hurt to check. return; @@ -295,7 +295,7 @@ void ContextVK::Setup(Settings settings) { device_info.setQueueCreateInfos(queue_create_infos); device_info.setPEnabledExtensionNames(enabled_device_extensions_c); - device_info.setPEnabledFeatures(&required_features.value()); + device_info.setPEnabledFeatures(&enabled_features.value()); // Device layers are deprecated and ignored. { @@ -308,7 +308,7 @@ void ContextVK::Setup(Settings settings) { device_holder->device = std::move(device_result.value); } - if (!caps->SetDevice(device_holder->physical_device)) { + if (!caps->SetPhysicalDevice(device_holder->physical_device)) { VALIDATION_LOG << "Capabilities could not be updated."; return; } diff --git a/impeller/renderer/backend/vulkan/pipeline_cache_vk.cc b/impeller/renderer/backend/vulkan/pipeline_cache_vk.cc index 8220d1d55c123..c7cd74a45f2c9 100644 --- a/impeller/renderer/backend/vulkan/pipeline_cache_vk.cc +++ b/impeller/renderer/backend/vulkan/pipeline_cache_vk.cc @@ -186,4 +186,8 @@ void PipelineCacheVK::PersistCacheToDisk() const { } } +const CapabilitiesVK* PipelineCacheVK::GetCapabilities() const { + return CapabilitiesVK::Cast(caps_.get()); +} + } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/pipeline_cache_vk.h b/impeller/renderer/backend/vulkan/pipeline_cache_vk.h index 519ad7c6b3fa3..ad26855c1e0e8 100644 --- a/impeller/renderer/backend/vulkan/pipeline_cache_vk.h +++ b/impeller/renderer/backend/vulkan/pipeline_cache_vk.h @@ -31,6 +31,8 @@ class PipelineCacheVK { vk::UniquePipeline CreatePipeline(const vk::ComputePipelineCreateInfo& info); + const CapabilitiesVK* GetCapabilities() const; + void PersistCacheToDisk() const; private: diff --git a/impeller/renderer/backend/vulkan/pipeline_library_vk.cc b/impeller/renderer/backend/vulkan/pipeline_library_vk.cc index 2cf6a0fed89e4..27efb372a1ed8 100644 --- a/impeller/renderer/backend/vulkan/pipeline_library_vk.cc +++ b/impeller/renderer/backend/vulkan/pipeline_library_vk.cc @@ -4,11 +4,14 @@ #include "impeller/renderer/backend/vulkan/pipeline_library_vk.h" +#include #include +#include #include "flutter/fml/container.h" #include "flutter/fml/trace_event.h" #include "impeller/base/promise.h" +#include "impeller/base/timing.h" #include "impeller/base/validation.h" #include "impeller/renderer/backend/vulkan/context_vk.h" #include "impeller/renderer/backend/vulkan/formats_vk.h" @@ -138,10 +141,105 @@ constexpr vk::FrontFace ToVKFrontFace(WindingOrder order) { FML_UNREACHABLE(); } +static vk::PipelineCreationFeedbackEXT EmptyFeedback() { + vk::PipelineCreationFeedbackEXT feedback; + // If the VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT is not set in flags, an + // implementation must not set any other bits in flags, and the values of all + // other VkPipelineCreationFeedback data members are undefined. + feedback.flags = vk::PipelineCreationFeedbackFlagBits::eValid; + return feedback; +} + +static void ReportPipelineCreationFeedbackToLog( + std::stringstream& stream, + const vk::PipelineCreationFeedbackEXT& feedback) { + const auto pipeline_cache_hit = + feedback.flags & + vk::PipelineCreationFeedbackFlagBits::eApplicationPipelineCacheHit; + const auto base_pipeline_accl = + feedback.flags & + vk::PipelineCreationFeedbackFlagBits::eBasePipelineAcceleration; + auto duration = std::chrono::duration_cast( + std::chrono::nanoseconds{feedback.duration}); + stream << "Time: " << duration.count() << "ms" + << " Cache Hit: " << static_cast(pipeline_cache_hit) + << " Base Accel: " << static_cast(base_pipeline_accl) + << " Thread: " << std::this_thread::get_id(); +} + +static void ReportPipelineCreationFeedbackToLog( + const PipelineDescriptor& desc, + const vk::PipelineCreationFeedbackCreateInfoEXT& feedback) { + std::stringstream stream; + stream << std::fixed << std::showpoint << std::setprecision(2); + stream << std::endl << ">>>>>>" << std::endl; + stream << "Pipeline '" << desc.GetLabel() << "' "; + ReportPipelineCreationFeedbackToLog(stream, + *feedback.pPipelineCreationFeedback); + if (feedback.pipelineStageCreationFeedbackCount != 0) { + stream << std::endl; + } + for (size_t i = 0, count = feedback.pipelineStageCreationFeedbackCount; + i < count; i++) { + stream << "\tStage " << i + 1 << ": "; + ReportPipelineCreationFeedbackToLog( + stream, feedback.pPipelineStageCreationFeedbacks[i]); + if (i != count - 1) { + stream << std::endl; + } + } + stream << std::endl << "<<<<<<" << std::endl; + FML_LOG(ERROR) << stream.str(); +} + +static void ReportPipelineCreationFeedbackToTrace( + const PipelineDescriptor& desc, + const vk::PipelineCreationFeedbackCreateInfoEXT& feedback) { + static int64_t gPipelineCacheHits = 0; + static int64_t gPipelineCacheMisses = 0; + static int64_t gPipelines = 0; + if (feedback.pPipelineCreationFeedback->flags & + vk::PipelineCreationFeedbackFlagBits::eApplicationPipelineCacheHit) { + gPipelineCacheHits++; + } else { + gPipelineCacheMisses++; + } + gPipelines++; + FML_TRACE_COUNTER("impeller", // + "PipelineCacheHits", gPipelineCacheHits, // + "PipelineCacheMisses", gPipelineCacheMisses, // + "TotalPipelines", gPipelines // + ); +} + +static void ReportPipelineCreationFeedback( + const PipelineDescriptor& desc, + const vk::PipelineCreationFeedbackCreateInfoEXT& feedback) { + constexpr bool kReportPipelineCreationFeedbackToLogs = false; + constexpr bool kReportPipelineCreationFeedbackToTraces = true; + if (kReportPipelineCreationFeedbackToLogs) { + ReportPipelineCreationFeedbackToLog(desc, feedback); + } + if (kReportPipelineCreationFeedbackToTraces) { + ReportPipelineCreationFeedbackToTrace(desc, feedback); + } +} + std::unique_ptr PipelineLibraryVK::CreatePipeline( const PipelineDescriptor& desc) { TRACE_EVENT0("flutter", __FUNCTION__); - vk::GraphicsPipelineCreateInfo pipeline_info; + vk::StructureChain + chain; + + const auto& supports_pipeline_creation_feedback = + pso_cache_->GetCapabilities()->HasOptionalDeviceExtension( + OptionalDeviceExtensionVK::kEXTPipelineCreationFeedback); + if (!supports_pipeline_creation_feedback) { + chain.unlink(); + } + + auto& pipeline_info = chain.get(); //---------------------------------------------------------------------------- /// Dynamic States @@ -319,6 +417,17 @@ std::unique_ptr PipelineLibraryVK::CreatePipeline( desc.GetBackStencilAttachmentDescriptor()); pipeline_info.setPDepthStencilState(&depth_stencil_state); + //---------------------------------------------------------------------------- + /// Setup the optional pipeline creation feedback struct so we can understand + /// how Vulkan created the PSO. + /// + auto& feedback = chain.get(); + auto pipeline_feedback = EmptyFeedback(); + std::vector stage_feedbacks( + pipeline_info.stageCount, EmptyFeedback()); + feedback.setPPipelineCreationFeedback(&pipeline_feedback); + feedback.setPipelineStageCreationFeedbacks(stage_feedbacks); + //---------------------------------------------------------------------------- /// Finally, all done with the setup info. Create the pipeline itself. /// @@ -328,6 +437,10 @@ std::unique_ptr PipelineLibraryVK::CreatePipeline( return nullptr; } + if (supports_pipeline_creation_feedback) { + ReportPipelineCreationFeedback(desc, feedback); + } + ContextVK::SetDebugName(strong_device->GetDevice(), *pipeline_layout.value, "Pipeline Layout " + desc.GetLabel()); ContextVK::SetDebugName(strong_device->GetDevice(), *pipeline,