| // 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 |