vulkan: initial loader and null driver
Change-Id: Id5ebb5f01e61e9b114990f49c64c88fbbb7b730e
(cherry picked from commit 4df205cdfc61e66de774ba50be9ef59a08cf88bb)
diff --git a/vulkan/libvulkan/loader.cpp b/vulkan/libvulkan/loader.cpp
new file mode 100644
index 0000000..a99e90a
--- /dev/null
+++ b/vulkan/libvulkan/loader.cpp
@@ -0,0 +1,532 @@
+// module header
+#include "loader.h"
+// standard C headers
+#include <inttypes.h>
+#include <malloc.h>
+#include <pthread.h>
+#include <string.h>
+// standard C++ headers
+#include <algorithm>
+#include <mutex>
+// platform/library headers
+#include <hardware/hwvulkan.h>
+#include <log/log.h>
+
+using namespace vulkan;
+
+static const uint32_t kMaxPhysicalDevices = 4;
+
+struct VkInstance_T {
+ VkInstance_T(const VkAllocCallbacks* alloc_callbacks)
+ : vtbl(&vtbl_storage), alloc(alloc_callbacks), num_physical_devices(0) {
+ memset(&vtbl_storage, 0, sizeof(vtbl_storage));
+ memset(physical_devices, 0, sizeof(physical_devices));
+ memset(&drv.vtbl, 0, sizeof(drv.vtbl));
+ drv.GetDeviceProcAddr = nullptr;
+ drv.num_physical_devices = 0;
+ }
+
+ InstanceVtbl* vtbl;
+ InstanceVtbl vtbl_storage;
+
+ const VkAllocCallbacks* alloc;
+ uint32_t num_physical_devices;
+ VkPhysicalDevice physical_devices[kMaxPhysicalDevices];
+
+ struct Driver {
+ // Pointers to driver entry points. Used explicitly by the loader; not
+ // set as the dispatch table for any objects.
+ InstanceVtbl vtbl;
+
+ // Pointer to the driver's get_device_proc_addr, must be valid for any
+ // of the driver's physical devices. Not part of the InstanceVtbl since
+ // it's not an Instance/PhysicalDevice function.
+ PFN_vkGetDeviceProcAddr GetDeviceProcAddr;
+
+ // Number of physical devices owned by this driver.
+ uint32_t num_physical_devices;
+ } drv; // may eventually be an array
+};
+
+// -----------------------------------------------------------------------------
+
+namespace {
+
+typedef VkInstance_T Instance;
+
+struct Device {
+ Device(const VkAllocCallbacks* alloc_callbacks) : alloc(alloc_callbacks) {
+ memset(&vtbl_storage, 0, sizeof(vtbl_storage));
+ vtbl_storage.device = this;
+ }
+ DeviceVtbl vtbl_storage;
+ const VkAllocCallbacks* alloc;
+};
+
+// -----------------------------------------------------------------------------
+// Utility Code
+
+inline const InstanceVtbl* GetVtbl(VkPhysicalDevice physicalDevice) {
+ return *reinterpret_cast<InstanceVtbl**>(physicalDevice);
+}
+
+inline const DeviceVtbl* GetVtbl(VkDevice device) {
+ return *reinterpret_cast<DeviceVtbl**>(device);
+}
+
+void* DefaultAlloc(void*, size_t size, size_t alignment, VkSystemAllocType) {
+ return memalign(alignment, size);
+}
+
+void DefaultFree(void*, void* pMem) {
+ free(pMem);
+}
+
+const VkAllocCallbacks kDefaultAllocCallbacks = {
+ .pUserData = nullptr,
+ .pfnAlloc = DefaultAlloc,
+ .pfnFree = DefaultFree,
+};
+
+hwvulkan_device_t* g_hwdevice;
+bool EnsureInitialized() {
+ static std::once_flag once_flag;
+ static const hwvulkan_module_t* module;
+
+ std::call_once(once_flag, []() {
+ int result;
+ result = hw_get_module("vulkan",
+ reinterpret_cast<const hw_module_t**>(&module));
+ if (result != 0) {
+ ALOGE("failed to load vulkan hal: %s (%d)", strerror(-result),
+ result);
+ return;
+ }
+ result = module->common.methods->open(
+ &module->common, HWVULKAN_DEVICE_0,
+ reinterpret_cast<hw_device_t**>(&g_hwdevice));
+ if (result != 0) {
+ ALOGE("failed to open vulkan driver: %s (%d)", strerror(-result),
+ result);
+ module = nullptr;
+ return;
+ }
+ });
+
+ return module != nullptr && g_hwdevice != nullptr;
+}
+
+void DestroyDevice(Device* device) {
+ const VkAllocCallbacks* alloc = device->alloc;
+ device->~Device();
+ alloc->pfnFree(alloc->pUserData, device);
+}
+
+// -----------------------------------------------------------------------------
+// "Bottom" functions. These are called at the end of the instance dispatch
+// chain.
+
+VkResult DestroyInstanceBottom(VkInstance instance) {
+ // These checks allow us to call DestroyInstanceBottom from any error path
+ // in CreateInstanceBottom, before the driver instance is fully initialized.
+ if (instance->drv.vtbl.instance != VK_NULL_HANDLE &&
+ instance->drv.vtbl.DestroyInstance) {
+ instance->drv.vtbl.DestroyInstance(instance->drv.vtbl.instance);
+ }
+ const VkAllocCallbacks* alloc = instance->alloc;
+ instance->~VkInstance_T();
+ alloc->pfnFree(alloc->pUserData, instance);
+ return VK_SUCCESS;
+}
+
+VkResult CreateInstanceBottom(const VkInstanceCreateInfo* create_info,
+ VkInstance* instance_ptr) {
+ Instance* instance = *instance_ptr;
+ VkResult result;
+
+ result =
+ g_hwdevice->CreateInstance(create_info, &instance->drv.vtbl.instance);
+ if (result != VK_SUCCESS) {
+ DestroyInstanceBottom(instance);
+ return result;
+ }
+
+ if (!LoadInstanceVtbl(instance->drv.vtbl.instance,
+ g_hwdevice->GetInstanceProcAddr,
+ instance->drv.vtbl)) {
+ DestroyInstanceBottom(instance);
+ return VK_ERROR_INITIALIZATION_FAILED;
+ }
+
+ // vkGetDeviceProcAddr has a bootstrapping problem. We require that it be
+ // queryable from the Instance, and that the resulting function work for any
+ // VkDevice created from the instance.
+ instance->drv.GetDeviceProcAddr = reinterpret_cast<PFN_vkGetDeviceProcAddr>(
+ g_hwdevice->GetInstanceProcAddr(instance->drv.vtbl.instance,
+ "vkGetDeviceProcAddr"));
+ if (!instance->drv.GetDeviceProcAddr) {
+ ALOGE("missing instance proc: \"%s\"", "vkGetDeviceProcAddr");
+ DestroyInstanceBottom(instance);
+ return VK_ERROR_INITIALIZATION_FAILED;
+ }
+
+ hwvulkan_dispatch_t* dispatch =
+ reinterpret_cast<hwvulkan_dispatch_t*>(instance->drv.vtbl.instance);
+ if (dispatch->magic == HWVULKAN_DISPATCH_MAGIC) {
+ // Skip setting dispatch->vtbl on the driver instance handle, since we
+ // never intentionally call through it; we go through Instance::drv.vtbl
+ // instead.
+ } else {
+ ALOGE("invalid VkInstance dispatch magic: 0x%" PRIxPTR,
+ dispatch->magic);
+ DestroyInstanceBottom(instance);
+ return VK_ERROR_INITIALIZATION_FAILED;
+ }
+
+ uint32_t num_physical_devices = 0;
+ result = instance->drv.vtbl.EnumeratePhysicalDevices(
+ instance->drv.vtbl.instance, &num_physical_devices, nullptr);
+ if (result != VK_SUCCESS) {
+ DestroyInstanceBottom(instance);
+ return VK_ERROR_INITIALIZATION_FAILED;
+ }
+ num_physical_devices = std::min(num_physical_devices, kMaxPhysicalDevices);
+ result = instance->drv.vtbl.EnumeratePhysicalDevices(
+ instance->drv.vtbl.instance, &num_physical_devices,
+ instance->physical_devices);
+ if (result != VK_SUCCESS) {
+ DestroyInstanceBottom(instance);
+ return VK_ERROR_INITIALIZATION_FAILED;
+ }
+ for (uint32_t i = 0; i < num_physical_devices; i++) {
+ dispatch = reinterpret_cast<hwvulkan_dispatch_t*>(
+ instance->physical_devices[i]);
+ if (dispatch->magic != HWVULKAN_DISPATCH_MAGIC) {
+ ALOGE("invalid VkPhysicalDevice dispatch magic: 0x%" PRIxPTR,
+ dispatch->magic);
+ DestroyInstanceBottom(instance);
+ return VK_ERROR_INITIALIZATION_FAILED;
+ }
+ dispatch->vtbl = instance->vtbl;
+ }
+ instance->drv.num_physical_devices = num_physical_devices;
+
+ instance->num_physical_devices = instance->drv.num_physical_devices;
+ return VK_SUCCESS;
+}
+
+VkResult EnumeratePhysicalDevicesBottom(VkInstance instance,
+ uint32_t* pdev_count,
+ VkPhysicalDevice* pdevs) {
+ uint32_t count = instance->num_physical_devices;
+ if (pdevs) {
+ count = std::min(count, *pdev_count);
+ std::copy(instance->physical_devices,
+ instance->physical_devices + count, pdevs);
+ }
+ *pdev_count = count;
+ return VK_SUCCESS;
+}
+
+VkResult GetPhysicalDeviceFeaturesBottom(VkPhysicalDevice pdev,
+ VkPhysicalDeviceFeatures* features) {
+ return GetVtbl(pdev)
+ ->instance->drv.vtbl.GetPhysicalDeviceFeatures(pdev, features);
+}
+
+VkResult GetPhysicalDeviceFormatPropertiesBottom(
+ VkPhysicalDevice pdev,
+ VkFormat format,
+ VkFormatProperties* properties) {
+ return GetVtbl(pdev)->instance->drv.vtbl.GetPhysicalDeviceFormatProperties(
+ pdev, format, properties);
+}
+
+VkResult GetPhysicalDeviceImageFormatPropertiesBottom(
+ VkPhysicalDevice pdev,
+ VkFormat format,
+ VkImageType type,
+ VkImageTiling tiling,
+ VkImageUsageFlags usage,
+ VkImageFormatProperties* properties) {
+ return GetVtbl(pdev)
+ ->instance->drv.vtbl.GetPhysicalDeviceImageFormatProperties(
+ pdev, format, type, tiling, usage, properties);
+}
+
+VkResult GetPhysicalDeviceLimitsBottom(VkPhysicalDevice pdev,
+ VkPhysicalDeviceLimits* limits) {
+ return GetVtbl(pdev)
+ ->instance->drv.vtbl.GetPhysicalDeviceLimits(pdev, limits);
+}
+
+VkResult GetPhysicalDevicePropertiesBottom(
+ VkPhysicalDevice pdev,
+ VkPhysicalDeviceProperties* properties) {
+ return GetVtbl(pdev)
+ ->instance->drv.vtbl.GetPhysicalDeviceProperties(pdev, properties);
+}
+
+VkResult GetPhysicalDeviceQueueCountBottom(VkPhysicalDevice pdev,
+ uint32_t* count) {
+ return GetVtbl(pdev)
+ ->instance->drv.vtbl.GetPhysicalDeviceQueueCount(pdev, count);
+}
+
+VkResult GetPhysicalDeviceQueuePropertiesBottom(
+ VkPhysicalDevice pdev,
+ uint32_t count,
+ VkPhysicalDeviceQueueProperties* properties) {
+ return GetVtbl(pdev)->instance->drv.vtbl.GetPhysicalDeviceQueueProperties(
+ pdev, count, properties);
+}
+
+VkResult GetPhysicalDeviceMemoryPropertiesBottom(
+ VkPhysicalDevice pdev,
+ VkPhysicalDeviceMemoryProperties* properties) {
+ return GetVtbl(pdev)->instance->drv.vtbl.GetPhysicalDeviceMemoryProperties(
+ pdev, properties);
+}
+
+VkResult CreateDeviceBottom(VkPhysicalDevice pdev,
+ const VkDeviceCreateInfo* create_info,
+ VkDevice* out_device) {
+ const Instance& instance = *static_cast<Instance*>(GetVtbl(pdev)->instance);
+ VkResult result;
+
+ void* mem = instance.alloc->pfnAlloc(instance.alloc->pUserData,
+ sizeof(Device), alignof(Device),
+ VK_SYSTEM_ALLOC_TYPE_API_OBJECT);
+ if (!mem)
+ return VK_ERROR_OUT_OF_HOST_MEMORY;
+ Device* device = new (mem) Device(instance.alloc);
+
+ VkDevice drv_device;
+ result = instance.drv.vtbl.CreateDevice(pdev, create_info, &drv_device);
+ if (result != VK_SUCCESS) {
+ DestroyDevice(device);
+ return result;
+ }
+
+ if (!LoadDeviceVtbl(drv_device, instance.drv.GetDeviceProcAddr,
+ device->vtbl_storage)) {
+ if (device->vtbl_storage.DestroyDevice)
+ device->vtbl_storage.DestroyDevice(drv_device);
+ DestroyDevice(device);
+ return VK_ERROR_INITIALIZATION_FAILED;
+ }
+
+ hwvulkan_dispatch_t* dispatch =
+ reinterpret_cast<hwvulkan_dispatch_t*>(drv_device);
+ if (dispatch->magic != HWVULKAN_DISPATCH_MAGIC) {
+ ALOGE("invalid VkDevice dispatch magic: 0x%" PRIxPTR, dispatch->magic);
+ device->vtbl_storage.DestroyDevice(drv_device);
+ DestroyDevice(device);
+ return VK_ERROR_INITIALIZATION_FAILED;
+ }
+ dispatch->vtbl = &device->vtbl_storage;
+
+ // TODO: insert device layer entry points into device->vtbl_storage here?
+
+ *out_device = drv_device;
+ return VK_SUCCESS;
+}
+
+VkResult GetPhysicalDeviceExtensionPropertiesBottom(
+ VkPhysicalDevice pdev,
+ const char* layer_name,
+ uint32_t* properties_count,
+ VkExtensionProperties* properties) {
+ // TODO: what are we supposed to do with layer_name here?
+ return GetVtbl(pdev)
+ ->instance->drv.vtbl.GetPhysicalDeviceExtensionProperties(
+ pdev, layer_name, properties_count, properties);
+}
+
+VkResult GetPhysicalDeviceLayerPropertiesBottom(VkPhysicalDevice pdev,
+ uint32_t* properties_count,
+ VkLayerProperties* properties) {
+ return GetVtbl(pdev)->instance->drv.vtbl.GetPhysicalDeviceLayerProperties(
+ pdev, properties_count, properties);
+}
+
+VkResult GetPhysicalDeviceSparseImageFormatPropertiesBottom(
+ VkPhysicalDevice pdev,
+ VkFormat format,
+ VkImageType type,
+ uint32_t samples,
+ VkImageUsageFlags usage,
+ VkImageTiling tiling,
+ uint32_t* properties_count,
+ VkSparseImageFormatProperties* properties) {
+ return GetVtbl(pdev)
+ ->instance->drv.vtbl.GetPhysicalDeviceSparseImageFormatProperties(
+ pdev, format, type, samples, usage, tiling, properties_count,
+ properties);
+}
+
+PFN_vkVoidFunction GetInstanceProcAddrBottom(VkInstance, const char*);
+
+const InstanceVtbl kBottomInstanceFunctions = {
+ // clang-format off
+ .instance = nullptr,
+ .CreateInstance = CreateInstanceBottom,
+ .DestroyInstance = DestroyInstanceBottom,
+ .GetInstanceProcAddr = GetInstanceProcAddrBottom,
+ .EnumeratePhysicalDevices = EnumeratePhysicalDevicesBottom,
+ .GetPhysicalDeviceFeatures = GetPhysicalDeviceFeaturesBottom,
+ .GetPhysicalDeviceFormatProperties = GetPhysicalDeviceFormatPropertiesBottom,
+ .GetPhysicalDeviceImageFormatProperties = GetPhysicalDeviceImageFormatPropertiesBottom,
+ .GetPhysicalDeviceLimits = GetPhysicalDeviceLimitsBottom,
+ .GetPhysicalDeviceProperties = GetPhysicalDevicePropertiesBottom,
+ .GetPhysicalDeviceQueueCount = GetPhysicalDeviceQueueCountBottom,
+ .GetPhysicalDeviceQueueProperties = GetPhysicalDeviceQueuePropertiesBottom,
+ .GetPhysicalDeviceMemoryProperties = GetPhysicalDeviceMemoryPropertiesBottom,
+ .CreateDevice = CreateDeviceBottom,
+ .GetPhysicalDeviceExtensionProperties = GetPhysicalDeviceExtensionPropertiesBottom,
+ .GetPhysicalDeviceLayerProperties = GetPhysicalDeviceLayerPropertiesBottom,
+ .GetPhysicalDeviceSparseImageFormatProperties = GetPhysicalDeviceSparseImageFormatPropertiesBottom,
+ // clang-format on
+};
+
+PFN_vkVoidFunction GetInstanceProcAddrBottom(VkInstance, const char* name) {
+ // The bottom GetInstanceProcAddr is only called by the innermost layer,
+ // when there is one, when it initializes its own dispatch table.
+ return GetSpecificInstanceProcAddr(&kBottomInstanceFunctions, name);
+}
+
+} // namespace
+
+// -----------------------------------------------------------------------------
+// Global functions. These are called directly from the loader entry points,
+// without going through a dispatch table.
+
+namespace vulkan {
+
+VkResult GetGlobalExtensionProperties(const char* /*layer_name*/,
+ uint32_t* count,
+ VkExtensionProperties* /*properties*/) {
+ if (!count)
+ return VK_ERROR_INVALID_POINTER;
+ if (!EnsureInitialized())
+ return VK_ERROR_UNAVAILABLE;
+
+ // TODO: not yet implemented
+ ALOGW("vkGetGlobalExtensionProperties not implemented");
+
+ *count = 0;
+ return VK_SUCCESS;
+}
+
+VkResult GetGlobalLayerProperties(uint32_t* count,
+ VkLayerProperties* /*properties*/) {
+ if (!count)
+ return VK_ERROR_INVALID_POINTER;
+ if (!EnsureInitialized())
+ return VK_ERROR_UNAVAILABLE;
+
+ // TODO: not yet implemented
+ ALOGW("vkGetGlobalLayerProperties not implemented");
+
+ *count = 0;
+ return VK_SUCCESS;
+}
+
+VkResult CreateInstance(const VkInstanceCreateInfo* create_info,
+ VkInstance* out_instance) {
+ VkResult result;
+
+ if (!EnsureInitialized())
+ return VK_ERROR_UNAVAILABLE;
+
+ VkInstanceCreateInfo local_create_info = *create_info;
+ if (!local_create_info.pAllocCb)
+ local_create_info.pAllocCb = &kDefaultAllocCallbacks;
+ create_info = &local_create_info;
+
+ void* instance_mem = create_info->pAllocCb->pfnAlloc(
+ create_info->pAllocCb->pUserData, sizeof(Instance), alignof(Instance),
+ VK_SYSTEM_ALLOC_TYPE_API_OBJECT);
+ if (!instance_mem)
+ return VK_ERROR_OUT_OF_HOST_MEMORY;
+ Instance* instance = new (instance_mem) Instance(create_info->pAllocCb);
+
+ instance->vtbl_storage = kBottomInstanceFunctions;
+ instance->vtbl_storage.instance = instance;
+
+ // TODO: Insert enabled layers into instance->dispatch_vtbl here.
+
+ // TODO: We'll want to call CreateInstance through the dispatch table
+ // instead of calling the loader's terminator
+ *out_instance = instance;
+ result = CreateInstanceBottom(create_info, out_instance);
+ if (result <= 0) {
+ // For every layer, including the loader top and bottom layers:
+ // - If a call to the next CreateInstance fails, the layer must clean
+ // up anything it has successfully done so far, and propagate the
+ // error upwards.
+ // - If a layer successfully calls the next layer's CreateInstance, and
+ // afterwards must fail for some reason, it must call the next layer's
+ // DestroyInstance before returning.
+ // - The layer must not call the next layer's DestroyInstance if that
+ // layer's CreateInstance wasn't called, or returned failure.
+
+ // On failure, CreateInstanceBottom frees the instance struct, so it's
+ // already gone at this point. Nothing to do.
+ }
+
+ return result;
+}
+
+PFN_vkVoidFunction GetInstanceProcAddr(VkInstance instance, const char* name) {
+ if (!instance)
+ return GetGlobalInstanceProcAddr(name);
+ // For special-case functions we always return the loader entry
+ if (strcmp(name, "vkGetInstanceProcAddr") == 0 ||
+ strcmp(name, "vkGetDeviceProcAddr") == 0) {
+ return GetGlobalInstanceProcAddr(name);
+ }
+ return GetSpecificInstanceProcAddr(instance->vtbl, name);
+}
+
+PFN_vkVoidFunction GetDeviceProcAddr(VkDevice device, const char* name) {
+ if (!device)
+ return GetGlobalDeviceProcAddr(name);
+ // For special-case functions we always return the loader entry
+ if (strcmp(name, "vkGetDeviceQueue") == 0 ||
+ strcmp(name, "vkDestroyDevice") == 0) {
+ return GetGlobalDeviceProcAddr(name);
+ }
+ return GetSpecificDeviceProcAddr(GetVtbl(device), name);
+}
+
+VkResult GetDeviceQueue(VkDevice drv_device,
+ uint32_t family,
+ uint32_t index,
+ VkQueue* out_queue) {
+ VkResult result;
+ VkQueue queue;
+ const DeviceVtbl* vtbl = GetVtbl(drv_device);
+ result = vtbl->GetDeviceQueue(drv_device, family, index, &queue);
+ if (result != VK_SUCCESS)
+ return result;
+ hwvulkan_dispatch_t* dispatch =
+ reinterpret_cast<hwvulkan_dispatch_t*>(queue);
+ if (dispatch->magic != HWVULKAN_DISPATCH_MAGIC && dispatch->vtbl != &vtbl) {
+ ALOGE("invalid VkQueue dispatch magic: 0x%" PRIxPTR, dispatch->magic);
+ return VK_ERROR_INITIALIZATION_FAILED;
+ }
+ dispatch->vtbl = vtbl;
+ *out_queue = queue;
+ return VK_SUCCESS;
+}
+
+VkResult DestroyDevice(VkDevice drv_device) {
+ const DeviceVtbl* vtbl = GetVtbl(drv_device);
+ Device* device = static_cast<Device*>(vtbl->device);
+ vtbl->DestroyDevice(drv_device);
+ DestroyDevice(device);
+ return VK_SUCCESS;
+}
+
+} // namespace vulkan