| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "impeller/renderer/backend/vulkan/capabilities_vk.h" |
| |
| #include <algorithm> |
| |
| #include "impeller/base/validation.h" |
| #include "impeller/core/formats.h" |
| #include "impeller/renderer/backend/vulkan/vk.h" |
| |
| namespace impeller { |
| |
| static constexpr const char* kInstanceLayer = "ImpellerInstance"; |
| |
| CapabilitiesVK::CapabilitiesVK(bool enable_validations, |
| bool fatal_missing_validations) { |
| auto extensions = vk::enumerateInstanceExtensionProperties(); |
| auto layers = vk::enumerateInstanceLayerProperties(); |
| |
| if (extensions.result != vk::Result::eSuccess || |
| layers.result != vk::Result::eSuccess) { |
| return; |
| } |
| |
| for (const auto& ext : extensions.value) { |
| exts_[kInstanceLayer].insert(ext.extensionName); |
| } |
| |
| for (const auto& layer : layers.value) { |
| const std::string layer_name = layer.layerName; |
| auto layer_exts = vk::enumerateInstanceExtensionProperties(layer_name); |
| if (layer_exts.result != vk::Result::eSuccess) { |
| return; |
| } |
| for (const auto& layer_ext : layer_exts.value) { |
| exts_[layer_name].insert(layer_ext.extensionName); |
| } |
| } |
| |
| validations_enabled_ = |
| enable_validations && HasLayer("VK_LAYER_KHRONOS_validation"); |
| if (enable_validations && !validations_enabled_) { |
| FML_LOG(ERROR) |
| << "Requested Impeller context creation with validations but the " |
| "validation layers could not be found. Expect no Vulkan validation " |
| "checks!"; |
| if (fatal_missing_validations) { |
| FML_LOG(FATAL) << "Validation missing. Exiting."; |
| } |
| } |
| if (validations_enabled_) { |
| FML_LOG(INFO) << "Vulkan validations are enabled."; |
| } |
| is_valid_ = true; |
| } |
| |
| CapabilitiesVK::~CapabilitiesVK() = default; |
| |
| bool CapabilitiesVK::IsValid() const { |
| return is_valid_; |
| } |
| |
| bool CapabilitiesVK::AreValidationsEnabled() const { |
| return validations_enabled_; |
| } |
| |
| std::optional<std::vector<std::string>> CapabilitiesVK::GetEnabledLayers() |
| const { |
| std::vector<std::string> required; |
| |
| if (validations_enabled_) { |
| // The presence of this layer is already checked in the ctor. |
| required.push_back("VK_LAYER_KHRONOS_validation"); |
| } |
| |
| return required; |
| } |
| |
| std::optional<std::vector<std::string>> |
| CapabilitiesVK::GetEnabledInstanceExtensions() const { |
| std::vector<std::string> required; |
| |
| if (!HasExtension("VK_KHR_surface")) { |
| // Swapchain support is required and this is a dependency of |
| // VK_KHR_swapchain. |
| VALIDATION_LOG << "Could not find the surface extension."; |
| return std::nullopt; |
| } |
| required.push_back("VK_KHR_surface"); |
| |
| auto has_wsi = false; |
| if (HasExtension("VK_MVK_macos_surface")) { |
| required.push_back("VK_MVK_macos_surface"); |
| has_wsi = true; |
| } |
| |
| if (HasExtension("VK_EXT_metal_surface")) { |
| required.push_back("VK_EXT_metal_surface"); |
| has_wsi = true; |
| } |
| |
| if (HasExtension("VK_KHR_portability_enumeration")) { |
| required.push_back("VK_KHR_portability_enumeration"); |
| has_wsi = true; |
| } |
| |
| if (HasExtension("VK_KHR_win32_surface")) { |
| required.push_back("VK_KHR_win32_surface"); |
| has_wsi = true; |
| } |
| |
| if (HasExtension("VK_KHR_android_surface")) { |
| required.push_back("VK_KHR_android_surface"); |
| has_wsi = true; |
| } |
| |
| if (HasExtension("VK_KHR_xcb_surface")) { |
| required.push_back("VK_KHR_xcb_surface"); |
| has_wsi = true; |
| } |
| |
| if (HasExtension("VK_KHR_xlib_surface")) { |
| required.push_back("VK_KHR_xlib_surface"); |
| has_wsi = true; |
| } |
| |
| if (HasExtension("VK_KHR_wayland_surface")) { |
| required.push_back("VK_KHR_wayland_surface"); |
| has_wsi = true; |
| } |
| |
| if (!has_wsi) { |
| // Don't really care which WSI extension there is as long there is at least |
| // one. |
| VALIDATION_LOG << "Could not find a WSI extension."; |
| return std::nullopt; |
| } |
| |
| if (validations_enabled_) { |
| if (!HasExtension("VK_EXT_debug_utils")) { |
| VALIDATION_LOG << "Requested validations but could not find the " |
| "VK_EXT_debug_utils extension."; |
| return std::nullopt; |
| } |
| required.push_back("VK_EXT_debug_utils"); |
| |
| if (HasExtension("VK_EXT_validation_features")) { |
| // It's valid to not have `VK_EXT_validation_features` available. That's |
| // the case when using AGI as a frame debugger. |
| required.push_back("VK_EXT_validation_features"); |
| } |
| } |
| |
| return required; |
| } |
| |
| static const char* GetExtensionName(RequiredCommonDeviceExtensionVK ext) { |
| switch (ext) { |
| case RequiredCommonDeviceExtensionVK::kKHRSwapchain: |
| return VK_KHR_SWAPCHAIN_EXTENSION_NAME; |
| case RequiredCommonDeviceExtensionVK::kLast: |
| return "Unknown"; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| static const char* GetExtensionName(RequiredAndroidDeviceExtensionVK ext) { |
| switch (ext) { |
| case RequiredAndroidDeviceExtensionVK:: |
| kANDROIDExternalMemoryAndroidHardwareBuffer: |
| return "VK_ANDROID_external_memory_android_hardware_buffer"; |
| case RequiredAndroidDeviceExtensionVK::kKHRSamplerYcbcrConversion: |
| return VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME; |
| case RequiredAndroidDeviceExtensionVK::kKHRExternalMemory: |
| return VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME; |
| case RequiredAndroidDeviceExtensionVK::kEXTQueueFamilyForeign: |
| return VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME; |
| case RequiredAndroidDeviceExtensionVK::kKHRDedicatedAllocation: |
| return VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME; |
| case RequiredAndroidDeviceExtensionVK::kLast: |
| return "Unknown"; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| static const char* GetExtensionName(OptionalDeviceExtensionVK ext) { |
| switch (ext) { |
| case OptionalDeviceExtensionVK::kEXTPipelineCreationFeedback: |
| return VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME; |
| case OptionalDeviceExtensionVK::kVKKHRPortabilitySubset: |
| return "VK_KHR_portability_subset"; |
| case OptionalDeviceExtensionVK::kLast: |
| return "Unknown"; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| template <class T> |
| static bool IterateExtensions(const std::function<bool(T)>& it) { |
| if (!it) { |
| return false; |
| } |
| for (size_t i = 0; i < static_cast<uint32_t>(T::kLast); i++) { |
| if (!it(static_cast<T>(i))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static std::optional<std::set<std::string>> GetSupportedDeviceExtensions( |
| const vk::PhysicalDevice& physical_device) { |
| auto device_extensions = physical_device.enumerateDeviceExtensionProperties(); |
| if (device_extensions.result != vk::Result::eSuccess) { |
| return std::nullopt; |
| } |
| |
| std::set<std::string> exts; |
| for (const auto& device_extension : device_extensions.value) { |
| exts.insert(device_extension.extensionName); |
| }; |
| |
| return exts; |
| } |
| |
| std::optional<std::vector<std::string>> |
| CapabilitiesVK::GetEnabledDeviceExtensions( |
| const vk::PhysicalDevice& physical_device) const { |
| auto exts = GetSupportedDeviceExtensions(physical_device); |
| |
| if (!exts.has_value()) { |
| return std::nullopt; |
| } |
| |
| std::vector<std::string> enabled; |
| |
| auto for_each_common_extension = [&](RequiredCommonDeviceExtensionVK ext) { |
| auto name = GetExtensionName(ext); |
| if (exts->find(name) == exts->end()) { |
| VALIDATION_LOG << "Device does not support required extension: " << name; |
| return false; |
| } |
| enabled.push_back(name); |
| return true; |
| }; |
| |
| auto for_each_android_extension = [&](RequiredAndroidDeviceExtensionVK ext) { |
| #ifdef FML_OS_ANDROID |
| auto name = GetExtensionName(ext); |
| if (exts->find(name) == exts->end()) { |
| VALIDATION_LOG << "Device does not support required Android extension: " |
| << name; |
| return false; |
| } |
| enabled.push_back(name); |
| #endif // FML_OS_ANDROID |
| return true; |
| }; |
| |
| auto for_each_optional_extension = [&](OptionalDeviceExtensionVK ext) { |
| auto name = GetExtensionName(ext); |
| if (exts->find(name) != exts->end()) { |
| enabled.push_back(name); |
| } |
| return true; |
| }; |
| |
| const auto iterate_extensions = |
| IterateExtensions<RequiredCommonDeviceExtensionVK>( |
| for_each_common_extension) && |
| IterateExtensions<RequiredAndroidDeviceExtensionVK>( |
| for_each_android_extension) && |
| IterateExtensions<OptionalDeviceExtensionVK>(for_each_optional_extension); |
| |
| if (!iterate_extensions) { |
| VALIDATION_LOG << "Device not suitable since required extensions are not " |
| "supported."; |
| return std::nullopt; |
| } |
| |
| return enabled; |
| } |
| |
| static bool HasSuitableColorFormat(const vk::PhysicalDevice& device, |
| vk::Format format) { |
| const auto props = device.getFormatProperties(format); |
| // This needs to be more comprehensive. |
| return !!(props.optimalTilingFeatures & |
| vk::FormatFeatureFlagBits::eColorAttachment); |
| } |
| |
| static bool HasSuitableDepthStencilFormat(const vk::PhysicalDevice& device, |
| vk::Format format) { |
| const auto props = device.getFormatProperties(format); |
| return !!(props.optimalTilingFeatures & |
| vk::FormatFeatureFlagBits::eDepthStencilAttachment); |
| } |
| |
| static bool PhysicalDeviceSupportsRequiredFormats( |
| const vk::PhysicalDevice& device) { |
| const auto has_color_format = |
| HasSuitableColorFormat(device, vk::Format::eR8G8B8A8Unorm); |
| const auto has_stencil_format = |
| HasSuitableDepthStencilFormat(device, vk::Format::eD32SfloatS8Uint) || |
| HasSuitableDepthStencilFormat(device, vk::Format::eD24UnormS8Uint); |
| return has_color_format && has_stencil_format; |
| } |
| |
| static bool HasRequiredProperties(const vk::PhysicalDevice& physical_device) { |
| auto properties = physical_device.getProperties(); |
| if (!(properties.limits.framebufferColorSampleCounts & |
| (vk::SampleCountFlagBits::e1 | vk::SampleCountFlagBits::e4))) { |
| return false; |
| } |
| return true; |
| } |
| |
| static bool HasRequiredQueues(const vk::PhysicalDevice& physical_device) { |
| auto queue_flags = vk::QueueFlags{}; |
| for (const auto& queue : physical_device.getQueueFamilyProperties()) { |
| if (queue.queueCount == 0) { |
| continue; |
| } |
| queue_flags |= queue.queueFlags; |
| } |
| return static_cast<VkQueueFlags>(queue_flags & |
| (vk::QueueFlagBits::eGraphics | |
| vk::QueueFlagBits::eCompute | |
| vk::QueueFlagBits::eTransfer)); |
| } |
| |
| template <class ExtensionEnum> |
| static bool IsExtensionInList(const std::vector<std::string>& list, |
| ExtensionEnum ext) { |
| const std::string name = GetExtensionName(ext); |
| return std::find(list.begin(), list.end(), name) != list.end(); |
| } |
| |
| std::optional<CapabilitiesVK::PhysicalDeviceFeatures> |
| CapabilitiesVK::GetEnabledDeviceFeatures( |
| const vk::PhysicalDevice& device) const { |
| if (!PhysicalDeviceSupportsRequiredFormats(device)) { |
| VALIDATION_LOG << "Device doesn't support the required formats."; |
| return std::nullopt; |
| } |
| |
| if (!HasRequiredProperties(device)) { |
| VALIDATION_LOG << "Device doesn't support the required properties."; |
| return std::nullopt; |
| } |
| |
| if (!HasRequiredQueues(device)) { |
| VALIDATION_LOG << "Device doesn't support the required queues."; |
| return std::nullopt; |
| } |
| |
| const auto enabled_extensions = GetEnabledDeviceExtensions(device); |
| if (!enabled_extensions.has_value()) { |
| VALIDATION_LOG << "Device doesn't support the required queues."; |
| return std::nullopt; |
| } |
| |
| PhysicalDeviceFeatures supported_chain; |
| device.getFeatures2(&supported_chain.get()); |
| |
| PhysicalDeviceFeatures required_chain; |
| |
| // Base features. |
| { |
| auto& required = required_chain.get().features; |
| const auto& supported = supported_chain.get().features; |
| |
| // We require this for enabling wireframes in the playground. But its not |
| // necessarily a big deal if we don't have this feature. |
| required.fillModeNonSolid = supported.fillModeNonSolid; |
| } |
| // VK_KHR_sampler_ycbcr_conversion features. |
| if (IsExtensionInList( |
| enabled_extensions.value(), |
| RequiredAndroidDeviceExtensionVK::kKHRSamplerYcbcrConversion)) { |
| auto& required = |
| required_chain |
| .get<vk::PhysicalDeviceSamplerYcbcrConversionFeaturesKHR>(); |
| const auto& supported = |
| supported_chain |
| .get<vk::PhysicalDeviceSamplerYcbcrConversionFeaturesKHR>(); |
| |
| required.samplerYcbcrConversion = supported.samplerYcbcrConversion; |
| } |
| |
| return required_chain; |
| } |
| |
| bool CapabilitiesVK::HasLayer(const std::string& layer) const { |
| for (const auto& [found_layer, exts] : exts_) { |
| if (found_layer == layer) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool CapabilitiesVK::HasExtension(const std::string& ext) const { |
| for (const auto& [layer, exts] : exts_) { |
| if (exts.find(ext) != exts.end()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void CapabilitiesVK::SetOffscreenFormat(PixelFormat pixel_format) const { |
| default_color_format_ = pixel_format; |
| } |
| |
| bool CapabilitiesVK::SetPhysicalDevice(const vk::PhysicalDevice& device) { |
| if (HasSuitableColorFormat(device, vk::Format::eR8G8B8A8Unorm)) { |
| default_color_format_ = PixelFormat::kR8G8B8A8UNormInt; |
| } else { |
| default_color_format_ = PixelFormat::kUnknown; |
| } |
| |
| if (HasSuitableDepthStencilFormat(device, vk::Format::eD32SfloatS8Uint)) { |
| default_depth_stencil_format_ = PixelFormat::kD32FloatS8UInt; |
| } else if (HasSuitableDepthStencilFormat(device, |
| vk::Format::eD24UnormS8Uint)) { |
| default_depth_stencil_format_ = PixelFormat::kD24UnormS8Uint; |
| } else { |
| default_depth_stencil_format_ = PixelFormat::kUnknown; |
| } |
| |
| if (HasSuitableDepthStencilFormat(device, vk::Format::eS8Uint)) { |
| default_stencil_format_ = PixelFormat::kS8UInt; |
| } else if (default_depth_stencil_format_ != PixelFormat::kUnknown) { |
| default_stencil_format_ = default_depth_stencil_format_; |
| } |
| |
| device_properties_ = device.getProperties(); |
| |
| auto physical_properties_2 = |
| device.getProperties2<vk::PhysicalDeviceProperties2, |
| vk::PhysicalDeviceSubgroupProperties>(); |
| |
| // Currently shaders only want access to arithmetic subgroup features. |
| // If that changes this needs to get updated, and so does Metal (which right |
| // now assumes it from compile time flags based on the MSL target version). |
| |
| supports_compute_subgroups_ = |
| !!(physical_properties_2.get<vk::PhysicalDeviceSubgroupProperties>() |
| .supportedOperations & |
| vk::SubgroupFeatureFlagBits::eArithmetic); |
| |
| { |
| // Query texture support. |
| // TODO(jonahwilliams): |
| // https://github.com/flutter/flutter/issues/129784 |
| vk::PhysicalDeviceMemoryProperties memory_properties; |
| device.getMemoryProperties(&memory_properties); |
| |
| for (auto i = 0u; i < memory_properties.memoryTypeCount; i++) { |
| if (memory_properties.memoryTypes[i].propertyFlags & |
| vk::MemoryPropertyFlagBits::eLazilyAllocated) { |
| supports_device_transient_textures_ = true; |
| } |
| } |
| } |
| |
| // Determine the optional device extensions this physical device supports. |
| { |
| required_common_device_extensions_.clear(); |
| required_android_device_extensions_.clear(); |
| optional_device_extensions_.clear(); |
| auto exts = GetSupportedDeviceExtensions(device); |
| if (!exts.has_value()) { |
| return false; |
| } |
| IterateExtensions<RequiredCommonDeviceExtensionVK>([&](auto ext) -> bool { |
| auto ext_name = GetExtensionName(ext); |
| if (exts->find(ext_name) != exts->end()) { |
| required_common_device_extensions_.insert(ext); |
| } |
| return true; |
| }); |
| IterateExtensions<RequiredAndroidDeviceExtensionVK>([&](auto ext) -> bool { |
| auto ext_name = GetExtensionName(ext); |
| if (exts->find(ext_name) != exts->end()) { |
| required_android_device_extensions_.insert(ext); |
| } |
| return true; |
| }); |
| IterateExtensions<OptionalDeviceExtensionVK>([&](auto ext) -> bool { |
| auto ext_name = GetExtensionName(ext); |
| if (exts->find(ext_name) != exts->end()) { |
| optional_device_extensions_.insert(ext); |
| } |
| return true; |
| }); |
| } |
| |
| return true; |
| } |
| |
| // |Capabilities| |
| bool CapabilitiesVK::SupportsOffscreenMSAA() const { |
| return true; |
| } |
| |
| // |Capabilities| |
| bool CapabilitiesVK::SupportsImplicitResolvingMSAA() const { |
| return false; |
| } |
| |
| // |Capabilities| |
| bool CapabilitiesVK::SupportsSSBO() const { |
| return true; |
| } |
| |
| // |Capabilities| |
| bool CapabilitiesVK::SupportsBufferToTextureBlits() const { |
| return true; |
| } |
| |
| // |Capabilities| |
| bool CapabilitiesVK::SupportsTextureToTextureBlits() const { |
| return true; |
| } |
| |
| // |Capabilities| |
| bool CapabilitiesVK::SupportsFramebufferFetch() const { |
| return true; |
| } |
| |
| // |Capabilities| |
| bool CapabilitiesVK::SupportsCompute() const { |
| // Vulkan 1.1 requires support for compute. |
| return true; |
| } |
| |
| // |Capabilities| |
| bool CapabilitiesVK::SupportsComputeSubgroups() const { |
| // Set by |SetPhysicalDevice|. |
| return supports_compute_subgroups_; |
| } |
| |
| // |Capabilities| |
| bool CapabilitiesVK::SupportsReadFromResolve() const { |
| return false; |
| } |
| |
| bool CapabilitiesVK::SupportsDecalSamplerAddressMode() const { |
| return true; |
| } |
| |
| // |Capabilities| |
| bool CapabilitiesVK::SupportsDeviceTransientTextures() const { |
| return supports_device_transient_textures_; |
| } |
| |
| // |Capabilities| |
| PixelFormat CapabilitiesVK::GetDefaultColorFormat() const { |
| return default_color_format_; |
| } |
| |
| // |Capabilities| |
| PixelFormat CapabilitiesVK::GetDefaultStencilFormat() const { |
| return default_stencil_format_; |
| } |
| |
| // |Capabilities| |
| PixelFormat CapabilitiesVK::GetDefaultDepthStencilFormat() const { |
| return default_depth_stencil_format_; |
| } |
| |
| const vk::PhysicalDeviceProperties& |
| CapabilitiesVK::GetPhysicalDeviceProperties() const { |
| return device_properties_; |
| } |
| |
| PixelFormat CapabilitiesVK::GetDefaultGlyphAtlasFormat() const { |
| return PixelFormat::kR8UNormInt; |
| } |
| |
| bool CapabilitiesVK::HasExtension(RequiredCommonDeviceExtensionVK ext) const { |
| return required_common_device_extensions_.find(ext) != |
| required_common_device_extensions_.end(); |
| } |
| |
| bool CapabilitiesVK::HasExtension(RequiredAndroidDeviceExtensionVK ext) const { |
| return required_android_device_extensions_.find(ext) != |
| required_android_device_extensions_.end(); |
| } |
| |
| bool CapabilitiesVK::HasExtension(OptionalDeviceExtensionVK ext) const { |
| return optional_device_extensions_.find(ext) != |
| optional_device_extensions_.end(); |
| } |
| |
| } // namespace impeller |