blob: 58f6fa923f96a276e4fb10226acc6a1aec0b7ebc [file] [log] [blame]
// 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()) {
FML_LOG(INFO) << "Dedicated transfer queue not avialable.";
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