| // 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/context_vk.h" | |
| #include "fml/concurrent_message_loop.h" | |
| #include "impeller/renderer/backend/vulkan/command_queue_vk.h" | |
| #include "impeller/renderer/backend/vulkan/render_pass_builder_vk.h" | |
| #include "impeller/renderer/render_target.h" | |
| #ifdef FML_OS_ANDROID | |
| #include <pthread.h> | |
| #include <sys/resource.h> | |
| #include <sys/time.h> | |
| #endif // FML_OS_ANDROID | |
| #include <map> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <vector> | |
| #include "flutter/fml/cpu_affinity.h" | |
| #include "flutter/fml/trace_event.h" | |
| #include "impeller/base/validation.h" | |
| #include "impeller/renderer/backend/vulkan/allocator_vk.h" | |
| #include "impeller/renderer/backend/vulkan/capabilities_vk.h" | |
| #include "impeller/renderer/backend/vulkan/command_buffer_vk.h" | |
| #include "impeller/renderer/backend/vulkan/command_encoder_vk.h" | |
| #include "impeller/renderer/backend/vulkan/command_pool_vk.h" | |
| #include "impeller/renderer/backend/vulkan/command_queue_vk.h" | |
| #include "impeller/renderer/backend/vulkan/debug_report_vk.h" | |
| #include "impeller/renderer/backend/vulkan/fence_waiter_vk.h" | |
| #include "impeller/renderer/backend/vulkan/gpu_tracer_vk.h" | |
| #include "impeller/renderer/backend/vulkan/resource_manager_vk.h" | |
| #include "impeller/renderer/backend/vulkan/surface_context_vk.h" | |
| #include "impeller/renderer/backend/vulkan/yuv_conversion_library_vk.h" | |
| #include "impeller/renderer/capabilities.h" | |
| VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE | |
| namespace impeller { | |
| // TODO(csg): Fix this after caps are reworked. | |
| static bool gHasValidationLayers = false; | |
| bool HasValidationLayers() { | |
| return gHasValidationLayers; | |
| } | |
| static std::optional<vk::PhysicalDevice> PickPhysicalDevice( | |
| const CapabilitiesVK& caps, | |
| const vk::Instance& instance) { | |
| for (const auto& device : instance.enumeratePhysicalDevices().value) { | |
| if (caps.GetEnabledDeviceFeatures(device).has_value()) { | |
| return device; | |
| } | |
| } | |
| return std::nullopt; | |
| } | |
| static std::vector<vk::DeviceQueueCreateInfo> GetQueueCreateInfos( | |
| std::initializer_list<QueueIndexVK> queues) { | |
| std::map<size_t /* family */, size_t /* index */> family_index_map; | |
| for (const auto& queue : queues) { | |
| family_index_map[queue.family] = 0; | |
| } | |
| for (const auto& queue : queues) { | |
| auto value = family_index_map[queue.family]; | |
| family_index_map[queue.family] = std::max(value, queue.index); | |
| } | |
| static float kQueuePriority = 1.0f; | |
| std::vector<vk::DeviceQueueCreateInfo> infos; | |
| for (const auto& item : family_index_map) { | |
| vk::DeviceQueueCreateInfo info; | |
| info.setQueueFamilyIndex(item.first); | |
| info.setQueueCount(item.second + 1); | |
| info.setQueuePriorities(kQueuePriority); | |
| infos.push_back(info); | |
| } | |
| return infos; | |
| } | |
| static std::optional<QueueIndexVK> PickQueue(const vk::PhysicalDevice& device, | |
| vk::QueueFlagBits flags) { | |
| // This can be modified to ensure that dedicated queues are returned for each | |
| // queue type depending on support. | |
| const auto families = device.getQueueFamilyProperties(); | |
| for (size_t i = 0u; i < families.size(); i++) { | |
| if (!(families[i].queueFlags & flags)) { | |
| continue; | |
| } | |
| return QueueIndexVK{.family = i, .index = 0}; | |
| } | |
| return std::nullopt; | |
| } | |
| std::shared_ptr<ContextVK> ContextVK::Create(Settings settings) { | |
| auto context = std::shared_ptr<ContextVK>(new ContextVK()); | |
| context->Setup(std::move(settings)); | |
| if (!context->IsValid()) { | |
| return nullptr; | |
| } | |
| return context; | |
| } | |
| // static | |
| size_t ContextVK::ChooseThreadCountForWorkers(size_t hardware_concurrency) { | |
| // Never create more than 4 worker threads. Attempt to use up to | |
| // half of the available concurrency. | |
| return std::clamp(hardware_concurrency / 2ull, /*lo=*/1ull, /*hi=*/4ull); | |
| } | |
| namespace { | |
| thread_local uint64_t tls_context_count = 0; | |
| uint64_t CalculateHash(void* ptr) { | |
| // You could make a context once per nanosecond for 584 years on one thread | |
| // before this overflows. | |
| return ++tls_context_count; | |
| } | |
| } // namespace | |
| ContextVK::ContextVK() : hash_(CalculateHash(this)) {} | |
| ContextVK::~ContextVK() { | |
| if (device_holder_ && device_holder_->device) { | |
| [[maybe_unused]] auto result = device_holder_->device->waitIdle(); | |
| } | |
| CommandPoolRecyclerVK::DestroyThreadLocalPools(this); | |
| } | |
| Context::BackendType ContextVK::GetBackendType() const { | |
| return Context::BackendType::kVulkan; | |
| } | |
| void ContextVK::Setup(Settings settings) { | |
| TRACE_EVENT0("impeller", "ContextVK::Setup"); | |
| if (!settings.proc_address_callback) { | |
| return; | |
| } | |
| raster_message_loop_ = fml::ConcurrentMessageLoop::Create( | |
| ChooseThreadCountForWorkers(std::thread::hardware_concurrency())); | |
| raster_message_loop_->PostTaskToAllWorkers([]() { | |
| // Currently we only use the worker task pool for small parts of a frame | |
| // workload, if this changes this setting may need to be adjusted. | |
| fml::RequestAffinity(fml::CpuAffinity::kNotPerformance); | |
| #ifdef FML_OS_ANDROID | |
| if (::setpriority(PRIO_PROCESS, gettid(), -5) != 0) { | |
| FML_LOG(ERROR) << "Failed to set Workers task runner priority"; | |
| } | |
| #endif // FML_OS_ANDROID | |
| }); | |
| auto& dispatcher = VULKAN_HPP_DEFAULT_DISPATCHER; | |
| dispatcher.init(settings.proc_address_callback); | |
| // Enable Vulkan validation if either: | |
| // 1. The user has explicitly enabled it. | |
| // 2. We are in a combination of debug mode, and running on Android. | |
| // (It's possible 2 is overly conservative and we can simplify this) | |
| auto enable_validation = settings.enable_validation; | |
| #if defined(FML_OS_ANDROID) && !defined(NDEBUG) | |
| enable_validation = true; | |
| #endif | |
| auto caps = std::shared_ptr<CapabilitiesVK>(new CapabilitiesVK( | |
| enable_validation, settings.fatal_missing_validations)); | |
| if (!caps->IsValid()) { | |
| VALIDATION_LOG << "Could not determine device capabilities."; | |
| return; | |
| } | |
| gHasValidationLayers = caps->AreValidationsEnabled(); | |
| 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."; | |
| return; | |
| } | |
| vk::InstanceCreateFlags instance_flags = {}; | |
| if (std::find(enabled_extensions.value().begin(), | |
| enabled_extensions.value().end(), | |
| "VK_KHR_portability_enumeration") != | |
| enabled_extensions.value().end()) { | |
| instance_flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR; | |
| } | |
| std::vector<const char*> enabled_layers_c; | |
| std::vector<const char*> enabled_extensions_c; | |
| for (const auto& layer : enabled_layers.value()) { | |
| enabled_layers_c.push_back(layer.c_str()); | |
| } | |
| for (const auto& ext : enabled_extensions.value()) { | |
| enabled_extensions_c.push_back(ext.c_str()); | |
| } | |
| vk::ApplicationInfo application_info; | |
| application_info.setApplicationVersion(VK_API_VERSION_1_0); | |
| application_info.setApiVersion(VK_API_VERSION_1_1); | |
| application_info.setEngineVersion(VK_API_VERSION_1_0); | |
| application_info.setPEngineName("Impeller"); | |
| application_info.setPApplicationName("Impeller"); | |
| vk::StructureChain<vk::InstanceCreateInfo, vk::ValidationFeaturesEXT> | |
| instance_chain; | |
| if (!caps->AreValidationsEnabled()) { | |
| instance_chain.unlink<vk::ValidationFeaturesEXT>(); | |
| } | |
| std::vector<vk::ValidationFeatureEnableEXT> enabled_validations = { | |
| vk::ValidationFeatureEnableEXT::eSynchronizationValidation, | |
| }; | |
| auto validation = instance_chain.get<vk::ValidationFeaturesEXT>(); | |
| validation.setEnabledValidationFeatures(enabled_validations); | |
| auto instance_info = instance_chain.get<vk::InstanceCreateInfo>(); | |
| instance_info.setPEnabledLayerNames(enabled_layers_c); | |
| instance_info.setPEnabledExtensionNames(enabled_extensions_c); | |
| instance_info.setPApplicationInfo(&application_info); | |
| instance_info.setFlags(instance_flags); | |
| auto device_holder = std::make_shared<DeviceHolderImpl>(); | |
| { | |
| auto instance = vk::createInstanceUnique(instance_info); | |
| if (instance.result != vk::Result::eSuccess) { | |
| VALIDATION_LOG << "Could not create Vulkan instance: " | |
| << vk::to_string(instance.result); | |
| return; | |
| } | |
| device_holder->instance = std::move(instance.value); | |
| } | |
| dispatcher.init(device_holder->instance.get()); | |
| //---------------------------------------------------------------------------- | |
| /// Setup the debug report. | |
| /// | |
| /// Do this as early as possible since we could use the debug report from | |
| /// initialization issues. | |
| /// | |
| auto debug_report = | |
| std::make_unique<DebugReportVK>(*caps, device_holder->instance.get()); | |
| if (!debug_report->IsValid()) { | |
| VALIDATION_LOG << "Could not set up debug report."; | |
| return; | |
| } | |
| //---------------------------------------------------------------------------- | |
| /// Pick the physical device. | |
| /// | |
| { | |
| auto physical_device = | |
| PickPhysicalDevice(*caps, device_holder->instance.get()); | |
| if (!physical_device.has_value()) { | |
| VALIDATION_LOG << "No valid Vulkan device found."; | |
| return; | |
| } | |
| device_holder->physical_device = physical_device.value(); | |
| } | |
| //---------------------------------------------------------------------------- | |
| /// Pick device queues. | |
| /// | |
| auto graphics_queue = | |
| PickQueue(device_holder->physical_device, vk::QueueFlagBits::eGraphics); | |
| auto transfer_queue = | |
| PickQueue(device_holder->physical_device, vk::QueueFlagBits::eTransfer); | |
| auto compute_queue = | |
| PickQueue(device_holder->physical_device, vk::QueueFlagBits::eCompute); | |
| if (!graphics_queue.has_value()) { | |
| VALIDATION_LOG << "Could not pick graphics queue."; | |
| return; | |
| } | |
| if (!transfer_queue.has_value()) { | |
| transfer_queue = graphics_queue.value(); | |
| } | |
| if (!compute_queue.has_value()) { | |
| VALIDATION_LOG << "Could not pick compute queue."; | |
| return; | |
| } | |
| //---------------------------------------------------------------------------- | |
| /// Create the logical device. | |
| /// | |
| auto enabled_device_extensions = | |
| 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. | |
| return; | |
| } | |
| std::vector<const char*> enabled_device_extensions_c; | |
| for (const auto& ext : enabled_device_extensions.value()) { | |
| enabled_device_extensions_c.push_back(ext.c_str()); | |
| } | |
| const auto queue_create_infos = GetQueueCreateInfos( | |
| {graphics_queue.value(), compute_queue.value(), transfer_queue.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; | |
| } | |
| vk::DeviceCreateInfo device_info; | |
| device_info.setPNext(&enabled_features.value().get()); | |
| device_info.setQueueCreateInfos(queue_create_infos); | |
| device_info.setPEnabledExtensionNames(enabled_device_extensions_c); | |
| // Device layers are deprecated and ignored. | |
| { | |
| auto device_result = | |
| device_holder->physical_device.createDeviceUnique(device_info); | |
| if (device_result.result != vk::Result::eSuccess) { | |
| VALIDATION_LOG << "Could not create logical device."; | |
| return; | |
| } | |
| device_holder->device = std::move(device_result.value); | |
| } | |
| if (!caps->SetPhysicalDevice(device_holder->physical_device)) { | |
| VALIDATION_LOG << "Capabilities could not be updated."; | |
| return; | |
| } | |
| //---------------------------------------------------------------------------- | |
| /// Create the allocator. | |
| /// | |
| auto allocator = std::shared_ptr<AllocatorVK>(new AllocatorVK( | |
| weak_from_this(), // | |
| application_info.apiVersion, // | |
| device_holder->physical_device, // | |
| device_holder, // | |
| device_holder->instance.get(), // | |
| *caps // | |
| )); | |
| if (!allocator->IsValid()) { | |
| VALIDATION_LOG << "Could not create memory allocator."; | |
| return; | |
| } | |
| //---------------------------------------------------------------------------- | |
| /// Setup the pipeline library. | |
| /// | |
| auto pipeline_library = std::shared_ptr<PipelineLibraryVK>( | |
| new PipelineLibraryVK(device_holder, // | |
| caps, // | |
| std::move(settings.cache_directory), // | |
| raster_message_loop_->GetTaskRunner() // | |
| )); | |
| if (!pipeline_library->IsValid()) { | |
| VALIDATION_LOG << "Could not create pipeline library."; | |
| return; | |
| } | |
| auto sampler_library = | |
| std::shared_ptr<SamplerLibraryVK>(new SamplerLibraryVK(device_holder)); | |
| auto shader_library = std::shared_ptr<ShaderLibraryVK>( | |
| new ShaderLibraryVK(device_holder, // | |
| settings.shader_libraries_data) // | |
| ); | |
| if (!shader_library->IsValid()) { | |
| VALIDATION_LOG << "Could not create shader library."; | |
| return; | |
| } | |
| //---------------------------------------------------------------------------- | |
| /// Create the fence waiter. | |
| /// | |
| auto fence_waiter = | |
| std::shared_ptr<FenceWaiterVK>(new FenceWaiterVK(device_holder)); | |
| //---------------------------------------------------------------------------- | |
| /// Create the resource manager and command pool recycler. | |
| /// | |
| auto resource_manager = ResourceManagerVK::Create(); | |
| if (!resource_manager) { | |
| VALIDATION_LOG << "Could not create resource manager."; | |
| return; | |
| } | |
| auto command_pool_recycler = | |
| std::make_shared<CommandPoolRecyclerVK>(weak_from_this()); | |
| if (!command_pool_recycler) { | |
| VALIDATION_LOG << "Could not create command pool recycler."; | |
| return; | |
| } | |
| auto descriptor_pool_recycler = | |
| std::make_shared<DescriptorPoolRecyclerVK>(weak_from_this()); | |
| if (!descriptor_pool_recycler) { | |
| VALIDATION_LOG << "Could not create descriptor pool recycler."; | |
| return; | |
| } | |
| //---------------------------------------------------------------------------- | |
| /// Fetch the queues. | |
| /// | |
| QueuesVK queues(device_holder->device.get(), // | |
| graphics_queue.value(), // | |
| compute_queue.value(), // | |
| transfer_queue.value() // | |
| ); | |
| if (!queues.IsValid()) { | |
| VALIDATION_LOG << "Could not fetch device queues."; | |
| return; | |
| } | |
| VkPhysicalDeviceProperties physical_device_properties; | |
| dispatcher.vkGetPhysicalDeviceProperties(device_holder->physical_device, | |
| &physical_device_properties); | |
| //---------------------------------------------------------------------------- | |
| /// All done! | |
| /// | |
| device_holder_ = std::move(device_holder); | |
| driver_info_ = | |
| std::make_unique<DriverInfoVK>(device_holder_->physical_device); | |
| debug_report_ = std::move(debug_report); | |
| allocator_ = std::move(allocator); | |
| shader_library_ = std::move(shader_library); | |
| sampler_library_ = std::move(sampler_library); | |
| pipeline_library_ = std::move(pipeline_library); | |
| yuv_conversion_library_ = std::shared_ptr<YUVConversionLibraryVK>( | |
| new YUVConversionLibraryVK(device_holder_)); | |
| queues_ = std::move(queues); | |
| device_capabilities_ = std::move(caps); | |
| fence_waiter_ = std::move(fence_waiter); | |
| resource_manager_ = std::move(resource_manager); | |
| command_pool_recycler_ = std::move(command_pool_recycler); | |
| descriptor_pool_recycler_ = std::move(descriptor_pool_recycler); | |
| device_name_ = std::string(physical_device_properties.deviceName); | |
| command_queue_vk_ = std::make_shared<CommandQueueVK>(weak_from_this()); | |
| is_valid_ = true; | |
| // Create the GPU Tracer later because it depends on state from | |
| // the ContextVK. | |
| gpu_tracer_ = std::make_shared<GPUTracerVK>(weak_from_this(), | |
| settings.enable_gpu_tracing); | |
| gpu_tracer_->InitializeQueryPool(*this); | |
| //---------------------------------------------------------------------------- | |
| /// Label all the relevant objects. This happens after setup so that the | |
| /// debug messengers have had a chance to be set up. | |
| /// | |
| SetDebugName(GetDevice(), device_holder_->device.get(), "ImpellerDevice"); | |
| } | |
| void ContextVK::SetOffscreenFormat(PixelFormat pixel_format) { | |
| CapabilitiesVK::Cast(*device_capabilities_).SetOffscreenFormat(pixel_format); | |
| } | |
| // |Context| | |
| std::string ContextVK::DescribeGpuModel() const { | |
| return device_name_; | |
| } | |
| bool ContextVK::IsValid() const { | |
| return is_valid_; | |
| } | |
| std::shared_ptr<Allocator> ContextVK::GetResourceAllocator() const { | |
| return allocator_; | |
| } | |
| std::shared_ptr<ShaderLibrary> ContextVK::GetShaderLibrary() const { | |
| return shader_library_; | |
| } | |
| std::shared_ptr<SamplerLibrary> ContextVK::GetSamplerLibrary() const { | |
| return sampler_library_; | |
| } | |
| std::shared_ptr<PipelineLibrary> ContextVK::GetPipelineLibrary() const { | |
| return pipeline_library_; | |
| } | |
| std::shared_ptr<CommandBuffer> ContextVK::CreateCommandBuffer() const { | |
| return std::shared_ptr<CommandBufferVK>( | |
| new CommandBufferVK(shared_from_this(), // | |
| CreateGraphicsCommandEncoderFactory()) // | |
| ); | |
| } | |
| vk::Instance ContextVK::GetInstance() const { | |
| return *device_holder_->instance; | |
| } | |
| const vk::Device& ContextVK::GetDevice() const { | |
| return device_holder_->device.get(); | |
| } | |
| const std::shared_ptr<fml::ConcurrentTaskRunner> | |
| ContextVK::GetConcurrentWorkerTaskRunner() const { | |
| return raster_message_loop_->GetTaskRunner(); | |
| } | |
| void ContextVK::Shutdown() { | |
| // There are multiple objects, for example |CommandPoolVK|, that in their | |
| // destructors make a strong reference to |ContextVK|. Resetting these shared | |
| // pointers ensures that cleanup happens in a correct order. | |
| // | |
| // tl;dr: Without it, we get thread::join failures on shutdown. | |
| fence_waiter_.reset(); | |
| resource_manager_.reset(); | |
| raster_message_loop_->Terminate(); | |
| } | |
| std::shared_ptr<SurfaceContextVK> ContextVK::CreateSurfaceContext() { | |
| return std::make_shared<SurfaceContextVK>(shared_from_this()); | |
| } | |
| const std::shared_ptr<const Capabilities>& ContextVK::GetCapabilities() const { | |
| return device_capabilities_; | |
| } | |
| const std::shared_ptr<QueueVK>& ContextVK::GetGraphicsQueue() const { | |
| return queues_.graphics_queue; | |
| } | |
| vk::PhysicalDevice ContextVK::GetPhysicalDevice() const { | |
| return device_holder_->physical_device; | |
| } | |
| std::shared_ptr<FenceWaiterVK> ContextVK::GetFenceWaiter() const { | |
| return fence_waiter_; | |
| } | |
| std::shared_ptr<ResourceManagerVK> ContextVK::GetResourceManager() const { | |
| return resource_manager_; | |
| } | |
| std::shared_ptr<CommandPoolRecyclerVK> ContextVK::GetCommandPoolRecycler() | |
| const { | |
| return command_pool_recycler_; | |
| } | |
| std::unique_ptr<CommandEncoderFactoryVK> | |
| ContextVK::CreateGraphicsCommandEncoderFactory() const { | |
| return std::make_unique<CommandEncoderFactoryVK>(weak_from_this()); | |
| } | |
| std::shared_ptr<GPUTracerVK> ContextVK::GetGPUTracer() const { | |
| return gpu_tracer_; | |
| } | |
| std::shared_ptr<DescriptorPoolRecyclerVK> ContextVK::GetDescriptorPoolRecycler() | |
| const { | |
| return descriptor_pool_recycler_; | |
| } | |
| std::shared_ptr<CommandQueue> ContextVK::GetCommandQueue() const { | |
| return command_queue_vk_; | |
| } | |
| // Creating a render pass is observed to take an additional 6ms on a Pixel 7 | |
| // device as the driver will lazily bootstrap and compile shaders to do so. | |
| // The render pass does not need to be begun or executed. | |
| void ContextVK::InitializeCommonlyUsedShadersIfNeeded() const { | |
| RenderTargetAllocator rt_allocator(GetResourceAllocator()); | |
| RenderTarget render_target = | |
| rt_allocator.CreateOffscreenMSAA(*this, {1, 1}, 1); | |
| RenderPassBuilderVK builder; | |
| for (const auto& [bind_point, color] : render_target.GetColorAttachments()) { | |
| builder.SetColorAttachment( | |
| bind_point, // | |
| color.texture->GetTextureDescriptor().format, // | |
| color.texture->GetTextureDescriptor().sample_count, // | |
| color.load_action, // | |
| color.store_action // | |
| ); | |
| } | |
| if (auto depth = render_target.GetDepthAttachment(); depth.has_value()) { | |
| builder.SetDepthStencilAttachment( | |
| depth->texture->GetTextureDescriptor().format, // | |
| depth->texture->GetTextureDescriptor().sample_count, // | |
| depth->load_action, // | |
| depth->store_action // | |
| ); | |
| } else if (auto stencil = render_target.GetStencilAttachment(); | |
| stencil.has_value()) { | |
| builder.SetStencilAttachment( | |
| stencil->texture->GetTextureDescriptor().format, // | |
| stencil->texture->GetTextureDescriptor().sample_count, // | |
| stencil->load_action, // | |
| stencil->store_action // | |
| ); | |
| } | |
| auto pass = builder.Build(GetDevice()); | |
| } | |
| const std::shared_ptr<YUVConversionLibraryVK>& | |
| ContextVK::GetYUVConversionLibrary() const { | |
| return yuv_conversion_library_; | |
| } | |
| const std::unique_ptr<DriverInfoVK>& ContextVK::GetDriverInfo() const { | |
| return driver_info_; | |
| } | |
| } // namespace impeller |