逻辑设备与队列
原文链接:https://kylemayes.github.io/vulkanalia/setup/logical_device_and_queues.html
Commit Hash: 7becee96b0029bf721f833039c00ea2a417714dd
本章代码:main.rs
选择了要使用的物理设备之后,我们需要创建一个与之交互的逻辑设备。创建逻辑设备的过程与创建实例的过程相似,即描述我们希望使用的功能。既然我们已经查询了可用的队列族,我们还需要指定要创建哪些队列。如果你有不同的需求,甚至可以从同一个物理设备创建多个逻辑设备。
首先,在 App
中添加一个新的字段来存储逻辑设备:
struct App {
// ...
device: Device,
}
接下来,在 App::create
中调用 create_logical_device
函数,并将得到的逻辑设备添加到 App
的构造器中:
impl App {
unsafe fn create(window: &Window) -> Result<Self> {
// ...
let device = create_logical_device(&entry, &instance, &mut data)?;
Ok(Self { entry, instance, data, device })
}
}
unsafe fn create_logical_device(
entry: &Entry,
instance: &Instance,
data: &mut AppData,
) -> Result<Device> {
}
指定要创建的队列
创建逻辑设备需要再创建一堆结构体来指定一堆细节,首先是 vk::DeviceQueueCreateInfo
。这个结构体描述了我们单个队列族需要的队列数量。现在,我们只对具备图形功能的队列感兴趣。
let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?;
let queue_priorities = &[1.0];
let queue_info = vk::DeviceQueueCreateInfo::builder()
.queue_family_index(indices.graphics)
.queue_priorities(queue_priorities);
当前可用的驱动程序只允许你为每个队列族创建少量队列,实际上你也确实不需要多个队列。因为你可以在多个线程上创建指令缓冲,然后在主线程上一次性提交它们,这样只需要一次低开销的调用。
Vulkan 允许你为队列分配优先级,使用介于 0.0
和 1.0
之间的浮点数来影响指令缓冲执行的调度。即使只创建一个队列,也需要指定优先级。
指定要启用的层
接下来要提供的信息与 vk::InstanceCreateInfo
结构体相似。同样地,我们需要指定要启用的任何校验层或扩展,但这次指定的扩展是设备特定的,而不是全局的。
一个设备特定扩展的例子是 VK_KHR_swapchain
,它允许你将该设备渲染的图像呈现到窗口中。一些 Vulkan 设备,例如仅支持计算操作的设备,可能不具备此功能。我们将在交换链章节中再次提到这个扩展。
Vulkan 以前的实现区分了实例和设备特定的校验层,但现在不再是这样了。这意味着在最新的实现中,我们传递给 enabled_layer_names
的层名将被忽略。不过,为了与旧版本兼容,还是应该设置这些名称。
我们还不会启用任何设备特定的扩展。因此,如果启用了校验,我们将构建一个包含校验层的层名列表。
let layers = if VALIDATION_ENABLED {
vec![VALIDATION_LAYER.as_ptr()]
} else {
vec![]
};
指定要启用的扩展
正如 实例
一章中所讨论的,对于使用不完全符合 Vulkan 规范的 Vulkan 实现的应用程序,必须启用某些 Vulkan 扩展。在本章中,我们启用了与这些不符合规范的实现兼容所需的实例扩展。在这里,我们将启用出于同样目的所需的设备扩展。
let mut extensions = vec![];
// Required by Vulkan SDK on macOS since 1.3.216.
if cfg!(target_os = "macos") && entry.version()? >= PORTABILITY_MACOS_VERSION {
extensions.push(vk::KHR_PORTABILITY_SUBSET_EXTENSION.name.as_ptr());
}
指定使用的设备功能
下一个需要指定的信息是我们将要使用的设备特性。这些特性是我们在上一章中通过 get_physical_device_features
查询到的,比如几何着色器。现在我们不需要任何特殊的东西,所以我们可以简单地定义它,并将所有东西都保留为默认值(false
)。一旦我们要开始使用 Vulkan 做更有趣的事情,我们会再回到这个结构。
let features = vk::PhysicalDeviceFeatures::builder();
创建逻辑设备
有了前面两个结构体、启用的校验层(如果启用)以及设备扩展,我们可以填充最主要的 vk::DeviceCreateInfo
结构体。
let queue_infos = &[queue_info];
let info = vk::DeviceCreateInfo::builder()
.queue_create_infos(queue_infos)
.enabled_layer_names(&layers)
.enabled_extension_names(&extensions)
.enabled_features(&features);
就是这样,我们现在可以调用名为 create_device
的方法来实例化逻辑设备了。
let device = instance.create_device(data.physical_device, &info, None)?;
参数是要与逻辑设备交互的物理设备、我们刚刚指定的队列和使用信息,以及可选的分配回调。与实例创建函数类似,如果启用不存在的扩展或指定了不支持的功能,则此调用可能会返回错误。
设备应在 App::destroy
中被销毁:
unsafe fn destroy(&mut self) {
self.device.destroy_device(None);
// ...
}
逻辑设备不直接与实例交互,因此不作为参数。
检索队列句柄
队列会随着逻辑设备的创建而自动创建,但我们还没有取得与它们交互所用的句柄。首先,在 AppData
中添加一个新的字段来存储图形队列的句柄:
struct AppData {
// ...
graphics_queue: vk::Queue,
}
设备队列会在设备销毁时自动清理,所以我们不需要在 App::destroy
中做任何处理。
我们可以使用 get_device_queue
函数来检索每个队列族的队列句柄。参数是逻辑设备、队列族和队列序号。因为我们只从该族中创建一个队列,所以我们只需使用序号 0
。
data.graphics_queue = device.get_device_queue(indices.graphics, 0);
最后,在 create_logical_device
中返回创建的逻辑设备:
Ok(device)
有了逻辑设备和队列句柄,我们现在就可以真正开始使用显卡来执行任务了!在接下来的几章中,我们将设置资源以将结果呈现给窗口系统。