blob: f51fe1e39c896042d714917a8ced461765ef65e7 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 "src/ui/lib/escher/vk/vulkan_device_queues.h"
#include <set>
#include "src/lib/fxl/logging.h"
#include "src/ui/lib/escher/impl/vulkan_utils.h"
namespace escher {
VulkanDeviceQueues::Caps::Caps(vk::PhysicalDevice device) {
{
vk::PhysicalDeviceProperties props = device.getProperties();
max_image_width = props.limits.maxImageDimension2D;
max_image_height = props.limits.maxImageDimension2D;
device_api_version = props.apiVersion;
}
{
auto formats = impl::GetSupportedDepthFormats(device, {
vk::Format::eD16Unorm,
vk::Format::eX8D24UnormPack32,
vk::Format::eD32Sfloat,
vk::Format::eS8Uint,
vk::Format::eD16UnormS8Uint,
vk::Format::eD24UnormS8Uint,
vk::Format::eD32SfloatS8Uint,
});
depth_stencil_formats.insert(formats.begin(), formats.end());
}
}
vk::Format VulkanDeviceQueues::Caps::GetMatchingDepthStencilFormat(
std::vector<vk::Format> formats) const {
for (auto& fmt : formats) {
if (depth_stencil_formats.find(fmt) != depth_stencil_formats.end()) {
return fmt;
}
}
FXL_CHECK(false) << "no matching depth format found.";
return vk::Format::eUndefined;
}
namespace {
// Helper for PopulateProcAddrs().
template <typename FuncT>
static FuncT GetDeviceProcAddr(vk::Device device, const char* func_name) {
FuncT func = reinterpret_cast<FuncT>(device.getProcAddr(func_name));
FXL_CHECK(func) << "failed to find function address for: " << func_name;
return func;
}
// Helper for VulkanDeviceQueues constructor.
VulkanDeviceQueues::ProcAddrs PopulateProcAddrs(vk::Device device,
const VulkanDeviceQueues::Params& params) {
#define GET_DEVICE_PROC_ADDR(XXX) result.XXX = GetDeviceProcAddr<PFN_vk##XXX>(device, "vk" #XXX)
VulkanDeviceQueues::ProcAddrs result;
if (params.required_extension_names.find(VK_KHR_SWAPCHAIN_EXTENSION_NAME) !=
params.required_extension_names.end() ||
params.desired_extension_names.find(VK_KHR_SWAPCHAIN_EXTENSION_NAME) !=
params.desired_extension_names.end()) {
GET_DEVICE_PROC_ADDR(CreateSwapchainKHR);
GET_DEVICE_PROC_ADDR(DestroySwapchainKHR);
GET_DEVICE_PROC_ADDR(GetSwapchainImagesKHR);
GET_DEVICE_PROC_ADDR(AcquireNextImageKHR);
GET_DEVICE_PROC_ADDR(QueuePresentKHR);
}
return result;
#undef GET_DEVICE_PROC_ADDR
}
// Return value for FindSuitablePhysicalDeviceAndQueueFamilies(). Valid if and
// only if device is non-null. Otherwise, no suitable device was found.
struct SuitablePhysicalDeviceAndQueueFamilies {
vk::PhysicalDevice physical_device;
uint32_t main_queue_family;
uint32_t transfer_queue_family;
};
SuitablePhysicalDeviceAndQueueFamilies FindSuitablePhysicalDeviceAndQueueFamilies(
const VulkanInstancePtr& instance, const VulkanDeviceQueues::Params& params) {
auto physical_devices =
ESCHER_CHECKED_VK_RESULT(instance->vk_instance().enumeratePhysicalDevices());
// A suitable main queue needs to support graphics and compute.
const auto kMainQueueFlags = vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eCompute;
// A specialized transfer queue will only support transfer; see comment below,
// where these flags are used.
const auto kTransferQueueFlags =
vk::QueueFlagBits::eTransfer | vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eCompute;
for (auto& physical_device : physical_devices) {
// Look for a physical device that has all required extensions.
if (!VulkanDeviceQueues::ValidateExtensions(physical_device, params.required_extension_names,
instance->params().layer_names)) {
continue;
}
// Find the main queue family. If none is found, continue on to the next
// physical device.
auto queues = physical_device.getQueueFamilyProperties();
const bool filter_queues_for_present =
params.surface &&
!(params.flags & VulkanDeviceQueues::Params::kDisableQueueFilteringForPresent);
for (size_t i = 0; i < queues.size(); ++i) {
if (kMainQueueFlags == (queues[i].queueFlags & kMainQueueFlags)) {
if (filter_queues_for_present) {
// TODO: it is possible that there is no queue family that supports
// both graphics/compute and present. In this case, we would need a
// separate present queue. For now, just look for a single queue that
// meets all of our needs.
auto get_surface_support_result =
physical_device.getSurfaceSupportKHR(i, params.surface, instance->proc_addrs());
FXL_CHECK(get_surface_support_result.result == vk::Result::eSuccess);
VkBool32 supports_present = get_surface_support_result.value;
if (supports_present != VK_TRUE) {
FXL_LOG(INFO) << "Queue supports graphics/compute, but not presentation";
continue;
}
}
// At this point, we have already succeeded. Now, try to find the
// optimal transfer queue family.
SuitablePhysicalDeviceAndQueueFamilies result;
result.physical_device = physical_device;
result.main_queue_family = i;
result.transfer_queue_family = i;
for (size_t j = 0; j < queues.size(); ++j) {
if ((queues[i].queueFlags & kTransferQueueFlags) == vk::QueueFlagBits::eTransfer) {
// We have found a transfer-only queue. This is the fastest way to
// upload data to the GPU.
result.transfer_queue_family = j;
break;
}
}
return result;
}
}
}
return {vk::PhysicalDevice(), 0, 0};
}
// Helper for ValidateExtensions().
bool ValidateExtension(vk::PhysicalDevice device, const std::string name,
const std::vector<vk::ExtensionProperties>& base_extensions,
const std::set<std::string>& required_layer_names) {
auto found = std::find_if(base_extensions.begin(), base_extensions.end(),
[&name](const vk::ExtensionProperties& extension) {
return !strncmp(extension.extensionName, name.c_str(),
VK_MAX_EXTENSION_NAME_SIZE);
});
if (found != base_extensions.end())
return true;
// Didn't find the extension in the base list of extensions. Perhaps it is
// implemented in a layer.
for (auto& layer_name : required_layer_names) {
auto layer_extensions =
ESCHER_CHECKED_VK_RESULT(device.enumerateDeviceExtensionProperties(layer_name));
FXL_LOG(INFO) << "Looking for Vulkan device extension: " << name << " in layer: " << layer_name;
auto found = std::find_if(layer_extensions.begin(), layer_extensions.end(),
[&name](vk::ExtensionProperties& extension) {
return !strncmp(extension.extensionName, name.c_str(),
VK_MAX_EXTENSION_NAME_SIZE);
});
if (found != layer_extensions.end())
return true;
}
return false;
}
} // namespace
fxl::RefPtr<VulkanDeviceQueues> VulkanDeviceQueues::New(VulkanInstancePtr instance, Params params) {
// Escher requires the memory_requirements_2 extension for the
// vma_gpu_allocator to function.
params.required_extension_names.insert(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME);
// If the params contain a surface, then ensure that the swapchain extension
// is supported so that we can render to that surface.
if (params.surface) {
params.required_extension_names.insert(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}
#if defined(OS_FUCHSIA)
// If we're running on Fuchsia, make sure we have our semaphore extensions.
params.required_extension_names.insert(VK_FUCHSIA_EXTERNAL_SEMAPHORE_EXTENSION_NAME);
params.required_extension_names.insert(VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME);
#endif
vk::PhysicalDevice physical_device;
uint32_t main_queue_family;
uint32_t transfer_queue_family;
{
SuitablePhysicalDeviceAndQueueFamilies result =
FindSuitablePhysicalDeviceAndQueueFamilies(instance, params);
FXL_CHECK(result.physical_device) << "Unable to find a suitable physical device.";
physical_device = result.physical_device;
main_queue_family = result.main_queue_family;
transfer_queue_family = result.transfer_queue_family;
}
// Partially populate device capabilities from the physical device.
// Other stuff (e.g. which extensions are supported) will be added below.
VulkanDeviceQueues::Caps caps(physical_device);
auto physical_device_memory_features = vk::PhysicalDeviceProtectedMemoryFeatures();
auto physical_device_features =
vk::PhysicalDeviceFeatures2().setPNext(&physical_device_memory_features);
physical_device.getFeatures2(&physical_device_features);
// Get the maximum supported Vulkan API version (minimum of device version and instance version).
uint32_t max_api_version = std::min(caps.device_api_version, instance->api_version());
// Protected memory is only supported with Vulkan API version 1.1.
if (!physical_device_memory_features.protectedMemory || max_api_version < VK_API_VERSION_1_1) {
FXL_LOG(INFO) << "Protected memory is not supported.";
caps.allow_protected_memory = false;
} else {
caps.allow_protected_memory = params.flags & VulkanDeviceQueues::Params::kAllowProtectedMemory;
}
// Prepare to create the Device and Queues.
vk::DeviceQueueCreateInfo queue_info[2];
const float kQueuePriority = 0;
queue_info[0] = vk::DeviceQueueCreateInfo();
queue_info[0].queueFamilyIndex = main_queue_family;
queue_info[0].flags = caps.allow_protected_memory ? vk::DeviceQueueCreateFlagBits::eProtected
: vk::DeviceQueueCreateFlags();
queue_info[0].queueCount = 1;
queue_info[0].pQueuePriorities = &kQueuePriority;
queue_info[1] = vk::DeviceQueueCreateInfo();
queue_info[1].queueFamilyIndex = transfer_queue_family;
queue_info[1].queueCount = 1;
queue_info[1].pQueuePriorities = &kQueuePriority;
// Prepare the list of extension names that will be used to create the device.
{
// These extensions were already validated by
// FindSuitablePhysicalDeviceAndQueueFamilies();
caps.extensions = params.required_extension_names;
// Request as many of the desired (but optional) extensions as possible.
auto extensions =
ESCHER_CHECKED_VK_RESULT(physical_device.enumerateDeviceExtensionProperties());
std::set<std::string> available_desired_extensions;
for (auto& name : params.desired_extension_names) {
if (ValidateExtension(physical_device, name, extensions, instance->params().layer_names)) {
caps.extensions.insert(name);
}
}
}
std::vector<const char*> extension_names;
for (auto& extension : caps.extensions) {
extension_names.push_back(extension.c_str());
}
// Specify the required physical device features, and verify that they are all
// supported.
// TODO(ES-111): instead of hard-coding the required features here, provide a
// mechanism for Escher clients to specify additional required features.
vk::PhysicalDeviceFeatures supported_device_features;
physical_device.getFeatures(&supported_device_features);
bool device_has_all_required_features = true;
#define ADD_DESIRED_FEATURE(X) \
if (supported_device_features.X) { \
caps.enabled_features.X = true; \
} else { \
FXL_LOG(INFO) << "Desired Vulkan Device feature not supported: " << #X; \
}
#define ADD_REQUIRED_FEATURE(X) \
caps.enabled_features.X = true; \
if (!supported_device_features.X) { \
FXL_LOG(ERROR) << "Required Vulkan Device feature not supported: " << #X; \
device_has_all_required_features = false; \
}
// TODO(MA-478): We would like to make 'shaderClipDistance' a requirement on
// all Scenic platforms. For now, treat it as a DESIRED_FEATURE.
ADD_DESIRED_FEATURE(shaderClipDistance);
ADD_DESIRED_FEATURE(fillModeNonSolid);
#undef ADD_DESIRED_FEATURE
#undef ADD_REQUIRED_FEATURE
if (!device_has_all_required_features) {
return fxl::RefPtr<VulkanDeviceQueues>();
}
// Almost ready to create the device; start populating the VkDeviceCreateInfo.
vk::DeviceCreateInfo device_info;
device_info.queueCreateInfoCount = 2;
device_info.pQueueCreateInfos = queue_info;
device_info.enabledExtensionCount = extension_names.size();
device_info.ppEnabledExtensionNames = extension_names.data();
device_info.pEnabledFeatures = &caps.enabled_features;
if (caps.allow_protected_memory) {
device_info.pNext = &physical_device_memory_features;
}
// It's possible that the main queue and transfer queue are in the same
// queue family. Adjust the device-creation parameters to account for this.
uint32_t main_queue_index = 0;
uint32_t transfer_queue_index = 0;
if (main_queue_family == transfer_queue_family) {
#if 0
// TODO: it may be worthwhile to create multiple queues in the same family.
// However, we would need to look at VkQueueFamilyProperties.queueCount to
// make sure that we can create multiple queues for that family. For now,
// it is easier to share a single queue when the main/transfer queues are in
// the same family.
queue_info[0].queueCount = 2;
device_info.queueCreateInfoCount = 1;
transfer_queue_index = 1;
#else
device_info.queueCreateInfoCount = 1;
#endif
}
// Create the device.
auto result = physical_device.createDevice(device_info);
if (result.result != vk::Result::eSuccess) {
FXL_LOG(WARNING) << "Could not create Vulkan Device: " << vk::to_string(result.result) << ".";
return fxl::RefPtr<VulkanDeviceQueues>();
}
vk::Device device = result.value;
// Obtain the queues that we requested to be created with the device.
vk::Queue main_queue;
if (caps.allow_protected_memory) {
auto main_queue_info2 = vk::DeviceQueueInfo2()
.setFlags(vk::DeviceQueueCreateFlagBits::eProtected)
.setQueueFamilyIndex(main_queue_family)
.setQueueIndex(main_queue_index);
main_queue = device.getQueue2(main_queue_info2);
} else {
main_queue = device.getQueue(main_queue_family, main_queue_index);
}
vk::Queue transfer_queue;
if (main_queue_family == transfer_queue_family && main_queue_index == transfer_queue_index) {
transfer_queue = main_queue;
} else {
transfer_queue = device.getQueue(transfer_queue_family, transfer_queue_index);
}
return fxl::AdoptRef(new VulkanDeviceQueues(
device, physical_device, main_queue, main_queue_family, transfer_queue, transfer_queue_family,
std::move(instance), std::move(params), std::move(caps)));
}
VulkanDeviceQueues::VulkanDeviceQueues(vk::Device device, vk::PhysicalDevice physical_device,
vk::Queue main_queue, uint32_t main_queue_family,
vk::Queue transfer_queue, uint32_t transfer_queue_family,
VulkanInstancePtr instance, Params params, Caps caps)
: device_(device),
physical_device_(physical_device),
dispatch_loader_(instance->vk_instance(), device_),
main_queue_(main_queue),
main_queue_family_(main_queue_family),
transfer_queue_(transfer_queue),
transfer_queue_family_(transfer_queue_family),
instance_(std::move(instance)),
params_(std::move(params)),
caps_(std::move(caps)),
proc_addrs_(PopulateProcAddrs(device_, params_)) {}
VulkanDeviceQueues::~VulkanDeviceQueues() { device_.destroy(); }
bool VulkanDeviceQueues::ValidateExtensions(vk::PhysicalDevice device,
const std::set<std::string>& required_extension_names,
const std::set<std::string>& required_layer_names) {
auto extensions = ESCHER_CHECKED_VK_RESULT(device.enumerateDeviceExtensionProperties());
for (auto& name : required_extension_names) {
if (!ValidateExtension(device, name, extensions, required_layer_names)) {
FXL_LOG(WARNING) << "Vulkan has no device extension named: " << name;
return false;
}
}
return true;
}
VulkanContext VulkanDeviceQueues::GetVulkanContext() const {
return escher::VulkanContext(instance_->vk_instance(), vk_physical_device(), vk_device(),
dispatch_loader(), vk_main_queue(), vk_main_queue_family(),
vk_transfer_queue(), vk_transfer_queue_family());
}
} // namespace escher