Windowing introduction
Up until now, we have only created applications that perform one quick action and then exit. What we are going to do next is create a window in order to draw graphics on it, and keep our application running forever until the window is closed.
Strictly speaking, creating a window and handling events is not covered by vulkano. Vulkano, however, is capable of rendering to window(s).
Note: You can find the full source code of this chapter here.
Creating a window
In order to create a window, we will use the winit
crate.
Add to your Cargo.toml
dependencies:
winit = "0.28.0"
We encourage you to browse the documentation of winit
.
Because the objects that come with creating a window are not part of Vulkan itself, the first thing
that you will need to do is to enable all non-core extensions required to draw a window.
Surface::required_extensions
automatically provides them for us, so the only thing left is to
pass them on to the instance creation:
#![allow(unused)] fn main() { use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}; use vulkano::swapchain::Surface; use winit::event_loop::EventLoop; let event_loop = EventLoop::new(); // ignore this for now let library = VulkanLibrary::new().expect("no local Vulkan library/DLL"); let required_extensions = Surface::required_extensions(&event_loop); let instance = Instance::new( library, InstanceCreateInfo { flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, enabled_extensions: required_extensions, ..Default::default() }, ) .expect("failed to create instance"); }
Now, let's create the actual window:
#![allow(unused)] fn main() { use winit::window::WindowBuilder; let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap()); }
We need to wrap the Window
in an Arc
, so that both you and vulkano can keep a reference to it.
Next we need to create a surface, which is a cross-platform abstraction over the actual window object, that vulkano can use for rendering:
#![allow(unused)] fn main() { let surface = Surface::from_window(instance.clone(), window.clone()); }
After you made the change, running the program should now open a window, then immediately close it
when the main
function exits.
Event handling
In order to make our application run for as long as the window is alive, we need to handle the
window's events. This is typically done after initialization, and right before the end of the
main
function. Using the event_loop
object:
#![allow(unused)] fn main() { use winit::event::{Event, WindowEvent}; use winit::event_loop::ControlFlow; event_loop.run(|event, _, control_flow| { match event { Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => { *control_flow = ControlFlow::Exit; }, _ => () } }); }
What this code does is block the main thread forever, and calls the closure whenever the event loop (which we used to create our window) receives an event. These events include the events that are tied to our window, such as mouse movements.
When the user wants to close the window, a WindowEvent::CloseRequested
event is received, which
makes our closure set the control_flow
to ControlFlow::Exit
which signals to winit that we want
an exit.
Right now, all we're doing is creating a window and keeping our program alive for as long as the window isn't closed. The next section will show how to initialize what is called a swapchain on the window's surface.
Next: Swapchain creation