/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkAutoMalloc.h"
#include "vk/GrVkBackendContext.h"
#include "vk/GrVkExtensions.h"
#include "vk/GrVkInterface.h"
#include "vk/GrVkUtil.h"
////////////////////////////////////////////////////////////////////////////////
// Helper code to set up Vulkan context objects
#ifdef SK_ENABLE_VK_LAYERS
const char* kDebugLayerNames[] = {
// elements of VK_LAYER_LUNARG_standard_validation
"VK_LAYER_GOOGLE_threading",
"VK_LAYER_LUNARG_parameter_validation",
"VK_LAYER_LUNARG_object_tracker",
"VK_LAYER_LUNARG_image",
"VK_LAYER_LUNARG_core_validation",
"VK_LAYER_LUNARG_swapchain",
"VK_LAYER_GOOGLE_unique_objects",
// not included in standard_validation
//"VK_LAYER_LUNARG_api_dump",
//"VK_LAYER_LUNARG_vktrace",
//"VK_LAYER_LUNARG_screenshot",
};
#endif
// the minimum version of Vulkan supported
#ifdef SK_BUILD_FOR_ANDROID
const uint32_t kGrVkMinimumVersion = VK_MAKE_VERSION(1, 0, 3);
#else
const uint32_t kGrVkMinimumVersion = VK_MAKE_VERSION(1, 0, 8);
#endif
#define ACQUIRE_VK_PROC(name, instance, device) \
PFN_vk##name grVk##name = \
reinterpret_cast<PFN_vk##name>(getProc("vk" #name, instance, device)); \
if (grVk##name == nullptr) { \
SkDebugf("Function ptr for vk%s could not be acquired\n", #name); \
return nullptr; \
}
// Create the base Vulkan objects needed by the GrVkGpu object
const GrVkBackendContext* GrVkBackendContext::Create(uint32_t* presentQueueIndexPtr,
CanPresentFn canPresent,
GrVkInterface::GetProc getProc) {
if (!getProc) {
return nullptr;
}
SkASSERT(getProc);
VkPhysicalDevice physDev;
VkDevice device;
VkInstance inst;
VkResult err;
const VkApplicationInfo app_info = {
VK_STRUCTURE_TYPE_APPLICATION_INFO, // sType
nullptr, // pNext
"vktest", // pApplicationName
0, // applicationVersion
"vktest", // pEngineName
0, // engineVerison
kGrVkMinimumVersion, // apiVersion
};
GrVkExtensions extensions(getProc);
extensions.initInstance(kGrVkMinimumVersion);
SkTArray<const char*> instanceLayerNames;
SkTArray<const char*> instanceExtensionNames;
uint32_t extensionFlags = 0;
#ifdef SK_ENABLE_VK_LAYERS
for (size_t i = 0; i < SK_ARRAY_COUNT(kDebugLayerNames); ++i) {
if (extensions.hasInstanceLayer(kDebugLayerNames[i])) {
instanceLayerNames.push_back(kDebugLayerNames[i]);
}
}
if (extensions.hasInstanceExtension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME)) {
instanceExtensionNames.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
extensionFlags |= kEXT_debug_report_GrVkExtensionFlag;
}
#endif
if (extensions.hasInstanceExtension(VK_KHR_SURFACE_EXTENSION_NAME)) {
instanceExtensionNames.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
extensionFlags |= kKHR_surface_GrVkExtensionFlag;
}
if (extensions.hasInstanceExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
instanceExtensionNames.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
extensionFlags |= kKHR_swapchain_GrVkExtensionFlag;
}
#ifdef SK_BUILD_FOR_WIN
if (extensions.hasInstanceExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME)) {
instanceExtensionNames.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
extensionFlags |= kKHR_win32_surface_GrVkExtensionFlag;
}
#elif defined(SK_BUILD_FOR_ANDROID)
if (extensions.hasInstanceExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME)) {
instanceExtensionNames.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
extensionFlags |= kKHR_android_surface_GrVkExtensionFlag;
}
#elif defined(SK_BUILD_FOR_UNIX) && !defined(__Fuchsia__)
if (extensions.hasInstanceExtension(VK_KHR_XCB_SURFACE_EXTENSION_NAME)) {
instanceExtensionNames.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
extensionFlags |= kKHR_xcb_surface_GrVkExtensionFlag;
}
#endif
const VkInstanceCreateInfo instance_create = {
VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, // sType
nullptr, // pNext
0, // flags
&app_info, // pApplicationInfo
(uint32_t) instanceLayerNames.count(), // enabledLayerNameCount
instanceLayerNames.begin(), // ppEnabledLayerNames
(uint32_t) instanceExtensionNames.count(), // enabledExtensionNameCount
instanceExtensionNames.begin(), // ppEnabledExtensionNames
};
ACQUIRE_VK_PROC(CreateInstance, VK_NULL_HANDLE, VK_NULL_HANDLE);
err = grVkCreateInstance(&instance_create, nullptr, &inst);
if (err < 0) {
SkDebugf("vkCreateInstance failed: %d\n", err);
return nullptr;
}
ACQUIRE_VK_PROC(DestroyInstance, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(EnumeratePhysicalDevices, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(GetPhysicalDeviceQueueFamilyProperties, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(GetPhysicalDeviceFeatures, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(CreateDevice, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(GetDeviceQueue, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(DeviceWaitIdle, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(DestroyDevice, inst, VK_NULL_HANDLE);
uint32_t gpuCount;
err = grVkEnumeratePhysicalDevices(inst, &gpuCount, nullptr);
if (err) {
SkDebugf("vkEnumeratePhysicalDevices failed: %d\n", err);
grVkDestroyInstance(inst, nullptr);
return nullptr;
}
if (!gpuCount) {
SkDebugf("vkEnumeratePhysicalDevices returned no supported devices.\n");
grVkDestroyInstance(inst, nullptr);
return nullptr;
}
// Just returning the first physical device instead of getting the whole array.
// TODO: find best match for our needs
gpuCount = 1;
err = grVkEnumeratePhysicalDevices(inst, &gpuCount, &physDev);
if (err) {
SkDebugf("vkEnumeratePhysicalDevices failed: %d\n", err);
grVkDestroyInstance(inst, nullptr);
return nullptr;
}
// query to get the initial queue props size
uint32_t queueCount;
grVkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr);
if (!queueCount) {
SkDebugf("vkGetPhysicalDeviceQueueFamilyProperties returned no queues.\n");
grVkDestroyInstance(inst, nullptr);
return nullptr;
}
SkAutoMalloc queuePropsAlloc(queueCount * sizeof(VkQueueFamilyProperties));
// now get the actual queue props
VkQueueFamilyProperties* queueProps = (VkQueueFamilyProperties*)queuePropsAlloc.get();
grVkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueProps);
// iterate to find the graphics queue
uint32_t graphicsQueueIndex = queueCount;
for (uint32_t i = 0; i < queueCount; i++) {
if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
graphicsQueueIndex = i;
break;
}
}
if (graphicsQueueIndex == queueCount) {
SkDebugf("Could not find any supported graphics queues.\n");
grVkDestroyInstance(inst, nullptr);
return nullptr;
}
// iterate to find the present queue, if needed
uint32_t presentQueueIndex = queueCount;
if (presentQueueIndexPtr && canPresent) {
for (uint32_t i = 0; i < queueCount; i++) {
if (canPresent(inst, physDev, i)) {
presentQueueIndex = i;
break;
}
}
if (presentQueueIndex == queueCount) {
SkDebugf("Could not find any supported present queues.\n");
grVkDestroyInstance(inst, nullptr);
return nullptr;
}
*presentQueueIndexPtr = presentQueueIndex;
} else {
// Just setting this so we end up make a single queue for graphics since there was no
// request for a present queue.
presentQueueIndex = graphicsQueueIndex;
}
extensions.initDevice(kGrVkMinimumVersion, inst, physDev);
SkTArray<const char*> deviceLayerNames;
SkTArray<const char*> deviceExtensionNames;
#ifdef SK_ENABLE_VK_LAYERS
for (size_t i = 0; i < SK_ARRAY_COUNT(kDebugLayerNames); ++i) {
if (extensions.hasDeviceLayer(kDebugLayerNames[i])) {
deviceLayerNames.push_back(kDebugLayerNames[i]);
}
}
#endif
if (extensions.hasDeviceExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
deviceExtensionNames.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
extensionFlags |= kKHR_swapchain_GrVkExtensionFlag;
}
if (extensions.hasDeviceExtension("VK_NV_glsl_shader")) {
deviceExtensionNames.push_back("VK_NV_glsl_shader");
extensionFlags |= kNV_glsl_shader_GrVkExtensionFlag;
}
// query to get the physical device properties
VkPhysicalDeviceFeatures deviceFeatures;
grVkGetPhysicalDeviceFeatures(physDev, &deviceFeatures);
// this looks like it would slow things down,
// and we can't depend on it on all platforms
deviceFeatures.robustBufferAccess = VK_FALSE;
uint32_t featureFlags = 0;
if (deviceFeatures.geometryShader) {
featureFlags |= kGeometryShader_GrVkFeatureFlag;
}
if (deviceFeatures.dualSrcBlend) {
featureFlags |= kDualSrcBlend_GrVkFeatureFlag;
}
if (deviceFeatures.sampleRateShading) {
featureFlags |= kSampleRateShading_GrVkFeatureFlag;
}
float queuePriorities[1] = { 0.0 };
// Here we assume no need for swapchain queue
// If one is needed, the client will need its own setup code
const VkDeviceQueueCreateInfo queueInfo[2] = {
{
VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // sType
nullptr, // pNext
0, // VkDeviceQueueCreateFlags
graphicsQueueIndex, // queueFamilyIndex
1, // queueCount
queuePriorities, // pQueuePriorities
},
{
VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // sType
nullptr, // pNext
0, // VkDeviceQueueCreateFlags
presentQueueIndex, // queueFamilyIndex
1, // queueCount
queuePriorities, // pQueuePriorities
}
};
uint32_t queueInfoCount = (presentQueueIndex != graphicsQueueIndex) ? 2 : 1;
const VkDeviceCreateInfo deviceInfo = {
VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, // sType
nullptr, // pNext
0, // VkDeviceCreateFlags
queueInfoCount, // queueCreateInfoCount
queueInfo, // pQueueCreateInfos
(uint32_t) deviceLayerNames.count(), // layerCount
deviceLayerNames.begin(), // ppEnabledLayerNames
(uint32_t) deviceExtensionNames.count(), // extensionCount
deviceExtensionNames.begin(), // ppEnabledExtensionNames
&deviceFeatures // ppEnabledFeatures
};
err = grVkCreateDevice(physDev, &deviceInfo, nullptr, &device);
if (err) {
SkDebugf("CreateDevice failed: %d\n", err);
grVkDestroyInstance(inst, nullptr);
return nullptr;
}
auto interface =
sk_make_sp<GrVkInterface>(getProc, inst, device, extensionFlags);
if (!interface->validate(extensionFlags)) {
SkDebugf("Vulkan interface validation failed\n");
grVkDeviceWaitIdle(device);
grVkDestroyDevice(device, nullptr);
grVkDestroyInstance(inst, nullptr);
return nullptr;
}
VkQueue queue;
grVkGetDeviceQueue(device, graphicsQueueIndex, 0, &queue);
GrVkBackendContext* ctx = new GrVkBackendContext();
ctx->fInstance = inst;
ctx->fPhysicalDevice = physDev;
ctx->fDevice = device;
ctx->fQueue = queue;
ctx->fGraphicsQueueIndex = graphicsQueueIndex;
ctx->fMinAPIVersion = kGrVkMinimumVersion;
ctx->fExtensions = extensionFlags;
ctx->fFeatures = featureFlags;
ctx->fInterface.reset(interface.release());
ctx->fOwnsInstanceAndDevice = true;
return ctx;
}
GrVkBackendContext::~GrVkBackendContext() {
if (fInterface == nullptr || !fOwnsInstanceAndDevice) {
return;
}
fInterface->fFunctions.fDeviceWaitIdle(fDevice);
fInterface->fFunctions.fDestroyDevice(fDevice, nullptr);
fDevice = VK_NULL_HANDLE;
fInterface->fFunctions.fDestroyInstance(fInstance, nullptr);
fInstance = VK_NULL_HANDLE;
}