1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
use std::sync::Arc;
use vulkano::{
VulkanLibrary,
device::{
Device, DeviceCreateInfo, DeviceExtensions, Queue, QueueCreateInfo, QueueFlags,
physical::PhysicalDeviceType,
},
instance::{Instance, InstanceCreateFlags, InstanceCreateInfo},
swapchain::Surface,
};
use winit::{event_loop::ActiveEventLoop, window::Window};
use super::renderer::VulkanRenderer;
#[derive(Default)]
pub struct VulkanRenderContext {
pub queue: Option<Arc<Queue>>,
}
impl VulkanRenderContext {
pub fn renderer_for_window(
&mut self,
event_loop: &ActiveEventLoop,
window: Arc<Window>,
) -> VulkanRenderer {
// lazily set up a shared instance, device, and queue to use for all subsequent renderers
let queue = self
.queue
.get_or_insert_with(|| Self::shared_queue(event_loop, window.clone()));
VulkanRenderer::new(window.clone(), queue.clone())
}
fn shared_queue(event_loop: &ActiveEventLoop, window: Arc<Window>) -> Arc<Queue> {
let library = VulkanLibrary::new().expect("Vulkan libraries not found on system");
// The first step of any Vulkan program is to create an instance.
//
// When we create an instance, we have to pass a list of extensions that we want to enable.
//
// All the window-drawing functionalities are part of non-core extensions that we need to
// enable manually. To do so, we ask `Surface` for the list of extensions required to draw
// to a window.
let required_extensions = Surface::required_extensions(event_loop).unwrap();
// Now creating the instance.
let instance = Instance::new(
library,
InstanceCreateInfo {
// Enable enumerating devices that use non-conformant Vulkan implementations.
// (e.g. MoltenVK)
flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
enabled_extensions: required_extensions,
..Default::default()
},
)
.unwrap_or_else(|_| {
panic!("Could not create instance supporting: {required_extensions:?}")
});
// Choose device extensions that we're going to use. In order to present images to a
// surface, we need a `Swapchain`, which is provided by the `khr_swapchain` extension.
let device_extensions = DeviceExtensions {
khr_swapchain: true,
..DeviceExtensions::empty()
};
// In order to select the proper queue family we need a reference to the window's surface
// so we can check whether the queue supports it. Note that in a future vulkano release
// this requirement will go away once it can check for `presentation_support` from the
// event_loop's display (see commented usage below…)
let surface = Surface::from_window(instance.clone(), window.clone()).unwrap();
// We then choose which physical device to use. First, we enumerate all the available
// physical devices, then apply filters to narrow them down to those that can support our
// needs.
let (physical_device, queue_family_index) = instance
.enumerate_physical_devices()
.unwrap()
.filter(|p| {
// Some devices may not support the extensions or features that your application,
// or report properties and limits that are not sufficient for your application.
// These should be filtered out here.
p.supported_extensions().contains(&device_extensions)
})
.filter_map(|p| {
// For each physical device, we try to find a suitable queue family that will
// execute our draw commands.
//
// Devices can provide multiple queues to run commands in parallel (for example a
// draw queue and a compute queue), similar to CPU threads. This is
// something you have to have to manage manually in Vulkan. Queues
// of the same type belong to the same queue family.
//
// Here, we look for a single queue family that is suitable for our purposes. In a
// real-world application, you may want to use a separate dedicated transfer queue
// to handle data transfers in parallel with graphics operations.
// You may also need a separate queue for compute operations, if
// your application uses those.
p.queue_family_properties()
.iter()
.enumerate()
.position(|(i, q)| {
// We select a queue family that supports graphics operations. When drawing
// to a window surface, as we do in this example, we also need to check
// that queues in this queue family are capable of presenting images to the
// surface.
q.queue_flags.intersects(QueueFlags::GRAPHICS)
&& p.surface_support(i as u32, &surface).unwrap_or(false)
// && p.presentation_support(_i as u32, event_loop).unwrap() // unreleased
})
// The code here searches for the first queue family that is suitable. If none
// is found, `None` is returned to `filter_map`, which
// disqualifies this physical device.
.map(|i| (p, i as u32))
})
// All the physical devices that pass the filters above are suitable for the
// application. However, not every device is equal, some are preferred over others.
// Now, we assign each physical device a score, and pick the device with the lowest
// ("best") score.
//
// In this example, we simply select the best-scoring device to use in the application.
// In a real-world setting, you may want to use the best-scoring device only as a
// "default" or "recommended" device, and let the user choose the device themself.
.min_by_key(|(p, _)| {
// We assign a lower score to device types that are likely to be faster/better.
match p.properties().device_type {
PhysicalDeviceType::DiscreteGpu => 0,
PhysicalDeviceType::IntegratedGpu => 1,
PhysicalDeviceType::VirtualGpu => 2,
PhysicalDeviceType::Cpu => 3,
PhysicalDeviceType::Other => 4,
_ => 5,
}
})
.expect("No suitable physical device found");
// Print out the device we selected
/*println!(
"Using device: {} (type: {:?})",
physical_device.properties().device_name,
physical_device.properties().device_type,
);*/
// Now initializing the device. This is probably the most important object of Vulkan.
//
// An iterator of created queues is returned by the function alongside the device. Each
// queue has a reference to its instance so we don't need to store that directly.
let (_, mut queues) = Device::new(
// Which physical device to connect to.
physical_device,
DeviceCreateInfo {
// A list of optional features and extensions that our program needs to work
// correctly. Some parts of the Vulkan specs are optional and must be enabled
// manually at device creation. In this example the only thing we are going to need
// is the `khr_swapchain` extension that allows us to draw to a window.
enabled_extensions: device_extensions,
// The list of queues that we are going to use. Here we only use one queue, from
// the previously chosen queue family.
queue_create_infos: vec![QueueCreateInfo {
queue_family_index,
..Default::default()
}],
..Default::default()
},
)
.expect("Device initialization failed");
// Since we can request multiple queues, the `queues` variable is in fact an iterator. We
// only use one queue in this example, so we just retrieve the first and only element of
// the iterator.
queues.next().unwrap()
}
}