├── .gitignore ├── chapter-code ├── .gitignore ├── wip │ ├── game_objects │ │ ├── mod.rs │ │ └── square.rs │ ├── shaders │ │ ├── mod.rs │ │ ├── static_triangle │ │ │ ├── fragment.glsl │ │ │ ├── vertex.glsl │ │ │ └── mod.rs │ │ └── movable_square │ │ │ ├── fragment.glsl │ │ │ ├── mod.rs │ │ │ └── vertex.glsl │ ├── models │ │ ├── mod.rs │ │ ├── traits.rs │ │ └── square.rs │ ├── bin │ │ ├── more_on_buffers │ │ │ ├── render │ │ │ │ ├── mod.rs │ │ │ │ ├── render_loop.rs │ │ │ │ └── renderer.rs │ │ │ ├── main.rs │ │ │ └── app.rs │ │ └── restructuring │ │ │ ├── render │ │ │ ├── mod.rs │ │ │ ├── render_loop.rs │ │ │ └── renderer.rs │ │ │ ├── app.rs │ │ │ └── main.rs │ ├── vulkano_objects │ │ ├── mod.rs │ │ ├── render_pass.rs │ │ ├── allocators.rs │ │ ├── instance.rs │ │ ├── physical_device.rs │ │ ├── swapchain.rs │ │ ├── pipeline.rs │ │ ├── command_buffers.rs │ │ └── buffers.rs │ ├── vertex_data.rs │ └── lib.rs ├── 05-images │ ├── main.rs │ ├── image_clear.rs │ └── mandelbrot.rs ├── Cargo.toml ├── README.md ├── src │ └── lib.rs ├── 03-buffer-creation │ └── main.rs └── 04-compute-pipeline │ └── main.rs ├── theme └── favicon.png ├── src ├── 05-images │ ├── guide-mandelbrot-1.png │ ├── guide-image-export-1.png │ ├── guide-image-creation-1.png │ ├── 02-image-clear.md │ ├── 03-image-export.md │ ├── 01-image-creation.md │ └── 04-mandelbrot.md ├── 06-graphics-pipeline │ ├── guide-graphics-pipeline-creation-1.png │ ├── 03-fragment-shader.md │ ├── 01-introduction.md │ ├── 02-vertex-shader.md │ ├── 04-render-pass-framebuffer.md │ ├── guide-vertex-input-2.svg │ └── 05-pipeline-creation.md ├── contributors.md ├── wip │ └── memory.md ├── SUMMARY.md ├── 04-compute-pipeline │ ├── 04-dispatch.md │ ├── 03-descriptor-sets.md │ ├── 01-compute-intro.md │ └── 02-compute-pipeline.md ├── about.md ├── 01-introduction │ └── 01-introduction.md ├── 02-initialization │ ├── 02-device-creation.md │ └── 01-initialization.md ├── 07-windowing │ ├── 01-introduction.md │ └── 02-swapchain-creation.md └── 03-buffer-creation │ ├── 02-example-operation.md │ └── 01-buffer-creation.md ├── book.toml ├── Cargo.toml ├── README.md ├── LICENSE-MIT └── .github └── workflows └── mdbook.yml /.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | 3 | /target 4 | .idea 5 | Cargo.lock 6 | -------------------------------------------------------------------------------- /chapter-code/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | image.png 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /chapter-code/wip/game_objects/mod.rs: -------------------------------------------------------------------------------- 1 | mod square; 2 | 3 | pub use square::Square; 4 | -------------------------------------------------------------------------------- /chapter-code/wip/shaders/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod movable_square; 2 | pub mod static_triangle; 3 | -------------------------------------------------------------------------------- /theme/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulkano-rs/vulkano-book/HEAD/theme/favicon.png -------------------------------------------------------------------------------- /chapter-code/wip/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod square; 2 | mod traits; 3 | 4 | pub use square::SquareModel; 5 | pub use traits::Model; 6 | -------------------------------------------------------------------------------- /chapter-code/wip/bin/more_on_buffers/render/mod.rs: -------------------------------------------------------------------------------- 1 | mod render_loop; 2 | mod renderer; 3 | 4 | pub use render_loop::RenderLoop; 5 | -------------------------------------------------------------------------------- /chapter-code/wip/bin/restructuring/render/mod.rs: -------------------------------------------------------------------------------- 1 | mod render_loop; 2 | mod renderer; 3 | 4 | pub use render_loop::RenderLoop; 5 | -------------------------------------------------------------------------------- /src/05-images/guide-mandelbrot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulkano-rs/vulkano-book/HEAD/src/05-images/guide-mandelbrot-1.png -------------------------------------------------------------------------------- /src/05-images/guide-image-export-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulkano-rs/vulkano-book/HEAD/src/05-images/guide-image-export-1.png -------------------------------------------------------------------------------- /src/05-images/guide-image-creation-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulkano-rs/vulkano-book/HEAD/src/05-images/guide-image-creation-1.png -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["The vulkano contributors"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Vulkano" 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["chapter-code"] 4 | default-members = ["chapter-code"] 5 | 6 | [profile.dev] 7 | opt-level = 1 8 | -------------------------------------------------------------------------------- /src/06-graphics-pipeline/guide-graphics-pipeline-creation-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulkano-rs/vulkano-book/HEAD/src/06-graphics-pipeline/guide-graphics-pipeline-creation-1.png -------------------------------------------------------------------------------- /chapter-code/wip/shaders/static_triangle/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 460 2 | 3 | layout(location = 0) out vec4 f_color; 4 | 5 | void main() { 6 | f_color = vec4(1.0, 0.0, 0.0, 1.0); 7 | } 8 | -------------------------------------------------------------------------------- /chapter-code/wip/shaders/static_triangle/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 460 2 | 3 | layout(location = 0) in vec2 position; 4 | 5 | void main() { 6 | gl_Position = vec4(position, 0.0, 1.0); 7 | } 8 | -------------------------------------------------------------------------------- /chapter-code/wip/shaders/movable_square/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 460 2 | 3 | layout(location = 0) in vec3 color; 4 | 5 | layout(location = 0) out vec4 f_color; 6 | 7 | void main() { 8 | f_color = vec4(color, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /chapter-code/wip/vulkano_objects/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod allocators; 2 | pub mod buffers; 3 | pub mod command_buffers; 4 | pub mod instance; 5 | pub mod physical_device; 6 | pub mod pipeline; 7 | pub mod render_pass; 8 | pub mod swapchain; 9 | -------------------------------------------------------------------------------- /chapter-code/wip/models/traits.rs: -------------------------------------------------------------------------------- 1 | use vulkano::buffer::BufferContents; 2 | 3 | pub trait Model { 4 | fn get_indices() -> Vec; 5 | fn get_vertices() -> Vec; 6 | fn get_initial_uniform_data() -> U; 7 | } 8 | -------------------------------------------------------------------------------- /chapter-code/wip/shaders/movable_square/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod vs { 2 | vulkano_shaders::shader! { 3 | ty: "vertex", 4 | path: "src/shaders/movable_square/vertex.glsl", 5 | } 6 | } 7 | 8 | pub mod fs { 9 | vulkano_shaders::shader! { 10 | ty: "fragment", 11 | path: "src/shaders/movable_square/fragment.glsl", 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chapter-code/wip/shaders/static_triangle/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod vs { 2 | vulkano_shaders::shader! { 3 | ty: "vertex", 4 | path: "src/shaders/static_triangle/vertex.glsl", 5 | } 6 | } 7 | 8 | pub mod fs { 9 | vulkano_shaders::shader! { 10 | ty: "fragment", 11 | path: "src/shaders/static_triangle/fragment.glsl", 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chapter-code/wip/vertex_data.rs: -------------------------------------------------------------------------------- 1 | use vulkano::buffer::BufferContents; 2 | use vulkano::pipeline::graphics::vertex_input::Vertex; 3 | 4 | #[derive(BufferContents, Vertex)] 5 | #[repr(C)] 6 | pub struct Vertex2d { 7 | #[format(R32G32_SFLOAT)] 8 | pub position: [f32; 2], 9 | } 10 | 11 | #[derive(BufferContents, Vertex)] 12 | #[repr(C)] 13 | pub struct Vertex3d { 14 | #[format(R32G32B32_SFLOAT)] 15 | pub position: [f32; 3], 16 | } 17 | -------------------------------------------------------------------------------- /chapter-code/wip/shaders/movable_square/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 460 2 | 3 | layout(location = 0) in vec2 position; 4 | 5 | layout(set = 0, binding = 0) uniform Data { 6 | vec3 color; 7 | vec2 position; 8 | } uniforms; 9 | 10 | layout(location = 0) out vec3 outColor; 11 | 12 | void main() { 13 | outColor = uniforms.color; 14 | gl_Position = vec4( 15 | position.x + uniforms.position.x, 16 | position.y + uniforms.position.y, 17 | 0.0, 18 | 1.0 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /chapter-code/wip/bin/restructuring/app.rs: -------------------------------------------------------------------------------- 1 | use winit::event_loop::EventLoop; 2 | 3 | use crate::render::RenderLoop; 4 | 5 | pub struct App { 6 | render_loop: RenderLoop, 7 | } 8 | 9 | impl App { 10 | pub fn start(event_loop: &EventLoop<()>) -> Self { 11 | Self { 12 | render_loop: RenderLoop::new(event_loop), 13 | } 14 | } 15 | 16 | pub fn update(&mut self) { 17 | self.render_loop.update(); 18 | } 19 | 20 | pub fn handle_window_resize(&mut self) { 21 | self.render_loop.handle_window_resize() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter-code/05-images/main.rs: -------------------------------------------------------------------------------- 1 | mod image_clear; 2 | mod mandelbrot; 3 | 4 | use chapter_code::select_example_to_run; 5 | 6 | const EXAMPLES: [&str; 2] = ["image_clear", "mandelbrot"]; 7 | 8 | fn execute_example(selection: &str) { 9 | println!("Running '{}'", selection); 10 | match selection { 11 | "image_clear" => { 12 | image_clear::main(); 13 | } 14 | "mandelbrot" => { 15 | mandelbrot::main(); 16 | } 17 | _ => panic!(), 18 | } 19 | } 20 | 21 | fn main() { 22 | select_example_to_run(&EXAMPLES, execute_example); 23 | } 24 | -------------------------------------------------------------------------------- /src/contributors.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | This book wouldn't be possible without the following contributors: 4 | - [tomaka](https://github.com/tomaka) 5 | - [ZakStar17](https://github.com/ZakStar17) 6 | - [rukai](https://github.com/rukai) 7 | - [CodesOtakuYT](https://github.com/CodesOtakuYT) 8 | - [marc0246](https://github.com/marc0246) 9 | - [ryco117](https://github.com/ryco117) 10 | - [hakolao](https://github.com/hakolao) 11 | - [jimblandy](https://github.com/jimblandy) 12 | - [Rua](https://github.com/Rua) 13 | - [AustinJ235](https://github.com/AustinJ235) 14 | 15 | If you feel you're missing from this list, feel free to add yourself in a PR. 16 | -------------------------------------------------------------------------------- /chapter-code/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chapter-code" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "buffer-creation" 8 | path = "03-buffer-creation/main.rs" 9 | 10 | [[bin]] 11 | name = "compute-pipeline" 12 | path = "04-compute-pipeline/main.rs" 13 | 14 | [[bin]] 15 | name = "images" 16 | path = "05-images/main.rs" 17 | 18 | [[bin]] 19 | name = "graphics-pipeline" 20 | path = "06-graphics-pipeline/main.rs" 21 | 22 | [[bin]] 23 | name = "windowing" 24 | path = "07-windowing/main.rs" 25 | 26 | [dependencies] 27 | vulkano = "0.34.0" 28 | vulkano-shaders = "0.34.0" 29 | image = "0.24.0" 30 | winit = "0.28.0" 31 | rand = "0.8.5" 32 | -------------------------------------------------------------------------------- /chapter-code/wip/vulkano_objects/render_pass.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::device::Device; 4 | use vulkano::render_pass::RenderPass; 5 | use vulkano::swapchain::Swapchain; 6 | 7 | pub fn create_render_pass(device: Arc, swapchain: Arc) -> Arc { 8 | vulkano::single_pass_renderpass!( 9 | device, 10 | attachments: { 11 | color: { 12 | format: swapchain.image_format(), 13 | samples: 1, 14 | load_op: Clear, 15 | store_op: Store, 16 | }, 17 | }, 18 | pass: { 19 | color: [color], 20 | depth_stencil: {}, 21 | }, 22 | ) 23 | .unwrap() 24 | } 25 | -------------------------------------------------------------------------------- /chapter-code/README.md: -------------------------------------------------------------------------------- 1 | # Chapter source code 2 | 3 | This folder contains the source code used in the book. 4 | 5 | ## Viewing the source code 6 | 7 | To view the source code from each chapter, navigate to the corresponding subdirectory in this 8 | directory. Each subdirectory starting with a number corresponds to a chapter of the same number and 9 | name. 10 | 11 | Some chapters contain multiple examples, in which case the source code will be subdivided inside 12 | the chapter folder. For example, in case of `images`, there will be two examples: 13 | `chapter-code/05-images/image_clear.rs` and `chapter-code/05-images/mandelbrot.rs`. 14 | 15 | ## Running the source code 16 | 17 | If you want to run the source code and experiment for yourself, run `cargo run --bin ` 18 | inside this folder. For example: 19 | 20 | ```bash 21 | cargo run --bin windowing 22 | ``` 23 | -------------------------------------------------------------------------------- /chapter-code/wip/bin/restructuring/main.rs: -------------------------------------------------------------------------------- 1 | pub mod app; 2 | pub mod render; 3 | 4 | use winit::event::{Event, WindowEvent}; 5 | use winit::event_loop::{ControlFlow, EventLoop}; 6 | 7 | use crate::app::App; 8 | 9 | fn main() { 10 | let event_loop = EventLoop::new(); 11 | let mut app = App::start(&event_loop); 12 | 13 | event_loop.run(move |event, _, control_flow| match event { 14 | Event::WindowEvent { 15 | event: WindowEvent::CloseRequested, 16 | .. 17 | } => { 18 | *control_flow = ControlFlow::Exit; 19 | } 20 | Event::WindowEvent { 21 | event: WindowEvent::Resized(_), 22 | .. 23 | } => { 24 | app.handle_window_resize(); 25 | } 26 | Event::MainEventsCleared => { 27 | app.update(); 28 | } 29 | _ => (), 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Source and chapter code of the Vulkano book 2 | 3 | ## Running a local instance of the book 4 | 5 | - `mdbook` is required to build and test. It can be installed via `cargo install mdbook`. 6 | - Running `mdbook serve --open` will open a local instance in your web browser. 7 | 8 | ## Running the chapter code 9 | 10 | ``` 11 | cargo run --bin 12 | ``` 13 | 14 | ## License 15 | 16 | Licensed under either of 17 | 18 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 19 | http://www.apache.org/licenses/LICENSE-2.0) 20 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 21 | 22 | at your option. 23 | 24 | ### Contribution 25 | 26 | Unless you explicitly state otherwise, any contribution intentionally submitted 27 | for inclusion in the work by you shall be dual licensed as above, without any 28 | additional terms or conditions. 29 | -------------------------------------------------------------------------------- /chapter-code/wip/vulkano_objects/allocators.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; 4 | use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; 5 | use vulkano::device::Device; 6 | use vulkano::memory::allocator::StandardMemoryAllocator; 7 | 8 | pub struct Allocators { 9 | pub memory: Arc, 10 | pub command_buffer: StandardCommandBufferAllocator, 11 | pub descriptor_set: StandardDescriptorSetAllocator, 12 | } 13 | 14 | impl Allocators { 15 | pub fn new(device: Arc) -> Self { 16 | Allocators { 17 | memory: Arc::new(StandardMemoryAllocator::new_default(device.clone())), 18 | command_buffer: StandardCommandBufferAllocator::new(device.clone(), Default::default()), 19 | descriptor_set: StandardDescriptorSetAllocator::new(device, Default::default()), 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chapter-code/wip/models/square.rs: -------------------------------------------------------------------------------- 1 | use crate::models::Model; 2 | use crate::shaders::movable_square; 3 | use crate::Vertex2d; 4 | 5 | pub struct SquareModel; 6 | 7 | type UniformData = movable_square::vs::Data; 8 | 9 | impl Model for SquareModel { 10 | fn get_vertices() -> Vec { 11 | vec![ 12 | Vertex2d { 13 | position: [-0.25, -0.25], 14 | }, 15 | Vertex2d { 16 | position: [0.25, -0.25], 17 | }, 18 | Vertex2d { 19 | position: [-0.25, 0.25], 20 | }, 21 | Vertex2d { 22 | position: [0.25, 0.25], 23 | }, 24 | ] 25 | } 26 | 27 | fn get_indices() -> Vec { 28 | vec![0, 1, 2, 1, 2, 3] 29 | } 30 | 31 | fn get_initial_uniform_data() -> UniformData { 32 | UniformData { 33 | color: [0.0, 0.0, 0.0].into(), 34 | position: [0.0, 0.0], 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 The Vulkano Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /chapter-code/wip/game_objects/square.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | 3 | pub struct Square { 4 | pub color: [f32; 3], 5 | pub position: [f32; 2], 6 | pub speed: f32, 7 | } 8 | 9 | impl Square { 10 | #[allow(clippy::new_without_default)] 11 | pub fn new() -> Self { 12 | Self { 13 | color: [1.0, 0.0, 0.0], 14 | position: [0.0, 0.0], 15 | speed: 1.3, 16 | } 17 | } 18 | 19 | pub fn change_to_random_color(&mut self) { 20 | let get_random_float = || rand::thread_rng().gen_range(0..100) as f32 / 100.0; 21 | self.color = [get_random_float(), get_random_float(), get_random_float()]; 22 | } 23 | 24 | pub fn move_right(&mut self, seconds_passed: f32) { 25 | self.position[0] += seconds_passed * self.speed 26 | } 27 | 28 | pub fn move_left(&mut self, seconds_passed: f32) { 29 | self.position[0] -= seconds_passed * self.speed 30 | } 31 | 32 | pub fn move_up(&mut self, seconds_passed: f32) { 33 | self.position[1] -= seconds_passed * self.speed 34 | } 35 | 36 | pub fn move_down(&mut self, seconds_passed: f32) { 37 | self.position[1] += seconds_passed * self.speed 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /chapter-code/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | pub fn select_example_to_run(examples: &[&str], execute: fn(&str)) { 4 | println!("Select example to run: (default 0)"); 5 | 6 | for (i, example) in examples.iter().enumerate() { 7 | println!("{} {}", i, example); 8 | } 9 | 10 | let mut selection = String::new(); 11 | io::stdin() 12 | .read_line(&mut selection) 13 | .expect("failed to read line"); 14 | 15 | selection = selection.trim().to_string(); 16 | 17 | if selection.is_empty() { 18 | execute(examples[0]); 19 | // else if selection is numeric 20 | } else if let Ok(i) = selection.parse::() { 21 | if i >= examples.len() { 22 | println!( 23 | "The given index \"{}\" doesn't correspond to any known example", 24 | selection 25 | ); 26 | } else { 27 | execute(examples[i]); 28 | } 29 | } else { 30 | match examples.iter().position(|&s| s == selection) { 31 | Some(i) => { 32 | execute(examples[i]); 33 | } 34 | None => { 35 | println!("\"{}\" doesn't correspond to any known example", selection); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /chapter-code/wip/vulkano_objects/instance.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::{ 4 | instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, LayerProperties}, 5 | swapchain::Surface, 6 | }; 7 | use winit::event_loop::EventLoop; 8 | 9 | const LIST_AVAILABLE_LAYERS: bool = false; 10 | const ENABLE_VALIDATION_LAYERS: bool = false; 11 | const VALIDATION_LAYERS: &[&str] = &["VK_LAYER_LUNARG_api_dump"]; 12 | 13 | pub fn get_instance(event_loop: &EventLoop<()>) -> Arc { 14 | let library = vulkano::VulkanLibrary::new().expect("no local Vulkan library/DLL"); 15 | let required_extensions = Surface::required_extensions(event_loop); 16 | 17 | if LIST_AVAILABLE_LAYERS { 18 | let layers: Vec<_> = library.layer_properties().unwrap().collect(); 19 | let layer_names = layers.iter().map(LayerProperties::name); 20 | println!( 21 | "Available layers:\n {:?}", 22 | layer_names.clone().collect::>() 23 | ); 24 | } 25 | 26 | let mut create_info = InstanceCreateInfo { 27 | flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, 28 | enabled_extensions: required_extensions, 29 | ..Default::default() 30 | }; 31 | 32 | if ENABLE_VALIDATION_LAYERS { 33 | create_info.enabled_layers = VALIDATION_LAYERS.iter().map(|s| s.to_string()).collect(); 34 | } 35 | 36 | Instance::new(library, create_info).unwrap() 37 | } 38 | -------------------------------------------------------------------------------- /chapter-code/wip/vulkano_objects/physical_device.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType}; 4 | use vulkano::device::{DeviceExtensions, QueueFlags}; 5 | use vulkano::instance::Instance; 6 | use vulkano::swapchain::Surface; 7 | 8 | pub fn select_physical_device( 9 | instance: &Arc, 10 | surface: Arc, 11 | device_extensions: &DeviceExtensions, 12 | ) -> (Arc, u32) { 13 | instance 14 | .enumerate_physical_devices() 15 | .expect("failed to enumerate physical devices") 16 | .filter(|p| p.supported_extensions().contains(device_extensions)) 17 | .filter_map(|p| { 18 | p.queue_family_properties() 19 | .iter() 20 | .enumerate() 21 | .position(|(i, q)| { 22 | q.queue_flags.contains(QueueFlags::GRAPHICS) 23 | && p.surface_support(i as u32, &surface).unwrap_or(false) 24 | }) 25 | .map(|q| (p, q as u32)) 26 | }) 27 | .min_by_key(|(p, _)| match p.properties().device_type { 28 | PhysicalDeviceType::DiscreteGpu => 0, 29 | PhysicalDeviceType::IntegratedGpu => 1, 30 | PhysicalDeviceType::VirtualGpu => 2, 31 | PhysicalDeviceType::Cpu => 3, 32 | _ => 4, 33 | }) 34 | .expect("no device available") 35 | } 36 | -------------------------------------------------------------------------------- /chapter-code/wip/bin/more_on_buffers/main.rs: -------------------------------------------------------------------------------- 1 | pub mod app; 2 | pub mod render; 3 | 4 | use std::time::Instant; 5 | 6 | use winit::event::{Event, WindowEvent}; 7 | use winit::event_loop::{ControlFlow, EventLoop}; 8 | 9 | use crate::app::App; 10 | 11 | fn main() { 12 | let event_loop = EventLoop::new(); 13 | let mut app = App::start(&event_loop); 14 | 15 | let mut previous_frame_time = Instant::now(); 16 | event_loop.run(move |event, _, control_flow| match event { 17 | Event::WindowEvent { 18 | event: WindowEvent::CloseRequested, 19 | .. 20 | } => { 21 | *control_flow = ControlFlow::Exit; 22 | } 23 | Event::WindowEvent { 24 | event: WindowEvent::Resized(_), 25 | .. 26 | } => { 27 | app.handle_window_resize(); 28 | } 29 | Event::WindowEvent { 30 | event: WindowEvent::KeyboardInput { input, .. }, 31 | .. 32 | } => { 33 | if let Some(key_code) = input.virtual_keycode { 34 | app.handle_keyboard_input(key_code, input.state) 35 | } 36 | } 37 | Event::MainEventsCleared => { 38 | let this_frame_time = Instant::now(); 39 | let duration_from_last_frame = this_frame_time - previous_frame_time; 40 | 41 | app.update(&duration_from_last_frame); 42 | 43 | previous_frame_time = this_frame_time; 44 | } 45 | _ => (), 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /src/wip/memory.md: -------------------------------------------------------------------------------- 1 | ## Introduction to memory 2 | 3 | When you write some program (either in Rust or any other programming language) and run it, the 4 | program's instructions are executed by the CPU and the program's variables are typically stored in 5 | RAM. The CPU and RAM are physically close to each other on the motherboard. 6 | 7 | Similarly, your video card has its own microprocessor called the *GPU* or the *graphics processor*, 8 | and its own RAM which we usually call the *video RAM*, the *VRAM*, or the *video memory*. It can 9 | be seen more or less as a secondary machine within your main machine. 10 | 11 | The CPU can read and write very quickly to the RAM (as they are close to each other), and the GPU 12 | can read and write very quickly to the video memory (as they are close to each other as well). It 13 | is also possible for the GPU to access RAM, and usually also possible for the CPU to access the 14 | video memory, but the read and write accesses are going to be much slower as they have to go 15 | through the PCI Express bus that connects your video card to your motherboard. 16 | 17 |
18 | 19 |
20 | 21 | > **Note**: All of this is true only for desktop machines with video cards, which is what we 22 | > are going to focus on. Mobile machines usually share the same memory for both the CPU and the 23 | > graphics processor. In addition to this, it is possible to have for example a software 24 | > implementation of Vulkan that doesn't even use a GPU. 25 | -------------------------------------------------------------------------------- /chapter-code/wip/lib.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | pub mod game_objects; 4 | pub mod models; 5 | pub mod shaders; 6 | mod vertex_data; 7 | pub mod vulkano_objects; 8 | 9 | pub use vertex_data::{Vertex2d, Vertex3d}; 10 | 11 | #[cfg(test)] 12 | mod tests { 13 | #[test] 14 | fn it_works() { 15 | let result = 2 + 2; 16 | assert_eq!(result, 4); 17 | } 18 | } 19 | 20 | pub fn select_example_to_run(examples: &Vec<&str>, execute: fn(&str)) { 21 | println!("Select example to run: (default 0)"); 22 | 23 | for (i, example) in examples.iter().enumerate() { 24 | println!("{} {}", i, example); 25 | } 26 | 27 | let mut selection = String::new(); 28 | io::stdin() 29 | .read_line(&mut selection) 30 | .expect("Failed to read line"); 31 | 32 | selection = selection.trim().to_string(); 33 | 34 | if selection.is_empty() { 35 | execute(examples[0]); 36 | // else if selection is numeric 37 | } else if let Ok(i) = selection.parse::() { 38 | if i >= examples.len() { 39 | println!( 40 | "The given index \"{}\" doesn't correspond to any known example", 41 | selection 42 | ); 43 | } else { 44 | execute(examples[i]); 45 | } 46 | } else { 47 | match examples.iter().position(|&s| s == selection) { 48 | Some(i) => { 49 | execute(examples[i]); 50 | } 51 | None => { 52 | println!("\"{}\" doesn't correspond to any known example", selection); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/06-graphics-pipeline/03-fragment-shader.md: -------------------------------------------------------------------------------- 1 | # Fragment shader 2 | 3 | After the vertex shader has run on each vertex, the next step that the GPU performs is to determine 4 | which pixels of the target image are within the shape of the triangle. Only these pixels will be 5 | modified on the final image. 6 | 7 | > **Note**: More precisely, it is only if the center of a pixel is within the triangle that the 8 | > GPU considers that the whole pixel is inside. 9 | 10 |
11 | 13 | 14 |
15 | 16 | The GPU then takes each of these pixels one by one (the ones in red in the image above) and runs 17 | another type of shader named a **fragment shader** which we also need to provide in order to start 18 | our draw operation. 19 | 20 | Here is what an example fragment shader looks like: 21 | 22 | ```glsl 23 | #version 460 24 | 25 | layout(location = 0) out vec4 f_color; 26 | 27 | void main() { 28 | f_color = vec4(1.0, 0.0, 0.0, 1.0); 29 | } 30 | ``` 31 | 32 | The `layout(location = 0) out vec4 f_color;` line declares an output named `f_color`. Vulkan gives 33 | you the possibility to draw to multiple images at once, which is why we need to declare each output 34 | and its type. Drawing to multiple images at once is an advanced topic that isn't covered here. 35 | 36 | The `main()` function is executed once for each pixel covered by the triangle and must write in 37 | `f_color` the value that we want to write to the target image. As explained in [a previous 38 | section](../05-images/02-image-clear.html) these values are normalized, in other words the value 39 | `1.0` will in reality write `255` in memory. In this example since our target image contains 40 | colors, we write the color red. 41 | 42 | Next: [Render passes and framebuffers](04-render-pass-framebuffer.html) 43 | -------------------------------------------------------------------------------- /.github/workflows/mdbook.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a mdBook site to GitHub Pages 2 | # 3 | # To get started with mdBook see: https://rust-lang.github.io/mdBook/index.html 4 | # 5 | name: Deploy mdBook site to Pages 6 | 7 | on: 8 | # Runs on pushes targeting the default branch 9 | push: 10 | branches: ["main"] 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 16 | permissions: 17 | contents: read 18 | pages: write 19 | id-token: write 20 | 21 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 22 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 23 | concurrency: 24 | group: "pages" 25 | cancel-in-progress: false 26 | 27 | jobs: 28 | # Build job 29 | build: 30 | runs-on: ubuntu-latest 31 | env: 32 | MDBOOK_VERSION: 0.4.21 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Install mdBook 36 | run: | 37 | curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh 38 | rustup update 39 | cargo install --version ${MDBOOK_VERSION} mdbook 40 | - name: Setup Pages 41 | id: pages 42 | uses: actions/configure-pages@v5 43 | - name: Build with mdBook 44 | run: mdbook build 45 | - name: Upload artifact 46 | uses: actions/upload-pages-artifact@v4 47 | with: 48 | path: ./book 49 | 50 | # Deployment job 51 | deploy: 52 | environment: 53 | name: github-pages 54 | url: ${{ steps.deployment.outputs.page_url }} 55 | runs-on: ubuntu-latest 56 | needs: build 57 | steps: 58 | - name: Deploy to GitHub Pages 59 | id: deployment 60 | uses: actions/deploy-pages@v4 61 | -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [About](about.md) 4 | 5 | ----------- 6 | 7 | 8 | 9 | - [The book]() 10 | - [Introduction](01-introduction/01-introduction.md) 11 | 12 | - [Initialization]() 13 | - [Intialization](02-initialization/01-initialization.md) 14 | - [Device creation](02-initialization/02-device-creation.md) 15 | 16 | - [Buffer creation]() 17 | - [Creating a buffer](03-buffer-creation/01-buffer-creation.md) 18 | - [Example operation](03-buffer-creation/02-example-operation.md) 19 | 20 | - [Compute pipeline]() 21 | - [Introduction to compute operations](04-compute-pipeline/01-compute-intro.md) 22 | - [Compute pipelines](04-compute-pipeline/02-compute-pipeline.md) 23 | - [Descriptor sets](04-compute-pipeline/03-descriptor-sets.md) 24 | - [Dispatch](04-compute-pipeline/04-dispatch.md) 25 | 26 | - [Using images]() 27 | - [Image creation](05-images/01-image-creation.md) 28 | - [Clearing an image](05-images/02-image-clear.md) 29 | - [Exporting the result](05-images/03-image-export.md) 30 | - [Drawing a fractal with a compute shader](05-images/04-mandelbrot.md) 31 | 32 | - [Graphics pipeline]() 33 | - [What is a graphics pipeline?](06-graphics-pipeline/01-introduction.md) 34 | - [Vertex input](06-graphics-pipeline/02-vertex-shader.md) 35 | - [Fragment shader](06-graphics-pipeline/03-fragment-shader.md) 36 | - [Render passes and framebuffers](06-graphics-pipeline/04-render-pass-framebuffer.md) 37 | - [Putting it all together](06-graphics-pipeline/05-pipeline-creation.md) 38 | 39 | - [Windowing]() 40 | - [Window creation](07-windowing/01-introduction.md) 41 | - [Swapchain creation](07-windowing/02-swapchain-creation.md) 42 | - [Other initialization](07-windowing/03-other-initialization.md) 43 | - [Event Handling: Acquiring and presenting](07-windowing/04-event-handling.md) 44 | 45 | ----------- 46 | 47 | [Contributors](contributors.md) 48 | -------------------------------------------------------------------------------- /chapter-code/wip/vulkano_objects/swapchain.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::device::physical::PhysicalDevice; 4 | use vulkano::device::Device; 5 | use vulkano::image::view::ImageView; 6 | use vulkano::image::{Image, ImageUsage}; 7 | use vulkano::render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass}; 8 | use vulkano::swapchain::{Surface, Swapchain, SwapchainCreateInfo}; 9 | use winit::window::Window; 10 | 11 | pub fn create_swapchain( 12 | physical_device: &Arc, 13 | device: Arc, 14 | surface: Arc, 15 | ) -> (Arc, Vec>) { 16 | let caps = physical_device 17 | .surface_capabilities(&surface, Default::default()) 18 | .expect("failed to get surface capabilities"); 19 | 20 | let composite_alpha = caps.supported_composite_alpha.into_iter().next().unwrap(); 21 | let image_format = physical_device 22 | .surface_formats(&surface, Default::default()) 23 | .unwrap()[0] 24 | .0; 25 | 26 | Swapchain::new( 27 | device, 28 | surface.clone(), 29 | SwapchainCreateInfo { 30 | min_image_count: caps.min_image_count, 31 | image_format, 32 | image_extent: surface 33 | .object() 34 | .unwrap() 35 | .clone() 36 | .downcast::() 37 | .unwrap() 38 | .inner_size() 39 | .into(), 40 | image_usage: ImageUsage::COLOR_ATTACHMENT, 41 | composite_alpha, 42 | ..Default::default() 43 | }, 44 | ) 45 | .unwrap() 46 | } 47 | 48 | pub fn create_framebuffers_from_swapchain_images( 49 | images: &[Arc], 50 | render_pass: Arc, 51 | ) -> Vec> { 52 | images 53 | .iter() 54 | .map(|image| { 55 | let view = ImageView::new_default(image.clone()).unwrap(); 56 | Framebuffer::new( 57 | render_pass.clone(), 58 | FramebufferCreateInfo { 59 | attachments: vec![view], 60 | ..Default::default() 61 | }, 62 | ) 63 | .unwrap() 64 | }) 65 | .collect::>() 66 | } 67 | -------------------------------------------------------------------------------- /src/05-images/02-image-clear.md: -------------------------------------------------------------------------------- 1 | # Clearing an image 2 | 3 | Contrary to buffers, images have an opaque implementation-specific memory layout. What this means 4 | is that you can't modify an image by directly writing to its memory. There is no such thing as a 5 | `CpuAccessibleImage`. 6 | 7 | 8 | 9 | > **Note**: In reality Vulkan also allows you to create *linear* images, which can be modified but 10 | > are much slower and are supposed to be used only in some limited situations. Vulkano doesn't 11 | > support them yet. 12 | 13 | Therefore the only way to read or write to an image is to ask the GPU to do it. This is exactly 14 | what we are going to do by asking the GPU to fill our image with a specific color. This is called 15 | *clearing* an image. 16 | 17 | ```rust 18 | use vulkano::command_buffer::ClearColorImageInfo; 19 | use vulkano::format::ClearColorValue; 20 | 21 | let mut builder = AutoCommandBufferBuilder::primary( 22 | &command_buffer_allocator, 23 | queue.queue_family_index(), 24 | CommandBufferUsage::OneTimeSubmit, 25 | ) 26 | .unwrap(); 27 | 28 | builder 29 | .clear_color_image(ClearColorImageInfo { 30 | clear_value: ClearColorValue::Float([0.0, 0.0, 1.0, 1.0]), 31 | ..ClearColorImageInfo::image(image.clone()) 32 | }) 33 | .unwrap(); 34 | 35 | let command_buffer = builder.build().unwrap(); 36 | ``` 37 | 38 | > **Note**: The function is called clearing a *color* image, as opposed to depth and/or stencil 39 | > images which we haven't covered yet. 40 | 41 | ## Normalized components 42 | 43 | [The `ClearColorValue` enum](https://docs.rs/vulkano/0.34.0/vulkano/format/enum.ClearColorValue.html) indicates 44 | which color to fill the image with. Depending on the format of the image, we have to use the right 45 | enum variant of `ClearValue`. 46 | 47 | Here we pass floating-point values because the image was created with the `R8G8B8A8_UNORM` format. 48 | The `R8G8B8A8` part means that the four components are stored in 8 bits each, while the `UNORM` 49 | suffix means "unsigned normalized". The coordinates being "normalized" means that their value in 50 | memory (ranging between 0 and 255) is interpreted as floating point values. The in-memory value `0` 51 | is interpreted as the floating-point `0.0`, and the in-memory value `255` is interpreted as the 52 | floating-point `1.0`. 53 | 54 | With any format whose suffix is `UNORM` (but also `SNORM` and `SRGB`), all the operations that are 55 | performed on the image (with the exception of memory copies) treat the image as if it contained 56 | floating-point values. This is the reason why we pass `[0.0, 0.0, 1.0, 1.0]`. The values `1.0` will 57 | in fact be stored as `255` in memory. 58 | 59 | Next: [Exporting the result](03-image-export.html) 60 | -------------------------------------------------------------------------------- /chapter-code/wip/vulkano_objects/pipeline.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::device::Device; 4 | use vulkano::pipeline::graphics::color_blend::{ColorBlendAttachmentState, ColorBlendState}; 5 | use vulkano::pipeline::graphics::input_assembly::InputAssemblyState; 6 | use vulkano::pipeline::graphics::multisample::MultisampleState; 7 | use vulkano::pipeline::graphics::rasterization::RasterizationState; 8 | use vulkano::pipeline::graphics::vertex_input::{Vertex, VertexDefinition}; 9 | use vulkano::pipeline::graphics::viewport::{Viewport, ViewportState}; 10 | use vulkano::pipeline::graphics::GraphicsPipelineCreateInfo; 11 | use vulkano::pipeline::layout::PipelineDescriptorSetLayoutCreateInfo; 12 | use vulkano::pipeline::{GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo}; 13 | use vulkano::render_pass::{RenderPass, Subpass}; 14 | use vulkano::shader::ShaderModule; 15 | 16 | use crate::Vertex2d; 17 | 18 | pub fn create_pipeline( 19 | device: Arc, 20 | vs: Arc, 21 | fs: Arc, 22 | render_pass: Arc, 23 | viewport: Viewport, 24 | ) -> Arc { 25 | let vs = vs.entry_point("main").unwrap(); 26 | let fs = fs.entry_point("main").unwrap(); 27 | 28 | let vertex_input_state = Vertex2d::per_vertex() 29 | .definition(&vs.info().input_interface) 30 | .unwrap(); 31 | 32 | let stages = [ 33 | PipelineShaderStageCreateInfo::new(vs), 34 | PipelineShaderStageCreateInfo::new(fs), 35 | ]; 36 | 37 | let layout = PipelineLayout::new( 38 | device.clone(), 39 | PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) 40 | .into_pipeline_layout_create_info(device.clone()) 41 | .unwrap(), 42 | ) 43 | .unwrap(); 44 | 45 | let subpass = Subpass::from(render_pass.clone(), 0).unwrap(); 46 | 47 | GraphicsPipeline::new( 48 | device.clone(), 49 | None, 50 | GraphicsPipelineCreateInfo { 51 | stages: stages.into_iter().collect(), 52 | vertex_input_state: Some(vertex_input_state), 53 | input_assembly_state: Some(InputAssemblyState::default()), 54 | viewport_state: Some(ViewportState { 55 | viewports: [viewport].into_iter().collect(), 56 | ..Default::default() 57 | }), 58 | rasterization_state: Some(RasterizationState::default()), 59 | multisample_state: Some(MultisampleState::default()), 60 | color_blend_state: Some(ColorBlendState::with_attachment_states( 61 | subpass.num_color_attachments(), 62 | ColorBlendAttachmentState::default(), 63 | )), 64 | subpass: Some(subpass.into()), 65 | ..GraphicsPipelineCreateInfo::layout(layout) 66 | }, 67 | ) 68 | .unwrap() 69 | } 70 | -------------------------------------------------------------------------------- /src/04-compute-pipeline/04-dispatch.md: -------------------------------------------------------------------------------- 1 | # Dispatch 2 | 3 | Now that we have all the needed ingredients, we can create the command buffer that will execute 4 | our compute pipeline. This is called a *dispatch* operation. 5 | 6 | Creating a command buffer is similar to [the example operation in a previous 7 | section](../03-buffer-creation/02-example-operation.html). 8 | 9 | ```rust 10 | use vulkano::command_buffer::allocator::{ 11 | StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo, 12 | }; 13 | use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage}; 14 | use vulkano::pipeline::PipelineBindPoint; 15 | 16 | let command_buffer_allocator = StandardCommandBufferAllocator::new( 17 | device.clone(), 18 | StandardCommandBufferAllocatorCreateInfo::default(), 19 | ); 20 | 21 | let mut command_buffer_builder = AutoCommandBufferBuilder::primary( 22 | &command_buffer_allocator, 23 | queue.queue_family_index(), 24 | CommandBufferUsage::OneTimeSubmit, 25 | ) 26 | .unwrap(); 27 | 28 | let work_group_counts = [1024, 1, 1]; 29 | 30 | command_buffer_builder 31 | .bind_pipeline_compute(compute_pipeline.clone()) 32 | .unwrap() 33 | .bind_descriptor_sets( 34 | PipelineBindPoint::Compute, 35 | compute_pipeline.layout().clone(), 36 | descriptor_set_layout_index as u32, 37 | descriptor_set, 38 | ) 39 | .unwrap() 40 | .dispatch(work_group_counts) 41 | .unwrap(); 42 | 43 | let command_buffer = command_buffer_builder.build().unwrap(); 44 | ``` 45 | 46 | First, we bind the pipeline and then the *descriptor set*s, indicating the type of set, the layout 47 | and the descriptor sets we are going to use. Here the number of sets could have actually been many, 48 | in which case we would indicate our desired one with an index. Because we only have one, the index 49 | is 0. 50 | 51 | As explained in [the compute pipeline section](02-compute-pipeline.html), we want to spawn 1024 52 | *work groups*. This value is indicated by the actual `.dispatch()` method. 53 | 54 | Just like we already covered, we submit the command buffer: 55 | 56 | ```rust 57 | let future = sync::now(device.clone()) 58 | .then_execute(queue.clone(), command_buffer) 59 | .unwrap() 60 | .then_signal_fence_and_flush() 61 | .unwrap(); 62 | ``` 63 | 64 | This just schedules the operation for execution and tells the GPU to signal when finished. 65 | We have to wait for it to complete: 66 | 67 | ```rust 68 | future.wait(None).unwrap(); 69 | ``` 70 | 71 | Once complete, we can check that the pipeline has been correctly executed: 72 | 73 | ```rust 74 | let content = data_buffer.read().unwrap(); 75 | for (n, val) in content.iter().enumerate() { 76 | assert_eq!(*val, n as u32 * 12); 77 | } 78 | 79 | println!("Everything succeeded!"); 80 | ``` 81 | 82 | Next: [Creating an image](../05-images/01-image-creation.html) 83 | -------------------------------------------------------------------------------- /src/about.md: -------------------------------------------------------------------------------- 1 | # Safe Rust wrapper around the Vulkan API 2 | 3 | Vulkano is a Rust wrapper around [the Vulkan graphics API](https://www.khronos.org/vulkan/). 4 | It follows the Rust philosophy, which is that as long as you don't use unsafe code you shouldn't 5 | be able to trigger any undefined behavior. In the case of Vulkan, this means that non-unsafe code 6 | should always conform to valid API usage. 7 | 8 | What does vulkano do? 9 | 10 | - Provides a low-levelish API around Vulkan. It doesn't hide what it does but provides some 11 | comfort types. 12 | - Plans to prevent all invalid API usages, even the most obscure ones. The purpose of Vulkano 13 | is not to simply let you draw a teapot, but to cover all possible usages of Vulkan and detect all 14 | the possible problems in order to write robust programs. Invalid API usage is prevented thanks to 15 | both compile-time checks and runtime checks. 16 | - Can handle synchronization on the GPU side for you (unless you choose to do that yourself), as this 17 | aspect of Vulkan is both annoying to handle and error-prone. Dependencies between submissions are 18 | automatically detected, and semaphores are managed automatically. The behavior of the library can 19 | be customized thanks to unsafe trait implementations. 20 | - Tries to be convenient to use. Nobody is going to use a library that requires you to browse 21 | the documentation for hours for every single operation. 22 | 23 |
24 | 25 | [![Build Status](https://github.com/vulkano-rs/vulkano/workflows/Rust/badge.svg)](https://github.com/vulkano-rs/vulkano/actions?query=workflow%3ARust) 26 | [![Discord](https://img.shields.io/discord/937149253296476201?label=discord)](https://discord.gg/xjHzXQp8hw) 27 | [![vulkano github](https://img.shields.io/badge/vulkano-github-lightgrey.svg)](https://github.com/vulkano-rs/vulkano) 28 | [![vulkano-book github](https://img.shields.io/badge/vulkano--book-github-lightgrey.svg)](https://github.com/vulkano-rs/vulkano-book) 29 |
30 | [![vulkano crates.io](https://img.shields.io/crates/v/vulkano?label=vulkano)](https://crates.io/crates/vulkano) 31 | [![vulkano-shaders crates.io](https://img.shields.io/crates/v/vulkano-shaders?label=shaders)](https://crates.io/crates/vulkano-shaders) 32 | [![vulkano-util crates.io](https://img.shields.io/crates/v/vulkano-util?label=util)](https://crates.io/crates/vulkano-util) 33 | [![vulkano-win crates.io](https://img.shields.io/crates/v/vulkano-win?label=win)](https://crates.io/crates/vulkano-win) 34 |
35 | [![vulkano docs](https://img.shields.io/docsrs/vulkano?label=vulkano%20docs)](https://docs.rs/vulkano) 36 | [![vulkano-shaders docs](https://img.shields.io/docsrs/vulkano-shaders?label=shaders%20docs)](https://docs.rs/vulkano-shaders) 37 | [![vulkano-util docs](https://img.shields.io/docsrs/vulkano-util?label=util%20docs)](https://docs.rs/vulkano-util) 38 | [![vulkano-win docs](https://img.shields.io/docsrs/vulkano-win?label=win%20docs)](https://docs.rs/vulkano-win) 39 | -------------------------------------------------------------------------------- /chapter-code/wip/bin/more_on_buffers/app.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use chapter_code::game_objects::Square; 4 | use winit::event::{ElementState, VirtualKeyCode}; 5 | use winit::event_loop::EventLoop; 6 | 7 | use crate::render::RenderLoop; 8 | 9 | #[derive(Default, PartialEq)] 10 | pub enum KeyState { 11 | Pressed, 12 | #[default] 13 | Released, 14 | } 15 | 16 | use KeyState::{Pressed, Released}; 17 | 18 | #[derive(Default)] 19 | struct Keys { 20 | a: KeyState, 21 | w: KeyState, 22 | s: KeyState, 23 | d: KeyState, 24 | space: KeyState, 25 | } 26 | 27 | pub struct App { 28 | render_loop: RenderLoop, 29 | square: Square, 30 | keys: Keys, 31 | } 32 | 33 | impl App { 34 | pub fn start(event_loop: &EventLoop<()>) -> Self { 35 | println!("Welcome to the movable square example!"); 36 | println!("Press WASD to move and SPACE to change color"); 37 | 38 | Self { 39 | render_loop: RenderLoop::new(event_loop), 40 | square: Square::new(), 41 | keys: Keys::default(), 42 | } 43 | } 44 | 45 | pub fn update(&mut self, duration_since_last_update: &Duration) { 46 | let seconds_passed = (duration_since_last_update.as_micros() as f32) / 1000000.0; 47 | 48 | self.update_movement(seconds_passed); 49 | 50 | self.render_loop.update(&self.square); 51 | } 52 | 53 | fn update_movement(&mut self, seconds_passed: f32) { 54 | if self.keys.w == Pressed && self.keys.s == Released { 55 | self.square.move_up(seconds_passed) 56 | } 57 | if self.keys.s == Pressed && self.keys.w == Released { 58 | self.square.move_down(seconds_passed) 59 | } 60 | if self.keys.a == Pressed && self.keys.d == Released { 61 | self.square.move_left(seconds_passed) 62 | } 63 | if self.keys.d == Pressed && self.keys.a == Released { 64 | self.square.move_right(seconds_passed) 65 | } 66 | } 67 | 68 | pub fn handle_keyboard_input(&mut self, key_code: VirtualKeyCode, state: ElementState) { 69 | let state = match state { 70 | ElementState::Pressed => Pressed, 71 | ElementState::Released => Released, 72 | }; 73 | 74 | match key_code { 75 | VirtualKeyCode::Space => { 76 | if state == Pressed && self.keys.space == Released { 77 | self.square.change_to_random_color(); 78 | } 79 | self.keys.space = state; 80 | } 81 | VirtualKeyCode::W => self.keys.w = state, 82 | VirtualKeyCode::A => self.keys.a = state, 83 | VirtualKeyCode::S => self.keys.s = state, 84 | VirtualKeyCode::D => self.keys.d = state, 85 | _ => {} 86 | } 87 | } 88 | 89 | pub fn handle_window_resize(&mut self) { 90 | self.render_loop.handle_window_resize() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/06-graphics-pipeline/01-introduction.md: -------------------------------------------------------------------------------- 1 | # Graphics pipeline introduction 2 | 3 | Up until now, we have created command buffers that perform two kind of operations: 4 | 5 | - Memory transfers (copying data between buffers and images, clearing an image). 6 | - Compute operations (dispatching a compute shader). 7 | 8 | While these two kind of operations are sufficient in order to use the power of the GPU for 9 | parallel calculations (as seen in [the Mandelbrot example](../05-images/04-mandelbrot.html)), there 10 | is a third kind of operations: graphical operations. 11 | 12 | Before they were used for general-purpose calculations, GPUs were used for graphics (hence their 13 | name). To benefit from this, GPUs provide to developers a specialized well-optimized series of 14 | steps called ***the graphics pipeline***. Using the graphics pipeline is more restrictive than 15 | using compute operations, but it is also much faster. 16 | 17 | > **Note**: There is nothing that the graphics pipeline can do that a compute pipeline couldn't do. 18 | > However the graphics pipeline is much more specialized and therefore much more optimized. Some 19 | > parts of the graphics pipeline are generally handled by dedicated chips on the hardware. 20 | 21 | Using the graphics pipeline can look complex if you haven't done any graphics programming before, 22 | but it is essential to understand it if you want to render images in an efficient way. 23 | 24 | ## Quick introduction 25 | 26 | The purpose of the graphics pipeline is to draw a certain shape on an image. This shape can be as 27 | simple as a single triangle, or as complex as a mountain range. 28 | 29 | In order to start a graphical operation (i.e. an operation that uses the graphics pipeline), you 30 | will need the following elements: 31 | 32 | - A **graphics pipeline object** that describes the way the GPU should behave, similar to the 33 | way [a compute pipeline object](../04-compute-pipeline/02-compute-pipeline.html) describes a 34 | compute operation. 35 | - One or multiple buffers containing the shape of the object we want to draw. 36 | - A ***framebuffer*** object, which is a collection of images to write to. 37 | - Just like compute pipelines, we can also pass descriptor sets (and push constants, which we 38 | haven't covered yet). 39 | 40 | When you start a graphics operation, the GPU will start by executing a ***vertex shader*** (that 41 | is part of the graphics pipeline object) on each vertex of the shape that you want to draw. This 42 | first step will allow you to position the shape on the screen. 43 | 44 | Then the GPU finds out which pixels of the target image are covered by the shape, and executes a 45 | ***fragment shader*** (also part of the graphics pipeline object) on each of these pixels. This 46 | shader is used to determine what is the color of the shape for the given pixel is. Finally the 47 | GPU will merge this color with the color that already exists at this location. 48 | 49 | The ***graphics pipeline object*** contains the vertex shader, the fragment shader, plus various 50 | options that allows one to further configure the behavior of the graphics card. 51 | 52 | > **Note**: This explanation only covers the fundamentals of graphics pipelines. Graphics pipelines 53 | > have tons of configurable options, plus additional optional shader stages. 54 | 55 | The next sections will be dedicated to covering graphics pipeline in more details. 56 | 57 | Next: [Vertex input](02-vertex-shader.html) 58 | -------------------------------------------------------------------------------- /chapter-code/wip/bin/restructuring/render/render_loop.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::VulkanError; 4 | use vulkano::{sync::GpuFuture, Validated}; 5 | use winit::event_loop::EventLoop; 6 | 7 | use crate::render::renderer::{Fence, Renderer}; 8 | 9 | pub struct RenderLoop { 10 | renderer: Renderer, 11 | recreate_swapchain: bool, 12 | window_resized: bool, 13 | fences: Vec>>, 14 | previous_fence_i: u32, 15 | } 16 | 17 | impl RenderLoop { 18 | pub fn new(event_loop: &EventLoop<()>) -> Self { 19 | let renderer = Renderer::initialize(event_loop); 20 | let frames_in_flight = renderer.get_image_count(); 21 | let fences: Vec>> = vec![None; frames_in_flight]; 22 | 23 | Self { 24 | renderer, 25 | recreate_swapchain: false, 26 | window_resized: false, 27 | fences, 28 | previous_fence_i: 0, 29 | } 30 | } 31 | 32 | pub fn update(&mut self) { 33 | if self.window_resized { 34 | self.window_resized = false; 35 | self.recreate_swapchain = false; 36 | self.renderer.handle_window_resize(); 37 | } 38 | if self.recreate_swapchain { 39 | self.recreate_swapchain = false; 40 | self.renderer.recreate_swapchain(); 41 | } 42 | 43 | let (image_i, suboptimal, acquire_future) = match self 44 | .renderer 45 | .acquire_swapchain_image() 46 | .map_err(Validated::unwrap) 47 | { 48 | Ok(r) => r, 49 | Err(VulkanError::OutOfDate) => { 50 | self.recreate_swapchain = true; 51 | return; 52 | } 53 | Err(e) => panic!("Failed to acquire next image: {:?}", e), 54 | }; 55 | 56 | if suboptimal { 57 | self.recreate_swapchain = true; 58 | } 59 | 60 | if let Some(image_fence) = &self.fences[image_i as usize] { 61 | image_fence.wait(None).unwrap(); 62 | } 63 | 64 | // logic that uses the GPU resources that are currently not used (have been waited upon) 65 | 66 | let something_needs_all_gpu_resources = false; 67 | let previous_future = match self.fences[self.previous_fence_i as usize].clone() { 68 | None => self.renderer.synchronize().boxed(), 69 | Some(fence) => { 70 | if something_needs_all_gpu_resources { 71 | fence.wait(None).unwrap(); 72 | } 73 | fence.boxed() 74 | } 75 | }; 76 | 77 | if something_needs_all_gpu_resources { 78 | // logic that can use every GPU resource (the GPU is sleeping) 79 | } 80 | 81 | let result = self 82 | .renderer 83 | .flush_next_future(previous_future, acquire_future, image_i); 84 | 85 | self.fences[image_i as usize] = match result.map_err(Validated::unwrap) { 86 | Ok(fence) => Some(Arc::new(fence)), 87 | Err(VulkanError::OutOfDate) => { 88 | self.recreate_swapchain = true; 89 | None 90 | } 91 | Err(e) => { 92 | println!("Failed to flush future: {:?}", e); 93 | None 94 | } 95 | }; 96 | 97 | self.previous_fence_i = image_i; 98 | } 99 | 100 | pub fn handle_window_resize(&mut self) { 101 | // impacts the next update 102 | self.window_resized = true; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/01-introduction/01-introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Welcome to the vulkano book! This guide will cover the basics of Vulkan and vulkano, and will help 4 | you get started with interfacing with a GPU. 5 | 6 | It will teach you the basics of graphics programming in the sense that you will know how to draw 7 | objects on the screen. However, this book doesn't cover actual graphics programming techniques, 8 | such as loading a 3D model or adding realistic lighting to a scene. At this point, the examples in 9 | the book are very basic, but we will be adding more comprehensive tutorials in the future. 10 | 11 | We will assume that you are familiar with the Rust programming language. If you are not, you should 12 | definitely [learn it first](https://www.rust-lang.org/documentation.html)! 13 | 14 | An excellent way to learn is to read examples. On top of this book, you should familiarize 15 | yourself with [vulkano examples](https://github.com/vulkano-rs/vulkano/tree/master/examples). To 16 | view the current release's examples you'll need to switch to a version tag. On the branch dropdown 17 | click the tags tab. There you'll find all released versions. Master branch will contain examples 18 | that are updated continuously to match changes that are unreleased. You should only use the master 19 | branch if you are using vulkano as a git dependency. 20 | 21 | To contribute to this book, you can create a pull request at 22 | [vulkano-book](https://github.com/vulkano-rs/vulkano-book) repository. 23 | 24 | ## Quick glossary 25 | 26 | When you create a program (either in Rust or any other programming language) and run it, the 27 | program's instructions are executed by the ***CPU*** (Central Processing Unit). 28 | 29 | But some computers also usually have a ***video card*** plugged in them. This video card has its 30 | own microprocessor called the ***GPU*** (Graphics Processing Unit) or the ***graphics processor***. 31 | It can be seen more or less as a secondary machine within your main machine. Your monitor is 32 | generally plugged in to your video card if you have one. 33 | 34 | ***Vulkan*** is a standard API whose version 1.0 was released in 2016 that lets you interface with 35 | the video card and the GPU of the machine your program is running on. ***Vulkano*** is a Rust 36 | library on top of Vulkan that makes it much easier and safer to use. After you have learned to 37 | use Vulkan/vulkano, you will be able to ask your GPU to perform operations and either write the 38 | result into memory (which you can then read from your Rust program), or to write the result to your 39 | monitor for you to physically see. 40 | 41 | ## Setup 42 | 43 | You may first need to set up some external dependencies as documented in the [vulkano 44 | readme](https://github.com/vulkano-rs/vulkano/blob/master/README.md#setup-and-troubleshooting) to 45 | avoid build failures in the future. 46 | 47 | To get started with vulkano, add it as a project dependency to your Cargo.toml: 48 | 49 | ```toml 50 | [dependencies] 51 | vulkano = "0.34.0" 52 | ``` 53 | 54 | You may want to consider adding the following minimum optimization level to your `Cargo.toml` as 55 | well. The purpose of this is to reduce performance artifacts resulting from the default debug 56 | optimization. 57 | 58 | ```toml 59 | [profile.dev] 60 | opt-level = 1 61 | ``` 62 | 63 | Note: If you run into any issues with this book, please [open an 64 | issue](https://github.com/vulkano-rs/vulkano-book/issues). If you have issues with vulkano itself, 65 | please also [open an issue](https://github.com/vulkano-rs/vulkano/issues). 66 | 67 | You are now ready to [get started](../02-initialization/01-initialization.html)! 68 | -------------------------------------------------------------------------------- /src/04-compute-pipeline/03-descriptor-sets.md: -------------------------------------------------------------------------------- 1 | # Descriptor sets 2 | 3 | In the GLSL code of the previous section, the buffer accessed by the shader was declared like 4 | this: 5 | 6 | ```glsl 7 | layout(set = 0, binding = 0) buffer Data { 8 | uint data[]; 9 | } buf; 10 | ``` 11 | 12 | In Vulkan, the buffers that a compute pipeline needs to access must be bound to what are called 13 | *descriptor*s. The code above declares such a descriptor. 14 | 15 | > **Note**: A descriptor can contain a buffer, but also other types that we haven't covered yet: 16 | > a buffer view, an image, a sampled image, etc. One or more descriptors of the same type can form 17 | > an array. 18 | 19 | A descriptor or array of descriptors is assigned to a *binding*, and bindings are grouped into 20 | *descriptor set*s. The `layout(set = 0, binding = 0)` attribute in the 21 | GLSL code indicates that this descriptor is assigned to binding 0 in the set 0. Binding indices 22 | and set indices are 0-based. 23 | 24 | What we declared in the GLSL code is actually not a descriptor set, but only a slot for a 25 | descriptor set. Before we can invoke the compute pipeline, we first need to bind an actual 26 | descriptor set to that slot. 27 | 28 |
29 | 30 | ## Creating a descriptor set 31 | 32 | Just like for buffers and command buffers, we also need an allocator for descriptor sets. 33 | 34 | For our application, we are going to use a `PersistentDescriptorSet`. When creating this descriptor 35 | set, we attach to it the result buffer wrapped in a `WriteDescriptorSet`. This object will describe 36 | how will the buffer be written: 37 | 38 | ```rust 39 | use vulkano::pipeline::Pipeline; 40 | use vulkano::descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet}; 41 | use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; 42 | 43 | let descriptor_set_allocator = 44 | StandardDescriptorSetAllocator::new(device.clone(), Default::default()); 45 | let pipeline_layout = compute_pipeline.layout(); 46 | let descriptor_set_layouts = pipeline_layout.set_layouts(); 47 | 48 | let descriptor_set_layout_index = 0; 49 | let descriptor_set_layout = descriptor_set_layouts 50 | .get(descriptor_set_layout_index) 51 | .unwrap(); 52 | let descriptor_set = PersistentDescriptorSet::new( 53 | &descriptor_set_allocator, 54 | descriptor_set_layout.clone(), 55 | [WriteDescriptorSet::buffer(0, data_buffer.clone())], // 0 is the binding 56 | [], 57 | ) 58 | .unwrap(); 59 | ``` 60 | 61 | In order to create a descriptor set, you'll need to know the layout that it is targeting. We do 62 | this by using the "Pipeline" trait and calling `.layout()` on our pipeline to obtain the pipeline's 63 | layout. Next we'll fetch the layout specific to the pass that we want to target by using 64 | `.set_layouts().get(0)` where zero indicates the first index of the pass that we are targeting. 65 | 66 | Once you have created a descriptor set, you may also use it with other pipelines, as long as the 67 | bindings' types match those the pipelines' shaders expect. But Vulkan requires that you provide a 68 | pipeline whenever you create a descriptor set; you cannot create one independently of any 69 | particular pipeline. 70 | 71 | We then bind each descriptor one by one in order, which here is just the `buf` variable. Just like 72 | for `compute_pipeline`, cloning `data_buffer` only clones an `Arc` and isn't expensive. 73 | 74 | > **Note**: `data_buffer` was created in [the introduction](01-compute-intro.html). 75 | 76 | Now that we have a compute pipeline and a descriptor set to bind to it, we can start our operation. 77 | This is covered in [the next section](04-dispatch.html). 78 | -------------------------------------------------------------------------------- /chapter-code/wip/bin/more_on_buffers/render/render_loop.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use chapter_code::game_objects::Square; 4 | use vulkano::{sync::GpuFuture, Validated, VulkanError}; 5 | use winit::event_loop::EventLoop; 6 | 7 | use crate::render::renderer::{Fence, Renderer}; 8 | 9 | pub struct RenderLoop { 10 | renderer: Renderer, 11 | recreate_swapchain: bool, 12 | window_resized: bool, 13 | fences: Vec>>, 14 | previous_fence_i: u32, 15 | } 16 | 17 | impl RenderLoop { 18 | pub fn new(event_loop: &EventLoop<()>) -> Self { 19 | let renderer = Renderer::initialize(event_loop); 20 | let frames_in_flight = renderer.get_image_count(); 21 | let fences: Vec>> = vec![None; frames_in_flight]; 22 | 23 | Self { 24 | renderer, 25 | recreate_swapchain: false, 26 | window_resized: false, 27 | fences, 28 | previous_fence_i: 0, 29 | } 30 | } 31 | 32 | pub fn update(&mut self, triangle: &Square) { 33 | if self.window_resized { 34 | self.window_resized = false; 35 | self.recreate_swapchain = false; 36 | self.renderer.handle_window_resize(); 37 | } 38 | if self.recreate_swapchain { 39 | self.recreate_swapchain = false; 40 | self.renderer.recreate_swapchain(); 41 | } 42 | 43 | let (image_i, suboptimal, acquire_future) = match self 44 | .renderer 45 | .acquire_swapchain_image() 46 | .map_err(Validated::unwrap) 47 | { 48 | Ok(r) => r, 49 | Err(VulkanError::OutOfDate) => { 50 | self.recreate_swapchain = true; 51 | return; 52 | } 53 | Err(e) => panic!("Failed to acquire next image: {:?}", e), 54 | }; 55 | 56 | if suboptimal { 57 | self.recreate_swapchain = true; 58 | } 59 | 60 | if let Some(image_fence) = &self.fences[image_i as usize] { 61 | image_fence.wait(None).unwrap(); 62 | } 63 | 64 | // logic that uses the GPU resources that are currently not used (have been waited upon) 65 | self.renderer.update_uniform(image_i, triangle); 66 | 67 | let something_needs_all_gpu_resources = false; 68 | let previous_future = match self.fences[self.previous_fence_i as usize].clone() { 69 | None => self.renderer.synchronize().boxed(), 70 | Some(fence) => { 71 | if something_needs_all_gpu_resources { 72 | fence.wait(None).unwrap(); 73 | } 74 | fence.boxed() 75 | } 76 | }; 77 | 78 | if something_needs_all_gpu_resources { 79 | // logic that can use every GPU resource (the GPU is sleeping) 80 | } 81 | 82 | let result = self 83 | .renderer 84 | .flush_next_future(previous_future, acquire_future, image_i); 85 | 86 | self.fences[image_i as usize] = match result.map_err(Validated::unwrap) { 87 | Ok(fence) => Some(Arc::new(fence)), 88 | Err(VulkanError::OutOfDate) => { 89 | self.recreate_swapchain = true; 90 | None 91 | } 92 | Err(e) => { 93 | println!("Failed to flush future: {:?}", e); 94 | None 95 | } 96 | }; 97 | 98 | self.previous_fence_i = image_i; 99 | } 100 | 101 | pub fn handle_window_resize(&mut self) { 102 | // impacts the next update 103 | self.window_resized = true; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/02-initialization/02-device-creation.md: -------------------------------------------------------------------------------- 1 | # Device creation 2 | 3 | In [the previous section](01-initialization.html) we created an instance and chose a physical 4 | device from this instance. 5 | 6 | But initialization isn't finished yet. Before being able to do anything, we have to create a 7 | ***device***. A *device* is an object that represents an open channel of communication with a 8 | *physical device*, and it is probably the most important object of the Vulkan API. 9 | 10 | ## About queues 11 | 12 | Just like how it's possible to use multiple threads in your program running on the CPU, it's also 13 | possible to run multiple operations in parallel on the GPU of your graphics card. The Vulkan 14 | equivalent of a CPU thread is a ***queue***. Queues are grouped by **queue families**. 15 | 16 | The queue families of a physical device can be enumerated like this: 17 | 18 | ```rust 19 | for family in physical_device.queue_family_properties() { 20 | println!("Found a queue family with {:?} queue(s)", family.queue_count); 21 | } 22 | ``` 23 | 24 | While some implementations only provide one family with one queue, some others have three or four 25 | families with up to sixteen queues in some of these families. 26 | 27 | > **Note**: If you want to get a more precise idea of the queue families provided by the various 28 | > Vulkan implementations, you can go to [vulkan.gpuinfo.org](http://vulkan.gpuinfo.org), click on 29 | > the report you want, and open the "Queue families" tab. 30 | 31 | Whenever we want the device to perform an operation, we have to submit this operation to a specific 32 | queue. Some queues support only graphical operations, some others support only compute operations, 33 | and some others support both. 34 | 35 | ## Creating a device 36 | 37 | The reason why queues are relevant right now is in order to create a *device*, we have to tell the 38 | Vulkan implementation which type of queues we want to use. Queues are grouped into *queue families*, 39 | which describe their capabilities. Let's locate a queue family that supports graphical operations: 40 | 41 | ```rust 42 | use vulkano::device::QueueFlags; 43 | 44 | let queue_family_index = physical_device 45 | .queue_family_properties() 46 | .iter() 47 | .position(|queue_family_properties| { 48 | queue_family_properties.queue_flags.contains(QueueFlags::GRAPHICS) 49 | }) 50 | .expect("couldn't find a graphical queue family") as u32; 51 | ``` 52 | 53 | Once we have the index of a viable queue family, we can use it to create the device: 54 | 55 | ```rust 56 | use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo}; 57 | 58 | let (device, mut queues) = Device::new( 59 | physical_device, 60 | DeviceCreateInfo { 61 | // here we pass the desired queue family to use by index 62 | queue_create_infos: vec![QueueCreateInfo { 63 | queue_family_index, 64 | ..Default::default() 65 | }], 66 | ..Default::default() 67 | }, 68 | ) 69 | .expect("failed to create device"); 70 | ``` 71 | 72 | Creating a device returns two things: the device itself, but also a list of *queue objects* that 73 | will later allow us to submit operations. 74 | 75 | Once this function call succeeds we have an open channel of communication with a Vulkan device! 76 | 77 | Since it is possible to request multiple queues, the `queues` variable returned by the function is 78 | in fact an iterator. In this example code this iterator contains just one element, so let's 79 | extract it: 80 | 81 | ```rust 82 | let queue = queues.next().unwrap(); 83 | ``` 84 | 85 | We now have our `device` and our `queue`, which means that we are ready to ask the GPU to perform 86 | operations. 87 | 88 | Next: [Creating a buffer](../03-buffer-creation/01-buffer-creation.html) 89 | -------------------------------------------------------------------------------- /src/02-initialization/01-initialization.md: -------------------------------------------------------------------------------- 1 | # Initialization 2 | 3 | ## Creating an instance 4 | 5 | Before you can start utilizing the Vulkan API, the first thing to do is to create 6 | an *instance*. An instance specifies the mapping between vulkano and the local Vulkan library. 7 | As of vulkano version `0.31.0`, the library needs to be explicitly specified by passing a 8 | `VulkanLibrary` to the `Instance` constructor. 9 | 10 | For starters, our program will be very simple, so, for now, creating an instance won't need any 11 | [additional parameters](https://docs.rs/vulkano/0.34.0/vulkano/instance/struct.InstanceCreateInfo.html), 12 | so we can create it with default configurations: 13 | 14 | ```rust 15 | use vulkano::VulkanLibrary; 16 | use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}; 17 | 18 | let library = VulkanLibrary::new().expect("no local Vulkan library/DLL"); 19 | let instance = Instance::new( 20 | library, 21 | InstanceCreateInfo { 22 | flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, 23 | ..Default::default() 24 | }, 25 | ) 26 | .expect("failed to create instance"); 27 | ``` 28 | 29 | Like many other functions in vulkano, creating an instance returns a `Result`. If Vulkan is not 30 | available on the system, this result will contain an error. For the sake of this example we call 31 | `expect` on the `Result`, which prints a message to stderr and terminates the application if it 32 | contains an error. In a real game or application you should handle that situation in a nicer way, 33 | for example by opening a dialog box with an explanation. This is out of scope of this book. 34 | 35 | The `InstanceCreateFlags::ENUMERATE_PORTABILITY` flag is set to support devices, such as those on 36 | MacOS and iOS systems, that do not fully conform to the Vulkan Specification. For more details, consult the 37 | [instance documentation](https://docs.rs/vulkano/0.34.1/vulkano/instance/index.html#portability-subset-devices-and-the-enumerate_portability-flag). 38 | 39 | Before going further you can try your code by running: 40 | 41 | ```bash 42 | cargo run 43 | ``` 44 | 45 | ## Enumerating physical devices 46 | 47 | The machine you run your program on may have multiple devices that support Vulkan. Before we can 48 | ask a video card to perform some operations, we have to enumerate all the *physical device*s that 49 | support Vulkan and choose which one we are going to use for this operation. 50 | 51 | In reality a physical device can be a dedicated graphics card, but also an integrated graphics 52 | processor or a software implementation. It can be basically anything that allows running Vulkan 53 | operations. 54 | 55 | As of the writing of this book, it is not yet possible to use multiple devices simultaneously 56 | in an efficient way (eg. SLI/Crossfire). You *can* use multiple devices simultaneously in the same 57 | program, but there is not much point in doing so because you cannot share anything between them. 58 | Consequently the best thing to do in practice is to choose one physical device which is going to 59 | run everything: 60 | 61 | ```rust 62 | let physical_device = instance 63 | .enumerate_physical_devices() 64 | .expect("could not enumerate devices") 65 | .next() 66 | .expect("no devices available"); 67 | ``` 68 | 69 | The `enumerate_physical_devices` function returns a `Result` of an iterator to the list of 70 | available physical devices. We call `next` on it to return the first device, if any. Note that the 71 | first device is not necessarily the best device. In a real program you probably want to leave the 72 | choice to the user (later we will see a better implementation of this). 73 | 74 | Keep in mind that the list of physical devices can be empty. This happens if Vulkan is installed 75 | on the system, but none of the physical devices of the machine are capable of supporting Vulkan. In 76 | a real-world application you are encouraged to handle this situation properly as well. 77 | 78 | Next: [Device creation](02-device-creation.html) 79 | -------------------------------------------------------------------------------- /src/05-images/03-image-export.md: -------------------------------------------------------------------------------- 1 | # Exporting the content of an image 2 | 3 | In [the previous section](02-image-clear.html) we filled the image with a color. 4 | 5 | But you may now wonder how to see the result of this operation. As explained previously, images 6 | are opaque structures whose actual layout is implementation-specific. So how do we read their 7 | content? 8 | 9 | The answer to this question is that we have to create a buffer and ask the GPU to copy the content 10 | of the image to the buffer. 11 | 12 | > **Note**: You can find the [full source code of this section 13 | > here](https://github.com/vulkano-rs/vulkano-book/blob/main/chapter-code/05-images/image_clear.rs). 14 | 15 | ## Copying from the image to the buffer 16 | 17 | The first step is to create the buffer, as we have already covered in previous sections. The buffer 18 | has to be large enough, otherwise the copy will result in an error. Each pixel of the image 19 | contains four unsigned 8-bit values, and the image dimensions are 1024 by 1024 pixels. Hence why 20 | the number of elements in the buffer is `1024 * 1024 * 4`. 21 | 22 | ```rust 23 | let buf = Buffer::from_iter( 24 | memory_allocator.clone(), 25 | BufferCreateInfo { 26 | usage: BufferUsage::TRANSFER_DST, 27 | ..Default::default() 28 | }, 29 | AllocationCreateInfo { 30 | memory_type_filter: MemoryTypeFilter::PREFER_HOST 31 | | MemoryTypeFilter::HOST_RANDOM_ACCESS, 32 | ..Default::default() 33 | }, 34 | (0..1024 * 1024 * 4).map(|_| 0u8), 35 | ) 36 | .expect("failed to create buffer"); 37 | ``` 38 | 39 | And let's modify the command buffer we created in the previous section to add the copy operation: 40 | 41 | ```rust 42 | use vulkano::command_buffer::CopyImageToBufferInfo; 43 | 44 | builder 45 | .clear_color_image(ClearColorImageInfo { 46 | clear_value: ClearColorValue::Float([0.0, 0.0, 1.0, 1.0]), 47 | ..ClearColorImageInfo::image(image.clone()) 48 | }) 49 | .unwrap() 50 | .copy_image_to_buffer(CopyImageToBufferInfo::image_buffer( 51 | image.clone(), 52 | buf.clone(), 53 | )) 54 | .unwrap(); 55 | ``` 56 | 57 | Since this is a memory transfer operation, this time the image values are *not* interpreted as 58 | floating-point values. The memory content of the image (unsigned 8-bit values) is directly copied 59 | to the buffer. 60 | 61 | Let's not forget to execute the command buffer and block until the operation is finished: 62 | 63 | ```rust 64 | use vulkano::sync::{self, GpuFuture}; 65 | 66 | let future = sync::now(device.clone()) 67 | .then_execute(queue.clone(), command_buffer) 68 | .unwrap() 69 | .then_signal_fence_and_flush() 70 | .unwrap(); 71 | 72 | future.wait(None).unwrap(); 73 | ``` 74 | 75 | ## Turning the image into a PNG 76 | 77 | Now that we have a buffer that contains our image data, we will visualize it by saving it as a PNG 78 | file. The Rust ecosystem has a crate named `image` that can do this. 79 | Let's add it to our Cargo.toml: 80 | 81 | ```toml 82 | image = "0.24" 83 | ``` 84 | 85 | In this library the main type that represents an image is the `ImageBuffer`. It can be created 86 | from a slice: 87 | 88 | ```rust 89 | use image::{ImageBuffer, Rgba}; 90 | 91 | let buffer_content = buf.read().unwrap(); 92 | let image = ImageBuffer::, _>::from_raw(1024, 1024, &buffer_content[..]).unwrap(); 93 | ``` 94 | 95 | The `ImageBuffer` can then be saved into a PNG file: 96 | 97 | ```rust 98 | image.save("image.png").unwrap(); 99 | 100 | println!("Everything succeeded!"); 101 | ``` 102 | 103 | And that's it! When running your program, a blue image named `image.png` should appear. 104 | 105 |
106 | 107 | 108 | *Here it is.* 109 | 110 |
111 | 112 | This might look stupid, but think about the fact that it's the GPU that wrote the content of 113 | the image. In the next sections we will do more than just fill an image with blue, but we will 114 | continue to retrieve the image's content and write it to a PNG file. 115 | 116 | Next: [Drawing a fractal with a compute shader](04-mandelbrot.html) 117 | -------------------------------------------------------------------------------- /src/04-compute-pipeline/01-compute-intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to compute operations 2 | 3 | Before we go further, we need to understand the difference between a CPU and a GPU. As a reminder, 4 | the CPU is what executes your Rust program, while the GPU is what we are trying to interface with. 5 | 6 | Both the CPU and the GPU execute instructions one by one. The instructions available for regular 7 | programs that run on the CPU include, for example, modifying a value in memory, or performing some 8 | mathematical operation. 9 | 10 | The instructions that a GPU can execute are often limited, but they can operate on a lot of 11 | data at once. You can, for example, instruct the GPU to multiply thirty-two values by a constant, 12 | in approximately the same time that a CPU would take to multiply a single value by that constant 13 | (ignoring the overhead of transferring data between the two devices). 14 | 15 | This is what makes GPUs very good at parallel computations which require executing the same 16 | sequence of operation on multiple values. While a CPU would perform this sequence on each value one 17 | by one, a GPU can perform it on multiple values at once. 18 | 19 | > **Note**: See also [SIMD](https://en.wikipedia.org/wiki/SIMD). 20 | 21 | > **Note**: In [a previous section](../02-initialization/02-device-creation.html) we talked about 22 | > *queues*. These queues are usually foremost *software* queues, and not actual hardware 23 | > constructs. 24 | 25 | > **Note**: You can find the [full source code of this chapter 26 | > here](https://github.com/vulkano-rs/vulkano-book/blob/main/chapter-code/04-compute-pipeline/main.rs). 27 | 28 | ## Usability 29 | 30 | Vulkan (or any other API) doesn't let you directly control the threading aspect of the GPU. 31 | In order to perform an operation with multiple values at once, you will only need to indicate the 32 | list of operations to perform on **one** value. The Vulkan implementation will automatically make 33 | the necessary adjustments to make your operation run on multiple values at once. 34 | 35 | This makes using a GPU much easier than if you had to manually control everything. However, you 36 | still need to be aware that your program will run multiple times in parallel, because it has 37 | consequences on what you can do without causing data races. 38 | 39 | ## Example in this book 40 | 41 | For the purpose of this book, we are going to do something very simple: we are going to multiply 42 | 65536 values by the constant 12. Even though this doesn't serve any purpose, it is a good starting 43 | point example. Most real-world usages of the GPU involve complex mathematical algorithms, and thus 44 | are not really appropriate for a tutorial. 45 | 46 | As explained above, you don't need to use any `for` loop or anything similar of that sort. All we 47 | have to do is write the operation that is performed on *one* value, and ask the GPU to execute 48 | it 65536 times. Our operation here is therefore simply (in pseudo-code): 49 | 50 | ```glsl 51 | // `index` will range from 0 to 65536 52 | buffer_content[index] *= 12; 53 | ``` 54 | 55 | While it may look like this code multiplies a single value by 12, in reality the Vulkan 56 | implementation will automatically handle all the details that make it possible to run this in 57 | parallel multiple times in the most optimized way. 58 | 59 | As a preliminary action we are going to create the buffer that will contain the values. This is 60 | similar to what we already did twice: 61 | 62 | ```rust 63 | let data_iter = 0..65536u32; 64 | let data_buffer = Buffer::from_iter( 65 | memory_allocator.clone(), 66 | BufferCreateInfo { 67 | usage: BufferUsage::STORAGE_BUFFER, 68 | ..Default::default() 69 | }, 70 | AllocationCreateInfo { 71 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 72 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 73 | ..Default::default() 74 | }, 75 | data_iter, 76 | ) 77 | .expect("failed to create buffer"); 78 | ``` 79 | 80 | The `data_buffer` buffer now contains the data before the transformation, and we are going to 81 | perform the calculation on each element. 82 | Although notice that we're using `STORAGE_BUFFER` usage this time, since the buffer will be used 83 | in the compute shader. 84 | 85 | [The next section of the book](02-compute-pipeline.html) will indicate how to actually code this 86 | operation. 87 | -------------------------------------------------------------------------------- /src/05-images/01-image-creation.md: -------------------------------------------------------------------------------- 1 | # Creating an image 2 | 3 | In [the buffers creation section of the book](../03-buffer-creation/01-buffer-creation.html) we 4 | saw that in order for the GPU to access data we had to put it in a *buffer*. 5 | This is not exactly true, as there is an alternative which are ***images***. 6 | 7 | An *image* in the context of Vulkan designates a multidimensional array of pixels. 8 | There are various hardcoded formats that the pixels of an image can use. 9 | 10 |
11 | 12 | 13 | *Example: the various images used by a Vulkan-using
14 | application, as seen from a debugger* 15 | 16 |
17 | 18 | We often use Vulkan images to store *images* in the common sense of the word, in which case each 19 | value of the array contains the color of the pixel. However Vulkan images can also be used to store 20 | arbitrary data (in other words, not just colors). 21 | 22 | > **Note**: Pixels inside images are sometimes called **texels**, which is short for 23 | > "texture pixel". **Textures** are a more specialized alternative to images but that no longer 24 | > exist in Vulkan. The word "texel" has been less and less used over time, but the word "texture" 25 | > is still very common. 26 | 27 | ## Properties of an image 28 | 29 | While we often think of images as being two-dimensional, in the context of Vulkan they can also be 30 | one-dimensional or three-dimensional. The dimensions of an image are chosen when you create it. 31 | 32 | > **Note**: There are two kinds of three-dimensional images: actual three-dimensional images, and 33 | > arrays of two-dimensional layers. The difference is that with the former the layers are expected 34 | > to be contiguous, while for the latter you can manage layers individually as if they were 35 | > separate two-dimensional images. 36 | 37 | When you create an image you must also choose a format for its pixels. Depending on the format, the 38 | pixels of an image can have between one and four components. In other words each pixel is an array 39 | of one to four values. The four components are named, in order, R, G, B and A. 40 | 41 | > **Note**: If you are familiar with RGBA, it may seem obvious to you that the R component 42 | > (the first) is supposed to contain the red value of the pixel, the G component (the second) is 43 | > supposed to contain the green value of the pixel, and same for blue and alpha. However remember 44 | > that we can store arbitrary data in this format instead of colors. 45 | 46 | You can check [the list of available formats 47 | here](https://docs.rs/vulkano/0.34.0/vulkano/format/enum.Format.html). 48 | 49 | For example if you create an image with the format `R8_SINT`, then it will only have one component. 50 | But with the format `A2R10G10B10_SSCALED_PACK32`, you have all four components. The first part of 51 | the name of each format corresponds to the memory layout of the four components. For example with 52 | `B10G11R11_UFLOAT_PACK32`, each pixel is 32 bits long where the first 10 bits is the blue component, 53 | the next 11 bits are the green component, and the last 11 bits are the red component. Don't worry 54 | if you are confused, as we will only use the most simple formats in this book. 55 | 56 | ## Image creation 57 | 58 | Similar to buffers, images are created by providing information about the image and allocation. 59 | However, unlike buffers, images always begin in an uninitialized state. 60 | 61 | ```rust 62 | use vulkano::image::{Image, ImageCreateInfo, ImageType, ImageUsage}; 63 | use vulkano::format::Format; 64 | 65 | let image = Image::new( 66 | memory_allocator.clone(), 67 | ImageCreateInfo { 68 | image_type: ImageType::Dim2d, 69 | format: Format::R8G8B8A8_UNORM, 70 | extent: [1024, 1024, 1], 71 | usage: ImageUsage::TRANSFER_DST | ImageUsage::TRANSFER_SRC, 72 | ..Default::default() 73 | }, 74 | AllocationCreateInfo { 75 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE, 76 | ..Default::default() 77 | }, 78 | ) 79 | .unwrap(); 80 | ``` 81 | 82 | We pass the dimensions of the image and the desired format. Just like buffers, images also need to 83 | be created with flags that describe how the image will be used, and using it in a way that wasn't 84 | specified when creating it will result in an error. 85 | 86 | Next: [Clearing an image](02-image-clear.html) 87 | -------------------------------------------------------------------------------- /chapter-code/wip/vulkano_objects/command_buffers.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::buffer::{BufferContents, Subbuffer}; 4 | use vulkano::command_buffer::{ 5 | AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, RenderPassBeginInfo, 6 | SubpassBeginInfo, SubpassContents, 7 | }; 8 | use vulkano::device::Queue; 9 | use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; 10 | use vulkano::render_pass::Framebuffer; 11 | 12 | use super::allocators::Allocators; 13 | use crate::vulkano_objects::buffers::Buffers; 14 | use crate::Vertex2d; 15 | 16 | pub fn create_only_vertex_command_buffers( 17 | allocators: &Allocators, 18 | queue: Arc, 19 | pipeline: Arc, 20 | framebuffers: &[Arc], 21 | vertex_buffer: Subbuffer<[Vertex2d]>, 22 | ) -> Vec> { 23 | framebuffers 24 | .iter() 25 | .map(|framebuffer| { 26 | let mut builder = AutoCommandBufferBuilder::primary( 27 | &allocators.command_buffer, 28 | queue.queue_family_index(), 29 | CommandBufferUsage::MultipleSubmit, 30 | ) 31 | .unwrap(); 32 | 33 | builder 34 | .begin_render_pass( 35 | RenderPassBeginInfo { 36 | clear_values: vec![Some([0.1, 0.1, 0.1, 1.0].into())], 37 | ..RenderPassBeginInfo::framebuffer(framebuffer.clone()) 38 | }, 39 | SubpassBeginInfo { 40 | contents: SubpassContents::Inline, 41 | ..Default::default() 42 | }, 43 | ) 44 | .unwrap() 45 | .bind_pipeline_graphics(pipeline.clone()) 46 | .unwrap() 47 | .bind_vertex_buffers(0, vertex_buffer.clone()) 48 | .unwrap() 49 | .draw(vertex_buffer.len() as u32, 1, 0, 0) 50 | .unwrap() 51 | .end_render_pass(Default::default()) 52 | .unwrap(); 53 | 54 | builder.build().unwrap() 55 | }) 56 | .collect() 57 | } 58 | 59 | pub fn create_simple_command_buffers( 60 | allocators: &Allocators, 61 | queue: Arc, 62 | pipeline: Arc, 63 | framebuffers: &[Arc], 64 | buffers: &Buffers, 65 | ) -> Vec> { 66 | framebuffers 67 | .iter() 68 | .enumerate() 69 | .map(|(i, framebuffer)| { 70 | let mut builder = AutoCommandBufferBuilder::primary( 71 | &allocators.command_buffer, 72 | queue.queue_family_index(), 73 | CommandBufferUsage::MultipleSubmit, 74 | ) 75 | .unwrap(); 76 | 77 | let index_buffer = buffers.get_index(); 78 | let index_buffer_length = index_buffer.len(); 79 | 80 | builder 81 | .begin_render_pass( 82 | RenderPassBeginInfo { 83 | clear_values: vec![Some([0.1, 0.1, 0.1, 1.0].into())], 84 | ..RenderPassBeginInfo::framebuffer(framebuffer.clone()) 85 | }, 86 | SubpassBeginInfo { 87 | contents: SubpassContents::Inline, 88 | ..Default::default() 89 | }, 90 | ) 91 | .unwrap() 92 | .bind_pipeline_graphics(pipeline.clone()) 93 | .unwrap() 94 | .bind_descriptor_sets( 95 | PipelineBindPoint::Graphics, 96 | pipeline.layout().clone(), 97 | 0, 98 | buffers.get_uniform_descriptor_set(i), 99 | ) 100 | .unwrap() 101 | .bind_vertex_buffers(0, buffers.get_vertex()) 102 | .unwrap() 103 | .bind_index_buffer(index_buffer) 104 | .unwrap() 105 | .draw_indexed(index_buffer_length as u32, 1, 0, 0, 0) 106 | .unwrap() 107 | .end_render_pass(Default::default()) 108 | .unwrap(); 109 | 110 | builder.build().unwrap() 111 | }) 112 | .collect() 113 | } 114 | -------------------------------------------------------------------------------- /src/07-windowing/01-introduction.md: -------------------------------------------------------------------------------- 1 | # Windowing introduction 2 | 3 | Up until now, we have only created applications that perform one quick action and then exit. What 4 | we are going to do next is create a window in order to draw graphics on it, and keep our 5 | application running forever until the window is closed. 6 | 7 | Strictly speaking, creating a window and handling events is **not** covered by vulkano. Vulkano, 8 | however, is capable of rendering to window(s). 9 | 10 | > **Note**: You can find the [full source code of this chapter 11 | > here](https://github.com/vulkano-rs/vulkano-book/blob/main/chapter-code/07-windowing/main.rs). 12 | 13 | ## Creating a window 14 | 15 | In order to create a window, we will use the `winit` crate. 16 | 17 | Add to your `Cargo.toml` dependencies: 18 | 19 | ```toml 20 | winit = "0.28.0" 21 | ``` 22 | 23 | We encourage you to browse [the documentation of `winit`](https://docs.rs/winit/0.28.0/winit/). 24 | 25 | Because the objects that come with creating a window are not part of Vulkan itself, the first thing 26 | that you will need to do is to enable all non-core extensions required to draw a window. 27 | `Surface::required_extensions` automatically provides them for us, so the only thing left is to 28 | pass them on to the instance creation: 29 | 30 | ```rust 31 | use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}; 32 | use vulkano::swapchain::Surface; 33 | use winit::event_loop::EventLoop; 34 | 35 | let event_loop = EventLoop::new(); // ignore this for now 36 | 37 | let library = VulkanLibrary::new().expect("no local Vulkan library/DLL"); 38 | let required_extensions = Surface::required_extensions(&event_loop); 39 | let instance = Instance::new( 40 | library, 41 | InstanceCreateInfo { 42 | flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, 43 | enabled_extensions: required_extensions, 44 | ..Default::default() 45 | }, 46 | ) 47 | .expect("failed to create instance"); 48 | ``` 49 | 50 | Now, let's create the actual window: 51 | 52 | ```rust 53 | use winit::window::WindowBuilder; 54 | 55 | let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap()); 56 | ``` 57 | 58 | We need to wrap the `Window` in an `Arc`, so that both you and vulkano can keep a reference to it. 59 | 60 | Next we need to create a *surface*, which is a cross-platform abstraction over the actual window 61 | object, that vulkano can use for rendering: 62 | 63 | ```rust 64 | let surface = Surface::from_window(instance.clone(), window.clone()); 65 | ``` 66 | 67 | After you made the change, running the program should now open a window, then immediately close it 68 | when the `main` function exits. 69 | 70 | ## Event handling 71 | 72 | In order to make our application run for as long as the window is alive, we need to handle the 73 | window's events. This is typically done after initialization, and right before the end of the 74 | `main` function. Using the `event_loop` object: 75 | 76 | ```rust 77 | use winit::event::{Event, WindowEvent}; 78 | use winit::event_loop::ControlFlow; 79 | 80 | event_loop.run(|event, _, control_flow| { 81 | match event { 82 | Event::WindowEvent { 83 | event: WindowEvent::CloseRequested, 84 | .. 85 | } => { 86 | *control_flow = ControlFlow::Exit; 87 | }, 88 | _ => () 89 | } 90 | }); 91 | ``` 92 | 93 | What this code does is block the main thread forever, and calls the closure whenever the event 94 | loop (which we used to create our window) receives an event. These events include the events that 95 | are tied to our window, such as mouse movements. 96 | 97 | When the user wants to close the window, a `WindowEvent::CloseRequested` event is received, which 98 | makes our closure set the `control_flow` to `ControlFlow::Exit` which signals to winit that we want 99 | an exit. 100 | 101 | 102 | 103 | 106 | 107 | Right now, all we're doing is creating a window and keeping our program alive for as long as the 108 | window isn't closed. The next section will show how to initialize what is called a *swapchain* on 109 | the window's surface. 110 | 111 | Next: [Swapchain creation](02-swapchain-creation.html) 112 | -------------------------------------------------------------------------------- /chapter-code/03-buffer-creation/main.rs: -------------------------------------------------------------------------------- 1 | //! This is the source code of the "Buffer Creation" chapter at http://vulkano.rs. 2 | //! 3 | //! It is not commented, as the explanations can be found in the book itself. 4 | 5 | use std::sync::Arc; 6 | 7 | use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage}; 8 | use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; 9 | use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, CopyBufferInfo}; 10 | use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo, QueueFlags}; 11 | use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}; 12 | use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}; 13 | use vulkano::sync::{self, GpuFuture}; 14 | use vulkano::VulkanLibrary; 15 | 16 | fn main() { 17 | // Initialization 18 | let library = VulkanLibrary::new().expect("no local Vulkan library/DLL"); 19 | let instance = Instance::new( 20 | library, 21 | InstanceCreateInfo { 22 | flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, 23 | ..Default::default() 24 | }, 25 | ) 26 | .expect("failed to create instance"); 27 | 28 | let physical_device = instance 29 | .enumerate_physical_devices() 30 | .expect("could not enumerate devices") 31 | .next() 32 | .expect("no devices available"); 33 | 34 | // Device creation 35 | let queue_family_index = physical_device 36 | .queue_family_properties() 37 | .iter() 38 | .enumerate() 39 | .position(|(_, q)| q.queue_flags.contains(QueueFlags::GRAPHICS)) 40 | .expect("couldn't find a graphical queue family") as u32; 41 | 42 | let (device, mut queues) = Device::new( 43 | physical_device, 44 | DeviceCreateInfo { 45 | // here we pass the desired queue family to use by index 46 | queue_create_infos: vec![QueueCreateInfo { 47 | queue_family_index, 48 | ..Default::default() 49 | }], 50 | ..Default::default() 51 | }, 52 | ) 53 | .expect("failed to create device"); 54 | 55 | let queue = queues.next().unwrap(); 56 | 57 | let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); 58 | 59 | // Example operation 60 | let source_content = 0..64; 61 | let source = Buffer::from_iter( 62 | memory_allocator.clone(), 63 | BufferCreateInfo { 64 | usage: BufferUsage::TRANSFER_SRC, 65 | ..Default::default() 66 | }, 67 | AllocationCreateInfo { 68 | memory_type_filter: MemoryTypeFilter::PREFER_HOST 69 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 70 | ..Default::default() 71 | }, 72 | source_content, 73 | ) 74 | .expect("failed to create source buffer"); 75 | 76 | let destination_content = (0..64).map(|_| 0); 77 | let destination = Buffer::from_iter( 78 | memory_allocator.clone(), 79 | BufferCreateInfo { 80 | usage: BufferUsage::TRANSFER_DST, 81 | ..Default::default() 82 | }, 83 | AllocationCreateInfo { 84 | memory_type_filter: MemoryTypeFilter::PREFER_HOST 85 | | MemoryTypeFilter::HOST_RANDOM_ACCESS, 86 | ..Default::default() 87 | }, 88 | destination_content, 89 | ) 90 | .expect("failed to create destination buffer"); 91 | 92 | let command_buffer_allocator = 93 | StandardCommandBufferAllocator::new(device.clone(), Default::default()); 94 | 95 | let mut builder = AutoCommandBufferBuilder::primary( 96 | &command_buffer_allocator, 97 | queue_family_index, 98 | CommandBufferUsage::OneTimeSubmit, 99 | ) 100 | .unwrap(); 101 | 102 | builder 103 | .copy_buffer(CopyBufferInfo::buffers(source.clone(), destination.clone())) 104 | .unwrap(); 105 | 106 | let command_buffer = builder.build().unwrap(); 107 | 108 | // Start the execution 109 | let future = sync::now(device) 110 | .then_execute(queue, command_buffer) 111 | .unwrap() 112 | .then_signal_fence_and_flush() // same as signal fence, and then flush 113 | .unwrap(); 114 | // Wait for the GPU to finish 115 | future.wait(None).unwrap(); 116 | 117 | let src_content = source.read().unwrap(); 118 | let destination_content = destination.read().unwrap(); 119 | assert_eq!(&*src_content, &*destination_content); 120 | 121 | println!("Everything succeeded!"); 122 | } 123 | -------------------------------------------------------------------------------- /chapter-code/05-images/image_clear.rs: -------------------------------------------------------------------------------- 1 | //! This is the source code of the first three subchapters from the "Using images" chapter at http://vulkano.rs. 2 | //! 3 | //! It is not commented, as the explanations can be found in the book itself. 4 | 5 | use std::sync::Arc; 6 | 7 | use image::{ImageBuffer, Rgba}; 8 | use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage}; 9 | use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; 10 | use vulkano::command_buffer::{ 11 | AutoCommandBufferBuilder, ClearColorImageInfo, CommandBufferUsage, CopyImageToBufferInfo, 12 | }; 13 | use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo, QueueFlags}; 14 | use vulkano::format::{ClearColorValue, Format}; 15 | use vulkano::image::{Image, ImageCreateInfo, ImageType, ImageUsage}; 16 | use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}; 17 | use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}; 18 | use vulkano::sync; 19 | use vulkano::sync::GpuFuture; 20 | 21 | pub fn main() { 22 | let library = vulkano::VulkanLibrary::new().expect("no local Vulkan library/DLL"); 23 | let instance = Instance::new( 24 | library, 25 | InstanceCreateInfo { 26 | flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, 27 | ..Default::default() 28 | }, 29 | ) 30 | .expect("failed to create instance"); 31 | 32 | let physical = instance 33 | .enumerate_physical_devices() 34 | .expect("could not enumerate devices") 35 | .next() 36 | .expect("no devices available"); 37 | 38 | let queue_family_index = physical 39 | .queue_family_properties() 40 | .iter() 41 | .enumerate() 42 | .position(|(_, q)| q.queue_flags.contains(QueueFlags::GRAPHICS)) 43 | .expect("couldn't find a graphical queue family") as u32; 44 | 45 | let (device, mut queues) = Device::new( 46 | physical, 47 | DeviceCreateInfo { 48 | queue_create_infos: vec![QueueCreateInfo { 49 | queue_family_index, 50 | ..Default::default() 51 | }], 52 | ..Default::default() 53 | }, 54 | ) 55 | .expect("failed to create device"); 56 | 57 | let queue = queues.next().unwrap(); 58 | 59 | let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); 60 | let command_buffer_allocator = 61 | StandardCommandBufferAllocator::new(device.clone(), Default::default()); 62 | 63 | // Image creation 64 | let image = Image::new( 65 | memory_allocator.clone(), 66 | ImageCreateInfo { 67 | image_type: ImageType::Dim2d, 68 | format: Format::R8G8B8A8_UNORM, 69 | extent: [1024, 1024, 1], 70 | usage: ImageUsage::TRANSFER_DST | ImageUsage::TRANSFER_SRC, 71 | ..Default::default() 72 | }, 73 | AllocationCreateInfo { 74 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE, 75 | ..Default::default() 76 | }, 77 | ) 78 | .unwrap(); 79 | 80 | let buf = Buffer::from_iter( 81 | memory_allocator.clone(), 82 | BufferCreateInfo { 83 | usage: BufferUsage::TRANSFER_DST, 84 | ..Default::default() 85 | }, 86 | AllocationCreateInfo { 87 | memory_type_filter: MemoryTypeFilter::PREFER_HOST 88 | | MemoryTypeFilter::HOST_RANDOM_ACCESS, 89 | ..Default::default() 90 | }, 91 | (0..1024 * 1024 * 4).map(|_| 0u8), 92 | ) 93 | .expect("failed to create buffer"); 94 | 95 | let mut builder = AutoCommandBufferBuilder::primary( 96 | &command_buffer_allocator, 97 | queue.queue_family_index(), 98 | CommandBufferUsage::OneTimeSubmit, 99 | ) 100 | .unwrap(); 101 | builder 102 | .clear_color_image(ClearColorImageInfo { 103 | clear_value: ClearColorValue::Float([0.0, 0.0, 1.0, 1.0]), 104 | ..ClearColorImageInfo::image(image.clone()) 105 | }) 106 | .unwrap() 107 | .copy_image_to_buffer(CopyImageToBufferInfo::image_buffer(image, buf.clone())) 108 | .unwrap(); 109 | let command_buffer = builder.build().unwrap(); 110 | 111 | let future = sync::now(device) 112 | .then_execute(queue, command_buffer) 113 | .unwrap() 114 | .then_signal_fence_and_flush() 115 | .unwrap(); 116 | 117 | future.wait(None).unwrap(); 118 | 119 | // Exporting the result 120 | let buffer_content = buf.read().unwrap(); 121 | let image = ImageBuffer::, _>::from_raw(1024, 1024, &buffer_content[..]).unwrap(); 122 | image.save("image.png").unwrap(); 123 | 124 | println!("Everything succeeded!"); 125 | } 126 | -------------------------------------------------------------------------------- /src/06-graphics-pipeline/02-vertex-shader.md: -------------------------------------------------------------------------------- 1 | # Vertex input 2 | 3 | ## Vertex buffer 4 | 5 | The first part of drawing an object with the graphics pipeline is to describe the shape of this 6 | object. When you think "shape", you may think of squares, circles, etc., but in graphics 7 | programming the most common shapes that one will need to work with are triangles. 8 | 9 | > **Note**: Tessellation shaders and alternative `PrimitiveTopology` values unlock the possibility 10 | > to use other polygons, but this is a more advanced topic. 11 | 12 | Each triangle is made of three vertices, and the shape of an object is just a collection of 13 | vertices linked together to form triangles. For the purpose of this book, we are only going to 14 | draw a single triangle first. 15 | 16 | The first step to describe a shape with vulkano is to create a struct named `MyVertex` (the actual 17 | name doesn't matter) whose purpose is to describe the properties of a single vertex. Once this is 18 | done, the shape of our triangle is going to be a buffer whose content is an array of three 19 | `MyVertex` objects. 20 | 21 | ```rust 22 | use vulkano::buffer::BufferContents; 23 | use vulkano::pipeline::graphics::vertex_input::Vertex; 24 | 25 | #[derive(BufferContents, Vertex)] 26 | #[repr(C)] 27 | struct MyVertex { 28 | #[format(R32G32_SFLOAT)] 29 | position: [f32; 2], 30 | } 31 | ``` 32 | 33 | Our struct contains a `position` field which we will use to store the position of the vertex on the 34 | image we are drawing to. Being a vectorial renderer, Vulkan doesn't use coordinates in pixels. 35 | Instead it considers that the image has a width and a height of 2 units (-1.0 to 1.0), and that the 36 | origin is at the center of the image. 37 | 38 |
39 | 40 | When we give positions to Vulkan, we need to use its coordinate system. 41 | 42 | In this book we are going to draw only a single triangle for now. Let's pick a shape for it, 43 | for example this one: 44 | 45 |
46 | 47 | Which translates into this code: 48 | 49 | ```rust 50 | let vertex1 = MyVertex { position: [-0.5, -0.5] }; 51 | let vertex2 = MyVertex { position: [ 0.0, 0.5] }; 52 | let vertex3 = MyVertex { position: [ 0.5, -0.25] }; 53 | ``` 54 | 55 | > **Note**: The field that contains the position is named `position`, but note that this name is 56 | > arbitrary. We will see below how to actually pass that position to the GPU. 57 | 58 | Now all we have to do is create a buffer that contains these three vertices. This buffer 59 | will be passed as a parameter when we start the drawing operation. 60 | 61 | ```rust 62 | let vertex_buffer = Buffer::from_iter( 63 | memory_allocator.clone(), 64 | BufferCreateInfo { 65 | usage: BufferUsage::VERTEX_BUFFER, 66 | ..Default::default() 67 | }, 68 | AllocationCreateInfo { 69 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 70 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 71 | ..Default::default() 72 | }, 73 | vec![vertex1, vertex2, vertex3], 74 | ) 75 | .unwrap(); 76 | ``` 77 | 78 | A buffer that contains a collection of vertices is commonly named a *vertex buffer*. Because we 79 | know the specific use of this buffer is for storing vertices, we specify the usage flag 80 | `VERTEX_BUFFER`. 81 | 82 | > **Note**: Vertex buffers are not special in any way. The term *vertex buffer* indicates the way 83 | > the programmer intends to use the buffer, and it is not a property of the buffer. 84 | 85 | ## Vertex shader 86 | 87 | At the start of the drawing operation, the GPU is going to pick each element from this buffer one 88 | by one and call a ***vertex shader*** on them. 89 | 90 | Here is what the source code of a vertex shader looks like: 91 | 92 | ```glsl 93 | #version 460 94 | 95 | layout(location = 0) in vec2 position; 96 | 97 | void main() { 98 | gl_Position = vec4(position, 0.0, 1.0); 99 | } 100 | ``` 101 | 102 | The line `layout(location = 0) in vec2 position;` declares that each vertex has an *attribute* 103 | named `position` and of type `vec2`. This corresponds to the definition of the `MyVertex` struct we 104 | created. 105 | 106 | > **Note**: The `Vertex` trait is used to describe the attributes of an individual vertex that 107 | > can be read by a vertex shader. It provides methods for specifying the format of the vertex's 108 | > fields, which can be done using field attributes like `format` and `name` when deriving the 109 | > trait using the `Vertex` derive macro. 110 | 111 | The `main` function is called once for each vertex, and sets the value of the `gl_Position` 112 | variable to a `vec4` whose first two components are the position of the vertex. 113 | 114 | `gl_Position` is a special "magic" global variable that exists only in the context of a vertex 115 | shader and whose value must be set to the position of the vertex on the surface. This is how the 116 | GPU knows how to position our shape. 117 | 118 | ## After the vertex shader 119 | 120 | After the vertex shader has run on each vertex, the GPU knows where our shape is located on the 121 | screen. It then proceeds to call [the fragment shader](03-fragment-shader.html). 122 | -------------------------------------------------------------------------------- /src/06-graphics-pipeline/04-render-pass-framebuffer.md: -------------------------------------------------------------------------------- 1 | # Render passes 2 | 3 | In order to fully optimize and parallelize command execution, we can't just ask the GPU 4 | to draw a shape whenever we want. Instead we first have to enter a special "rendering mode" by 5 | *entering* what is called a ***render pass***. It is only once we have entered a render pass that 6 | you can draw. 7 | 8 | ## What is a render pass? 9 | 10 | The term "render pass" describes two things: 11 | 12 | - It designates the "rendering mode" we have to enter before we can add drawing commands to 13 | a command buffer. 14 | 15 | - It also designates a kind of object that describes this rendering mode. 16 | 17 | Entering a render pass (as in "the rendering mode") requires passing a render pass object. 18 | 19 | ## Creating a render pass 20 | 21 | For the moment, the only thing we want to do is draw some color to a single image. This is the most 22 | simple case possible, and we only need to provide two things to a render pass: the format of 23 | the image, and the fact that we don't use multisampling (which is an anti-aliasing technique). 24 | 25 | More complex games can use render passes in very complex ways, with multiple subpasses and 26 | multiple attachments, and with various micro-optimizations. Vulkano's API is suitable for both the 27 | simple cases and the complex usages, which is why it may look complex at first. 28 | 29 | ```rust 30 | let render_pass = vulkano::single_pass_renderpass!( 31 | device.clone(), 32 | attachments: { 33 | color: { 34 | format: Format::R8G8B8A8_UNORM, 35 | samples: 1, 36 | load_op: Clear, 37 | store_op: Store, 38 | }, 39 | }, 40 | pass: { 41 | color: [color], 42 | depth_stencil: {}, 43 | }, 44 | ) 45 | .unwrap(); 46 | ``` 47 | 48 | A render pass is made of **attachments** and **passes**. Here we declare one attachment whose name 49 | is `color` (the name is arbitrary), and one pass that will use `color` as its single output. 50 | 51 | The `load_op: Clear` line indicates that we want the GPU to *clear* the image when entering the 52 | render pass (i.e. fill it with a single color), while `store_op: Store` indicates that we want the 53 | GPU to actually store the output of our draw commands to the image. 54 | 55 | > **Note**: It is possible to create temporary images whose content is only relevant inside of a 56 | > render pass, in which case it is optimal to use `store_op: DontCare` instead of 57 | > `store_op: Store`. 58 | 59 | ## Entering the render pass 60 | 61 | A render pass only describes the format and the way we load and store the image we are going to 62 | draw upon. It is enough to initialize all the objects we need. 63 | 64 | But before we can draw, we also need to indicate the actual list of attachments. This is done 65 | by creating a *framebuffer*. 66 | 67 | Creating a framebuffer is typically done as part of the rendering process. It is not a 68 | bad idea to keep the framebuffer objects alive between frames, but it won't kill your 69 | performance to create and destroy a few framebuffer objects during some frames. 70 | 71 | ```rust 72 | use vulkano::render_pass::{Framebuffer, FramebufferCreateInfo}; 73 | 74 | let view = ImageView::new_default(image.clone()).unwrap(); 75 | let framebuffer = Framebuffer::new( 76 | render_pass.clone(), 77 | FramebufferCreateInfo { 78 | attachments: vec![view], 79 | ..Default::default() 80 | }, 81 | ) 82 | .unwrap(); 83 | ``` 84 | 85 | We are now ready the enter drawing mode! 86 | 87 | This is done by calling the `begin_render_pass` function on the command buffer builder. 88 | This function takes as parameter the framebuffer, a enum, and a `Vec` that contains the colors 89 | to fill the attachments with. Since we have only one single attachment, this `Vec` contains only 90 | one element. 91 | 92 | Clearing our attachment has exactly the same effect as the `clear_color_image` function we covered 93 | previously, except that this time it is done by the rendering engine. 94 | 95 | The enum passed as second parameter describes whether we are going to directly invoke draw 96 | commands or use secondary command buffers instead. Secondary command buffers are a more advanced 97 | topic. Be we are using only direct commands, we will leave it as `::Inline` 98 | 99 | As a demonstration, let's just enter a render pass and leave it immediately after: 100 | 101 | ```rust 102 | use vulkano::command_buffer::{ 103 | RenderPassBeginInfo, SubpassBeginInfo, SubpassContents, SubpassEndInfo, 104 | }; 105 | 106 | let mut builder = AutoCommandBufferBuilder::primary( 107 | &command_buffer_allocator, 108 | queue.queue_family_index(), 109 | CommandBufferUsage::OneTimeSubmit, 110 | ) 111 | .unwrap(); 112 | 113 | builder 114 | .begin_render_pass( 115 | RenderPassBeginInfo { 116 | clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into())], 117 | ..RenderPassBeginInfo::framebuffer(framebuffer.clone()) 118 | }, 119 | SubpassBeginInfo { 120 | contents: SubpassContents::Inline, 121 | ..Default::default() 122 | }, 123 | ) 124 | .unwrap() 125 | .end_render_pass(SubpassEndInfo::default()) 126 | .unwrap(); 127 | ``` 128 | 129 | The [next section](05-pipeline-creation.html) will introduce the `draw` command, which will 130 | be inserted between `begin_render_pass` and `end_render_pass`. 131 | -------------------------------------------------------------------------------- /src/06-graphics-pipeline/guide-vertex-input-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 23 | 24 | 55 | 57 | 58 | 60 | image/svg+xml 61 | 63 | 64 | 65 | 66 | 67 | 72 | 79 | (-0.5, -0.5) 90 | (0, 0.5) 101 | (0.5, -0.25) 112 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /chapter-code/04-compute-pipeline/main.rs: -------------------------------------------------------------------------------- 1 | //! This is the source code of the "Compute pipeline" chapter at http://vulkano.rs. 2 | //! 3 | //! It is not commented, as the explanations can be found in the book itself. 4 | 5 | use std::sync::Arc; 6 | 7 | use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage}; 8 | use vulkano::command_buffer::allocator::{ 9 | StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo, 10 | }; 11 | use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage}; 12 | use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; 13 | use vulkano::descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet}; 14 | use vulkano::device::{Device, DeviceCreateInfo, DeviceExtensions, QueueCreateInfo, QueueFlags}; 15 | use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}; 16 | use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}; 17 | use vulkano::pipeline::compute::ComputePipelineCreateInfo; 18 | use vulkano::pipeline::layout::PipelineDescriptorSetLayoutCreateInfo; 19 | use vulkano::pipeline::{ 20 | ComputePipeline, Pipeline, PipelineBindPoint, PipelineLayout, PipelineShaderStageCreateInfo, 21 | }; 22 | use vulkano::sync::{self, GpuFuture}; 23 | 24 | fn main() { 25 | let library = vulkano::VulkanLibrary::new().expect("no local Vulkan library/DLL"); 26 | let instance = Instance::new( 27 | library, 28 | InstanceCreateInfo { 29 | flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, 30 | ..Default::default() 31 | }, 32 | ) 33 | .expect("failed to create instance"); 34 | 35 | let physical_device = instance 36 | .enumerate_physical_devices() 37 | .expect("could not enumerate devices") 38 | .next() 39 | .expect("no devices available"); 40 | 41 | let queue_family_index = physical_device 42 | .queue_family_properties() 43 | .iter() 44 | .enumerate() 45 | .position(|(_, queue_family_properties)| { 46 | queue_family_properties 47 | .queue_flags 48 | .contains(QueueFlags::COMPUTE) 49 | }) 50 | .expect("couldn't find a compute queue family") as u32; 51 | 52 | let (device, mut queues) = Device::new( 53 | physical_device, 54 | DeviceCreateInfo { 55 | queue_create_infos: vec![QueueCreateInfo { 56 | queue_family_index, 57 | ..Default::default() 58 | }], 59 | enabled_extensions: DeviceExtensions { 60 | khr_storage_buffer_storage_class: true, 61 | ..DeviceExtensions::empty() 62 | }, 63 | ..Default::default() 64 | }, 65 | ) 66 | .expect("failed to create device"); 67 | 68 | let queue = queues.next().unwrap(); 69 | 70 | // Introduction to compute operations 71 | 72 | let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); 73 | 74 | let data_iter = 0..65536u32; 75 | let data_buffer = Buffer::from_iter( 76 | memory_allocator.clone(), 77 | BufferCreateInfo { 78 | usage: BufferUsage::STORAGE_BUFFER, 79 | ..Default::default() 80 | }, 81 | AllocationCreateInfo { 82 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 83 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 84 | ..Default::default() 85 | }, 86 | data_iter, 87 | ) 88 | .expect("failed to create buffer"); 89 | 90 | // Compute pipelines 91 | mod cs { 92 | vulkano_shaders::shader! { 93 | ty: "compute", 94 | src: " 95 | #version 460 96 | 97 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 98 | 99 | layout(set = 0, binding = 0) buffer Data { 100 | uint data[]; 101 | } buf; 102 | 103 | void main() { 104 | uint idx = gl_GlobalInvocationID.x; 105 | buf.data[idx] *= 12; 106 | } 107 | " 108 | } 109 | } 110 | 111 | let shader = cs::load(device.clone()).expect("failed to create shader module"); 112 | 113 | let cs = shader.entry_point("main").unwrap(); 114 | let stage = PipelineShaderStageCreateInfo::new(cs); 115 | let layout = PipelineLayout::new( 116 | device.clone(), 117 | PipelineDescriptorSetLayoutCreateInfo::from_stages([&stage]) 118 | .into_pipeline_layout_create_info(device.clone()) 119 | .unwrap(), 120 | ) 121 | .unwrap(); 122 | 123 | let compute_pipeline = ComputePipeline::new( 124 | device.clone(), 125 | None, 126 | ComputePipelineCreateInfo::stage_layout(stage, layout), 127 | ) 128 | .expect("failed to create compute pipeline"); 129 | 130 | let descriptor_set_allocator = 131 | StandardDescriptorSetAllocator::new(device.clone(), Default::default()); 132 | 133 | let pipeline_layout = compute_pipeline.layout(); 134 | let descriptor_set_layouts = pipeline_layout.set_layouts(); 135 | let descriptor_set_layout_index = 0; 136 | let descriptor_set_layout = descriptor_set_layouts 137 | .get(descriptor_set_layout_index) 138 | .unwrap(); 139 | 140 | let descriptor_set = PersistentDescriptorSet::new( 141 | &descriptor_set_allocator, 142 | descriptor_set_layout.clone(), 143 | [WriteDescriptorSet::buffer(0, data_buffer.clone())], // 0 is the binding 144 | [], 145 | ) 146 | .unwrap(); 147 | 148 | let command_buffer_allocator = StandardCommandBufferAllocator::new( 149 | device.clone(), 150 | StandardCommandBufferAllocatorCreateInfo::default(), 151 | ); 152 | 153 | let mut command_buffer_builder = AutoCommandBufferBuilder::primary( 154 | &command_buffer_allocator, 155 | queue.queue_family_index(), 156 | CommandBufferUsage::OneTimeSubmit, 157 | ) 158 | .unwrap(); 159 | 160 | let work_group_counts = [1024, 1, 1]; 161 | 162 | command_buffer_builder 163 | .bind_pipeline_compute(compute_pipeline.clone()) 164 | .unwrap() 165 | .bind_descriptor_sets( 166 | PipelineBindPoint::Compute, 167 | compute_pipeline.layout().clone(), 168 | descriptor_set_layout_index as u32, 169 | descriptor_set, 170 | ) 171 | .unwrap() 172 | .dispatch(work_group_counts) 173 | .unwrap(); 174 | 175 | let command_buffer = command_buffer_builder.build().unwrap(); 176 | 177 | let future = sync::now(device) 178 | .then_execute(queue, command_buffer) 179 | .unwrap() 180 | .then_signal_fence_and_flush() 181 | .unwrap(); 182 | 183 | future.wait(None).unwrap(); 184 | 185 | let content = data_buffer.read().unwrap(); 186 | for (n, val) in content.iter().enumerate() { 187 | assert_eq!(*val, n as u32 * 12); 188 | } 189 | 190 | println!("Everything succeeded!"); 191 | } 192 | -------------------------------------------------------------------------------- /chapter-code/05-images/mandelbrot.rs: -------------------------------------------------------------------------------- 1 | //! This is the source code of the "Drawing a fractal with a compute shader" subchapter 2 | //! from the "Using images" chapter at http://vulkano.rs. 3 | //! 4 | //! It is not commented, as the explanations can be found in the book itself. 5 | 6 | use std::sync::Arc; 7 | 8 | use image::{ImageBuffer, Rgba}; 9 | use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage}; 10 | use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; 11 | use vulkano::command_buffer::{ 12 | AutoCommandBufferBuilder, CommandBufferUsage, CopyImageToBufferInfo, 13 | }; 14 | use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; 15 | use vulkano::descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet}; 16 | use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo, QueueFlags}; 17 | use vulkano::format::Format; 18 | use vulkano::image::view::ImageView; 19 | use vulkano::image::{Image, ImageCreateInfo, ImageType, ImageUsage}; 20 | use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}; 21 | use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}; 22 | use vulkano::pipeline::compute::ComputePipelineCreateInfo; 23 | use vulkano::pipeline::layout::PipelineDescriptorSetLayoutCreateInfo; 24 | use vulkano::pipeline::{ 25 | ComputePipeline, Pipeline, PipelineBindPoint, PipelineLayout, PipelineShaderStageCreateInfo, 26 | }; 27 | use vulkano::sync::{self, GpuFuture}; 28 | 29 | pub fn main() { 30 | let library = vulkano::VulkanLibrary::new().expect("no local Vulkan library/DLL"); 31 | let instance = Instance::new( 32 | library, 33 | InstanceCreateInfo { 34 | flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, 35 | ..Default::default() 36 | }, 37 | ) 38 | .expect("failed to create instance"); 39 | 40 | let physical = instance 41 | .enumerate_physical_devices() 42 | .expect("could not enumerate devices") 43 | .next() 44 | .expect("no devices available"); 45 | 46 | let queue_family_index = physical 47 | .queue_family_properties() 48 | .iter() 49 | .enumerate() 50 | .position(|(_, q)| q.queue_flags.contains(QueueFlags::GRAPHICS)) 51 | .expect("couldn't find a graphical queue family") as u32; 52 | 53 | let (device, mut queues) = Device::new( 54 | physical, 55 | DeviceCreateInfo { 56 | queue_create_infos: vec![QueueCreateInfo { 57 | queue_family_index, 58 | ..Default::default() 59 | }], 60 | ..Default::default() 61 | }, 62 | ) 63 | .expect("failed to create device"); 64 | 65 | let queue = queues.next().unwrap(); 66 | 67 | mod cs { 68 | vulkano_shaders::shader! { 69 | ty: "compute", 70 | src: r" 71 | #version 460 72 | 73 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; 74 | 75 | layout(set = 0, binding = 0, rgba8) uniform writeonly image2D img; 76 | 77 | void main() { 78 | vec2 norm_coordinates = (gl_GlobalInvocationID.xy + vec2(0.5)) / vec2(imageSize(img)); 79 | 80 | vec2 c = (norm_coordinates - vec2(0.5)) * 2.0 - vec2(1.0, 0.0); 81 | 82 | vec2 z = vec2(0.0, 0.0); 83 | float i; 84 | for (i = 0.0; i < 1.0; i += 0.005) { 85 | z = vec2( 86 | z.x * z.x - z.y * z.y + c.x, 87 | z.y * z.x + z.x * z.y + c.y 88 | ); 89 | 90 | if (length(z) > 4.0) { 91 | break; 92 | } 93 | } 94 | 95 | vec4 to_write = vec4(vec3(i), 1.0); 96 | imageStore(img, ivec2(gl_GlobalInvocationID.xy), to_write); 97 | } 98 | ", 99 | } 100 | } 101 | 102 | let shader = cs::load(device.clone()).expect("failed to create shader module"); 103 | 104 | let cs = shader.entry_point("main").unwrap(); 105 | let stage = PipelineShaderStageCreateInfo::new(cs); 106 | let layout = PipelineLayout::new( 107 | device.clone(), 108 | PipelineDescriptorSetLayoutCreateInfo::from_stages([&stage]) 109 | .into_pipeline_layout_create_info(device.clone()) 110 | .unwrap(), 111 | ) 112 | .unwrap(); 113 | 114 | let compute_pipeline = ComputePipeline::new( 115 | device.clone(), 116 | None, 117 | ComputePipelineCreateInfo::stage_layout(stage, layout), 118 | ) 119 | .expect("failed to create compute pipeline"); 120 | 121 | let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); 122 | 123 | let image = Image::new( 124 | memory_allocator.clone(), 125 | ImageCreateInfo { 126 | image_type: ImageType::Dim2d, 127 | format: Format::R8G8B8A8_UNORM, 128 | extent: [1024, 1024, 1], 129 | usage: ImageUsage::STORAGE | ImageUsage::TRANSFER_SRC, 130 | ..Default::default() 131 | }, 132 | AllocationCreateInfo { 133 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE, 134 | ..Default::default() 135 | }, 136 | ) 137 | .unwrap(); 138 | 139 | let view = ImageView::new_default(image.clone()).unwrap(); 140 | 141 | let descriptor_set_allocator = 142 | StandardDescriptorSetAllocator::new(device.clone(), Default::default()); 143 | 144 | let layout = compute_pipeline.layout().set_layouts().get(0).unwrap(); 145 | let set = PersistentDescriptorSet::new( 146 | &descriptor_set_allocator, 147 | layout.clone(), 148 | [WriteDescriptorSet::image_view(0, view)], // 0 is the binding 149 | [], 150 | ) 151 | .unwrap(); 152 | 153 | let buf = Buffer::from_iter( 154 | memory_allocator.clone(), 155 | BufferCreateInfo { 156 | usage: BufferUsage::TRANSFER_DST, 157 | ..Default::default() 158 | }, 159 | AllocationCreateInfo { 160 | memory_type_filter: MemoryTypeFilter::PREFER_HOST 161 | | MemoryTypeFilter::HOST_RANDOM_ACCESS, 162 | ..Default::default() 163 | }, 164 | (0..1024 * 1024 * 4).map(|_| 0u8), 165 | ) 166 | .expect("failed to create buffer"); 167 | 168 | let command_buffer_allocator = 169 | StandardCommandBufferAllocator::new(device.clone(), Default::default()); 170 | 171 | let mut builder = AutoCommandBufferBuilder::primary( 172 | &command_buffer_allocator, 173 | queue.queue_family_index(), 174 | CommandBufferUsage::OneTimeSubmit, 175 | ) 176 | .unwrap(); 177 | builder 178 | .bind_pipeline_compute(compute_pipeline.clone()) 179 | .unwrap() 180 | .bind_descriptor_sets( 181 | PipelineBindPoint::Compute, 182 | compute_pipeline.layout().clone(), 183 | 0, 184 | set, 185 | ) 186 | .unwrap() 187 | .dispatch([1024 / 8, 1024 / 8, 1]) 188 | .unwrap() 189 | .copy_image_to_buffer(CopyImageToBufferInfo::image_buffer(image, buf.clone())) 190 | .unwrap(); 191 | 192 | let command_buffer = builder.build().unwrap(); 193 | 194 | let future = sync::now(device) 195 | .then_execute(queue, command_buffer) 196 | .unwrap() 197 | .then_signal_fence_and_flush() 198 | .unwrap(); 199 | 200 | future.wait(None).unwrap(); 201 | 202 | let buffer_content = buf.read().unwrap(); 203 | let image = ImageBuffer::, _>::from_raw(1024, 1024, &buffer_content[..]).unwrap(); 204 | image.save("image.png").unwrap(); 205 | 206 | println!("Everything succeeded!"); 207 | } 208 | -------------------------------------------------------------------------------- /src/04-compute-pipeline/02-compute-pipeline.md: -------------------------------------------------------------------------------- 1 | # Compute pipelines 2 | 3 | In order to ask the GPU to perform an operation, we have to write some kind of code for it, like we 4 | would for a regular program. A program that runs on the GPU is called a ***shader***. 5 | 6 | This is done in two steps: 7 | 8 | - First we write the source code of the program in a programming language called *GLSL*. Vulkano 9 | will compile the GLSL code at compile-time into an intermediate representation called *SPIR-V*. 10 | - At runtime, we pass this *SPIR-V* to the Vulkan implementation (GPU driver), which in turn 11 | converts it into its own implementation-specific format. 12 | 13 |
14 | 15 | > **Note**: In the very far future it may be possible to write shaders in Rust, or in a 16 | > domain specific language that resembles Rust. 17 | 18 | ## The GLSL code 19 | 20 | But first, we need to write the source code of the operation. The GLSL language looks a lot like 21 | the C programming language, but has some differences. 22 | 23 | This book is not going to cover teaching you GLSL, as it is an entire programming language. As 24 | with many programming languages, the easiest way to learn GLSL is by looking at examples. 25 | 26 | Let's take a look at some GLSL that takes each element of a buffer and multiplies it by 12: 27 | 28 | ```glsl 29 | #version 460 30 | 31 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 32 | 33 | layout(set = 0, binding = 0) buffer Data { 34 | uint data[]; 35 | } buf; 36 | 37 | void main() { 38 | uint idx = gl_GlobalInvocationID.x; 39 | buf.data[idx] *= 12; 40 | } 41 | ``` 42 | 43 | Let's break it down a bit. 44 | 45 | ```glsl 46 | #version 460 47 | ``` 48 | 49 | The first line indicates which version of GLSL to use. Since GLSL was already the shading language 50 | of the OpenGL API (Vulkan's predecessor), we are in fact already at the version 4.60 of the 51 | language. You should always include this line at the start of every shader. 52 | 53 | > **Note**: You can use an older version for compatibility with older GPUs and Vulkan 54 | > implementations. 55 | 56 | ```glsl 57 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 58 | ``` 59 | 60 | We want to invoke the compute shader 65536 times in total, once for each element in the buffer. 61 | But in practice we are going to ask the GPU to spawn 1024 ***work groups***, where each work group 62 | has a ***local size*** of 64. This line of code declares what the *local size* is. Each element of 63 | the local size corresponds to one invocation of the shader, which gives us 1024 * 64 = 65536 64 | invocations. 65 | 66 | You should always try to aim for a local size of at least 32 to 64. While we could ask to spawn 67 | 65536 work groups with a local size of 1, in practice this has been benchmarked to be slower than 68 | using a larger local size. 69 | 70 | For convenience, the number of work groups and the local size are three-dimensional. You can use 71 | the `y` and `z` coordinates if you access a two-dimensional or a three-dimensional data structure. 72 | The shader will be called once for each possible combination of values for `x`, `y` and `z`. 73 | 74 | ```glsl 75 | layout(set = 0, binding = 0) buffer Data { 76 | uint data[]; 77 | } buf; 78 | ``` 79 | 80 | This declares a *descriptor*, which we are going to cover in more details [in the next 81 | section](03-descriptor-sets.html). In particular, we declare a buffer whose name is `buf` and that 82 | we are going to access in our code. 83 | 84 | The content of the buffer is an unsized array of `uint`s. A `uint` is always similar to a `u32` 85 | in Rust. In other words the line `uint data[];` is equivalent in Rust to a variable named `data` 86 | of type `[u32]`. 87 | 88 | ```glsl 89 | void main() { 90 | ``` 91 | 92 | Every GLSL code has an entry point named `main`, similar to C or Rust. This is the function that 93 | is going to be invoked 65536 times. 94 | 95 | ```glsl 96 | uint idx = gl_GlobalInvocationID.x; 97 | ``` 98 | 99 | As explained above we are going to spawn 1024 work groups, each having a local size of 64. Compute 100 | shaders in GLSL have access to several read-only static variables that let us know the index of 101 | the invocation we are currently in. The `gl_GlobalInvocationID.x` value here will contain a value 102 | that ranges between 0 and 65535. We are going to use it to determine which element of the array 103 | to modify. 104 | 105 | ```glsl 106 | buf.data[idx] *= 12; 107 | ``` 108 | 109 | The content of the buffer is accessed with `buf.data`. We multiply the value at the given index 110 | with 12. 111 | 112 | > **Note**: You can easily trigger a data race by calling, for example, `buf.data[0] *= 12;`, as all 113 | > of the shader invocations will access the buffer simultaneously. This is a safety problem that 114 | > vulkano doesn't detect or handle yet. Doing so will lead to an undefined result, but not in an 115 | > undefined behavior. 116 | 117 | ## Embedding the GLSL code in the Rust code 118 | 119 | Now that we've written the shader in GLSL, we're going to be compiling the shaders *at 120 | application compile-time*. We'll accomplish this using `vulkano-shaders` 121 | which is a procedural macro that manages the compile-time compilation of GLSL into SPIR-V 122 | and generation of associated rust code. 123 | 124 | To use `vulkano-shaders`, we first have to add a dependency: 125 | 126 | ```toml 127 | # Notice that it uses the same version as vulkano 128 | vulkano-shaders = "0.34.0" 129 | ``` 130 | 131 | > **Note**: `vulkano-shaders` uses the crate `shaderc-sys` for the actual GLSL compilation step. 132 | > When you build your project, an attempt will be made to automatically install shaderc if you 133 | > don't already have it. shaderc also comes in [the Vulkan 134 | > SDK](https://www.vulkan.org/tools#download-these-essential-development-tools)). See [shaderc-sys 135 | > crate](https://lib.rs/crates/shaderc-sys) for installation instructions should the automatic 136 | > system fail. 137 | 138 | Here is the syntax: 139 | 140 | ```rust 141 | mod cs { 142 | vulkano_shaders::shader!{ 143 | ty: "compute", 144 | src: r" 145 | ##version 460 146 | 147 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 148 | 149 | layout(set = 0, binding = 0) buffer Data { 150 | uint data[]; 151 | } buf; 152 | 153 | void main() { 154 | uint idx = gl_GlobalInvocationID.x; 155 | buf.data[idx] *= 12; 156 | } 157 | ", 158 | } 159 | } 160 | ``` 161 | 162 | As you can see, we specify some "fields" in the `vulkano_shaders::shader!` macro to specify our 163 | shader. The macro will then compile the GLSL code (outputting compilation errors if any) and 164 | generate several structs and methods, including one named `load`. This is the method that we have 165 | to use next: 166 | 167 | ```rust 168 | let shader = cs::load(device.clone()).expect("failed to create shader module"); 169 | ``` 170 | 171 | This feeds the shader to the Vulkan implementation. The last step to perform at runtime is to 172 | create a ***compute pipeline*** object from that shader. This is the object that actually describes 173 | the compute operation that we are going to perform. Before we can create any kind of pipeline, we 174 | need to create a ***pipeline layout***, which most notably describes what kinds of resources will 175 | be bound to the pipeline. We are going to let vulkano auto-generate this layout for us by using 176 | shader reflection. 177 | 178 | > **Note**: Auto-generated pipeline layouts are great for starting out or quick prototyping, but 179 | > are oftentimes suboptimal. You might be able to optimize better by creating one by hand. 180 | 181 | ```rust 182 | use vulkano::pipeline::compute::ComputePipelineCreateInfo; 183 | use vulkano::pipeline::layout::PipelineDescriptorSetLayoutCreateInfo; 184 | use vulkano::pipeline::{ComputePipeline, PipelineLayout, PipelineShaderStageCreateInfo}; 185 | 186 | let cs = shader.entry_point("main").unwrap(); 187 | let stage = PipelineShaderStageCreateInfo::new(cs); 188 | let layout = PipelineLayout::new( 189 | device.clone(), 190 | PipelineDescriptorSetLayoutCreateInfo::from_stages([&stage]) 191 | .into_pipeline_layout_create_info(device.clone()) 192 | .unwrap(), 193 | ) 194 | .unwrap(); 195 | 196 | let compute_pipeline = ComputePipeline::new( 197 | device.clone(), 198 | None, 199 | ComputePipelineCreateInfo::stage_layout(stage, layout), 200 | ) 201 | .expect("failed to create compute pipeline"); 202 | ``` 203 | 204 | Before invoking that compute pipeline, we need to bind a buffer to it. This is covered by [the 205 | next section](03-descriptor-sets.html). 206 | -------------------------------------------------------------------------------- /chapter-code/wip/bin/more_on_buffers/render/renderer.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use chapter_code::game_objects::Square; 4 | use chapter_code::models::SquareModel; 5 | use chapter_code::shaders::movable_square; 6 | use chapter_code::vulkano_objects::allocators::Allocators; 7 | use chapter_code::vulkano_objects::buffers::Buffers; 8 | use chapter_code::{vulkano_objects, Vertex2d}; 9 | use vulkano::command_buffer::{CommandBufferExecFuture, PrimaryAutoCommandBuffer}; 10 | use vulkano::device::{Device, DeviceCreateInfo, DeviceExtensions, Queue, QueueCreateInfo}; 11 | use vulkano::image::Image; 12 | use vulkano::instance::Instance; 13 | use vulkano::pipeline::graphics::viewport::Viewport; 14 | use vulkano::pipeline::{GraphicsPipeline, Pipeline}; 15 | use vulkano::render_pass::{Framebuffer, RenderPass}; 16 | use vulkano::shader::ShaderModule; 17 | use vulkano::swapchain::{ 18 | self, PresentFuture, Surface, Swapchain, SwapchainAcquireFuture, SwapchainCreateInfo, 19 | SwapchainPresentInfo, 20 | }; 21 | use vulkano::sync::future::{FenceSignalFuture, JoinFuture, NowFuture}; 22 | use vulkano::sync::{self, GpuFuture}; 23 | use vulkano::{Validated, VulkanError}; 24 | use winit::dpi::LogicalSize; 25 | use winit::event_loop::EventLoop; 26 | use winit::window::{Window, WindowBuilder}; 27 | 28 | pub type Fence = FenceSignalFuture< 29 | PresentFuture, SwapchainAcquireFuture>>>, 30 | >; 31 | 32 | pub struct Renderer { 33 | _instance: Arc, 34 | window: Arc, 35 | device: Arc, 36 | queue: Arc, 37 | swapchain: Arc, 38 | images: Vec>, 39 | render_pass: Arc, 40 | framebuffers: Vec>, 41 | allocators: Allocators, 42 | buffers: Buffers, 43 | vertex_shader: Arc, 44 | fragment_shader: Arc, 45 | viewport: Viewport, 46 | pipeline: Arc, 47 | command_buffers: Vec>, 48 | } 49 | 50 | impl Renderer { 51 | pub fn initialize(event_loop: &EventLoop<()>) -> Self { 52 | let instance = vulkano_objects::instance::get_instance(event_loop); 53 | 54 | let window = Arc::new(WindowBuilder::new().build(event_loop).unwrap()); 55 | 56 | let surface = Surface::from_window(instance.clone(), window.clone()).unwrap(); 57 | 58 | window.set_title("Movable Square"); 59 | window.set_inner_size(LogicalSize::new(600.0f32, 600.0)); 60 | 61 | let device_extensions = DeviceExtensions { 62 | khr_swapchain: true, 63 | ..DeviceExtensions::empty() 64 | }; 65 | 66 | let (physical_device, queue_family_index) = 67 | vulkano_objects::physical_device::select_physical_device( 68 | &instance, 69 | surface.clone(), 70 | &device_extensions, 71 | ); 72 | 73 | let (device, mut queues) = Device::new( 74 | physical_device.clone(), 75 | DeviceCreateInfo { 76 | queue_create_infos: vec![QueueCreateInfo { 77 | queue_family_index, 78 | ..Default::default() 79 | }], 80 | enabled_extensions: device_extensions, // new 81 | ..Default::default() 82 | }, 83 | ) 84 | .expect("failed to create device"); 85 | 86 | let queue = queues.next().unwrap(); 87 | 88 | let (swapchain, images) = 89 | vulkano_objects::swapchain::create_swapchain(&physical_device, device.clone(), surface); 90 | 91 | let render_pass = 92 | vulkano_objects::render_pass::create_render_pass(device.clone(), swapchain.clone()); 93 | let framebuffers = vulkano_objects::swapchain::create_framebuffers_from_swapchain_images( 94 | &images, 95 | render_pass.clone(), 96 | ); 97 | 98 | let vertex_shader = 99 | movable_square::vs::load(device.clone()).expect("failed to create shader module"); 100 | let fragment_shader = 101 | movable_square::fs::load(device.clone()).expect("failed to create shader module"); 102 | 103 | let viewport = Viewport { 104 | offset: [0.0, 0.0], 105 | extent: window.inner_size().into(), 106 | depth_range: 0.0..=1.0, 107 | }; 108 | 109 | let pipeline = vulkano_objects::pipeline::create_pipeline( 110 | device.clone(), 111 | vertex_shader.clone(), 112 | fragment_shader.clone(), 113 | render_pass.clone(), 114 | viewport.clone(), 115 | ); 116 | 117 | let allocators = Allocators::new(device.clone()); 118 | 119 | let buffers = Buffers::initialize_device_local::( 120 | &allocators, 121 | pipeline.layout().set_layouts().get(0).unwrap().clone(), 122 | images.len(), 123 | queue.clone(), 124 | ); 125 | 126 | let command_buffers = vulkano_objects::command_buffers::create_simple_command_buffers( 127 | &allocators, 128 | queue.clone(), 129 | pipeline.clone(), 130 | &framebuffers, 131 | &buffers, 132 | ); 133 | 134 | Self { 135 | _instance: instance, 136 | window, 137 | device, 138 | queue, 139 | swapchain, 140 | images, 141 | render_pass, 142 | framebuffers, 143 | allocators, 144 | buffers, 145 | vertex_shader, 146 | fragment_shader, 147 | viewport, 148 | pipeline, 149 | command_buffers, 150 | } 151 | } 152 | 153 | pub fn recreate_swapchain(&mut self) { 154 | let (new_swapchain, new_images) = self 155 | .swapchain 156 | .recreate(SwapchainCreateInfo { 157 | image_extent: self.window.inner_size().into(), 158 | ..self.swapchain.create_info() 159 | }) 160 | .expect("failed to recreate swapchain"); 161 | 162 | self.swapchain = new_swapchain; 163 | self.framebuffers = vulkano_objects::swapchain::create_framebuffers_from_swapchain_images( 164 | &new_images, 165 | self.render_pass.clone(), 166 | ); 167 | } 168 | 169 | pub fn handle_window_resize(&mut self) { 170 | self.recreate_swapchain(); 171 | self.viewport.extent = self.window.inner_size().into(); 172 | 173 | self.pipeline = vulkano_objects::pipeline::create_pipeline( 174 | self.device.clone(), 175 | self.vertex_shader.clone(), 176 | self.fragment_shader.clone(), 177 | self.render_pass.clone(), 178 | self.viewport.clone(), 179 | ); 180 | 181 | self.command_buffers = vulkano_objects::command_buffers::create_simple_command_buffers( 182 | &self.allocators, 183 | self.queue.clone(), 184 | self.pipeline.clone(), 185 | &self.framebuffers, 186 | &self.buffers, 187 | ); 188 | } 189 | 190 | pub fn get_image_count(&self) -> usize { 191 | self.images.len() 192 | } 193 | 194 | pub fn acquire_swapchain_image( 195 | &self, 196 | ) -> Result<(u32, bool, SwapchainAcquireFuture), Validated> { 197 | swapchain::acquire_next_image(self.swapchain.clone(), None) 198 | } 199 | 200 | pub fn synchronize(&self) -> NowFuture { 201 | let mut now = sync::now(self.device.clone()); 202 | now.cleanup_finished(); 203 | 204 | now 205 | } 206 | 207 | pub fn flush_next_future( 208 | &self, 209 | previous_future: Box, 210 | swapchain_acquire_future: SwapchainAcquireFuture, 211 | image_i: u32, 212 | ) -> Result> { 213 | previous_future 214 | .join(swapchain_acquire_future) 215 | .then_execute( 216 | self.queue.clone(), 217 | self.command_buffers[image_i as usize].clone(), 218 | ) 219 | .unwrap() 220 | .then_swapchain_present( 221 | self.queue.clone(), 222 | SwapchainPresentInfo::swapchain_image_index(self.swapchain.clone(), image_i), 223 | ) 224 | .then_signal_fence_and_flush() 225 | } 226 | 227 | pub fn update_uniform(&self, index: u32, square: &Square) { 228 | let mut uniform_content = self.buffers.uniforms[index as usize] 229 | .0 230 | .write() 231 | .unwrap_or_else(|e| panic!("Failed to write to uniform buffer\n{}", e)); 232 | 233 | uniform_content.color = square.color.into(); 234 | uniform_content.position = square.position; 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/03-buffer-creation/02-example-operation.md: -------------------------------------------------------------------------------- 1 | # Example operation 2 | 3 | Now that we are familiar with devices, queues, and buffers, we are going to see how to ask the GPU 4 | to actually do something. 5 | 6 | What we are going to ask in this example is very simple: we will ask it to copy data from one 7 | buffer to another. 8 | 9 | > **Note**: You can find the [full source code of this chapter 10 | > here](https://github.com/vulkano-rs/vulkano-book/blob/main/chapter-code/03-buffer-creation/main.rs). 11 | 12 | ## Creating the buffers 13 | 14 | The first step is to create two CPU-accessible buffers: the source and the destination. This was 15 | covered in [the previous section](01-buffer-creation.html). 16 | 17 | ```rust 18 | let source_content = 0..64; 19 | let source = Buffer::from_iter( 20 | memory_allocator.clone(), 21 | BufferCreateInfo { 22 | usage: BufferUsage::TRANSFER_SRC, 23 | ..Default::default() 24 | }, 25 | AllocationCreateInfo { 26 | memory_type_filter: MemoryTypeFilter::PREFER_HOST 27 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 28 | ..Default::default() 29 | }, 30 | source_content, 31 | ) 32 | .expect("failed to create source buffer"); 33 | 34 | let destination_content = (0..64).map(|_| 0); 35 | let destination = Buffer::from_iter( 36 | memory_allocator.clone(), 37 | BufferCreateInfo { 38 | usage: BufferUsage::TRANSFER_DST, 39 | ..Default::default() 40 | }, 41 | AllocationCreateInfo { 42 | memory_type_filter: MemoryTypeFilter::PREFER_HOST 43 | | MemoryTypeFilter::HOST_RANDOM_ACCESS, 44 | ..Default::default() 45 | }, 46 | destination_content, 47 | ) 48 | .expect("failed to create destination buffer"); 49 | ``` 50 | 51 | The iterators might look a bit tricky. The `source_content` iterator produces 64 values ranging 52 | from 0 to 63. The `destination_content` iterator produces 64 values that are all equal to 0. 53 | In other words, once created the source buffer contains sixty-four values ranging from 0 to 63 54 | while the destination buffer contains sixty-four 0s. 55 | 56 | ## Creating a command buffer allocator 57 | 58 | Just like buffers, you need an allocator to allocate several command buffers, but you cannot use 59 | a memory allocator. You have to use a [command buffer 60 | allocator](https://docs.rs/vulkano/0.34.0/vulkano/command_buffer/allocator/trait.CommandBufferAllocator.html). 61 | In this case we just use the [standard 62 | one](https://docs.rs/vulkano/0.34.0/vulkano/command_buffer/allocator/struct.StandardCommandBufferAllocator.html). 63 | 64 | ```rust 65 | use vulkano::command_buffer::allocator::{ 66 | StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo, 67 | }; 68 | 69 | let command_buffer_allocator = StandardCommandBufferAllocator::new( 70 | device.clone(), 71 | StandardCommandBufferAllocatorCreateInfo::default(), 72 | ); 73 | ``` 74 | 75 | ## Creating command buffers 76 | 77 | In order to ask the GPU to perform an operation, we need to create a type of object that we 78 | haven't covered yet, the *command buffer*. 79 | 80 | With Vulkan and vulkano you can't just execute commands one by one, as it would be too inefficient. 81 | Instead, we need to build a command buffer that contains a list of commands that we want to 82 | execute. 83 | 84 | You can create many command buffers and use them at different times during the program. They can 85 | have different uses and can do many things. In this case, we are just going to create for the 86 | operation we are trying to achieve. 87 | 88 | Vulkan supports primary and secondary command buffers. Primary command buffers can be sent directly 89 | to the GPU while secondary command buffers allow you to store functionality that you can reuse 90 | multiple times in primary command buffers. We won't cover secondary command buffers here, but you 91 | can read [more about them](https://docs.rs/vulkano/0.34.0/vulkano/command_buffer/index.html). 92 | 93 | > **Note**: Submitting a command to the GPU can take up to several hundred microseconds, which is 94 | > why we submit as many things as we can at once. 95 | > OpenGL (Vulkan's predecessor) allows you to execute commands one by one, but in reality 96 | > implementations buffer commands internally into command buffers. In other words, OpenGL 97 | > automatically does what Vulkan requires us to do manually. In practice, OpenGL's automatic 98 | > buffering often causes more harm than good in performance-critical applications. 99 | 100 | We are going to submit the commands to the GPU, so let's create a primary command buffer: 101 | 102 | ```rust 103 | use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, CopyBufferInfo}; 104 | 105 | let mut builder = AutoCommandBufferBuilder::primary( 106 | &command_buffer_allocator, 107 | queue_family_index, 108 | CommandBufferUsage::OneTimeSubmit, 109 | ) 110 | .unwrap(); 111 | 112 | builder 113 | .copy_buffer(CopyBufferInfo::buffers(source.clone(), destination.clone())) 114 | .unwrap(); 115 | 116 | let command_buffer = builder.build().unwrap(); 117 | ``` 118 | 119 | As you can see, it is very straight-forward. We create a *builder*, add a copy command to it with 120 | `copy_buffer`, then turn that builder into an actual command buffer with `.build()`. Like we saw in 121 | [the buffers creation section](01-buffer-creation.html), we call `.clone()` multiple times, but we 122 | only clone `Arc`s. 123 | 124 | One thing to notice is that the `AutoCommandBufferBuilder::primary()` method takes as parameter a 125 | queue family index. This identifies the queue family that the command buffer is going to run on. 126 | In this example we don't have much choice anyway (as we only use one queue and thus one queue 127 | family), but when you design a real program you have to be aware of this requirement. 128 | 129 | ## Submission and synchronization 130 | 131 | The last step is to actually send the command buffer and execute it in the GPU. We can do that by 132 | synchronizing with the GPU, then executing the command buffer: 133 | 134 | ```rust 135 | use vulkano::sync::{self, GpuFuture}; 136 | 137 | sync::now(device.clone()) 138 | .then_execute(queue.clone(), command_buffer) 139 | .unwrap() 140 | .flush() 141 | .unwrap(); 142 | ``` 143 | 144 | No function in vulkano immediately sends an operation to the GPU (except some unsafe low-level 145 | functions). Instead, `sync::now()` creates a new type of object called a *future*, that keeps 146 | alive all the resources that will be used by the GPU and represents the execution in time of the 147 | actual operations. 148 | 149 | The future returned by `sync::now()` is in a pending state and makes it possible to append the 150 | execution of other command buffers and operations. Only by calling `.flush()` are these operations 151 | all submitted at once, and they actually start executing on the GPU. 152 | 153 | Using objects like this lets us build dependencies between operations and makes it possible to 154 | make an operation start only after a previous one is finished, while reducing the number of slow 155 | communication operations between the CPU and the GPU. 156 | 157 | After submitting the command buffer, we might be tempted to try to read the content of the 158 | `destination` buffer as demonstrated in [the previous section](01-buffer-creation.html). 159 | However, because the CPU and GPU are now executing in parallel, calling `destination.read()` 160 | now may sometimes return an error because the buffer could still be in use by the GPU. 161 | 162 | In order to read the content of `destination` and make sure that our copy succeeded, we need to 163 | wait until the operation is complete. To do that, we need to program the GPU to send back a special 164 | signal that will make us know it has finished. This kind of signal is called a *fence*, and it lets 165 | us know whenever the GPU has reached a certain point of execution. 166 | 167 | To do that, let's actually save the future from the above example and wait for the operations to 168 | finish: 169 | 170 | ```rust 171 | let future = sync::now(device.clone()) 172 | .then_execute(queue.clone(), command_buffer) 173 | .unwrap() 174 | .then_signal_fence_and_flush() // same as signal fence, and then flush 175 | .unwrap(); 176 | ``` 177 | 178 | Signaling a fence returns a future object called 179 | [`FenceSignalFuture`](https://docs.rs/vulkano/0.34.0/vulkano/sync/future/struct.FenceSignalFuture.html), 180 | that has a special method `.wait()`: 181 | 182 | ```rust 183 | future.wait(None).unwrap(); // None is an optional timeout 184 | ``` 185 | 186 | Only after this is done can we safely call `destination.read()` and check that our copy succeeded. 187 | 188 | ```rust 189 | let src_content = source.read().unwrap(); 190 | let destination_content = destination.read().unwrap(); 191 | assert_eq!(&*src_content, &*destination_content); 192 | 193 | println!("Everything succeeded!"); 194 | ``` 195 | 196 | Next: [Introduction to compute operations](../04-compute-pipeline/01-compute-intro.html) 197 | -------------------------------------------------------------------------------- /chapter-code/wip/bin/restructuring/render/renderer.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use chapter_code::shaders::static_triangle; 4 | use chapter_code::vulkano_objects::allocators::Allocators; 5 | use chapter_code::{vulkano_objects, Vertex2d}; 6 | use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}; 7 | use vulkano::command_buffer::{CommandBufferExecFuture, PrimaryAutoCommandBuffer}; 8 | use vulkano::device::{Device, DeviceCreateInfo, DeviceExtensions, Queue, QueueCreateInfo}; 9 | use vulkano::image::Image; 10 | use vulkano::instance::Instance; 11 | use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}; 12 | use vulkano::pipeline::graphics::viewport::Viewport; 13 | use vulkano::pipeline::GraphicsPipeline; 14 | use vulkano::render_pass::{Framebuffer, RenderPass}; 15 | use vulkano::shader::ShaderModule; 16 | use vulkano::swapchain::{ 17 | self, PresentFuture, Surface, Swapchain, SwapchainAcquireFuture, SwapchainCreateInfo, 18 | SwapchainPresentInfo, 19 | }; 20 | use vulkano::sync::future::{FenceSignalFuture, JoinFuture, NowFuture}; 21 | use vulkano::sync::{self, GpuFuture}; 22 | use vulkano::{Validated, VulkanError}; 23 | use winit::event_loop::EventLoop; 24 | use winit::window::{Window, WindowBuilder}; 25 | 26 | pub type Fence = FenceSignalFuture< 27 | PresentFuture, SwapchainAcquireFuture>>>, 28 | >; 29 | 30 | pub struct Renderer { 31 | _instance: Arc, 32 | window: Arc, 33 | device: Arc, 34 | queue: Arc, 35 | swapchain: Arc, 36 | images: Vec>, 37 | render_pass: Arc, 38 | framebuffers: Vec>, 39 | allocators: Allocators, 40 | vertex_buffer: Subbuffer<[Vertex2d]>, 41 | vertex_shader: Arc, 42 | fragment_shader: Arc, 43 | viewport: Viewport, 44 | pipeline: Arc, 45 | command_buffers: Vec>, 46 | } 47 | 48 | impl Renderer { 49 | pub fn initialize(event_loop: &EventLoop<()>) -> Self { 50 | let instance = vulkano_objects::instance::get_instance(event_loop); 51 | 52 | let window = Arc::new(WindowBuilder::new().build(event_loop).unwrap()); 53 | 54 | let surface = Surface::from_window(instance.clone(), window.clone()).unwrap(); 55 | 56 | let device_extensions = DeviceExtensions { 57 | khr_swapchain: true, 58 | ..DeviceExtensions::empty() 59 | }; 60 | 61 | let (physical_device, queue_family_index) = 62 | vulkano_objects::physical_device::select_physical_device( 63 | &instance, 64 | surface.clone(), 65 | &device_extensions, 66 | ); 67 | 68 | let (device, mut queues) = { 69 | Device::new( 70 | physical_device.clone(), 71 | DeviceCreateInfo { 72 | queue_create_infos: vec![QueueCreateInfo { 73 | queue_family_index, 74 | ..Default::default() 75 | }], 76 | enabled_extensions: device_extensions, 77 | ..Default::default() 78 | }, 79 | ) 80 | .expect("failed to create device") 81 | }; 82 | 83 | let queue = queues.next().unwrap(); 84 | 85 | let (swapchain, images) = 86 | vulkano_objects::swapchain::create_swapchain(&physical_device, device.clone(), surface); 87 | 88 | let render_pass = 89 | vulkano_objects::render_pass::create_render_pass(device.clone(), swapchain.clone()); 90 | let framebuffers = vulkano_objects::swapchain::create_framebuffers_from_swapchain_images( 91 | &images, 92 | render_pass.clone(), 93 | ); 94 | 95 | let vertex_shader = 96 | static_triangle::vs::load(device.clone()).expect("failed to create shader module"); 97 | let fragment_shader = 98 | static_triangle::fs::load(device.clone()).expect("failed to create shader module"); 99 | 100 | let viewport = Viewport { 101 | offset: [0.0, 0.0], 102 | extent: window.inner_size().into(), 103 | depth_range: 0.0..=1.0, 104 | }; 105 | 106 | let pipeline = vulkano_objects::pipeline::create_pipeline( 107 | device.clone(), 108 | vertex_shader.clone(), 109 | fragment_shader.clone(), 110 | render_pass.clone(), 111 | viewport.clone(), 112 | ); 113 | 114 | let allocators = Allocators::new(device.clone()); 115 | 116 | let vertex_buffer = create_vertex_buffer(&allocators); 117 | 118 | let command_buffers = vulkano_objects::command_buffers::create_only_vertex_command_buffers( 119 | &allocators, 120 | queue.clone(), 121 | pipeline.clone(), 122 | &framebuffers, 123 | vertex_buffer.clone(), 124 | ); 125 | 126 | Self { 127 | _instance: instance, 128 | window, 129 | device, 130 | queue, 131 | swapchain, 132 | images, 133 | render_pass, 134 | framebuffers, 135 | allocators, 136 | vertex_buffer, 137 | vertex_shader, 138 | fragment_shader, 139 | viewport, 140 | pipeline, 141 | command_buffers, 142 | } 143 | } 144 | 145 | pub fn recreate_swapchain(&mut self) { 146 | let (new_swapchain, new_images) = self 147 | .swapchain 148 | .recreate(SwapchainCreateInfo { 149 | image_extent: self.window.inner_size().into(), 150 | ..self.swapchain.create_info() 151 | }) 152 | .expect("failed to recreate swapchain"); 153 | 154 | self.swapchain = new_swapchain; 155 | self.framebuffers = vulkano_objects::swapchain::create_framebuffers_from_swapchain_images( 156 | &new_images, 157 | self.render_pass.clone(), 158 | ); 159 | } 160 | 161 | pub fn handle_window_resize(&mut self) { 162 | self.recreate_swapchain(); 163 | self.viewport.extent = self.window.inner_size().into(); 164 | 165 | self.pipeline = vulkano_objects::pipeline::create_pipeline( 166 | self.device.clone(), 167 | self.vertex_shader.clone(), 168 | self.fragment_shader.clone(), 169 | self.render_pass.clone(), 170 | self.viewport.clone(), 171 | ); 172 | 173 | self.command_buffers = vulkano_objects::command_buffers::create_only_vertex_command_buffers( 174 | &self.allocators, 175 | self.queue.clone(), 176 | self.pipeline.clone(), 177 | &self.framebuffers, 178 | self.vertex_buffer.clone(), 179 | ); 180 | } 181 | 182 | pub fn get_image_count(&self) -> usize { 183 | self.images.len() 184 | } 185 | 186 | pub fn acquire_swapchain_image( 187 | &self, 188 | ) -> Result<(u32, bool, SwapchainAcquireFuture), Validated> { 189 | swapchain::acquire_next_image(self.swapchain.clone(), None) 190 | } 191 | 192 | pub fn synchronize(&self) -> NowFuture { 193 | let mut now = sync::now(self.device.clone()); 194 | now.cleanup_finished(); 195 | 196 | now 197 | } 198 | 199 | pub fn flush_next_future( 200 | &self, 201 | previous_future: Box, 202 | swapchain_acquire_future: SwapchainAcquireFuture, 203 | image_i: u32, 204 | ) -> Result> { 205 | previous_future 206 | .join(swapchain_acquire_future) 207 | .then_execute( 208 | self.queue.clone(), 209 | self.command_buffers[image_i as usize].clone(), 210 | ) 211 | .unwrap() 212 | .then_swapchain_present( 213 | self.queue.clone(), 214 | SwapchainPresentInfo::swapchain_image_index(self.swapchain.clone(), image_i), 215 | ) 216 | .then_signal_fence_and_flush() 217 | } 218 | } 219 | 220 | pub fn create_vertex_buffer(allocators: &Allocators) -> Subbuffer<[Vertex2d]> { 221 | let vertex1 = Vertex2d { 222 | position: [-0.5, -0.5], 223 | }; 224 | let vertex2 = Vertex2d { 225 | position: [0.0, 0.5], 226 | }; 227 | let vertex3 = Vertex2d { 228 | position: [0.5, -0.25], 229 | }; 230 | 231 | Buffer::from_iter( 232 | allocators.memory.clone(), 233 | BufferCreateInfo { 234 | usage: BufferUsage::VERTEX_BUFFER, 235 | ..Default::default() 236 | }, 237 | AllocationCreateInfo { 238 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 239 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 240 | ..Default::default() 241 | }, 242 | vec![vertex1, vertex2, vertex3].into_iter(), 243 | ) 244 | .unwrap() 245 | } 246 | -------------------------------------------------------------------------------- /src/03-buffer-creation/01-buffer-creation.md: -------------------------------------------------------------------------------- 1 | # Creating a memory allocator 2 | 3 | Before you can create buffers in memory, you have to request (allocate) some memory first. 4 | It turns out [allocating memory](https://docs.rs/vulkano/0.34.0/vulkano/memory/allocator/index.html) 5 | efficiently and dynamically is challenging. Luckily, in vulkano, we have several kinds of memory 6 | allocators that we can pick from depending on our use case. Since we don't have any special needs, 7 | we can use the [`StandardMemoryAllocator`](https://docs.rs/vulkano/0.34.0/vulkano/memory/allocator/type.StandardMemoryAllocator.html) 8 | with default settings, that kind of allocator is general-purpose and will be your go-to option in 9 | most cases. 10 | 11 | ```rust 12 | use vulkano::memory::allocator::StandardMemoryAllocator; 13 | 14 | let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); 15 | ``` 16 | 17 | Since `device` is actually an `Arc`, the call to `.clone()` only clones the `Arc` 18 | which isn't expensive. You should get used to passing the device as a parameter, as you will 19 | need to do so for most of the Vulkan objects that you create. We encapsulate the memory allocator 20 | with an atomic reference counter since `Buffer::from_data` requires an `Arc`. 21 | 22 | # Creating a buffer 23 | 24 | When using Vulkan, you will very often need the GPU to read or write data in memory. In fact 25 | there isn't much point in using the GPU otherwise, as there is nothing you can do with the results 26 | of its work except write them to memory. 27 | 28 | In order for the GPU to be able to access some data (either for reading, writing or both), we 29 | first need to create a ***buffer*** object and put the data in it. 30 | 31 | ## Memory type filter 32 | 33 | A Vulkan implementation might (and most often does) have multiple *memory types*, each being best 34 | suited to certain tasks. There are many possible arrangements of memory types a Vulkan 35 | implementation might have, and picking the right one is important to ensure most optimal performance. 36 | 37 | When allocating memory for a buffer in vulkano, you have to provide a ***memory type filter***, 38 | which tells the memory allocator which memory types it should prefer, and which ones it should 39 | avoid, when picking the right one. For example, if you want to continuously upload data to the GPU, 40 | you should use `MemoryTypeFilter::PREFER_DEVICE | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE`; on the 41 | other hand, if you have some data that will largely remain visible only to the GPU, using 42 | `MemoryTypeFilter::PREFER_DEVICE` brings increased performance at the cost of more complicated 43 | data access from the CPU. For staging buffers, you should use 44 | `MemoryTypeFilter::PREFER_HOST | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE`. 45 | 46 | The simplest way to create a buffer is to create it in CPU-accessible memory, by using 47 | `MemoryTypeFilter::HOST_SEQUENTIAL_WRITE` or `MemoryTypeFilter::HOST_RANDOM_ACCESS`, together with 48 | one of the other filters depending of whether host or device-local memory is preferred. 49 | 50 | ```rust 51 | use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage}; 52 | use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}; 53 | 54 | let data: i32 = 12; 55 | let buffer = Buffer::from_data( 56 | memory_allocator.clone(), 57 | BufferCreateInfo { 58 | usage: BufferUsage::UNIFORM_BUFFER, 59 | ..Default::default() 60 | }, 61 | AllocationCreateInfo { 62 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 63 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 64 | ..Default::default() 65 | }, 66 | data, 67 | ) 68 | .expect("failed to create buffer"); 69 | ``` 70 | 71 | We have to indicate several things when creating the buffer. The first parameter is an `Arc` of the 72 | memory allocator to use. 73 | 74 | The second parameter is the create info for the buffer. The only field that you have to override 75 | is [the usage for which we are creating the 76 | buffer](https://docs.rs/vulkano/0.34.0/vulkano/buffer/struct.BufferUsage.html) for, which can help 77 | the implementation perform some optimizations. Trying to use a buffer in a way that wasn't 78 | indicated when creating it will result in an error. For the sake of the example, we just create a 79 | buffer that supports being used as a uniform buffer. 80 | 81 | The third parameter is the create info for the allocation. The field of interest is [the memory 82 | type filter](https://docs.rs/vulkano/0.34.0/vulkano/memory/allocator/struct.MemoryTypeFilter.html). 83 | When creating a CPU-accessible buffer, you will most commonly use 84 | `MemoryTypeFilter::PREFER_HOST | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE`, but in cases 85 | where the application is writing data through this buffer continuously, using 86 | `MemoryTypeFilter::PREFER_HOST | MemoryTypeFilter::HOST_RANDOM_ACCESS` is preferred as it may 87 | yield some performance gain. Using `MemoryTypeFilter::PREFER_DEVICE` will get you a buffer that 88 | is inaccessible from the CPU when such a memory type exists. Therefore, you can't use this memory 89 | usage together with `Buffer::from_data` directly, and instead have to create a *staging buffer* 90 | whose content is then copied to the device-local buffer. 91 | 92 | Finally, the fourth parameter is the content of the buffer. Here we create a buffer that contains 93 | a single integer with the value `12`. 94 | 95 | > **Note**: In a real application you shouldn't create buffers with only 4 bytes of data. Although 96 | > buffers aren't expensive, you should try to group as much related data as you can in the same 97 | > buffer. 98 | 99 | ## From_data and from_iter 100 | 101 | In the example above we create a buffer that contains the value `12`, which is of type `i32`, 102 | but you can put any type you want in a buffer, there is no restriction. In order to give our 103 | arbitrary types a representation that can be used in a generic way, we use the crate `bytemuck` 104 | and its "plain old data" trait, `AnyBitPattern`. Thus, any crate which exposes types with 105 | `bytemuck` support can be used in a buffer. You can also derive `AnyBitPattern` for you own types, 106 | or use the vulkano-provided `BufferContents` derive macro: 107 | 108 | ```rust 109 | use vulkano::buffer::BufferContents; 110 | 111 | #[derive(BufferContents)] 112 | #[repr(C)] 113 | struct MyStruct { 114 | a: u32, 115 | b: u32, 116 | } 117 | 118 | let data = MyStruct { a: 5, b: 69 }; 119 | 120 | let buffer = Buffer::from_data( 121 | memory_allocator.clone(), 122 | BufferCreateInfo { 123 | usage: BufferUsage::UNIFORM_BUFFER, 124 | ..Default::default() 125 | }, 126 | AllocationCreateInfo { 127 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 128 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 129 | ..Default::default() 130 | }, 131 | data, 132 | ) 133 | .unwrap(); 134 | ``` 135 | 136 | While it is sometimes useful to use a buffer that contains a single struct, in practice it is very 137 | common to put an array of values inside a buffer. You can, for example, put an array of fifty 138 | `i32`s in a buffer with the `Buffer::from_data` function. 139 | 140 | However, in practice it is also very common to not know the size of the array at compile-time. In 141 | order to handle this, `Buffer` provides a `from_iter` constructor that takes an iterator to the 142 | data as the last parameter, instead of the data itself. 143 | 144 | In the example below, we create a buffer that contains the value `5` of type `u8`, 128 times. The 145 | type of the content of the buffer is `[u8]`, which, in Rust, represents an array of `u8`s whose 146 | size is only known at runtime. 147 | 148 | ```rust 149 | let iter = (0..128).map(|_| 5u8); 150 | let buffer = Buffer::from_iter( 151 | memory_allocator.clone(), 152 | BufferCreateInfo { 153 | usage: BufferUsage::UNIFORM_BUFFER, 154 | ..Default::default() 155 | }, 156 | AllocationCreateInfo { 157 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 158 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 159 | ..Default::default() 160 | }, 161 | iter, 162 | ) 163 | .unwrap(); 164 | ``` 165 | 166 | ## Reading and writing the contents of a buffer 167 | 168 | Once a CPU-accessible buffer is created, you can access its content with the `read()` or `write()` 169 | methods. Using `read()` will grant you shared access to the content of the buffer, and using 170 | `write()` will grant you exclusive access. This is similar to using a `RwLock`. 171 | 172 | For example if `buffer` contains a `MyStruct` (see above): 173 | 174 | ```rust 175 | let mut content = buffer.write().unwrap(); 176 | // `content` implements `DerefMut` whose target is of type `MyStruct` (the content of the buffer) 177 | content.a *= 2; 178 | content.b = 9; 179 | ``` 180 | 181 | Alternatively, suppose that the content of `buffer` is of type `[u8]` (like with the example that 182 | uses `from_iter`): 183 | 184 | ```rust 185 | let mut content = buffer.write().unwrap(); 186 | // this time `content` derefs to `[u8]` 187 | content[12] = 83; 188 | content[7] = 3; 189 | ``` 190 | 191 | Just like the constructors, keep in mind that being able to read/write the content of the buffer 192 | like this is specific to buffer allocated in CPU-accessible memory. Device-local buffers cannot 193 | be accessed in this way. 194 | 195 | Next: [Example operation](02-example-operation.html) 196 | -------------------------------------------------------------------------------- /chapter-code/wip/vulkano_objects/buffers.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}; 4 | use vulkano::command_buffer::{ 5 | AutoCommandBufferBuilder, CommandBufferExecFuture, CommandBufferUsage, CopyBufferInfo, 6 | PrimaryCommandBufferAbstract, 7 | }; 8 | use vulkano::descriptor_set::layout::DescriptorSetLayout; 9 | use vulkano::descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet}; 10 | use vulkano::device::Queue; 11 | use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}; 12 | use vulkano::sync::future::NowFuture; 13 | use vulkano::sync::GpuFuture; 14 | use vulkano::DeviceSize; 15 | 16 | use super::allocators::Allocators; 17 | use crate::models::Model; 18 | 19 | pub type Uniform = (Subbuffer, Arc); 20 | 21 | /// Struct with a vertex, index and uniform buffer, with generic (V)ertices and (U)niforms. 22 | pub struct Buffers { 23 | pub vertex: Subbuffer<[V]>, 24 | pub index: Subbuffer<[u16]>, 25 | pub uniforms: Vec>, 26 | } 27 | 28 | impl Buffers { 29 | pub fn initialize_host_accessible>( 30 | allocators: &Allocators, 31 | descriptor_set_layout: Arc, 32 | uniform_buffer_count: usize, 33 | ) -> Self { 34 | Self { 35 | vertex: create_cpu_accessible_vertex::(allocators), 36 | index: create_cpu_accessible_index::(allocators), 37 | uniforms: create_cpu_accessible_uniforms::( 38 | allocators, 39 | descriptor_set_layout, 40 | uniform_buffer_count, 41 | ), 42 | } 43 | } 44 | 45 | pub fn initialize_device_local>( 46 | allocators: &Allocators, 47 | descriptor_set_layout: Arc, 48 | uniform_buffer_count: usize, 49 | transfer_queue: Arc, 50 | ) -> Self { 51 | let (vertex, vertex_future) = 52 | create_device_local_vertex::(allocators, transfer_queue.clone()); 53 | let (index, index_future) = 54 | create_device_local_index::(allocators, transfer_queue); 55 | 56 | let fence = vertex_future 57 | .join(index_future) 58 | .then_signal_fence_and_flush() 59 | .unwrap(); 60 | 61 | fence.wait(None).unwrap(); 62 | 63 | Self { 64 | vertex, 65 | index, 66 | uniforms: create_cpu_accessible_uniforms::( 67 | allocators, 68 | descriptor_set_layout, 69 | uniform_buffer_count, 70 | ), 71 | } 72 | } 73 | 74 | pub fn get_vertex(&self) -> Subbuffer<[V]> { 75 | self.vertex.clone() 76 | } 77 | 78 | pub fn get_index(&self) -> Subbuffer<[u16]> { 79 | self.index.clone() 80 | } 81 | 82 | pub fn get_uniform_descriptor_set(&self, i: usize) -> Arc { 83 | self.uniforms[i].1.clone() 84 | } 85 | } 86 | 87 | fn create_cpu_accessible_vertex(allocators: &Allocators) -> Subbuffer<[V]> 88 | where 89 | V: BufferContents, 90 | U: BufferContents, 91 | M: Model, 92 | { 93 | Buffer::from_iter( 94 | allocators.memory.clone(), 95 | BufferCreateInfo { 96 | usage: BufferUsage::VERTEX_BUFFER, 97 | ..Default::default() 98 | }, 99 | AllocationCreateInfo { 100 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 101 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 102 | ..Default::default() 103 | }, 104 | M::get_vertices(), 105 | ) 106 | .unwrap() 107 | } 108 | 109 | fn create_device_local_vertex( 110 | allocators: &Allocators, 111 | queue: Arc, 112 | ) -> (Subbuffer<[V]>, CommandBufferExecFuture) 113 | where 114 | V: BufferContents, 115 | U: BufferContents, 116 | M: Model, 117 | { 118 | let vertices = M::get_vertices(); 119 | 120 | let buffer = Buffer::new_slice( 121 | allocators.memory.clone(), 122 | BufferCreateInfo { 123 | usage: BufferUsage::VERTEX_BUFFER | BufferUsage::TRANSFER_DST, 124 | ..Default::default() 125 | }, 126 | AllocationCreateInfo { 127 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE, 128 | ..Default::default() 129 | }, 130 | vertices.len() as DeviceSize, 131 | ) 132 | .unwrap(); 133 | 134 | let staging_buffer = Buffer::from_iter( 135 | allocators.memory.clone(), 136 | BufferCreateInfo { 137 | usage: BufferUsage::TRANSFER_SRC, 138 | ..Default::default() 139 | }, 140 | AllocationCreateInfo { 141 | memory_type_filter: MemoryTypeFilter::PREFER_HOST 142 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 143 | ..Default::default() 144 | }, 145 | vertices, 146 | ) 147 | .unwrap(); 148 | 149 | let mut builder = AutoCommandBufferBuilder::primary( 150 | &allocators.command_buffer, 151 | queue.queue_family_index(), 152 | CommandBufferUsage::OneTimeSubmit, 153 | ) 154 | .unwrap(); 155 | builder 156 | .copy_buffer(CopyBufferInfo::buffers(staging_buffer, buffer.clone())) 157 | .unwrap(); 158 | 159 | let future = builder.build().unwrap().execute(queue).unwrap(); 160 | 161 | (buffer, future) 162 | } 163 | 164 | fn create_cpu_accessible_index(allocators: &Allocators) -> Subbuffer<[u16]> 165 | where 166 | V: BufferContents, 167 | U: BufferContents, 168 | M: Model, 169 | { 170 | Buffer::from_iter( 171 | allocators.memory.clone(), 172 | BufferCreateInfo { 173 | usage: BufferUsage::INDEX_BUFFER, 174 | ..Default::default() 175 | }, 176 | AllocationCreateInfo { 177 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 178 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 179 | ..Default::default() 180 | }, 181 | M::get_indices(), 182 | ) 183 | .unwrap() 184 | } 185 | 186 | fn create_device_local_index( 187 | allocators: &Allocators, 188 | queue: Arc, 189 | ) -> (Subbuffer<[u16]>, CommandBufferExecFuture) 190 | where 191 | V: BufferContents, 192 | U: BufferContents, 193 | M: Model, 194 | { 195 | let indices = M::get_indices(); 196 | 197 | let buffer = Buffer::new_slice( 198 | allocators.memory.clone(), 199 | BufferCreateInfo { 200 | usage: BufferUsage::INDEX_BUFFER | BufferUsage::TRANSFER_DST, 201 | ..Default::default() 202 | }, 203 | AllocationCreateInfo { 204 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE, 205 | ..Default::default() 206 | }, 207 | indices.len() as DeviceSize, 208 | ) 209 | .unwrap(); 210 | 211 | let staging_buffer = Buffer::from_iter( 212 | allocators.memory.clone(), 213 | BufferCreateInfo { 214 | usage: BufferUsage::TRANSFER_SRC, 215 | ..Default::default() 216 | }, 217 | AllocationCreateInfo { 218 | memory_type_filter: MemoryTypeFilter::PREFER_HOST 219 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 220 | ..Default::default() 221 | }, 222 | indices, 223 | ) 224 | .unwrap(); 225 | 226 | let mut builder = AutoCommandBufferBuilder::primary( 227 | &allocators.command_buffer, 228 | queue.queue_family_index(), 229 | CommandBufferUsage::OneTimeSubmit, 230 | ) 231 | .unwrap(); 232 | builder 233 | .copy_buffer(CopyBufferInfo::buffers(staging_buffer, buffer.clone())) 234 | .unwrap(); 235 | 236 | let future = builder.build().unwrap().execute(queue).unwrap(); 237 | 238 | (buffer, future) 239 | } 240 | 241 | fn create_cpu_accessible_uniforms( 242 | allocators: &Allocators, 243 | descriptor_set_layout: Arc, 244 | buffer_count: usize, 245 | ) -> Vec> 246 | where 247 | V: BufferContents, 248 | U: BufferContents, 249 | M: Model, 250 | { 251 | (0..buffer_count) 252 | .map(|_| { 253 | let buffer = Buffer::from_data( 254 | allocators.memory.clone(), 255 | BufferCreateInfo { 256 | usage: BufferUsage::UNIFORM_BUFFER, 257 | ..Default::default() 258 | }, 259 | AllocationCreateInfo { 260 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE 261 | | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, 262 | ..Default::default() 263 | }, 264 | M::get_initial_uniform_data(), 265 | ) 266 | .unwrap(); 267 | 268 | let descriptor_set = PersistentDescriptorSet::new( 269 | &allocators.descriptor_set, 270 | descriptor_set_layout.clone(), 271 | [WriteDescriptorSet::buffer(0, buffer.clone())], 272 | [], 273 | ) 274 | .unwrap(); 275 | 276 | (buffer, descriptor_set) 277 | }) 278 | .collect() 279 | } 280 | -------------------------------------------------------------------------------- /src/05-images/04-mandelbrot.md: -------------------------------------------------------------------------------- 1 | # Drawing a fractal with a compute shader 2 | 3 | This section isn't going to introduce any new concept, but will show a real world example by using 4 | a compute shader to write a [Mandelbrot set](https://en.wikipedia.org/wiki/Mandelbrot_set) to an 5 | image. 6 | 7 | Just like in [the introduction to compute 8 | pipelines](../04-compute-pipeline/02-compute-pipeline.html), we need to write some GLSL code and 9 | create a compute pipeline. This is done with the `vulkano_shader::shader!` macro, as explained in 10 | that section. Each invocation of the `main` function of the shader will write one pixel. 11 | 12 | > **Note**: You can find the [full source code of this section 13 | > here](https://github.com/vulkano-rs/vulkano-book/blob/main/chapter-code/05-images/mandelbrot.rs). 14 | 15 | ## The shader 16 | 17 | Let's spend some time on the GLSL code of the shader, which I wrote for you: 18 | 19 | ```glsl 20 | #version 460 21 | 22 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; 23 | 24 | layout(set = 0, binding = 0, rgba8) uniform writeonly image2D img; 25 | 26 | void main() { 27 | vec2 norm_coordinates = (gl_GlobalInvocationID.xy + vec2(0.5)) / vec2(imageSize(img)); 28 | vec2 c = (norm_coordinates - vec2(0.5)) * 2.0 - vec2(1.0, 0.0); 29 | 30 | vec2 z = vec2(0.0, 0.0); 31 | float i; 32 | for (i = 0.0; i < 1.0; i += 0.005) { 33 | z = vec2( 34 | z.x * z.x - z.y * z.y + c.x, 35 | z.y * z.x + z.x * z.y + c.y 36 | ); 37 | 38 | if (length(z) > 4.0) { 39 | break; 40 | } 41 | } 42 | 43 | vec4 to_write = vec4(vec3(i), 1.0); 44 | imageStore(img, ivec2(gl_GlobalInvocationID.xy), to_write); 45 | } 46 | ``` 47 | 48 | Let's go through this line by line: 49 | 50 | ```glsl 51 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; 52 | ``` 53 | 54 | For better parallelization, we decided that each invocation of the shader would write a value to a 55 | pixel of the image. As you can see, this time we use a local size of 8x8, which is two-dimensional. 56 | We will use the value of `gl_GlobalInvocationID` to decide which pixel we will write. 57 | 58 | ```glsl 59 | layout(set = 0, binding = 0, rgba8) uniform writeonly image2D img; 60 | ``` 61 | 62 | This line declares the presence of an image that we are going to use, at the slot 0 of the 63 | descriptor set 0. As you can see we have to specify its format. Trying to use an image whose format 64 | doesn't match what is expected will result in an error. 65 | 66 | ```glsl 67 | vec2 norm_coordinates = (gl_GlobalInvocationID.xy + vec2(0.5)) / vec2(imageSize(img)); 68 | vec2 c = (norm_coordinates - vec2(0.5)) * 2.0 - vec2(1.0, 0.0); 69 | ``` 70 | 71 | These lines declare two variables whose type is `vec2`. A `vec2` is equivalent to a `[f32; 2]` 72 | and is usually used to store 2D coordinates. Similarly `ivec4` is for example equivalent to 73 | `[i32; 4]`, `uvec3` is equivalent to `[u32; 3]`, and so on. The Mandelbrot set is a set of complex 74 | numbers, so in this shader we use several `vec2`s to store the real and imaginary parts of the 75 | complex numbers that we manipulate. 76 | 77 | The purpose of these two lines is to put in the variable `c` the complex number that corresponds 78 | to the pixel of the image that we modify. The pixel that we are going to write will have a color 79 | that depends on whether or not its corresponding complex number is within the set or not. 80 | 81 | ```glsl 82 | vec2 z = vec2(0.0, 0.0); 83 | float i; 84 | for (i = 0.0; i < 1.0; i += 0.005) { 85 | z = vec2( 86 | z.x * z.x - z.y * z.y + c.x, 87 | z.y * z.x + z.x * z.y + c.y 88 | ); 89 | 90 | if (length(z) > 4.0) { 91 | break; 92 | } 93 | } 94 | ``` 95 | 96 | We now want to find out whether the complex number that we are manipulating (i.e. `c`) is within 97 | the Mandelbrot set. The definition of the Mandelbrot set says that a number `c` is within the set 98 | if the function `f(z) = z² + c` diverges when iterated from `z = 0` (`z` being a complex number). 99 | 100 | This is exactly what we do in this code. We start from `z = vec2(0.0, 0.0)` and iterate with a 101 | *for* loop. Each iteration puts the value of the next iteration in `z` and checks whether it is 102 | diverging (we consider that it is diverging if `length(z) > 4.0`). 103 | 104 | > **Note**: The `length` function is a built-in function in GLSL. You can find its definition and 105 | > the definitions of all the built-in functions at [docs.gl](http://docs.gl/sl4/length). 106 | 107 | What we have left at the end of the *for* loop is the `i` variable. If `c` is in the set then the 108 | function didn't diverge, the *for* loop went to the end, and `i` will contain `1.0`. Otherwise `c` 109 | is not within the set and `i` will contain a number between `0.0` and `1.0`. The closer `c` is to 110 | the set, the higher `i` will be. Therefore the value of `i` is what we are going to store in our 111 | image. 112 | 113 | ```glsl 114 | vec4 to_write = vec4(vec3(i), 1.0); 115 | imageStore(img, ivec2(gl_GlobalInvocationID.xy), to_write); 116 | ``` 117 | 118 | In these two lines, `vec4(..)`, `vec3(..)` and `ivec2(..)` are conversion functions. They convert 119 | their parameters into respectively a `vec4`, a `vec3` and a `ivec2`. 120 | `vec3(i)` is a shortcut for `vec3(i, i, i)`. 121 | 122 | Writing the pixel of an image must be done with the `imageStore` function. As explained in [a 123 | previous section](02-image-clear.html) the content of the image is opaque and is always treated as 124 | floating-points, even though we know that its memory contains integers. 125 | 126 | ## Calling this shader 127 | 128 | Now that the shader is written, the rest should be straight-forward. We start by creating an image, 129 | as seen before: 130 | 131 | ```rust 132 | let image = Image::new( 133 | memory_allocator.clone(), 134 | ImageCreateInfo { 135 | image_type: ImageType::Dim2d, 136 | format: Format::R8G8B8A8_UNORM, 137 | extent: [1024, 1024, 1], 138 | usage: ImageUsage::STORAGE | ImageUsage::TRANSFER_SRC, 139 | ..Default::default() 140 | }, 141 | AllocationCreateInfo { 142 | memory_type_filter: MemoryTypeFilter::PREFER_DEVICE, 143 | ..Default::default() 144 | }, 145 | ) 146 | .unwrap(); 147 | ``` 148 | 149 | This time we can't just clear the image like we did earlier. To actually pass the image 150 | to the GPU shader, we first need to create a `ImageView` of it. An `ImageView` describes where 151 | and how the GPU should access or use the image. Here, we want a view of the entire image, 152 | so the creation isn't very difficult: 153 | 154 | ```rust 155 | use vulkano::image::view::ImageView; 156 | 157 | let view = ImageView::new_default(image.clone()).unwrap(); 158 | ``` 159 | 160 | Now, let's create the descriptor set by adding the image view, like we did 161 | [earlier](../04-compute-pipeline/03-descriptor-sets.html): 162 | 163 | ```rust 164 | let layout = compute_pipeline.layout().set_layouts().get(0).unwrap(); 165 | let set = PersistentDescriptorSet::new( 166 | &descriptor_set_allocator, 167 | layout.clone(), 168 | [WriteDescriptorSet::image_view(0, view.clone())], // 0 is the binding 169 | [], 170 | ) 171 | .unwrap(); 172 | ``` 173 | 174 | Next, we can create a buffer for storing the image output: 175 | 176 | ```rust 177 | let buf = Buffer::from_iter( 178 | memory_allocator.clone(), 179 | BufferCreateInfo { 180 | usage: BufferUsage::TRANSFER_DST, 181 | ..Default::default() 182 | }, 183 | AllocationCreateInfo { 184 | memory_type_filter: MemoryTypeFilter::PREFER_HOST 185 | | MemoryTypeFilter::HOST_RANDOM_ACCESS, 186 | ..Default::default() 187 | }, 188 | (0..1024 * 1024 * 4).map(|_| 0u8), 189 | ) 190 | .expect("failed to create buffer"); 191 | ``` 192 | 193 | The command buffer contains a dispatch command followed with a copy-image-to-buffer command: 194 | 195 | ```rust 196 | let mut builder = AutoCommandBufferBuilder::primary( 197 | &command_buffer_allocator, 198 | queue.queue_family_index(), 199 | CommandBufferUsage::OneTimeSubmit, 200 | ) 201 | .unwrap(); 202 | builder 203 | .bind_pipeline_compute(compute_pipeline.clone()) 204 | .unwrap() 205 | .bind_descriptor_sets( 206 | PipelineBindPoint::Compute, 207 | compute_pipeline.layout().clone(), 208 | 0, 209 | set, 210 | ) 211 | .unwrap() 212 | .dispatch([1024 / 8, 1024 / 8, 1]) 213 | .unwrap() 214 | .copy_image_to_buffer(CopyImageToBufferInfo::image_buffer( 215 | image.clone(), 216 | buf.clone(), 217 | )) 218 | .unwrap(); 219 | 220 | let command_buffer = builder.build().unwrap(); 221 | ``` 222 | 223 | And finally just like in [the previous section](03-image-export.html) we execute the command buffer 224 | and export the image as a PNG file: 225 | 226 | ```rust 227 | let future = sync::now(device.clone()) 228 | .then_execute(queue.clone(), command_buffer) 229 | .unwrap() 230 | .then_signal_fence_and_flush() 231 | .unwrap(); 232 | 233 | future.wait(None).unwrap(); 234 | 235 | let buffer_content = buf.read().unwrap(); 236 | let image = ImageBuffer::, _>::from_raw(1024, 1024, &buffer_content[..]).unwrap(); 237 | image.save("image.png").unwrap(); 238 | 239 | println!("Everything succeeded!"); 240 | ``` 241 | 242 | And here is what you should get: 243 | 244 |
245 | 246 |
247 | 248 | Next: [Graphics pipeline introduction](../06-graphics-pipeline/01-introduction.html) 249 | -------------------------------------------------------------------------------- /src/06-graphics-pipeline/05-pipeline-creation.md: -------------------------------------------------------------------------------- 1 | # Putting it all together 2 | 3 | In [the vertex input section](02-vertex-shader.html) we created a buffer named `vertex_buffer` which 4 | contains the shape of our triangle, and wrote the source code of a *vertex shader* that positions 5 | vertices on the image. 6 | 7 | In [the fragment shader section](03-fragment-shader.html) we wrote the source code of a 8 | *fragment shader* that fills pixels with a color. 9 | 10 | Finally in [the render passes section](04-render-pass-framebuffer.html) we create a *render pass* 11 | and a *framebuffer* that contains the target image. 12 | 13 | It is now time to put everything together and perform the draw operation! 14 | 15 | > **Note**: You can find the [full source code of this chapter 16 | > here](https://github.com/vulkano-rs/vulkano-book/blob/main/chapter-code/06-graphics-pipeline/main.rs). 17 | 18 | ## Creating a graphics pipeline 19 | 20 | Just like we had to create a compute pipeline in order to perform a compute operation, we have to 21 | create a graphics pipeline before we perform a draw operation. 22 | 23 | This is done by first creating the shaders, just like for a compute pipeline: 24 | 25 | ```rust 26 | mod vs { 27 | vulkano_shaders::shader!{ 28 | ty: "vertex", 29 | src: r" 30 | ##version 460 31 | 32 | layout(location = 0) in vec2 position; 33 | 34 | void main() { 35 | gl_Position = vec4(position, 0.0, 1.0); 36 | } 37 | ", 38 | } 39 | } 40 | 41 | mod fs { 42 | vulkano_shaders::shader!{ 43 | ty: "fragment", 44 | src: " 45 | ##version 460 46 | 47 | layout(location = 0) out vec4 f_color; 48 | 49 | void main() { 50 | f_color = vec4(1.0, 0.0, 0.0, 1.0); 51 | } 52 | ", 53 | } 54 | } 55 | 56 | let vs = vs::load(device.clone()).expect("failed to create shader module"); 57 | let fs = fs::load(device.clone()).expect("failed to create shader module"); 58 | ``` 59 | 60 | Then we can create the graphics pipeline. 61 | 62 | ```rust 63 | use vulkano::pipeline::graphics::input_assembly::InputAssemblyState; 64 | use vulkano::pipeline::graphics::vertex_input::Vertex; 65 | use vulkano::pipeline::graphics::viewport::{Viewport, ViewportState}; 66 | use vulkano::pipeline::GraphicsPipeline; 67 | use vulkano::render_pass::Subpass; 68 | 69 | // More on this latter. 70 | let viewport = Viewport { 71 | offset: [0.0, 0.0], 72 | extent: [1024.0, 1024.0], 73 | depth_range: 0.0..=1.0, 74 | }; 75 | 76 | let pipeline = { 77 | // A Vulkan shader can in theory contain multiple entry points, so we have to specify 78 | // which one. 79 | let vs = vs.entry_point("main").unwrap(); 80 | let fs = fs.entry_point("main").unwrap(); 81 | 82 | let vertex_input_state = MyVertex::per_vertex() 83 | .definition(&vs.info().input_interface) 84 | .unwrap(); 85 | 86 | let stages = [ 87 | PipelineShaderStageCreateInfo::new(vs), 88 | PipelineShaderStageCreateInfo::new(fs), 89 | ]; 90 | 91 | let layout = PipelineLayout::new( 92 | device.clone(), 93 | PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) 94 | .into_pipeline_layout_create_info(device.clone()) 95 | .unwrap(), 96 | ) 97 | .unwrap(); 98 | 99 | let subpass = Subpass::from(render_pass.clone(), 0).unwrap(); 100 | 101 | GraphicsPipeline::new( 102 | device.clone(), 103 | None, 104 | GraphicsPipelineCreateInfo { 105 | // The stages of our pipeline, we have vertex and fragment stages. 106 | stages: stages.into_iter().collect(), 107 | // Describes the layout of the vertex input and how should it behave. 108 | vertex_input_state: Some(vertex_input_state), 109 | // Indicate the type of the primitives (the default is a list of triangles). 110 | input_assembly_state: Some(InputAssemblyState::default()), 111 | // Set the fixed viewport. 112 | viewport_state: Some(ViewportState { 113 | viewports: [viewport].into_iter().collect(), 114 | ..Default::default() 115 | }), 116 | // Ignore these for now. 117 | rasterization_state: Some(RasterizationState::default()), 118 | multisample_state: Some(MultisampleState::default()), 119 | color_blend_state: Some(ColorBlendState::with_attachment_states( 120 | subpass.num_color_attachments(), 121 | ColorBlendAttachmentState::default(), 122 | )), 123 | // This graphics pipeline object concerns the first pass of the render pass. 124 | subpass: Some(subpass.into()), 125 | ..GraphicsPipelineCreateInfo::layout(layout) 126 | }, 127 | ) 128 | .unwrap() 129 | }; 130 | ``` 131 | 132 | When we draw, we have the possibility to draw only to a specific rectangle of the screen called a 133 | ***viewport***. The borders of the viewport will map to the `-1.0` and `1.0` logical coordinates 134 | that we covered in [the vertex input section of the book](02-vertex-shader.html). Any part of the 135 | shape that ends up outside of this rectangle will be discarded. 136 | 137 | We configured the pipeline so that we use one specific viewport, and that the state of this 138 | viewport is *fixed*. It is possible to specify *dynamic states* when creating the pipeline, but 139 | since we left those at the default which is empty, there are none. This makes it not possible to 140 | change the viewport for each draw command, but adds more performance. Because we are drawing only 141 | one image and not changing the viewport between draws, this is the optimal approach. If you wanted 142 | to draw to another image of a different size, you would have to create a new pipeline object. 143 | Another approach would be to use a dynamic viewport, where you would pass your viewport in the 144 | command buffer instead. 145 | 146 | > **Note**: If you configure multiple viewports, you can use geometry shaders to choose which 147 | > viewport the shape is going to be drawn to. This topic isn't covered here. 148 | 149 | ## Drawing 150 | 151 | Now that we have all the ingredients, it is time to bind everything and insert a draw call inside 152 | of our render pass. 153 | 154 | To draw the triangle, we need to pass the pipeline, the vertex_buffer and the actual draw command: 155 | 156 | ```rust 157 | let mut builder = AutoCommandBufferBuilder::primary( 158 | &command_buffer_allocator, 159 | queue.queue_family_index(), 160 | CommandBufferUsage::OneTimeSubmit, 161 | ) 162 | .unwrap(); 163 | 164 | builder 165 | .begin_render_pass( 166 | RenderPassBeginInfo { 167 | clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into())], 168 | ..RenderPassBeginInfo::framebuffer(framebuffer.clone()) 169 | }, 170 | SubpassBeginInfo { 171 | contents: SubpassContents::Inline, 172 | ..Default::default() 173 | }, 174 | ) 175 | .unwrap() 176 | 177 | // new stuff 178 | .bind_pipeline_graphics(pipeline.clone()) 179 | .unwrap() 180 | .bind_vertex_buffers(0, vertex_buffer.clone()) 181 | .unwrap() 182 | .draw( 183 | 3, 1, 0, 0, // 3 is the number of vertices, 1 is the number of instances 184 | ) 185 | .unwrap() 186 | 187 | .end_render_pass(SubpassEndInfo::default()) 188 | .unwrap() 189 | 190 | // (continued below) 191 | ``` 192 | 193 | The first parameter of the `.draw()` method is the number of vertices of our shape. All the other 194 | constants are in the case of drawing on multiple viewports or drawing multiple objects with 195 | instancing (we won't cover that here). 196 | 197 | > **Note**: If you wanted to draw multiple objects, the most straight-forward method is to call 198 | > `draw()` multiple time in a row. 199 | 200 | Once we have finished drawing, let's do the same thing as [in the mandelbrot 201 | example](../05-images/04-mandelbrot.html) and write the image to a PNG file. 202 | 203 | To do that, as before, let's first create the buffer: 204 | 205 | ```rust 206 | // crop 207 | 208 | let buf = Buffer::from_iter( 209 | memory_allocator.clone(), 210 | BufferCreateInfo { 211 | usage: BufferUsage::TRANSFER_DST, 212 | ..Default::default() 213 | }, 214 | AllocationCreateInfo { 215 | memory_type_filter: MemoryTypeFilter::PREFER_HOST 216 | | MemoryTypeFilter::HOST_RANDOM_ACCESS, 217 | ..Default::default() 218 | }, 219 | (0..1024 * 1024 * 4).map(|_| 0u8), 220 | ) 221 | .expect("failed to create buffer"); 222 | 223 | // crop 224 | ``` 225 | 226 | And then write the rest of the operations: 227 | 228 | ```rust 229 | .copy_image_to_buffer(CopyImageToBufferInfo::image_buffer(image, buf.clone())) 230 | .unwrap(); 231 | 232 | let command_buffer = builder.build().unwrap(); 233 | 234 | let future = sync::now(device.clone()) 235 | .then_execute(queue.clone(), command_buffer) 236 | .unwrap() 237 | .then_signal_fence_and_flush() 238 | .unwrap(); 239 | future.wait(None).unwrap(); 240 | 241 | let buffer_content = buf.read().unwrap(); 242 | let image = ImageBuffer::, _>::from_raw(1024, 1024, &buffer_content[..]).unwrap(); 243 | image.save("image.png").unwrap(); 244 | 245 | println!("Everything succeeded!"); 246 | ``` 247 | 248 | And here is what you should get: 249 | 250 |
251 | 252 |
253 | 254 | Next: [Windowing](../07-windowing/01-introduction.html) 255 | -------------------------------------------------------------------------------- /src/07-windowing/02-swapchain-creation.md: -------------------------------------------------------------------------------- 1 | # Introduction to swapchains 2 | 3 | Since we are going to draw to a window which is ultimately on the screen, things are a bit special. 4 | If you were going to write directly to the window's surface, you would introduce tearing and other 5 | strange artifacts, because you would be updating an image that's already visible on a screen. 6 | To ensure that only complete images are shown, Vulkan uses what is called a *swapchain*. 7 | 8 | > **Note**: See also [the wikipedia article for a swap 9 | > chain](https://en.wikipedia.org/wiki/Swap_Chain). 10 | 11 | A swapchain is a group of one or multiple images, sometimes two images but most commonly three. If 12 | you have ever heard terms such as *double buffering* or *triple buffering*, it refers to having 13 | respectively two or three swapchain images. 14 | 15 | The idea behind a swapchain is to draw to one of its images while another one of these images is 16 | being shown on the screen. When we are done drawing we ask the swapchain to show the image we have 17 | just drawn to, and in return the swapchain gives us drawing access to another of its images. 18 | 19 | ## (Optional) Checking for swapchain support 20 | 21 | As you may recall, previously we just selected the first physical device available: 22 | 23 | ```rust 24 | let physical = instance 25 | .enumerate_physical_devices() 26 | .expect("could not enumerate devices") 27 | .next() 28 | .expect("no devices available"); 29 | ``` 30 | 31 | However, some devices may not support swapchain creation or wouldn't be the best option. So, in 32 | this optional sub-chapter, we are going to write a simple function to filter devices for specific 33 | Vulkan extension support and select the best device. In a real application, this could be your 34 | "default" or "recommended" device, and the user could choose any other if needed. 35 | 36 | The first step is to select all the extensions needed for your application: 37 | 38 | ```rust 39 | use vulkano::device::DeviceExtensions; 40 | 41 | let device_extensions = DeviceExtensions { 42 | khr_swapchain: true, 43 | ..DeviceExtensions::empty() 44 | }; 45 | ``` 46 | 47 | Next, we are going to enumerate all the devices and filter them by supported extensions: 48 | 49 | ```rust 50 | use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType}; 51 | 52 | instance 53 | .enumerate_physical_devices() 54 | .expect("could not enumerate devices") 55 | .filter(|p| p.supported_extensions().contains(&device_extensions)) 56 | // continues bellow 57 | ``` 58 | 59 | Some devices that passed the test may not have the needed queue family(ies) to present images to 60 | the surface or even support graphical operations. So, we are going to filter them and at the same 61 | time select the first queue family that is suitable: 62 | 63 | ```rust 64 | .filter_map(|p| { 65 | p.queue_family_properties() 66 | .iter() 67 | .enumerate() 68 | // Find the first first queue family that is suitable. 69 | // If none is found, `None` is returned to `filter_map`, 70 | // which disqualifies this physical device. 71 | .position(|(i, q)| { 72 | q.queue_flags.contains(QueueFlags::GRAPHICS) 73 | && p.surface_support(i as u32, &surface).unwrap_or(false) 74 | }) 75 | .map(|q| (p, q as u32)) 76 | }) 77 | // continues bellow 78 | ``` 79 | 80 | All the physical devices that pass the filters above are suitable for the application. 81 | However, not every device is equal, some are preferred over others. Now, we assign each 82 | physical device a score, and pick the device with the lowest ("best") score. 83 | 84 | ```rust 85 | .min_by_key(|(p, _)| match p.properties().device_type { 86 | PhysicalDeviceType::DiscreteGpu => 0, 87 | PhysicalDeviceType::IntegratedGpu => 1, 88 | PhysicalDeviceType::VirtualGpu => 2, 89 | PhysicalDeviceType::Cpu => 3, 90 | 91 | // Note that there exists `PhysicalDeviceType::Other`, however, 92 | // `PhysicalDeviceType` is a non-exhaustive enum. Thus, one should 93 | // match wildcard `_` to catch all unknown device types. 94 | _ => 4, 95 | }) 96 | .expect("no device available"); 97 | ``` 98 | 99 | In the end, your new function for selecting the best physical device should look like this: 100 | 101 | ```rust 102 | use std::sync::Arc; 103 | 104 | // crop 105 | use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType}; 106 | use vulkano::device::DeviceExtensions; 107 | use vulkano::swapchain::Surface; 108 | 109 | fn select_physical_device( 110 | instance: &Arc, 111 | surface: &Arc, 112 | device_extensions: &DeviceExtensions, 113 | ) -> (Arc, u32) { 114 | instance 115 | .enumerate_physical_devices() 116 | .expect("could not enumerate devices") 117 | .filter(|p| p.supported_extensions().contains(&device_extensions)) 118 | .filter_map(|p| { 119 | p.queue_family_properties() 120 | .iter() 121 | .enumerate() 122 | // Find the first first queue family that is suitable. 123 | // If none is found, `None` is returned to `filter_map`, 124 | // which disqualifies this physical device. 125 | .position(|(i, q)| { 126 | q.queue_flags.contains(QueueFlags::GRAPHICS) 127 | && p.surface_support(i as u32, &surface).unwrap_or(false) 128 | }) 129 | .map(|q| (p, q as u32)) 130 | }) 131 | .min_by_key(|(p, _)| match p.properties().device_type { 132 | PhysicalDeviceType::DiscreteGpu => 0, 133 | PhysicalDeviceType::IntegratedGpu => 1, 134 | PhysicalDeviceType::VirtualGpu => 2, 135 | PhysicalDeviceType::Cpu => 3, 136 | 137 | // Note that there exists `PhysicalDeviceType::Other`, however, 138 | // `PhysicalDeviceType` is a non-exhaustive enum. Thus, one should 139 | // match wildcard `_` to catch all unknown device types. 140 | _ => 4, 141 | }) 142 | .expect("no device available") 143 | } 144 | 145 | fn main() { 146 | // crop 147 | 148 | let device_extensions = DeviceExtensions { 149 | khr_swapchain: true, 150 | ..DeviceExtensions::empty() 151 | }; 152 | 153 | let (physical_device, queue_family_index) = select_physical_device( 154 | &instance, 155 | &surface, 156 | &device_extensions, 157 | ); 158 | 159 | // crop 160 | } 161 | ``` 162 | 163 | ## Updating logical device creation 164 | 165 | Now that we have our desired physical device, the next step is to create a logical device that can 166 | support the swapchain. 167 | 168 | To do that, we need to pass all the previously required extensions: 169 | 170 | ```rust 171 | use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo}; 172 | 173 | let (device, mut queues) = Device::new( 174 | physical_device.clone(), 175 | DeviceCreateInfo { 176 | queue_create_infos: vec![QueueCreateInfo { 177 | queue_family_index, 178 | ..Default::default() 179 | }], 180 | enabled_extensions: device_extensions, 181 | ..Default::default() 182 | }, 183 | ) 184 | .expect("failed to create device"); 185 | 186 | let queue = queues.next().unwrap(); 187 | ``` 188 | 189 | ## Creating the swapchain 190 | 191 | Swapchains have a lot of properties: the format and dimensions of their images, an optional 192 | transformation, a presentation mode, and so on. We have to specify a value for each of these 193 | parameters when we create the swapchain. Therefore, we have to query the 194 | capabilities of the surface. 195 | 196 | ```rust 197 | let caps = physical_device 198 | .surface_capabilities(&surface, Default::default()) 199 | .expect("failed to get surface capabilities"); 200 | ``` 201 | 202 | Of all of these properties, we only care about some of them, mainly the dimensions of the image 203 | (which have to be constrained between a minimum and a maximum), the behavior when it comes to 204 | transparency (composite alpha), and the format of the images. 205 | 206 | ```rust 207 | let dimensions = window.inner_size(); 208 | let composite_alpha = caps.supported_composite_alpha.into_iter().next().unwrap(); 209 | let image_format = physical_device 210 | .surface_formats(&surface, Default::default()) 211 | .unwrap()[0] 212 | .0; 213 | ``` 214 | 215 | Combining everything, we can create the swapchain: 216 | 217 | ```rust 218 | use vulkano::image::ImageUsage; 219 | use vulkano::swapchain::{Swapchain, SwapchainCreateInfo}; 220 | 221 | let (mut swapchain, images) = Swapchain::new( 222 | device.clone(), 223 | surface.clone(), 224 | SwapchainCreateInfo { 225 | min_image_count: caps.min_image_count + 1, // How many buffers to use in the swapchain 226 | image_format, 227 | image_extent: dimensions.into(), 228 | image_usage: ImageUsage::COLOR_ATTACHMENT, // What the images are going to be used for 229 | composite_alpha, 230 | ..Default::default() 231 | }, 232 | ) 233 | .unwrap(); 234 | ``` 235 | 236 | It's good to have `min_image_count` be at least one more than the minimal, to give a bit more 237 | freedom to the image queue. 238 | 239 | For additional information, check the 240 | [swapchain documentation](https://docs.rs/vulkano/0.34.0/vulkano/swapchain/index.html#swapchains). 241 | 242 | Next: [Other initialization](03-other-initialization.html) 243 | --------------------------------------------------------------------------------