创建 VkInstance 后,我们需要查询系统中的显卡设备,选择一个支持我们需要的特性的设备使用。
Vulkan 允许我们选择任意数量的显卡设备,并能够同时使用它们,但在这里,我们只使用第一个满足我们需求的显卡设备。
队列族:
之前提到,Vulkan 的几乎所有操作,从绘制到加载纹理都需要将操作指令提交给一个队列,然后才能执行。
Vulkan 有多种不同类型的队列,它们属于不同的队列族,每个队列族的队列只允许执行特定的一部分指令。
比如,可能存在只允许执行计算相关指令的队列族和只允许执行内存传输相关指令的队列族。
部分代码示例:
//表示满足需求得队列族--2
struct QueueFamilyIndices{
//绘制指令的队列族索引
int graphicsFamily = -1;//-1表示没有找到满足需求的队列族
bool isComplete(){
return graphicsFamily >= 0;
}
};
//存储我们使用的显卡信息,物理设备象,可以在 VkInstance 进行清除操作时,自动清除自己
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
//检测设备支持的队列族,查找出满足我们需求的队列族,这一函数会返回满足需求得队列族的索引
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device){
uint32_t queueFamilyCount = 0;
//获取设备的队列族个数
vkGetPhysicalDeviceQueueFamilyProperties(device,
&queueFamilyCount,nullptr);
/**
VkQueueFamilyProperties包含队列族的很多信息,
比如支持的操作类型,该队列族可以创建的队列个数
*/
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device,
&queueFamilyCount,queueFamilies.data());
QueueFamilyIndices indices;
int i=0;
for(const auto& queueFamily : queueFamilies){
//VK_QUEUE_GRAPHICS_BIT表示支持图形指令
if(queueFamily.queueCount>0 &&
queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT){
indices.graphicsFamily = i;
}
if(indices.isComplete()){
break;
}
i++;
}
return indices;
}
//检查设备是否满足需求
bool isDeviceSuitable(VkPhysicalDevice device){
QueueFamilyIndices indices = findQueueFamilies(device);
return indices.isComplete() ;
//以下代码不使用,仅做了解
//查询基础设备属性,如名称/类型/支持的vulkan版本
VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(device,&deviceProperties);
//特征属性,如纹理压缩/64位浮点/多视口渲染(常用于 VR)
VkPhysicalDeviceFeatures deviceFeatures;
vkGetPhysicalDeviceFeatures(device,&deviceFeatures);
//判断显卡是否支持集合着色器
bool res =
(deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
&& deviceFeatures.geometryShader;
/**
除了直接选择第一个满足需求的设备这种方法,一个更好的方法是给每一个满足需求的设备,
按照特性加权打分,选择分数最高的设备使用.
此外,也可以显示满足需求的设备列表,让用户自己选择使用的设备
*/
return res;
}
//选择一个支持 Vulkan 的图形设备
void pickPhysicalDevice(){
//首先需要请求显卡的数量
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance,&deviceCount,nullptr);
if(deviceCount == 0){
throw std::runtime_error(
"failed to find gpus with vulkan support!");
}
//分配数组来存储 VkPhysicalDevice 对象
std::vector<VkPhysicalDevice> devices(deviceCount);
//获取所有显卡信息
vkEnumeratePhysicalDevices(instance,&deviceCount,devices.data());
for(const auto& device : devices){
//遍历每个物理设备,查看是否满足需求
if(isDeviceSuitable(device)){
physicalDevice = device;
break;
}
}
if(physicalDevice == VK_NULL_HANDLE){
throw std::runtime_error("failed to find a suitable gpu!");
}
}
从Vulkan获取对象列表
在Vulkan中获取对象列表是一种相当常见的操作,并且API具有一致的模式。返回列表的API函数具有count和pointer参数。count参数是一个指向整数的指针,以便API可以设置其值。步骤是:
1.使用指向count参数的整数的有效指针调用该函数,并为指针参数调用NULL。
2.API使用列表中的对象数填充count参数。
3.应用程序分配足够的空间来存储列表。
4.应用程序再次调用该函数,指针参数指向刚刚分配的空间。
您将在Vulkan API中经常看到此模式
查找设备
uint32_t gpu_count = 0;
res = vkEnumeratePhysicalDevices(inst, &gpu_count, NULL);
std::vector<VkPhysicalDevice> gpus(gpu_count);
//返回系统上每个物理设备的句柄列表
res = vkEnumeratePhysicalDevices(inst, &gpu_count, gpus.data());
//assert(!res && gpu_count >= 1);
std::cout <<res<<" "<<gpu_count<<" "<<gpus.size() <<std::endl;