第一次执行的查询是vkGet PhysicalDeviceProperties(),该函数会填充描述物理设备所有属性的结构体。其原型如下。
voidvkGetPhysicalDeviceProperties (VkPhysicalDevice physicalDevice,VkPhysicalDeviceProperties* pProperties);
当调用vkGetPhysicalDeviceProperties()时,向参数physicalDevice传递vkEnumeratePhysical Devices()返回的句柄之一,向参数pProperties传递一个指向结构体VkPhysicalDeviceProperties实例的指针。VkPhysicalDeviceProperties是个大结构体,包含了大量描述物理设备属性的字段。其定义如下。
typedefstructVkPhysicalDeviceProperties{uint32_t apiVersion;uint32_t driverVersion;uint32_t vendorID;uint32_t deviceID;VkPhysicalDeviceType deviceType;chardeviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE];uint8_t pipelineCacheUUID[VK_UUID_SIZE];VkPhysicalDeviceLimits limits;VkPhysicalDeviceSparseProperties sparseProperties;}VkPhysicalDeviceProperties;
字段apiVersion包含了设备支持的Vulkan的最高版本,字段driverVersion包含了用于控制设备的驱动的版本号。这是硬件生产商特定的,所以对比不同的生产商的驱动版本没有任何意义。字段vendorID与deviceID标识了生产商和设备,并且通常是PCI生产商和设备标识符[4]。
字段deviceName包含了可读字符串来命名设备。字段pipelineCacheUUID用于管线缓存,这会在第6章中讲到。
除了刚刚列出的属性之外,结构体VkPhysicalDeviceProperties内嵌了VkPhysicalDeviceLimits和VkPhysicalDeviceSparseProperties,包含了物理设备的最大和最小限制,以及和稀疏纹理有关的属性。这两个结构体里有大量信息,这些字段会在讨论相关特性时介绍,在此不再详述。
除了核心特性(有些有更高的限制或约束)之外,Vulkan还可能有一些物理设备支持的可选特性。如果设备宣传支持某个特性,它必须激活(非常像扩展)。但是一旦激活,这个特性就变成了API的“一等公民”,就像任何核心特性一样。为了判定物理设备支持哪些特性,调用vkGetPhysicalDeviceFeatures()。其原型如下。
voidvkGetPhysicalDeviceFeatures (VkPhysicalDevice physicalDevice,VkPhysicalDeviceFeatures* pFeatures);
结构体vkPhysicalDeviceFeatures也非常大,并且Vulkan支持的每一个可选特性都有一个布尔类型的字段。字段太多,就不在此详细罗列了,但是本章最后展示的例子会读取特性集并输出其内容。
1.2.3 物理设备内存
在许多情况下,Vulkan设备要么是一个独立于主机处理器之外的一块物理硬件,要么工作方式非常不同,以独有的方式访问内存。Vulkan里的设备内存是指,设备能够访问到并且用作纹理和其他数据的后备存储器的内存。内存可以分为几类,每一类都有一套属性,例如缓存标志位以及主机和设备之间的一致性行为。每种类型的内存都由设备的某个堆(可能会有多个堆)进行支持。
为了查询堆配置以及设备支持的内存类型,需要调用以下代码。
voidvkGetPhysicalDeviceMemoryProperties (VkPhysicalDevice physicalDevice,VkPhysicalDeviceMemoryProperties* pMemoryProperties);
查询到的内存组织信息会存储进结构体 VkPhysicalDeviceMemoryProperties中,地址通过pMemoryProperties传入。结构体VkPhysicalDeviceMemoryProperties包含了关于设备的堆以及其支持的内存类型的属性。该结构体的定义如下。
typedefstructVkPhysicalDeviceMemoryProperties{uint32_t memoryTypeCount;VkMemoryType memoryTypes[VK_MAX_MEMORY_TYPES];uint32_t memoryHeapCount;VkMemoryHeap memoryHeaps[VK_MAX_MEMORY_HEAPS];}VkPhysicalDeviceMemoryProperties;
内存类型数量包含在字段memoryTypeCount里。可能报告的内存类型的最大数量是VK_MAX_MEMORY_TYPES定义的值,这个宏定义为32。数组memoryTypes包含memoryTypeCount个结构体VkMemoryType对象,每个对象都描述了一种内存类型。VkMemoryType的定义如下。
typedefstructVkMemoryType{VkMemoryPropertyFlags propertyFlags;uint32_t heapIndex;}VkMemoryType;
这是个简单的结构体,只包含了一套标志位以及内存类型的堆栈索引。字段flags描述了内存的类型,并由VkMemoryPropertyFlagBits类型的标志位组合而成。标志位的含义如下。
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT意味着内存对于设备来说是本地的(也就是说,物理上是和设备连接的)。如果没有设置这个标志位,可以认为该内存对于主机来说是本地的。VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT意味着以这种方式分配的内存可以被主机映射以及读写。如果没有设置这个标志位,那么内存不能被主机直接访问,只能由设备使用。VK_MEMORY_PROPERTY_HOST_COHERENT_BIT意味着当这种内存同时被主机和设备访问时,这两个客户之间的访问保持一致。如果没有设置这个标志位,设备或者主机不能看到对方执行的写操作,直到显式地刷新缓存。VK_MEMORY_PROPERTY_HOST_CACHED_BIT意味着这种内存里的数据在主机里面进行缓存。对这种内存的读取操作比不设置这个标志位通常要快。然而,设备的访问延迟稍微高一些,尤其当内存也保持一致时。VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT意味着这种内存分配类型不一定立即使用关联的堆的空间,驱动可能延迟分配物理内存,直到内存对象用来支持某个资源。
每种内存类型都指定了从哪个堆上使用空间,这由结构体VkMemoryType里的字段heapIndex来标识。这个字段是数组memoryHeaps (在调用vkGetPhysicalDeviceMemoryProperties()返回的结构体VkPhysicalDeviceMemoryProperties里面)的索引。数组memoryHeaps里面的每一个元素描述了设备的一个内存堆。结构体的定义如下。
typedefstructVkMemoryHeap{VkDeviceSize size;VkMemoryHeapFlags flags;}VkMemoryHeap;
同样,这也是个简单的结构体,包含了堆的大小(单位是字节)以及描述这个堆的标识符。在Vulkan 1.0里,唯一定义的标识符是VK_MEMORY_HEAP_DEVICE_LOCAL_BIT。如果定义了这个标识符,堆对于设备来说就是本地的。这对应于以类似方式命名的用于描述内存类型的标识符。
1.2.4 设备队列
Vulkan设备执行提交给队列的工作。每个设备都有一个或者多个队列,每个队列都从属于设备的某个队列族。一个队列族是一组拥有相同功能同时又能并行运行的队列。队列族的数量、每个族的功能以及每个族拥有的队列数量都是物理设备的属性。为了查询设备的队列族,调用vkGetPhysicalDeviceQueueFamilyProperties(),其原型如下。
voidvkGetPhysicalDeviceQueueFamilyProperties (VkPhysicalDevice physicalDevice,uint32_t* pQueueFamilyPropertyCount,VkQueueFamilyProperties* pQueueFamilyProperties);
vkGetPhysicalDeviceQueueFamilyProperties()的运行方式在一定程度上和vkEnumeratePhysical Devices()类似,需要调用前者两次。第一次,将nullptr传递给pQueueFamilyProperties,并给pQueueFamilyPropertyCount传递一个指针,指向表示设备支持的队列族数量的变量。可以使用该值调整VkQueueFamilyProperties类型的数组的大小。接下来,在第二次调用中,将该数组传入pQueueFamilyProperties,Vulkan将会用队列的属性填充该数组。VkQueueFamilyProperties的定义如下。