blob: 2290f06a3263622fe93659b064ab8b95645b1e79 [file] [log] [blame]
/*
* Copyright 2025 Mesa3D authors
* SPDX-License-Identifier: MIT
*/
#include "GfxStreamVulkanMapper.h"
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <vector>
#include "util/detect_os.h"
#include "util/log.h"
#if DETECT_OS_WINDOWS
#include <io.h>
#define VK_LIBNAME "vulkan-1.dll"
#else
#include <unistd.h>
#if DETECT_OS_APPLE
#define VK_LIBNAME "libvulkan.1.dylib"
#elif DETECT_OS_ANDROID
#define VK_LIBNAME "libvulkan.so"
#else
#define VK_LIBNAME "libvulkan.so.1"
#endif
#endif
const char* VK_ICD_FILENAMES = "VK_ICD_FILENAMES";
#define GET_PROC_ADDR_INSTANCE_LOCAL(x) \
PFN_vk##x vk_##x = (PFN_vk##x)vk_GetInstanceProcAddr(nullptr, "vk" #x)
std::unique_ptr<GfxStreamVulkanMapper> sVkMapper;
GfxStreamVulkanMapper::GfxStreamVulkanMapper() {}
GfxStreamVulkanMapper::~GfxStreamVulkanMapper() {}
uint32_t chooseGfxQueueFamily(vk_uncompacted_dispatch_table* vk, VkPhysicalDevice phys_dev) {
uint32_t family_idx = UINT32_MAX;
uint32_t nProps = 0;
vk->GetPhysicalDeviceQueueFamilyProperties(phys_dev, &nProps, NULL);
std::vector<VkQueueFamilyProperties> props(nProps, VkQueueFamilyProperties{});
vk->GetPhysicalDeviceQueueFamilyProperties(phys_dev, &nProps, props.data());
// Choose the first graphics queue.
for (uint32_t i = 0; i < nProps; ++i) {
if ((props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && props[i].queueCount > 0) {
family_idx = i;
break;
}
}
return family_idx;
}
bool GfxStreamVulkanMapper::initialize(DeviceId& deviceId) {
mLoaderLib = util_dl_open(VK_LIBNAME);
if (!mLoaderLib) {
mesa_loge("failed to get loader library");
return false;
}
VkResult res;
auto vk_GetInstanceProcAddr =
(PFN_vkGetInstanceProcAddr)util_dl_get_proc_address(mLoaderLib, "vkGetInstanceProcAddr");
auto vk_GetDeviceProcAddr =
(PFN_vkGetDeviceProcAddr)util_dl_get_proc_address(mLoaderLib, "vkGetDeviceProcAddr");
if (!vk_GetInstanceProcAddr || !vk_GetDeviceProcAddr) {
mesa_loge("failed to get devuce or instance ProcAddr");
return false;
}
std::vector<const char*> externalMemoryInstanceExtNames = {
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
};
std::vector<const char*> externalMemoryDeviceExtNames = {
VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
#if DETECT_OS_WINDOWS
VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME,
#elif DETECT_OS_LINUX
VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME,
#endif
};
VkInstanceCreateInfo instCi = {
VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 0, 0, nullptr, 0, nullptr, 0, nullptr,
};
VkApplicationInfo appInfo = {
VK_STRUCTURE_TYPE_APPLICATION_INFO,
0,
"gfxstream_vk_mapper",
1,
"gfxstream_vk_mapper",
1,
VK_API_VERSION_1_1,
};
instCi.pApplicationInfo = &appInfo;
instCi.enabledExtensionCount = static_cast<uint32_t>(externalMemoryInstanceExtNames.size());
instCi.ppEnabledExtensionNames = externalMemoryInstanceExtNames.data();
GET_PROC_ADDR_INSTANCE_LOCAL(CreateInstance);
res = vk_CreateInstance(&instCi, nullptr, &mInstance);
if (res != VK_SUCCESS) {
mesa_loge("failed to create VkMapper Instance");
return false;
}
vk_instance_uncompacted_dispatch_table_load(&mVk.instance, vk_GetInstanceProcAddr, mInstance);
vk_physical_device_uncompacted_dispatch_table_load(&mVk.physical_device, vk_GetInstanceProcAddr,
mInstance);
uint32_t physicalDeviceCount = 0;
std::vector<VkPhysicalDevice> physicalDevices;
res = mVk.EnumeratePhysicalDevices(mInstance, &physicalDeviceCount, nullptr);
if (res != VK_SUCCESS) {
mesa_loge("failed to get physical device count");
return false;
}
physicalDevices.resize(physicalDeviceCount);
res = mVk.EnumeratePhysicalDevices(mInstance, &physicalDeviceCount, physicalDevices.data());
if (res != VK_SUCCESS) {
mesa_loge("failed to enumerate physical devices");
return false;
}
for (uint32_t i = 0; i < physicalDeviceCount; i++) {
VkPhysicalDeviceIDProperties idProps = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES_KHR,
};
VkPhysicalDeviceProperties2 deviceProps = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR,
.pNext = reinterpret_cast<void*>(&idProps),
};
mVk.GetPhysicalDeviceProperties2(physicalDevices[i], &deviceProps);
if (memcmp(&idProps.deviceUUID[0], &deviceId.deviceUUID[0], VK_UUID_SIZE)) {
continue;
}
if (memcmp(&idProps.driverUUID[0], &deviceId.driverUUID[0], VK_UUID_SIZE)) {
continue;
}
VkPhysicalDevice physDev = physicalDevices[i];
uint32_t gfxQueueFamilyIdx = chooseGfxQueueFamily(&mVk, physDev);
if (gfxQueueFamilyIdx == UINT32_MAX) {
mesa_loge("failed to get gfx queue family idx");
return false;
}
float priority = 1.0f;
VkDeviceQueueCreateInfo dqCi = {
VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, 0, 0, gfxQueueFamilyIdx, 1, &priority,
};
VkDeviceCreateInfo dCi = {};
dCi.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
dCi.queueCreateInfoCount = 1;
dCi.pQueueCreateInfos = &dqCi;
dCi.enabledExtensionCount = static_cast<uint32_t>(externalMemoryDeviceExtNames.size());
dCi.ppEnabledExtensionNames = externalMemoryDeviceExtNames.data();
res = mVk.CreateDevice(physDev, &dCi, nullptr, &mDevice);
if (res != VK_SUCCESS) {
mesa_loge("failed to create device");
return false;
}
}
vk_device_uncompacted_dispatch_table_load(&mVk.device, vk_GetDeviceProcAddr, mDevice);
return true;
}
// The Tesla V-100 driver seems to enter a power management mode and stops being available to
// the Vulkan loader if more than a certain number of VK instances are created in the same
// process.
//
// This behavior is reproducible via:
//
// GfxstreamEnd2EndTests --gtest_filter="*MultiThreadedVkMapMemory*"
//
// Workaround this by having a singleton mapper per-process.
GfxStreamVulkanMapper* GfxStreamVulkanMapper::getInstance(std::optional<DeviceId> deviceIdOpt) {
if (sVkMapper == nullptr && deviceIdOpt) {
// The idea is to make sure the gfxstream ICD isn't loaded when the mapper starts
// up. The Nvidia ICD should be loaded.
//
// This is mostly useful for developers. For AOSP hermetic gfxstream end2end
// testing, VK_ICD_FILENAMES shouldn't be defined. For deqp-vk, this is
// useful, but not safe for multi-threaded tests. For now, since this is only
// used for end2end tests, we should be good.
const char* driver = getenv(VK_ICD_FILENAMES);
unsetenv(VK_ICD_FILENAMES);
sVkMapper = std::make_unique<GfxStreamVulkanMapper>();
if (!sVkMapper->initialize(*deviceIdOpt)) {
sVkMapper = nullptr;
return nullptr;
}
setenv(VK_ICD_FILENAMES, driver, 1);
}
return sVkMapper.get();
}
int32_t GfxStreamVulkanMapper::map(struct VulkanMapperData* mapData) {
VkMemoryAllocateInfo mai = {};
mai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
mai.allocationSize = mapData->size;
mai.memoryTypeIndex = mapData->memoryIdx;
#if DETECT_OS_WINDOWS
VkImportMemoryWin32HandleInfoKHR importInfo{
VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR,
0,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT,
static_cast<HANDLE>(mapData->handle),
L"",
};
#elif DETECT_OS_LINUX
// check for VIRTGPU_KUMQUAT_HANDLE_TYPE_MEM_DMABUF
VkExternalMemoryHandleTypeFlagBits flagBits = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
if (mapData->handleType == 0x1) {
flagBits = (enum VkExternalMemoryHandleTypeFlagBits)(
uint32_t(flagBits) | uint32_t(VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT));
}
VkImportMemoryFdInfoKHR importInfo{
VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR,
0,
flagBits,
static_cast<int>(mapData->handle),
};
#endif
mai.pNext = reinterpret_cast<void*>(&importInfo);
VkResult result = mVk.AllocateMemory(mDevice, &mai, nullptr, &mapData->memory);
if (result != VK_SUCCESS) {
mesa_loge("failed to import memory");
return -EINVAL;
}
result = mVk.MapMemory(mDevice, mapData->memory, 0, mapData->size, 0, (void**)&mapData->ptr);
if (result != VK_SUCCESS) {
mesa_loge("failed to map memory");
return -EINVAL;
}
return 0;
}
void GfxStreamVulkanMapper::unmap(struct VulkanMapperData* mapData) {
mVk.UnmapMemory(mDevice, mapData->memory);
mVk.FreeMemory(mDevice, mapData->memory, nullptr);
}