├── .dockerignore ├── .gitignore ├── content ├── 404.html ├── home.html ├── guide │ ├── wip │ │ └── memory.md │ ├── graphics_pipeline │ │ ├── fragment_shader.md │ │ ├── introduction.md │ │ ├── vertex_shader.md │ │ ├── render_pass_framebuffer.md │ │ └── pipeline_creation.md │ ├── images │ │ ├── image_clear.md │ │ ├── image_export.md │ │ ├── image_creation.md │ │ └── mandelbrot.md │ ├── compute_pipeline │ │ ├── dispatch.md │ │ ├── descriptor_sets.md │ │ ├── compute_intro.md │ │ └── compute_pipeline.md │ ├── template.html │ ├── initialization │ │ ├── initialization.md │ │ └── device-creation.md │ ├── introduction │ │ └── introduction.md │ ├── windowing │ │ └── introduction.md │ └── buffer_creation │ │ ├── buffer_creation.md │ │ └── example_operation.md ├── template_main.html └── donate.html ├── chapter_code ├── .gitignore ├── src │ ├── 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 │ │ │ ├── app.rs │ │ │ └── main.rs │ │ ├── images │ │ │ ├── main.rs │ │ │ ├── image_clear.rs │ │ │ └── mandelbrot.rs │ │ ├── buffer_creation.rs │ │ ├── compute_pipeline.rs │ │ └── graphics_pipeline.rs │ ├── vulkano_objects │ │ ├── mod.rs │ │ ├── render_pass.rs │ │ ├── allocators.rs │ │ ├── pipeline.rs │ │ ├── instance.rs │ │ ├── physical_device.rs │ │ ├── swapchain.rs │ │ ├── command_buffers.rs │ │ └── buffers.rs │ ├── vertex_data.rs │ └── lib.rs ├── Cargo.toml └── README.md ├── static ├── logo.png ├── guide-mandelbrot-1.png ├── guide-image-export-1.png ├── guide-image-creation-1.png ├── guide-graphics-pipeline-creation-1.png ├── svg-styles.css ├── prism.css ├── style.css ├── guide-vertex-input-2.svg └── prism.js ├── Cargo.toml ├── Dockerfile ├── src └── bin │ └── main.rs ├── README.md ├── .circleci └── config.yml └── LICENSE-MIT /.dockerignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | -------------------------------------------------------------------------------- /content/404.html: -------------------------------------------------------------------------------- 1 |

404 - Not found.

2 | -------------------------------------------------------------------------------- /chapter_code/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | image.png -------------------------------------------------------------------------------- /chapter_code/src/game_objects/mod.rs: -------------------------------------------------------------------------------- 1 | mod square; 2 | 3 | pub use square::Square; 4 | -------------------------------------------------------------------------------- /chapter_code/src/shaders/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod movable_square; 2 | pub mod static_triangle; 3 | -------------------------------------------------------------------------------- /static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulkano-rs/vulkano-www/HEAD/static/logo.png -------------------------------------------------------------------------------- /static/guide-mandelbrot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulkano-rs/vulkano-www/HEAD/static/guide-mandelbrot-1.png -------------------------------------------------------------------------------- /static/guide-image-export-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulkano-rs/vulkano-www/HEAD/static/guide-image-export-1.png -------------------------------------------------------------------------------- /chapter_code/src/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod square; 2 | mod traits; 3 | 4 | pub use square::SquareModel; 5 | pub use traits::Model; 6 | -------------------------------------------------------------------------------- /static/guide-image-creation-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulkano-rs/vulkano-www/HEAD/static/guide-image-creation-1.png -------------------------------------------------------------------------------- /chapter_code/src/bin/more_on_buffers/render/mod.rs: -------------------------------------------------------------------------------- 1 | mod render_loop; 2 | mod renderer; 3 | 4 | pub use render_loop::RenderLoop; 5 | -------------------------------------------------------------------------------- /chapter_code/src/bin/restructuring/render/mod.rs: -------------------------------------------------------------------------------- 1 | mod render_loop; 2 | mod renderer; 3 | 4 | pub use render_loop::RenderLoop; 5 | -------------------------------------------------------------------------------- /static/guide-graphics-pipeline-creation-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulkano-rs/vulkano-www/HEAD/static/guide-graphics-pipeline-creation-1.png -------------------------------------------------------------------------------- /chapter_code/src/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/src/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/src/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/src/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/src/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 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulkano-www" 3 | version = "0.30.0" 4 | edition = "2021" 5 | authors = ["Pierre Krieger "] 6 | publish = false 7 | 8 | [dependencies] 9 | lazy_static = "1.1" 10 | mustache = "0.9" 11 | pulldown-cmark = "0.9.1" 12 | rouille = "3.0.0" 13 | -------------------------------------------------------------------------------- /chapter_code/src/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/src/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/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chapter_code" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | vulkano = "0.33.0" 10 | vulkano-shaders = "0.33.0" 11 | image = "0.24.0" 12 | winit = "0.28.3" 13 | vulkano-win = "0.33.0" 14 | rand = "0.8.5" 15 | 16 | [profile.dev] 17 | opt-level = 1 18 | -------------------------------------------------------------------------------- /chapter_code/src/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 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.10 AS builder 2 | RUN apk add --no-cache cargo rust 3 | COPY . /root/ 4 | RUN cargo build --release --manifest-path=/root/Cargo.toml 5 | 6 | FROM alpine:3.10 7 | RUN apk add --no-cache curl llvm-libunwind 8 | WORKDIR /root 9 | COPY ./static /root/static 10 | COPY --from=builder /root/target/release/main /root 11 | 12 | CMD /root/main 13 | ENV ADDR 0.0.0.0:80 14 | HEALTHCHECK --interval=1m --timeout=3s CMD curl -f http://localhost/ || exit 1 15 | -------------------------------------------------------------------------------- /chapter_code/src/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/src/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/src/bin/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.to_vec(), execute_example); 23 | } 24 | -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 The vulkano developers 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | use std::env; 11 | 12 | fn main() { 13 | let addr = env::var("ADDR").unwrap_or("0.0.0.0:8000".to_owned()); 14 | println!("Listening on {}", addr); 15 | vulkano_www::start(&addr) 16 | } 17 | -------------------------------------------------------------------------------- /chapter_code/src/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 | load: Clear, 13 | store: Store, 14 | format: swapchain.image_format(), 15 | samples: 1, 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 guide. 4 | 5 | ## Viewing the source code 6 | 7 | To view the source code from each chapter, navigate to `chapter_code/src/bin/`. Each file / folder 8 | corresponds to a specific chapter. 9 | 10 | Some chapters contain multiple examples, in which case the source code will be subdivided inside the chapter folder. 11 | For example, in case of `images`, there will be two examples: `chapter_code/src/bin/image_clear.rs` and `chapter_code/src/bin/mandelbrot.rs`. 12 | 13 | ## Running the source code 14 | 15 | If you want to run the source code and experiment for yourself, run `cargo run --bin ` inside this folder. 16 | For example: 17 | 18 | ```bash 19 | cargo run --bin windowing 20 | ``` 21 | -------------------------------------------------------------------------------- /chapter_code/src/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 | -------------------------------------------------------------------------------- /chapter_code/src/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: StandardMemoryAllocator, 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: StandardMemoryAllocator::new_default(device.clone()), 18 | command_buffer: StandardCommandBufferAllocator::new(device.clone(), Default::default()), 19 | descriptor_set: StandardDescriptorSetAllocator::new(device), 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > **Note** 2 | > This repository is no longer maintained and has been superseded by [vulkano-book](https://github.com/vulkano-rs/vulkano-book). 3 | 4 | # Source code of the vulkano website 5 | 6 | To run the website, just do: 7 | 8 | ```rust 9 | ADDR=0.0.0.0:8000 cargo run 10 | ``` 11 | 12 | To run chapter code: 13 | ``` 14 | cd chapter_code 15 | cargo run --bin 16 | ``` 17 | 18 | ## License 19 | 20 | Licensed under either of 21 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 22 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 23 | at your option. 24 | 25 | ### Contribution 26 | 27 | Unless you explicitly state otherwise, any contribution intentionally submitted 28 | for inclusion in the work by you shall be dual licensed as above, without any 29 | additional terms or conditions. 30 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | test: 5 | working_directory: ~/tgst 6 | docker: 7 | - image: mackeyja92/rustup 8 | steps: 9 | - run: 10 | name: Install dependencies 11 | command: | 12 | apt-get update 13 | apt-get install -yq sudo curl wget git file g++ cmake pkg-config bison flex unzip lib32stdc++6 lib32z1 14 | - checkout 15 | - run: cargo test --locked 16 | 17 | workflows: 18 | version: 2 19 | test-and-deploy: 20 | jobs: 21 | # We need this build job because it magically makes filtering work o.0 https://discuss.circleci.com/t/filters-branches-not-working-properly-with-workflows/23796/5 22 | - build: 23 | filters: 24 | branches: 25 | only: production 26 | - test: 27 | requires: 28 | - build 29 | filters: 30 | branches: 31 | only: production 32 | -------------------------------------------------------------------------------- /static/svg-styles.css: -------------------------------------------------------------------------------- 1 | /* The SVG diagrams in this directory import this style sheet via nodes like: 2 | 3 | 4 | 5 | (If the other tags in the SVG are namespaced, the starting and ending tags 6 | must be 'svg:style'.) 7 | 8 | You can edit this into the SVG manually, or use Inkscape's XML editor. It 9 | should a child of the 'defs' node. Once you've added it, Inkscape should 10 | leave it there. */ 11 | 12 | /* Definitions for fonts loaded from Google Fonts. For editing with Inkscape, 13 | you should install these fonts locally. */ 14 | 15 | /* Open Sans: https://fonts.google.com/specimen/Open+Sans 16 | Available under the Apache License, Version 2.0. */ 17 | @font-face { 18 | font-family: 'Open Sans'; 19 | font-style: normal; 20 | font-weight: 400; 21 | src: local('Open Sans Regular'), local('OpenSans-Regular'), url(https://fonts.gstatic.com/s/opensans/v15/mem8YaGs126MiZpBA-UFVZ0e.ttf) format('truetype'); 22 | } 23 | -------------------------------------------------------------------------------- /chapter_code/src/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/src/vulkano_objects/pipeline.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::device::Device; 4 | use vulkano::pipeline::graphics::input_assembly::InputAssemblyState; 5 | use vulkano::pipeline::graphics::vertex_input::Vertex; 6 | use vulkano::pipeline::graphics::viewport::{Viewport, ViewportState}; 7 | use vulkano::pipeline::GraphicsPipeline; 8 | use vulkano::render_pass::{RenderPass, Subpass}; 9 | use vulkano::shader::ShaderModule; 10 | 11 | use crate::Vertex2d; 12 | 13 | pub fn create_pipeline( 14 | device: Arc, 15 | vs: Arc, 16 | fs: Arc, 17 | render_pass: Arc, 18 | viewport: Viewport, 19 | ) -> Arc { 20 | GraphicsPipeline::start() 21 | .vertex_input_state(Vertex2d::per_vertex()) 22 | .vertex_shader(vs.entry_point("main").unwrap(), ()) 23 | .input_assembly_state(InputAssemblyState::new()) 24 | .viewport_state(ViewportState::viewport_fixed_scissor_irrelevant([viewport])) 25 | .fragment_shader(fs.entry_point("main").unwrap(), ()) 26 | .render_pass(Subpass::from(render_pass, 0).unwrap()) 27 | .build(device) 28 | .unwrap() 29 | } 30 | -------------------------------------------------------------------------------- /chapter_code/src/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/vulkano_objects/instance.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::instance::{Instance, InstanceCreateInfo, LayerProperties}; 4 | 5 | const LIST_AVAILABLE_LAYERS: bool = false; 6 | const ENABLE_VALIDATION_LAYERS: bool = false; 7 | const VALIDATION_LAYERS: &[&str] = &["VK_LAYER_LUNARG_api_dump"]; 8 | 9 | pub fn get_instance() -> Arc { 10 | let library = vulkano::VulkanLibrary::new().expect("no local Vulkan library/DLL"); 11 | let required_extensions = vulkano_win::required_extensions(&library); 12 | 13 | if LIST_AVAILABLE_LAYERS { 14 | let layers: Vec<_> = library.layer_properties().unwrap().collect(); 15 | let layer_names = layers.iter().map(LayerProperties::name); 16 | println!( 17 | "Available layers:\n {:?}", 18 | layer_names.clone().collect::>() 19 | ); 20 | } 21 | 22 | let mut create_info = InstanceCreateInfo { 23 | enabled_extensions: required_extensions, 24 | ..Default::default() 25 | }; 26 | 27 | if ENABLE_VALIDATION_LAYERS { 28 | create_info.enabled_layers = VALIDATION_LAYERS.iter().map(|s| s.to_string()).collect(); 29 | } 30 | 31 | Instance::new(library, create_info).unwrap() 32 | } 33 | -------------------------------------------------------------------------------- /chapter_code/src/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 | -------------------------------------------------------------------------------- /content/home.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • 4 |

    Safe

    5 | 6 |

    Vulkano follows the Rust definition of safety. It should be impossible for 7 | the user to get any undefined behavior using safe code, even when that code seems 8 | absurd.

    9 |
  • 10 | 11 |
  • 12 |

    Predictable

    13 | 14 |

    The actual Vulkan functions called by vulkano are precisely documented, making 15 | vulkano very easy to profile or debug with Vulkan tools.

    16 |
  • 17 | 18 |
  • 19 |

    Efficient

    20 | 21 |

    Vulkano tries to have as little runtime overhead as possible by moving some elements 22 | to compile-time, while still maintaining safety. 23 |

    24 |
  • 25 | 26 |
  • 27 |

    Convenient

    28 | 29 |

    Vulkano provides convenient methods on top of Vulkan making it faster to develop with. 30 | You can focus on the graphics. 31 |

    32 |
  • 33 |
34 | 35 |
36 | Guide 37 | Triangle example 38 |
39 |
40 | -------------------------------------------------------------------------------- /chapter_code/src/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 | -------------------------------------------------------------------------------- /content/guide/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/src/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 | -------------------------------------------------------------------------------- /content/guide/graphics_pipeline/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](/guide/image-clear) these values are normalized, in other words the value `1.0` will in 39 | reality write `255` in memory. In this example since our target image contains colors, we write the 40 | color red. 41 | 42 | Next: [Render passes and framebuffers](render-pass-framebuffer) 43 | -------------------------------------------------------------------------------- /content/template_main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Vulkano 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Fork me on GitHub 13 | 14 |
15 |

Vulkano

16 |

Safe Rust wrapper around 17 | the Vulkan API

18 | 19 | 27 |
28 | 29 |
{{{body}}}
30 | 31 | 32 | -------------------------------------------------------------------------------- /chapter_code/src/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::{ImageUsage, SwapchainImage}; 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 = Some( 22 | physical_device 23 | .surface_formats(&surface, Default::default()) 24 | .unwrap()[0] 25 | .0, 26 | ); 27 | 28 | Swapchain::new( 29 | device, 30 | surface.clone(), 31 | SwapchainCreateInfo { 32 | min_image_count: caps.min_image_count, 33 | image_format, 34 | image_extent: surface 35 | .object() 36 | .unwrap() 37 | .clone() 38 | .downcast::() 39 | .unwrap() 40 | .inner_size() 41 | .into(), 42 | image_usage: ImageUsage::COLOR_ATTACHMENT, 43 | composite_alpha, 44 | ..Default::default() 45 | }, 46 | ) 47 | .unwrap() 48 | } 49 | 50 | pub fn create_framebuffers_from_swapchain_images( 51 | images: &[Arc], 52 | render_pass: Arc, 53 | ) -> Vec> { 54 | images 55 | .iter() 56 | .map(|image| { 57 | let view = ImageView::new_default(image.clone()).unwrap(); 58 | Framebuffer::new( 59 | render_pass.clone(), 60 | FramebufferCreateInfo { 61 | attachments: vec![view], 62 | ..Default::default() 63 | }, 64 | ) 65 | .unwrap() 66 | }) 67 | .collect::>() 68 | } 69 | -------------------------------------------------------------------------------- /static/prism.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=clike+glsl+rust */ 2 | /** 3 | * okaidia theme for JavaScript, CSS and HTML 4 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 5 | * @author ocodia 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: #f8f8f2; 11 | background: none; 12 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | /* Code blocks */ 32 | pre[class*="language-"] { 33 | padding: 1rem; 34 | margin: .5rem 0; 35 | overflow: auto; 36 | border-radius: 0.3rem; 37 | } 38 | 39 | :not(pre) > code[class*="language-"], 40 | pre[class*="language-"] { 41 | background: #272822; 42 | } 43 | 44 | /* Inline code */ 45 | :not(pre) > code[class*="language-"] { 46 | padding: .1rem; 47 | border-radius: .3rem; 48 | white-space: normal; 49 | } 50 | 51 | .token.comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: slategray; 56 | } 57 | 58 | .token.punctuation { 59 | color: #f8f8f2; 60 | } 61 | 62 | .namespace { 63 | opacity: .7; 64 | } 65 | 66 | .token.property, 67 | .token.tag, 68 | .token.constant, 69 | .token.symbol, 70 | .token.deleted { 71 | color: #f92672; 72 | } 73 | 74 | .token.boolean, 75 | .token.number { 76 | color: #ae81ff; 77 | } 78 | 79 | .token.selector, 80 | .token.attr-name, 81 | .token.string, 82 | .token.char, 83 | .token.builtin, 84 | .token.inserted { 85 | color: #a6e22e; 86 | } 87 | 88 | .token.operator, 89 | .token.entity, 90 | .token.url, 91 | .language-css .token.string, 92 | .style .token.string, 93 | .token.variable { 94 | color: #f8f8f2; 95 | } 96 | 97 | .token.atrule, 98 | .token.attr-value, 99 | .token.function { 100 | color: #e6db74; 101 | } 102 | 103 | .token.keyword { 104 | color: #66d9ef; 105 | } 106 | 107 | .token.regex, 108 | .token.important { 109 | color: #fd971f; 110 | } 111 | 112 | .token.important, 113 | .token.bold { 114 | font-weight: bold; 115 | } 116 | .token.italic { 117 | font-style: italic; 118 | } 119 | 120 | .token.entity { 121 | cursor: help; 122 | } 123 | 124 | -------------------------------------------------------------------------------- /content/guide/images/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 | > **Note**: In reality Vulkan also allows you to create *linear* images, which can be modified but 9 | > are much slower and are supposed to be used only in some limited situations. Vulkano doesn't 10 | > support them yet. 11 | 12 | Therefore the only way to read or write to an image is to ask the GPU to do it. This is exactly 13 | what we are going to do by asking the GPU to fill our image with a specific color. This is called 14 | *clearing* an image. 15 | 16 | ```rust 17 | use vulkano::command_buffer::ClearColorImageInfo; 18 | use vulkano::format::ClearColorValue; 19 | 20 | let mut builder = AutoCommandBufferBuilder::primary( 21 | device.clone(), 22 | queue.queue_family_index(), 23 | CommandBufferUsage::OneTimeSubmit, 24 | ) 25 | .unwrap(); 26 | 27 | builder 28 | .clear_color_image(ClearColorImageInfo { 29 | clear_value: ClearColorValue::Float([0.0, 0.0, 1.0, 1.0]), 30 | ..ClearColorImageInfo::image(image.clone()) 31 | }) 32 | .unwrap(); 33 | 34 | let command_buffer = builder.build().unwrap(); 35 | ``` 36 | 37 | > **Note**: The function is called clearing a *color* image, as opposed to depth and/or stencil 38 | > images which we haven't covered yet. 39 | 40 | ## Normalized components 41 | 42 | [The `ClearColorValue` enum](https://docs.rs/vulkano/0.33.0/vulkano/format/enum.ClearColorValue.html) indicates 43 | which color to fill the image with. Depending on the format of the image, we have to use the right 44 | enum variant of `ClearValue`. 45 | 46 | Here we pass floating-point values because the image was created with the `R8G8B8A8_UNORM` format. 47 | The `R8G8B8A8` part means that the four components are stored in 8 bits each, while the `UNORM` 48 | suffix means "unsigned normalized". The coordinates being "normalized" means that their value in 49 | memory (ranging between 0 and 255) is interpreted as floating point values. The in-memory value `0` 50 | is interpreted as the floating-point `0.0`, and the in-memory value `255` is interpreted as the 51 | floating-point `1.0`. 52 | 53 | With any format whose suffix is `UNORM` (but also `SNORM` and `SRGB`), all the operations that are 54 | performed on the image (with the exception of memory copies) treat the image as if it contained 55 | floating-point values. This is the reason why we pass `[0.0, 0.0, 1.0, 1.0]`. The values `1.0` will 56 | in fact be stored as `255` in memory. 57 | 58 | Next: [Exporting the result](/guide/image-export) 59 | -------------------------------------------------------------------------------- /content/guide/compute_pipeline/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](/guide/example-operation). 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 | .bind_descriptor_sets( 33 | PipelineBindPoint::Compute, 34 | compute_pipeline.layout().clone(), 35 | descriptor_set_layout_index as u32, 36 | descriptor_set, 37 | ) 38 | .dispatch(work_group_counts) 39 | .unwrap(); 40 | 41 | let command_buffer = command_buffer_builder.build().unwrap(); 42 | ``` 43 | 44 | First, we bind the pipeline and then the *descriptor set*s, indicating the type of set, the layout 45 | and the descriptor sets we are going to use. Here the number of sets could have actually been many, 46 | in which case we would indicate our desired one with an index. Because we only have one, the index 47 | is 0. 48 | 49 | As explained in [the compute pipeline section](/guide/compute-pipeline), we want to spawn 1024 50 | *work groups*. This value is indicated by the actual `.dispatch()` method. 51 | 52 | Just like we already covered, we submit the command buffer: 53 | 54 | ```rust 55 | let future = sync::now(device.clone()) 56 | .then_execute(queue.clone(), command_buffer) 57 | .unwrap() 58 | .then_signal_fence_and_flush() 59 | .unwrap(); 60 | ``` 61 | 62 | This just schedules the operation for execution and tells the GPU to signal when finished. 63 | We have to wait for it to complete: 64 | 65 | ```rust 66 | future.wait(None).unwrap(); 67 | ``` 68 | 69 | Once complete, we can check that the pipeline has been correctly executed: 70 | 71 | ```rust 72 | let content = data_buffer.read().unwrap(); 73 | for (n, val) in content.iter().enumerate() { 74 | assert_eq!(*val, n as u32 * 12); 75 | } 76 | 77 | println!("Everything succeeded!"); 78 | ``` 79 | 80 | Next: [Creating an image](/guide/image-creation) 81 | -------------------------------------------------------------------------------- /chapter_code/src/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 | -------------------------------------------------------------------------------- /content/guide/template.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 78 | 79 |
{{{body}}}
80 |
81 | 82 | 87 | -------------------------------------------------------------------------------- /content/guide/graphics_pipeline/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](/guide/mandelbrot)), there is a third 10 | 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](/guide/compute-pipeline) describes a compute operation. 34 | - One or multiple buffers containing the shape of the object we want to draw. 35 | - A ***framebuffer*** object, which is a collection of images to write to. 36 | - Just like compute pipelines, we can also pass descriptor sets (and push constants, which we 37 | haven't covered yet). 38 | 39 | When you start a graphics operation, the GPU will start by executing a ***vertex shader*** (that 40 | is part of the graphics pipeline object) on each vertex of the shape that you want to draw. This 41 | first step will allow you to position the shape on the screen. 42 | 43 | Then the GPU finds out which pixels of the target image are covered by the shape, and executes a 44 | ***fragment shader*** (also part of the graphics pipeline object) on each of these pixels. This 45 | shader is used to determine what is the color of the shape for the given pixel is. Finally the 46 | GPU will merge this color with the color that already exists at this location. 47 | 48 | The ***graphics pipeline object*** contains the vertex shader, the fragment shader, plus various 49 | options that allows one to further configure the behavior of the graphics card. 50 | 51 | > **Note**: This explanation only covers the fundamentals of graphics pipelines. Graphics pipelines 52 | > have tons of configurable options, plus additional optional shader stages. 53 | 54 | The next sections will be dedicated to covering graphics pipeline in more details. 55 | 56 | Next: [Vertex input](/guide/vertex-input) 57 | -------------------------------------------------------------------------------- /content/guide/initialization/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.33.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, InstanceCreateInfo}; 17 | 18 | let library = VulkanLibrary::new().expect("no local Vulkan library/DLL"); 19 | let instance = Instance::new(library, InstanceCreateInfo::default()) 20 | .expect("failed to create instance"); 21 | ``` 22 | 23 | Like many other functions in vulkano, creating an instance returns a `Result`. If Vulkan is not 24 | available on the system, this result will contain an error. For the sake of this example we call 25 | `expect` on the `Result`, which prints a message to stderr and terminates the application if it 26 | contains an error. In a real game or application you should handle that situation in a nicer way, 27 | for example by opening a dialog box with an explanation. This is out of scope of this guide. 28 | 29 | Before going further you can try your code by running: 30 | 31 | ```bash 32 | cargo run 33 | ``` 34 | 35 | ## Enumerating physical devices 36 | 37 | The machine you run your program on may have multiple devices that support Vulkan. Before we can 38 | ask a video card to perform some operations, we have to enumerate all the *physical device*s that 39 | support Vulkan and choose which one we are going to use for this operation. 40 | 41 | In reality a physical device can be a dedicated graphics card, but also an integrated graphics 42 | processor or a software implementation. It can be basically anything that allows running Vulkan 43 | operations. 44 | 45 | As of the writing of this guide, it is not yet possible to use multiple devices simultaneously 46 | in an efficient way (eg. SLI/Crossfire). You *can* use multiple devices simultaneously in the same 47 | program, but there is not much point in doing so because you cannot share anything between them. 48 | Consequently the best thing to do in practice is to choose one physical device which is going to 49 | run everything: 50 | 51 | ```rust 52 | let physical_device = instance 53 | .enumerate_physical_devices() 54 | .expect("could not enumerate devices") 55 | .next() 56 | .expect("no devices available"); 57 | ``` 58 | 59 | The `enumerate_physical_devices` function returns a `Result` of an iterator to the list of 60 | available physical devices. We call `next` on it to return the first device, if any. Note that the 61 | first device is not necessarily the best device. In a real program you probably want to leave the 62 | choice to the user (later we will see a better implementation of this). 63 | 64 | Keep in mind that the list of physical devices can be empty. This happens if Vulkan is installed 65 | on the system, but none of the physical devices of the machine are capable of supporting Vulkan. In 66 | a real-world application you are encouraged to handle this situation properly as well. 67 | 68 | Next: [Device creation](/guide/device-creation) 69 | -------------------------------------------------------------------------------- /chapter_code/src/bin/restructuring/render/render_loop.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::swapchain::AcquireError; 4 | use vulkano::sync::{FlushError, GpuFuture}; 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.renderer.acquire_swapchain_image() { 44 | Ok(r) => r, 45 | Err(AcquireError::OutOfDate) => { 46 | self.recreate_swapchain = true; 47 | return; 48 | } 49 | Err(e) => panic!("Failed to acquire next image: {:?}", e), 50 | }; 51 | 52 | if suboptimal { 53 | self.recreate_swapchain = true; 54 | } 55 | 56 | if let Some(image_fence) = &self.fences[image_i as usize] { 57 | image_fence.wait(None).unwrap(); 58 | } 59 | 60 | // logic that uses the GPU resources that are currently not used (have been waited upon) 61 | 62 | let something_needs_all_gpu_resources = false; 63 | let previous_future = match self.fences[self.previous_fence_i as usize].clone() { 64 | None => self.renderer.synchronize().boxed(), 65 | Some(fence) => { 66 | if something_needs_all_gpu_resources { 67 | fence.wait(None).unwrap(); 68 | } 69 | fence.boxed() 70 | } 71 | }; 72 | 73 | if something_needs_all_gpu_resources { 74 | // logic that can use every GPU resource (the GPU is sleeping) 75 | } 76 | 77 | let result = self 78 | .renderer 79 | .flush_next_future(previous_future, acquire_future, image_i); 80 | 81 | self.fences[image_i as usize] = match result { 82 | Ok(fence) => Some(Arc::new(fence)), 83 | Err(FlushError::OutOfDate) => { 84 | self.recreate_swapchain = true; 85 | None 86 | } 87 | Err(e) => { 88 | println!("Failed to flush future: {:?}", e); 89 | None 90 | } 91 | }; 92 | 93 | self.previous_fence_i = image_i; 94 | } 95 | 96 | pub fn handle_window_resize(&mut self) { 97 | // impacts the next update 98 | self.window_resized = true; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /content/guide/introduction/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Welcome to the vulkano guide! 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 guide 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 guide 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 guide, 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 guide, you can create a pull request at 22 | [vulkano-www](https://github.com/vulkano-rs/vulkano-www) 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.33.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 guide, please [open an 64 | issue](https://github.com/vulkano-rs/vulkano-www/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](/guide/initialization)! 68 | -------------------------------------------------------------------------------- /chapter_code/src/bin/more_on_buffers/render/render_loop.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use chapter_code::game_objects::Square; 4 | use vulkano::swapchain::AcquireError; 5 | use vulkano::sync::{FlushError, GpuFuture}; 6 | use winit::event_loop::EventLoop; 7 | 8 | use crate::render::renderer::{Fence, Renderer}; 9 | 10 | pub struct RenderLoop { 11 | renderer: Renderer, 12 | recreate_swapchain: bool, 13 | window_resized: bool, 14 | fences: Vec>>, 15 | previous_fence_i: u32, 16 | } 17 | 18 | impl RenderLoop { 19 | pub fn new(event_loop: &EventLoop<()>) -> Self { 20 | let renderer = Renderer::initialize(event_loop); 21 | let frames_in_flight = renderer.get_image_count(); 22 | let fences: Vec>> = vec![None; frames_in_flight]; 23 | 24 | Self { 25 | renderer, 26 | recreate_swapchain: false, 27 | window_resized: false, 28 | fences, 29 | previous_fence_i: 0, 30 | } 31 | } 32 | 33 | pub fn update(&mut self, triangle: &Square) { 34 | if self.window_resized { 35 | self.window_resized = false; 36 | self.recreate_swapchain = false; 37 | self.renderer.handle_window_resize(); 38 | } 39 | if self.recreate_swapchain { 40 | self.recreate_swapchain = false; 41 | self.renderer.recreate_swapchain(); 42 | } 43 | 44 | let (image_i, suboptimal, acquire_future) = match self.renderer.acquire_swapchain_image() { 45 | Ok(r) => r, 46 | Err(AcquireError::OutOfDate) => { 47 | self.recreate_swapchain = true; 48 | return; 49 | } 50 | Err(e) => panic!("Failed to acquire next image: {:?}", e), 51 | }; 52 | 53 | if suboptimal { 54 | self.recreate_swapchain = true; 55 | } 56 | 57 | if let Some(image_fence) = &self.fences[image_i as usize] { 58 | image_fence.wait(None).unwrap(); 59 | } 60 | 61 | // logic that uses the GPU resources that are currently not used (have been waited upon) 62 | self.renderer.update_uniform(image_i, triangle); 63 | 64 | let something_needs_all_gpu_resources = false; 65 | let previous_future = match self.fences[self.previous_fence_i as usize].clone() { 66 | None => self.renderer.synchronize().boxed(), 67 | Some(fence) => { 68 | if something_needs_all_gpu_resources { 69 | fence.wait(None).unwrap(); 70 | } 71 | fence.boxed() 72 | } 73 | }; 74 | 75 | if something_needs_all_gpu_resources { 76 | // logic that can use every GPU resource (the GPU is sleeping) 77 | } 78 | 79 | let result = self 80 | .renderer 81 | .flush_next_future(previous_future, acquire_future, image_i); 82 | 83 | self.fences[image_i as usize] = match result { 84 | Ok(fence) => Some(Arc::new(fence)), 85 | Err(FlushError::OutOfDate) => { 86 | self.recreate_swapchain = true; 87 | None 88 | } 89 | Err(e) => { 90 | println!("Failed to flush future: {:?}", e); 91 | None 92 | } 93 | }; 94 | 95 | self.previous_fence_i = image_i; 96 | } 97 | 98 | pub fn handle_window_resize(&mut self) { 99 | // impacts the next update 100 | self.window_resized = true; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /content/guide/compute_pipeline/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 = StandardDescriptorSetAllocator::new(device.clone()); 44 | let pipeline_layout = compute_pipeline.layout(); 45 | let descriptor_set_layouts = pipeline_layout.set_layouts(); 46 | 47 | let descriptor_set_layout_index = 0; 48 | let descriptor_set_layout = descriptor_set_layouts 49 | .get(descriptor_set_layout_index) 50 | .unwrap(); 51 | let descriptor_set = PersistentDescriptorSet::new( 52 | &descriptor_set_allocator, 53 | descriptor_set_layout.clone(), 54 | [WriteDescriptorSet::buffer(0, data_buffer.clone())], // 0 is the binding 55 | ) 56 | .unwrap(); 57 | ``` 58 | 59 | In order to create a descriptor set, you'll need to know the layout that it is targeting. We do 60 | this by using the "Pipeline" trait and calling `.layout()` on our pipeline to obtain the pipeline's 61 | layout. Next we'll fetch the layout specific to the pass that we want to target by using 62 | `.set_layouts().get(0)` where zero indicates the first index of the pass that we are targeting. 63 | 64 | Once you have created a descriptor set, you may also use it with other pipelines, as long as the 65 | bindings' types match those the pipelines' shaders expect. But Vulkan requires that you provide a 66 | pipeline whenever you create a descriptor set; you cannot create one independently of any 67 | particular pipeline. 68 | 69 | We then bind each descriptor one by one in order, which here is just the `buf` variable. Just like 70 | for `compute_pipeline`, cloning `data_buffer` only clones an `Arc` and isn't expensive. 71 | 72 | > **Note**: `data_buffer` was created in [the introduction](/guide/compute-intro). 73 | 74 | Now that we have a compute pipeline and a descriptor set to bind to it, we can start our operation. 75 | This is covered in [the next section](/guide/dispatch). 76 | -------------------------------------------------------------------------------- /content/guide/initialization/device-creation.md: -------------------------------------------------------------------------------- 1 | # Device creation 2 | 3 | In [the previous section](/guide/initialization) 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 | .enumerate() 48 | .position(|(_queue_family_index, queue_family_properties)| { 49 | queue_family_properties.queue_flags.contains(QueueFlags::GRAPHICS) 50 | }) 51 | .expect("couldn't find a graphical queue family") as u32; 52 | ``` 53 | 54 | Once we have the index of a viable queue family, we can use it to create the device: 55 | 56 | ```rust 57 | use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo}; 58 | 59 | let (device, mut queues) = Device::new( 60 | physical_device, 61 | DeviceCreateInfo { 62 | // here we pass the desired queue family to use by index 63 | queue_create_infos: vec![QueueCreateInfo { 64 | queue_family_index, 65 | ..Default::default() 66 | }], 67 | ..Default::default() 68 | }, 69 | ) 70 | .expect("failed to create device"); 71 | ``` 72 | 73 | Creating a device returns two things: the device itself, but also a list of *queue objects* that 74 | will later allow us to submit operations. 75 | 76 | Once this function call succeeds we have an open channel of communication with a Vulkan device! 77 | 78 | Since it is possible to request multiple queues, the `queues` variable returned by the function is 79 | in fact an iterator. In this example code this iterator contains just one element, so let's 80 | extract it: 81 | 82 | ```rust 83 | let queue = queues.next().unwrap(); 84 | ``` 85 | 86 | We now have our `device` and our `queue`, which means that we are ready to ask the GPU to perform 87 | operations. 88 | 89 | Next: [Creating a buffer](/guide/buffer-creation) 90 | -------------------------------------------------------------------------------- /chapter_code/src/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 | 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 | SubpassContents::Inline, 40 | ) 41 | .unwrap() 42 | .bind_pipeline_graphics(pipeline.clone()) 43 | .bind_vertex_buffers(0, vertex_buffer.clone()) 44 | .draw(vertex_buffer.len() as u32, 1, 0, 0) 45 | .unwrap() 46 | .end_render_pass() 47 | .unwrap(); 48 | 49 | Arc::new(builder.build().unwrap()) 50 | }) 51 | .collect() 52 | } 53 | 54 | pub fn create_simple_command_buffers( 55 | allocators: &Allocators, 56 | queue: Arc, 57 | pipeline: Arc, 58 | framebuffers: &[Arc], 59 | buffers: &Buffers, 60 | ) -> Vec> { 61 | framebuffers 62 | .iter() 63 | .enumerate() 64 | .map(|(i, framebuffer)| { 65 | let mut builder = AutoCommandBufferBuilder::primary( 66 | &allocators.command_buffer, 67 | queue.queue_family_index(), 68 | CommandBufferUsage::MultipleSubmit, 69 | ) 70 | .unwrap(); 71 | 72 | let index_buffer = buffers.get_index(); 73 | let index_buffer_length = index_buffer.len(); 74 | 75 | builder 76 | .begin_render_pass( 77 | RenderPassBeginInfo { 78 | clear_values: vec![Some([0.1, 0.1, 0.1, 1.0].into())], 79 | ..RenderPassBeginInfo::framebuffer(framebuffer.clone()) 80 | }, 81 | SubpassContents::Inline, 82 | ) 83 | .unwrap() 84 | .bind_pipeline_graphics(pipeline.clone()) 85 | .bind_descriptor_sets( 86 | PipelineBindPoint::Graphics, 87 | pipeline.layout().clone(), 88 | 0, 89 | buffers.get_uniform_descriptor_set(i), 90 | ) 91 | .bind_vertex_buffers(0, buffers.get_vertex()) 92 | .bind_index_buffer(index_buffer) 93 | .draw_indexed(index_buffer_length as u32, 1, 0, 0, 0) 94 | .unwrap() 95 | .end_render_pass() 96 | .unwrap(); 97 | 98 | Arc::new(builder.build().unwrap()) 99 | }) 100 | .collect() 101 | } 102 | -------------------------------------------------------------------------------- /content/guide/images/image_export.md: -------------------------------------------------------------------------------- 1 | # Exporting the content of an image 2 | 3 | In [the previous section](/guide/image-clear) 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-www/blob/master/chapter_code/src/bin/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, 25 | BufferCreateInfo { 26 | usage: BufferUsage::TRANSFER_DST, 27 | ..Default::default() 28 | }, 29 | AllocationCreateInfo { 30 | usage: MemoryUsage::Download, 31 | ..Default::default() 32 | }, 33 | (0..1024 * 1024 * 4).map(|_| 0u8), 34 | ) 35 | .expect("failed to create buffer"); 36 | ``` 37 | 38 | And let's modify the command buffer we created in the previous section to add the copy operation: 39 | 40 | ```rust 41 | use vulkano::command_buffer::CopyImageToBufferInfo; 42 | 43 | builder 44 | .clear_color_image(ClearColorImageInfo { 45 | clear_value: ClearColorValue::Float([0.0, 0.0, 1.0, 1.0]), 46 | ..ClearColorImageInfo::image(image.clone()) 47 | }) 48 | .unwrap() 49 | .copy_image_to_buffer(CopyImageToBufferInfo::image_buffer( 50 | image.clone(), 51 | buf.clone(), 52 | )) 53 | .unwrap(); 54 | ``` 55 | 56 | Since this is a memory transfer operation, this time the image values are *not* interpreted as 57 | floating-point values. The memory content of the image (unsigned 8-bit values) is directly copied 58 | to the buffer. 59 | 60 | Let's not forget to execute the command buffer and block until the operation is finished: 61 | 62 | ```rust 63 | use vulkano::sync::{self, GpuFuture}; 64 | 65 | let future = sync::now(device.clone()) 66 | .then_execute(queue.clone(), command_buffer) 67 | .unwrap() 68 | .then_signal_fence_and_flush() 69 | .unwrap(); 70 | 71 | future.wait(None).unwrap(); 72 | ``` 73 | 74 | ## Turning the image into a PNG 75 | 76 | Now that we have a buffer that contains our image data, we will visualize it by saving it as a PNG 77 | file. The Rust ecosystem has a crate named `image` that can do this. 78 | Let's add it to our Cargo.toml: 79 | 80 | ```toml 81 | image = "0.24" 82 | ``` 83 | 84 | In this library the main type that represents an image is the `ImageBuffer`. It can be created 85 | from a slice: 86 | 87 | ```rust 88 | use image::{ImageBuffer, Rgba}; 89 | 90 | let buffer_content = buf.read().unwrap(); 91 | let image = ImageBuffer::, _>::from_raw(1024, 1024, &buffer_content[..]).unwrap(); 92 | ``` 93 | 94 | The `ImageBuffer` can then be saved into a PNG file: 95 | 96 | ```rust 97 | image.save("image.png").unwrap(); 98 | 99 | println!("Everything succeeded!"); 100 | ``` 101 | 102 | And that's it! When running your program, a blue image named `image.png` should appear. 103 | 104 |
105 | 106 | 107 | *Here it is.* 108 |
109 | 110 | This might look stupid, but think about the fact that it's the GPU that wrote the content of 111 | the image. In the next sections we will do more than just fill an image with blue, but we will 112 | continue to retrieve the image's content and write it to a PNG file. 113 | 114 | Next: [Drawing a fractal with a compute shader](/guide/mandelbrot) 115 | -------------------------------------------------------------------------------- /content/donate.html: -------------------------------------------------------------------------------- 1 | 115 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * General 3 | */ 4 | @import url('https://fonts.googleapis.com/css?family=Muli'); 5 | 6 | body { 7 | background-color: #222; 8 | color: white; 9 | font-family: 'Muli', Verdana, Geneva, Tahoma, sans-serif; 10 | font-size: 1.1rem; 11 | margin: 0; 12 | padding: 0; 13 | } 14 | 15 | @media (max-width: 1200px) { 16 | body { 17 | font-size: 10px; 18 | } 19 | } 20 | 21 | a { 22 | color: white; 23 | } 24 | 25 | pre { 26 | border: 1px dashed #888; 27 | border-radius: 2px; 28 | } 29 | 30 | /* 31 | * Header 32 | */ 33 | header { 34 | background-color: #2e3d9d; 35 | background-image: url(/logo.png); 36 | background-repeat: no-repeat, repeat; 37 | background-size: auto 80%, cover; 38 | background-position: 5% 70%; 39 | display: block; 40 | margin: 0; 41 | padding: 0; 42 | } 43 | 44 | header h1 { 45 | font-size: 3rem; 46 | margin: 0; 47 | padding: 1rem 0 0.4rem; 48 | text-align: center; 49 | } 50 | 51 | header h2 { 52 | font-size: 1.4rem; 53 | margin: 0; 54 | margin-bottom: 1rem; 55 | padding: 0; 56 | text-align: center; 57 | } 58 | 59 | header ul { 60 | display: block; 61 | margin: 0; 62 | padding: 0.9rem 0; 63 | text-align: center; 64 | } 65 | 66 | header ul li { 67 | display: inline; 68 | margin: 0; 69 | padding: 0; 70 | font-weight: bold; 71 | text-transform: lowercase; 72 | } 73 | 74 | header ul li:first-child::before { 75 | content: ""; 76 | } 77 | 78 | header ul li::before { 79 | content: " - "; 80 | } 81 | 82 | header ul li a { 83 | text-decoration: none; 84 | } 85 | 86 | header ul li a:hover { 87 | text-decoration: underline; 88 | } 89 | 90 | /* 91 | * Home page 92 | */ 93 | #home { 94 | padding: 2rem 0; 95 | } 96 | 97 | #home #infos { 98 | display: flex; 99 | align-items: stretch; 100 | flex-direction: row; 101 | flex-wrap: wrap; 102 | text-align: center; 103 | margin: 0 auto; 104 | padding: 3rem 0 5rem; 105 | } 106 | 107 | @media (min-width: 1200px) { 108 | #home #infos { 109 | width: 80%; 110 | } 111 | } 112 | 113 | #home #infos > li { 114 | display: block; 115 | flex-grow: 1; 116 | flex-basis: 10rem; 117 | padding: 2rem 2rem; 118 | } 119 | 120 | #home #infos > li + li { 121 | border-left: 1px dashed rgba(255, 255, 255, 0.5); 122 | } 123 | 124 | #home #jump { 125 | text-align: center; 126 | } 127 | 128 | #home #jump a { 129 | background-color: #2e3d9d; 130 | border: 10px solid rgba(0, 0, 0, 0.5); 131 | border-radius: 20px; 132 | display: inline-block; 133 | margin: 0 1rem 1rem; 134 | padding: 1rem 2rem; 135 | text-decoration: none; 136 | } 137 | 138 | #home #jump a:hover { 139 | background-color: #2e3d9d; 140 | } 141 | 142 | #home #jump a:active { 143 | border-color: black; 144 | } 145 | 146 | /* 147 | * Donate page 148 | */ 149 | #donate { 150 | margin: 0 10%; 151 | } 152 | 153 | /* 154 | * Guides 155 | */ 156 | #guides { 157 | display: flex; 158 | flex-direction: row; 159 | align-items: stretch; 160 | } 161 | 162 | #guides > nav { 163 | background-color: #444; 164 | font-size: 0.95rem; 165 | padding: 1rem 5rem 1rem 3rem; 166 | flex-basis: 10vw; 167 | flex-grow: 0.05; 168 | } 169 | 170 | #guides > nav a { 171 | text-decoration: none; 172 | } 173 | 174 | #guides > nav a:hover { 175 | color: #ddd; 176 | text-decoration: underline; 177 | } 178 | 179 | #guides > nav h2:first-child { 180 | margin-top: 0.5rem; 181 | } 182 | 183 | #guides > nav ul { 184 | list-style-type: none; 185 | margin: 1.7rem 0 0; 186 | padding: 0; 187 | } 188 | 189 | #guides > nav ul li { 190 | border-left: 2px solid rgba(255, 255, 255, 0.1); 191 | margin: 0; 192 | padding: 0.1rem 0; 193 | padding-left: 1.7rem; 194 | text-indent: -1.0rem; 195 | } 196 | 197 | #guides > nav ul li.current-page a { 198 | color: #6c7af3; 199 | font-weight: bold; 200 | } 201 | 202 | #guides > div { 203 | flex: 1; 204 | font-family: sans-serif; 205 | font-size: 0.9rem; 206 | padding: 1rem 2rem; 207 | } 208 | -------------------------------------------------------------------------------- /content/guide/compute_pipeline/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](/guide/device-creation) we talked about *queues*. These queues 22 | > are usually foremost *software* queues, and not actual hardware constructs. 23 | 24 | > **Note**: You can find the [full source code of this chapter 25 | > here](https://github.com/vulkano-rs/vulkano-www/blob/master/chapter_code/src/bin/compute_pipeline.rs). 26 | 27 | ## Usability 28 | 29 | Vulkan (or any other API) doesn't let you directly control the threading aspect of the GPU. 30 | In order to perform an operation with multiple values at once, you will only need to indicate the 31 | list of operations to perform on **one** value. The Vulkan implementation will automatically make 32 | the necessary adjustments to make your operation run on multiple values at once. 33 | 34 | This makes using a GPU much easier than if you had to manually control everything. However, you 35 | still need to be aware that your program will run multiple times in parallel, because it has 36 | consequences on what you can do without causing data races. 37 | 38 | ## Example in this guide 39 | 40 | For the purpose of this guide, we are going to do something very simple: we are going to multiply 41 | 65536 values by the constant 12. Even though this doesn't serve any purpose, it is a good starting 42 | point example. Most real-world usages of the GPU involve complex mathematical algorithms, and thus 43 | are not really appropriate for a tutorial. 44 | 45 | As explained above, you don't need to use any `for` loop or anything similar of that sort. All we 46 | have to do is write the operation that is performed on *one* value, and ask the GPU to execute 47 | it 65536 times. Our operation here is therefore simply (in pseudo-code): 48 | 49 | ```glsl 50 | // `index` will range from 0 to 65536 51 | buffer_content[index] *= 12; 52 | ``` 53 | 54 | While it may look like this code multiplies a single value by 12, in reality the Vulkan 55 | implementation will automatically handle all the details that make it possible to run this in 56 | parallel multiple times in the most optimized way. 57 | 58 | As a preliminary action we are going to create the buffer that will contain the values. This is 59 | similar to what we already did twice: 60 | 61 | ```rust 62 | let data_iter = 0..65536u32; 63 | let data_buffer = Buffer::from_iter( 64 | &memory_allocator, 65 | BufferCreateInfo { 66 | usage: BufferUsage::STORAGE_BUFFER, 67 | ..Default::default() 68 | }, 69 | AllocationCreateInfo { 70 | usage: MemoryUsage::Upload, 71 | ..Default::default() 72 | }, 73 | data_iter, 74 | ) 75 | .expect("failed to create buffer"); 76 | ``` 77 | 78 | The `data_buffer` buffer now contains the data before the transformation, and we are going to 79 | perform the calculation on each element. 80 | Although notice that we're using `STORAGE_BUFFER` usage this time, since the buffer will be used 81 | in the compute shader. 82 | 83 | [The next section of the guide](/guide/compute-pipeline) will indicate how to actually code this 84 | operation. 85 | -------------------------------------------------------------------------------- /content/guide/windowing/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-www/blob/master/chapter_code/src/bin/windowing.rs). 12 | 13 | ## Creating a window 14 | 15 | In order to create a window, we will use the `winit` crate. And while we're at it, we are also 16 | going to add a dependency to the `vulkano-win` crate which is a link between vulkano and winit. 17 | 18 | Add to your `Cargo.toml` dependencies: 19 | 20 | ```toml 21 | vulkano-win = "0.33.0" 22 | winit = "0.28.3" 23 | ``` 24 | 25 | We encourage you to browse [the documentation of `winit`](https://docs.rs/winit). 26 | 27 | Because the objects that come with creating a window are not part of Vulkan itself, the first thing 28 | that you will need to do is to enable all non-core extensions required to draw a window. 29 | `vulkano_win` automatically provides them for us, so the only thing left is to pass them on to the 30 | instance creation: 31 | 32 | ```rust 33 | use vulkano::instance::{Instance, InstanceCreateInfo}; 34 | 35 | let library = VulkanLibrary::new().expect("no local Vulkan library/DLL"); 36 | let required_extensions = vulkano_win::required_extensions(&library); 37 | let instance = Instance::new( 38 | library, 39 | InstanceCreateInfo { 40 | enabled_extensions: required_extensions, 41 | ..Default::default() 42 | }, 43 | ) 44 | .expect("failed to create instance"); 45 | ``` 46 | 47 | Now, let's create the actual window: 48 | 49 | ```rust 50 | use vulkano_win::VkSurfaceBuild; 51 | use winit::event_loop::{EventLoop}; 52 | use winit::window::{WindowBuilder}; 53 | 54 | let event_loop = EventLoop::new(); // ignore this for now 55 | let surface = WindowBuilder::new() 56 | .build_vk_surface(&event_loop, instance.clone()) 57 | .unwrap(); 58 | ``` 59 | 60 | As you can see, we created a new object, called *surface*. 61 | 62 | The *surface* is a cross-platform abstraction over the actual window object, that vulkano can use 63 | for rendering. As for the window itself, it can be retrieved by calling `surface.window()`, which 64 | you can use to manipulate and change its default properties. 65 | 66 | After you made the change, running the program should now open a window, then immediately 67 | close it when the `main` function exits. 68 | 69 | ## Events handling 70 | 71 | In order to make our application run for as long as the window is alive, we need to handle the 72 | window's events. This is typically done after initialization, and right before the end of the 73 | `main` function. Using the `event_loop` object: 74 | 75 | ```rust 76 | use winit::event::{Event, WindowEvent}; 77 | use winit::event_loop::ControlFlow; 78 | 79 | event_loop.run(|event, _, control_flow| { 80 | match event { 81 | Event::WindowEvent { 82 | event: WindowEvent::CloseRequested, 83 | .. 84 | } => { 85 | *control_flow = ControlFlow::Exit; 86 | }, 87 | _ => () 88 | } 89 | }); 90 | ``` 91 | 92 | What this code does is block the main thread forever, and calls the closure whenever the events 93 | loop (which we used to create our window) receives an event. These events include the events that 94 | are tied to our window, such as mouse movements. 95 | 96 | When the user wants to close the window, a `WindowEvent::CloseRequested` event is received, which 97 | makes our closure set the `control_flow` to `ControlFlow::Exit` which signals to winit that we want 98 | an exit. 99 | 100 | 101 | 104 | 105 | Right now, all we're doing is creating a window and keeping our program alive for as long as the 106 | window isn't closed. The next section will show how to initialize what is called a *swapchain* on 107 | the window's surface. 108 | 109 | Next: [Swapchain creation](/guide/windowing/swapchain-creation) 110 | -------------------------------------------------------------------------------- /content/guide/images/image_creation.md: -------------------------------------------------------------------------------- 1 | # Creating an image 2 | 3 | In [the buffers creation section of the guide](/guide/buffer-creation) we saw that in order for 4 | 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 | We often use Vulkan images to store *images* in the common sense of the word, in which case each 18 | value of the array contains the color of the pixel. However Vulkan images can also be used to store 19 | arbitrary data (in other words, not just colors). 20 | 21 | > **Note**: Pixels inside images are sometimes called **texels**, which is short for 22 | > "texture pixel". **Textures** are a more specialized alternative to images but that no longer 23 | > exist in Vulkan. The word "texel" has been less and less used over time, but the word "texture" 24 | > is still very common. 25 | 26 | ## Properties of an image 27 | 28 | While we often think of images as being two-dimensional, in the context of Vulkan they can also be 29 | one-dimensional or three-dimensional. The dimensions of an image are chosen when you create it. 30 | 31 | > **Note**: There are two kinds of three-dimensional images: actual three-dimensional images, and 32 | > arrays of two-dimensional layers. The difference is that with the former the layers are expected 33 | > to be contiguous, while for the latter you can manage layers individually as if they were 34 | > separate two-dimensional images. 35 | 36 | When you create an image you must also choose a format for its pixels. Depending on the format, the 37 | pixels of an image can have between one and four components. In other words each pixel is an array 38 | of one to four values. The four components are named, in order, R, G, B and A. 39 | 40 | > **Note**: If you are familiar with RGBA, it may seem obvious to you that the R component 41 | > (the first) is supposed to contain the red value of the pixel, the G component (the second) is 42 | > supposed to contain the green value of the pixel, and same for blue and alpha. However remember 43 | > that we can store arbitrary data in this format instead of colors. 44 | 45 | You can check [the list of available formats 46 | here](https://docs.rs/vulkano/0.33.0/vulkano/format/enum.Format.html). 47 | 48 | For example if you create an image with the format `R8_SINT`, then it will only have one component. 49 | But with the format `A2R10G10B10_SSCALED_PACK32`, you have all four components. The first part of 50 | the name of each format corresponds to the memory layout of the four components. For example with 51 | `B10G11R11_UFLOAT_PACK32`, each pixel is 32 bits long where the first 10 bits is the blue component, 52 | the next 11 bits are the green component, and the last 11 bits are the red component. Don't worry 53 | if you are confused, as we will only use the most simple formats in this guide. 54 | 55 | ## Image creation 56 | 57 | Creating an image is very similar to creating a buffer. Just like there are multiple different 58 | structs in vulkano that represent buffers, there are also multiple different structs that represent 59 | images. Here we are going to use a *StorageImage*, which is a general-purpose image. 60 | 61 | > **Note**: In practice the `StorageImage` is recommended for storing general-purpose values for 62 | > usage in shaders, like you would use a buffer. It is not recommended for storing actual images. 63 | 64 | ```rust 65 | use vulkano::image::{ImageDimensions, StorageImage}; 66 | use vulkano::format::Format; 67 | 68 | let image = StorageImage::new( 69 | device.clone(), 70 | ImageDimensions::Dim2d { 71 | width: 1024, 72 | height: 1024, 73 | array_layers: 1, // images can be arrays of layers 74 | }, 75 | Format::R8G8B8A8_UNORM, 76 | Some(queue.queue_family_index()), 77 | ) 78 | .unwrap(); 79 | ``` 80 | 81 | We pass the dimensions of the image and the desired format. The queue family to use is similar to 82 | the parameter when creating a buffer. It indicates which queue families are going to access the 83 | image. 84 | 85 | > **Note**: Images can be made of layers, but for this example we only have one layer. Also, images 86 | > have usage flags similar to buffers, but this precise constructor doesn't require them. 87 | 88 | Next: [Clearing an image](/guide/image-clear) 89 | -------------------------------------------------------------------------------- /chapter_code/src/bin/buffer_creation.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 The vulkano developers 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | //! This is the source code of the "Buffer Creation" chapter at http://vulkano.rs. 11 | //! 12 | //! It is not commented, as the explanations can be found in the guide itself. 13 | 14 | use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage}; 15 | use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; 16 | use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, CopyBufferInfo}; 17 | use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo, QueueFlags}; 18 | use vulkano::instance::{Instance, InstanceCreateInfo}; 19 | use vulkano::memory::allocator::{AllocationCreateInfo, MemoryUsage, StandardMemoryAllocator}; 20 | use vulkano::sync::{self, GpuFuture}; 21 | use vulkano::VulkanLibrary; 22 | 23 | fn main() { 24 | // Initialization 25 | let library = VulkanLibrary::new().expect("no local Vulkan library/DLL"); 26 | let instance = 27 | Instance::new(library, InstanceCreateInfo::default()).expect("failed to create instance"); 28 | 29 | let physical_device = instance 30 | .enumerate_physical_devices() 31 | .expect("could not enumerate devices") 32 | .next() 33 | .expect("no devices available"); 34 | 35 | // Device creation 36 | let queue_family_index = physical_device 37 | .queue_family_properties() 38 | .iter() 39 | .enumerate() 40 | .position(|(_, q)| q.queue_flags.contains(QueueFlags::GRAPHICS)) 41 | .expect("couldn't find a graphical queue family") as u32; 42 | 43 | let (device, mut queues) = Device::new( 44 | physical_device, 45 | DeviceCreateInfo { 46 | // here we pass the desired queue family to use by index 47 | queue_create_infos: vec![QueueCreateInfo { 48 | queue_family_index, 49 | ..Default::default() 50 | }], 51 | ..Default::default() 52 | }, 53 | ) 54 | .expect("failed to create device"); 55 | 56 | let queue = queues.next().unwrap(); 57 | 58 | let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); 59 | 60 | // Example operation 61 | let source_content: Vec = (0..64).collect(); 62 | let source = Buffer::from_iter( 63 | &memory_allocator, 64 | BufferCreateInfo { 65 | usage: BufferUsage::TRANSFER_SRC, 66 | ..Default::default() 67 | }, 68 | AllocationCreateInfo { 69 | usage: MemoryUsage::Upload, 70 | ..Default::default() 71 | }, 72 | source_content, 73 | ) 74 | .expect("failed to create source buffer"); 75 | 76 | let destination_content: Vec = (0..64).map(|_| 0).collect(); 77 | let destination = Buffer::from_iter( 78 | &memory_allocator, 79 | BufferCreateInfo { 80 | usage: BufferUsage::TRANSFER_DST, 81 | ..Default::default() 82 | }, 83 | AllocationCreateInfo { 84 | usage: MemoryUsage::Download, 85 | ..Default::default() 86 | }, 87 | destination_content, 88 | ) 89 | .expect("failed to create destination buffer"); 90 | 91 | let command_buffer_allocator = 92 | StandardCommandBufferAllocator::new(device.clone(), Default::default()); 93 | 94 | let mut builder = AutoCommandBufferBuilder::primary( 95 | &command_buffer_allocator, 96 | queue_family_index, 97 | CommandBufferUsage::OneTimeSubmit, 98 | ) 99 | .unwrap(); 100 | 101 | builder 102 | .copy_buffer(CopyBufferInfo::buffers(source.clone(), destination.clone())) 103 | .unwrap(); 104 | 105 | let command_buffer = builder.build().unwrap(); 106 | 107 | // Start the execution 108 | let future = sync::now(device) 109 | .then_execute(queue, command_buffer) 110 | .unwrap() 111 | .then_signal_fence_and_flush() // same as signal fence, and then flush 112 | .unwrap(); 113 | // Wait for the GPU to finish 114 | future.wait(None).unwrap(); 115 | 116 | let src_content = source.read().unwrap(); 117 | let destination_content = destination.read().unwrap(); 118 | assert_eq!(&*src_content, &*destination_content); 119 | 120 | println!("Everything succeeded!"); 121 | } 122 | -------------------------------------------------------------------------------- /chapter_code/src/bin/images/image_clear.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 The vulkano developers 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | //! This is the source code of the first three subchapters from the "Using images" chapter at http://vulkano.rs. 11 | //! 12 | //! It is not commented, as the explanations can be found in the guide itself. 13 | 14 | use image::{ImageBuffer, Rgba}; 15 | use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage}; 16 | use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; 17 | use vulkano::command_buffer::{ 18 | AutoCommandBufferBuilder, ClearColorImageInfo, CommandBufferUsage, CopyImageToBufferInfo, 19 | }; 20 | use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo, QueueFlags}; 21 | use vulkano::format::{ClearColorValue, Format}; 22 | use vulkano::image::{ImageDimensions, StorageImage}; 23 | use vulkano::instance::{Instance, InstanceCreateInfo}; 24 | use vulkano::memory::allocator::{AllocationCreateInfo, MemoryUsage, StandardMemoryAllocator}; 25 | use vulkano::sync; 26 | use vulkano::sync::GpuFuture; 27 | 28 | pub fn main() { 29 | let library = vulkano::VulkanLibrary::new().expect("no local Vulkan library/DLL"); 30 | let instance = 31 | Instance::new(library, InstanceCreateInfo::default()).expect("failed to create instance"); 32 | 33 | let physical = instance 34 | .enumerate_physical_devices() 35 | .expect("could not enumerate devices") 36 | .next() 37 | .expect("no devices available"); 38 | 39 | let queue_family_index = physical 40 | .queue_family_properties() 41 | .iter() 42 | .enumerate() 43 | .position(|(_, q)| q.queue_flags.contains(QueueFlags::GRAPHICS)) 44 | .expect("couldn't find a graphical queue family") as u32; 45 | 46 | let (device, mut queues) = Device::new( 47 | physical, 48 | DeviceCreateInfo { 49 | queue_create_infos: vec![QueueCreateInfo { 50 | queue_family_index, 51 | ..Default::default() 52 | }], 53 | ..Default::default() 54 | }, 55 | ) 56 | .expect("failed to create device"); 57 | 58 | let queue = queues.next().unwrap(); 59 | 60 | let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); 61 | let command_buffer_allocator = 62 | StandardCommandBufferAllocator::new(device.clone(), Default::default()); 63 | 64 | // Image creation 65 | let image = StorageImage::new( 66 | &memory_allocator, 67 | ImageDimensions::Dim2d { 68 | width: 1024, 69 | height: 1024, 70 | array_layers: 1, // images can be arrays of layers 71 | }, 72 | Format::R8G8B8A8_UNORM, 73 | Some(queue.queue_family_index()), 74 | ) 75 | .unwrap(); 76 | 77 | let buf = Buffer::from_iter( 78 | &memory_allocator, 79 | BufferCreateInfo { 80 | usage: BufferUsage::TRANSFER_DST, 81 | ..Default::default() 82 | }, 83 | AllocationCreateInfo { 84 | usage: MemoryUsage::Download, 85 | ..Default::default() 86 | }, 87 | (0..1024 * 1024 * 4).map(|_| 0u8), 88 | ) 89 | .expect("failed to create buffer"); 90 | 91 | let mut builder = AutoCommandBufferBuilder::primary( 92 | &command_buffer_allocator, 93 | queue.queue_family_index(), 94 | CommandBufferUsage::OneTimeSubmit, 95 | ) 96 | .unwrap(); 97 | builder 98 | .clear_color_image(ClearColorImageInfo { 99 | clear_value: ClearColorValue::Float([0.0, 0.0, 1.0, 1.0]), 100 | ..ClearColorImageInfo::image(image.clone()) 101 | }) 102 | .unwrap() 103 | .copy_image_to_buffer(CopyImageToBufferInfo::image_buffer(image, buf.clone())) 104 | .unwrap(); 105 | let command_buffer = builder.build().unwrap(); 106 | 107 | let future = sync::now(device) 108 | .then_execute(queue, command_buffer) 109 | .unwrap() 110 | .then_signal_fence_and_flush() 111 | .unwrap(); 112 | 113 | future.wait(None).unwrap(); 114 | 115 | // Exporting the result 116 | let buffer_content = buf.read().unwrap(); 117 | let image = ImageBuffer::, _>::from_raw(1024, 1024, &buffer_content[..]).unwrap(); 118 | image.save("image.png").unwrap(); 119 | 120 | println!("Everything succeeded!"); 121 | } 122 | -------------------------------------------------------------------------------- /content/guide/graphics_pipeline/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 guide, 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 guide 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, 64 | BufferCreateInfo { 65 | usage: BufferUsage::VERTEX_BUFFER, 66 | ..Default::default() 67 | }, 68 | AllocationCreateInfo { 69 | usage: MemoryUsage::Upload, 70 | ..Default::default() 71 | }, 72 | vec![vertex1, vertex2, vertex3], 73 | ) 74 | .unwrap(); 75 | ``` 76 | 77 | A buffer that contains a collection of vertices is commonly named a *vertex buffer*. Because we 78 | know the specific use of this buffer is for storing vertices, we specify the usage flag 79 | `VERTEX_BUFFER`. 80 | 81 | > **Note**: Vertex buffers are not special in any way. The term *vertex buffer* indicates the way 82 | > the programmer intends to use the buffer, and it is not a property of the buffer. 83 | 84 | ## Vertex shader 85 | 86 | At the start of the drawing operation, the GPU is going to pick each element from this buffer one 87 | by one and call a ***vertex shader*** on them. 88 | 89 | Here is what the source code of a vertex shader looks like: 90 | 91 | ```glsl 92 | #version 460 93 | 94 | layout(location = 0) in vec2 position; 95 | 96 | void main() { 97 | gl_Position = vec4(position, 0.0, 1.0); 98 | } 99 | ``` 100 | 101 | The line `layout(location = 0) in vec2 position;` declares that each vertex has an *attribute* 102 | named `position` and of type `vec2`. This corresponds to the definition of the `MyVertex` struct we 103 | created. 104 | 105 | > **Note**: Calling the `impl_vertex!` macro is what makes it possible for vulkano to build the 106 | > link between the content of the buffer and the input of the vertex shader. 107 | 108 | The `main` function is called once for each vertex, and sets the value of the `gl_Position` 109 | variable to a `vec4` whose first two components are the position of the vertex. 110 | 111 | `gl_Position` is a special "magic" global variable that exists only in the context of a vertex 112 | shader and whose value must be set to the position of the vertex on the surface. This is how the 113 | GPU knows how to position our shape. 114 | 115 | ## After the vertex shader 116 | 117 | After the vertex shader has run on each vertex, the GPU knows where our shape is located on the 118 | screen. It then proceeds to call [the fragment shader](/guide/fragment-shader). 119 | -------------------------------------------------------------------------------- /content/guide/graphics_pipeline/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 | load: Clear, 35 | store: Store, 36 | format: Format::R8G8B8A8_UNORM, 37 | samples: 1, 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: Clear` line indicates that we want the GPU to *clear* the image when entering the render 52 | pass (i.e. fill it with a single color), while `store: Store` indicates that we want the GPU to 53 | 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: DontCare` instead of `store: Store`. 57 | 58 | ## Entering the render pass 59 | 60 | A render pass only describes the format and the way we load and store the image we are going to 61 | draw upon. It is enough to initialize all the objects we need. 62 | 63 | But before we can draw, we also need to indicate the actual list of attachments. This is done 64 | by creating a *framebuffer*. 65 | 66 | Creating a framebuffer is typically done as part of the rendering process. It is not a 67 | bad idea to keep the framebuffer objects alive between frames, but it won't kill your 68 | performance to create and destroy a few framebuffer objects during some frames. 69 | 70 | ```rust 71 | use vulkano::render_pass::{Framebuffer, FramebufferCreateInfo}; 72 | 73 | let view = ImageView::new_default(image.clone()).unwrap(); 74 | let framebuffer = Framebuffer::new( 75 | render_pass.clone(), 76 | FramebufferCreateInfo { 77 | attachments: vec![view], 78 | ..Default::default() 79 | }, 80 | ) 81 | .unwrap(); 82 | ``` 83 | 84 | We are now ready the enter drawing mode! 85 | 86 | This is done by calling the `begin_render_pass` function on the command buffer builder. 87 | This function takes as parameter the framebuffer, a enum, and a `Vec` that contains the colors 88 | to fill the attachments with. Since we have only one single attachment, this `Vec` contains only 89 | one element. 90 | 91 | Clearing our attachment has exactly the same effect as the `clear_color_image` function we covered 92 | previously, except that this time it is done by the rendering engine. 93 | 94 | The enum passed as second parameter describes whether we are going to directly invoke draw 95 | commands or use secondary command buffers instead. Secondary command buffers are a more advanced 96 | topic. Be we are using only direct commands, we will leave it as `::Inline` 97 | 98 | As a demonstration, let's just enter a render pass and leave it immediately after: 99 | 100 | ```rust 101 | use vulkano::command_buffer::{RenderPassBeginInfo, SubpassContents}; 102 | 103 | let mut builder = AutoCommandBufferBuilder::primary( 104 | device.clone(), 105 | queue.queue_family_index(), 106 | CommandBufferUsage::OneTimeSubmit, 107 | ) 108 | .unwrap(); 109 | 110 | builder 111 | .begin_render_pass( 112 | RenderPassBeginInfo { 113 | clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into())], 114 | ..RenderPassBeginInfo::framebuffer(framebuffer.clone()) 115 | }, 116 | SubpassContents::Inline, 117 | ) 118 | .unwrap() 119 | .end_render_pass() 120 | .unwrap(); 121 | ``` 122 | 123 | The [next section](/guide/graphics-pipeline-creation) will introduce the `draw` command, which will 124 | be inserted between `begin_render_pass` and `end_render_pass`. 125 | -------------------------------------------------------------------------------- /static/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/src/bin/compute_pipeline.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 The vulkano developers 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | //! This is the source code of the "Compute pipeline" chapter at http://vulkano.rs. 11 | //! 12 | //! It is not commented, as the explanations can be found in the guide itself. 13 | 14 | use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage}; 15 | use vulkano::command_buffer::allocator::{ 16 | StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo, 17 | }; 18 | use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage}; 19 | use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; 20 | use vulkano::descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet}; 21 | use vulkano::device::{Device, DeviceCreateInfo, DeviceExtensions, QueueCreateInfo, QueueFlags}; 22 | use vulkano::instance::{Instance, InstanceCreateInfo}; 23 | use vulkano::memory::allocator::{AllocationCreateInfo, MemoryUsage, StandardMemoryAllocator}; 24 | use vulkano::pipeline::{ComputePipeline, Pipeline, PipelineBindPoint}; 25 | use vulkano::sync::{self, GpuFuture}; 26 | 27 | fn main() { 28 | let library = vulkano::VulkanLibrary::new().expect("no local Vulkan library/DLL"); 29 | let instance = 30 | Instance::new(library, InstanceCreateInfo::default()).expect("failed to create instance"); 31 | 32 | let physical_device = 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_device 39 | .queue_family_properties() 40 | .iter() 41 | .enumerate() 42 | .position(|(_, queue_family_properties)| { 43 | queue_family_properties 44 | .queue_flags 45 | .contains(QueueFlags::GRAPHICS) 46 | }) 47 | .expect("couldn't find a graphical queue family") as u32; 48 | 49 | let (device, mut queues) = Device::new( 50 | physical_device, 51 | DeviceCreateInfo { 52 | queue_create_infos: vec![QueueCreateInfo { 53 | queue_family_index, 54 | ..Default::default() 55 | }], 56 | enabled_extensions: DeviceExtensions { 57 | khr_storage_buffer_storage_class: true, 58 | ..DeviceExtensions::empty() 59 | }, 60 | ..Default::default() 61 | }, 62 | ) 63 | .expect("failed to create device"); 64 | 65 | let queue = queues.next().unwrap(); 66 | 67 | // Introduction to compute operations 68 | 69 | let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); 70 | 71 | let data_iter = 0..65536u32; 72 | let data_buffer = Buffer::from_iter( 73 | &memory_allocator, 74 | BufferCreateInfo { 75 | usage: BufferUsage::STORAGE_BUFFER, 76 | ..Default::default() 77 | }, 78 | AllocationCreateInfo { 79 | usage: MemoryUsage::Upload, 80 | ..Default::default() 81 | }, 82 | data_iter, 83 | ) 84 | .expect("failed to create buffer"); 85 | 86 | // Compute pipelines 87 | mod cs { 88 | vulkano_shaders::shader! { 89 | ty: "compute", 90 | src: " 91 | #version 460 92 | 93 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 94 | 95 | layout(set = 0, binding = 0) buffer Data { 96 | uint data[]; 97 | } buf; 98 | 99 | void main() { 100 | uint idx = gl_GlobalInvocationID.x; 101 | buf.data[idx] *= 12; 102 | } 103 | " 104 | } 105 | } 106 | 107 | let shader = cs::load(device.clone()).expect("failed to create shader module"); 108 | let compute_pipeline = ComputePipeline::new( 109 | device.clone(), 110 | shader.entry_point("main").unwrap(), 111 | &(), 112 | None, 113 | |_| {}, 114 | ) 115 | .expect("failed to create compute pipeline"); 116 | 117 | let descriptor_set_allocator = StandardDescriptorSetAllocator::new(device.clone()); 118 | 119 | let pipeline_layout = compute_pipeline.layout(); 120 | let descriptor_set_layouts = pipeline_layout.set_layouts(); 121 | let descriptor_set_layout_index = 0; 122 | let descriptor_set_layout = descriptor_set_layouts 123 | .get(descriptor_set_layout_index) 124 | .unwrap(); 125 | 126 | let descriptor_set = PersistentDescriptorSet::new( 127 | &descriptor_set_allocator, 128 | descriptor_set_layout.clone(), 129 | [WriteDescriptorSet::buffer(0, data_buffer.clone())], // 0 is the binding 130 | ) 131 | .unwrap(); 132 | 133 | let command_buffer_allocator = StandardCommandBufferAllocator::new( 134 | device.clone(), 135 | StandardCommandBufferAllocatorCreateInfo::default(), 136 | ); 137 | 138 | let mut command_buffer_builder = AutoCommandBufferBuilder::primary( 139 | &command_buffer_allocator, 140 | queue.queue_family_index(), 141 | CommandBufferUsage::OneTimeSubmit, 142 | ) 143 | .unwrap(); 144 | 145 | let work_group_counts = [1024, 1, 1]; 146 | 147 | command_buffer_builder 148 | .bind_pipeline_compute(compute_pipeline.clone()) 149 | .bind_descriptor_sets( 150 | PipelineBindPoint::Compute, 151 | compute_pipeline.layout().clone(), 152 | descriptor_set_layout_index as u32, 153 | descriptor_set, 154 | ) 155 | .dispatch(work_group_counts) 156 | .unwrap(); 157 | 158 | let command_buffer = command_buffer_builder.build().unwrap(); 159 | 160 | let future = sync::now(device) 161 | .then_execute(queue, command_buffer) 162 | .unwrap() 163 | .then_signal_fence_and_flush() 164 | .unwrap(); 165 | 166 | future.wait(None).unwrap(); 167 | 168 | let content = data_buffer.read().unwrap(); 169 | for (n, val) in content.iter().enumerate() { 170 | assert_eq!(*val, n as u32 * 12); 171 | } 172 | 173 | println!("Everything succeeded!"); 174 | } 175 | -------------------------------------------------------------------------------- /chapter_code/src/bin/images/mandelbrot.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 The vulkano developers 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | //! This is the source code of the "Drawing a fractal with a compute shader" subchapter 11 | //! from the "Using images" chapter at http://vulkano.rs. 12 | //! 13 | //! It is not commented, as the explanations can be found in the guide itself. 14 | 15 | use image::{ImageBuffer, Rgba}; 16 | use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage}; 17 | use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; 18 | use vulkano::command_buffer::{ 19 | AutoCommandBufferBuilder, CommandBufferUsage, CopyImageToBufferInfo, 20 | }; 21 | use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; 22 | use vulkano::descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet}; 23 | use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo, QueueFlags}; 24 | use vulkano::format::Format; 25 | use vulkano::image::view::ImageView; 26 | use vulkano::image::{ImageDimensions, StorageImage}; 27 | use vulkano::instance::{Instance, InstanceCreateInfo}; 28 | use vulkano::memory::allocator::{AllocationCreateInfo, MemoryUsage, StandardMemoryAllocator}; 29 | use vulkano::pipeline::{ComputePipeline, Pipeline, PipelineBindPoint}; 30 | use vulkano::sync::{self, GpuFuture}; 31 | 32 | pub fn main() { 33 | let library = vulkano::VulkanLibrary::new().expect("no local Vulkan library/DLL"); 34 | let instance = 35 | Instance::new(library, InstanceCreateInfo::default()).expect("failed to create instance"); 36 | 37 | let physical = instance 38 | .enumerate_physical_devices() 39 | .expect("could not enumerate devices") 40 | .next() 41 | .expect("no devices available"); 42 | 43 | let queue_family_index = physical 44 | .queue_family_properties() 45 | .iter() 46 | .enumerate() 47 | .position(|(_, q)| q.queue_flags.contains(QueueFlags::GRAPHICS)) 48 | .expect("couldn't find a graphical queue family") as u32; 49 | 50 | let (device, mut queues) = Device::new( 51 | physical, 52 | DeviceCreateInfo { 53 | queue_create_infos: vec![QueueCreateInfo { 54 | queue_family_index, 55 | ..Default::default() 56 | }], 57 | ..Default::default() 58 | }, 59 | ) 60 | .expect("failed to create device"); 61 | 62 | let queue = queues.next().unwrap(); 63 | 64 | mod cs { 65 | vulkano_shaders::shader! { 66 | ty: "compute", 67 | src: r" 68 | #version 460 69 | 70 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; 71 | 72 | layout(set = 0, binding = 0, rgba8) uniform writeonly image2D img; 73 | 74 | void main() { 75 | vec2 norm_coordinates = (gl_GlobalInvocationID.xy + vec2(0.5)) / vec2(imageSize(img)); 76 | 77 | vec2 c = (norm_coordinates - vec2(0.5)) * 2.0 - vec2(1.0, 0.0); 78 | 79 | vec2 z = vec2(0.0, 0.0); 80 | float i; 81 | for (i = 0.0; i < 1.0; i += 0.005) { 82 | z = vec2( 83 | z.x * z.x - z.y * z.y + c.x, 84 | z.y * z.x + z.x * z.y + c.y 85 | ); 86 | 87 | if (length(z) > 4.0) { 88 | break; 89 | } 90 | } 91 | 92 | vec4 to_write = vec4(vec3(i), 1.0); 93 | imageStore(img, ivec2(gl_GlobalInvocationID.xy), to_write); 94 | } 95 | ", 96 | } 97 | } 98 | 99 | let shader = cs::load(device.clone()).expect("failed to create shader module"); 100 | 101 | let compute_pipeline = ComputePipeline::new( 102 | device.clone(), 103 | shader.entry_point("main").unwrap(), 104 | &(), 105 | None, 106 | |_| {}, 107 | ) 108 | .expect("failed to create compute pipeline"); 109 | 110 | let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); 111 | 112 | let image = StorageImage::new( 113 | &memory_allocator, 114 | ImageDimensions::Dim2d { 115 | width: 1024, 116 | height: 1024, 117 | array_layers: 1, 118 | }, 119 | Format::R8G8B8A8_UNORM, 120 | Some(queue.queue_family_index()), 121 | ) 122 | .unwrap(); 123 | let view = ImageView::new_default(image.clone()).unwrap(); 124 | 125 | let descriptor_set_allocator = StandardDescriptorSetAllocator::new(device.clone()); 126 | 127 | let layout = compute_pipeline.layout().set_layouts().get(0).unwrap(); 128 | let set = PersistentDescriptorSet::new( 129 | &descriptor_set_allocator, 130 | layout.clone(), 131 | [WriteDescriptorSet::image_view(0, view)], // 0 is the binding 132 | ) 133 | .unwrap(); 134 | 135 | let buf = Buffer::from_iter( 136 | &memory_allocator, 137 | BufferCreateInfo { 138 | usage: BufferUsage::TRANSFER_DST, 139 | ..Default::default() 140 | }, 141 | AllocationCreateInfo { 142 | usage: MemoryUsage::Download, 143 | ..Default::default() 144 | }, 145 | (0..1024 * 1024 * 4).map(|_| 0u8), 146 | ) 147 | .expect("failed to create buffer"); 148 | 149 | let command_buffer_allocator = 150 | StandardCommandBufferAllocator::new(device.clone(), Default::default()); 151 | 152 | let mut builder = AutoCommandBufferBuilder::primary( 153 | &command_buffer_allocator, 154 | queue.queue_family_index(), 155 | CommandBufferUsage::OneTimeSubmit, 156 | ) 157 | .unwrap(); 158 | builder 159 | .bind_pipeline_compute(compute_pipeline.clone()) 160 | .bind_descriptor_sets( 161 | PipelineBindPoint::Compute, 162 | compute_pipeline.layout().clone(), 163 | 0, 164 | set, 165 | ) 166 | .dispatch([1024 / 8, 1024 / 8, 1]) 167 | .unwrap() 168 | .copy_image_to_buffer(CopyImageToBufferInfo::image_buffer(image, buf.clone())) 169 | .unwrap(); 170 | 171 | let command_buffer = builder.build().unwrap(); 172 | 173 | let future = sync::now(device) 174 | .then_execute(queue, command_buffer) 175 | .unwrap() 176 | .then_signal_fence_and_flush() 177 | .unwrap(); 178 | 179 | future.wait(None).unwrap(); 180 | 181 | let buffer_content = buf.read().unwrap(); 182 | let image = ImageBuffer::, _>::from_raw(1024, 1024, &buffer_content[..]).unwrap(); 183 | image.save("image.png").unwrap(); 184 | 185 | println!("Everything succeeded!"); 186 | } 187 | -------------------------------------------------------------------------------- /content/guide/graphics_pipeline/pipeline_creation.md: -------------------------------------------------------------------------------- 1 | # Putting it all together 2 | 3 | In [the vertex input section](/guide/vertex-input) 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](/guide/fragment-shader) we wrote the source code of a 8 | *fragment shader* that fills pixels with a color. 9 | 10 | Finally in [the render passes section](/guide/render-pass-framebuffer) 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-www/blob/master/chapter_code/src/bin/graphics_pipeline.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 by using a builder. 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 | origin: [0.0, 0.0], 72 | dimensions: [1024.0, 1024.0], 73 | depth_range: 0.0..1.0, 74 | }; 75 | 76 | let pipeline = GraphicsPipeline::start() 77 | // Describes the layout of the vertex input and how should it behave 78 | .vertex_input_state(MyVertex::per_vertex()) 79 | // A Vulkan shader can in theory contain multiple entry points, so we have to specify 80 | // which one. 81 | .vertex_shader(vs.entry_point("main").unwrap(), ()) 82 | // Indicate the type of the primitives (the default is a list of triangles) 83 | .input_assembly_state(InputAssemblyState::new()) 84 | // Set the fixed viewport 85 | .viewport_state(ViewportState::viewport_fixed_scissor_irrelevant([viewport])) 86 | // Same as the vertex input, but this for the fragment input 87 | .fragment_shader(fs.entry_point("main").unwrap(), ()) 88 | // This graphics pipeline object concerns the first pass of the render pass. 89 | .render_pass(Subpass::from(render_pass.clone(), 0).unwrap()) 90 | // Now that everything is specified, we call `build`. 91 | .build(device.clone()) 92 | .unwrap(); 93 | ``` 94 | 95 | When we draw, we have the possibility to draw only to a specific rectangle of the screen called a 96 | ***viewport***. The borders of the viewport will map to the `-1.0` and `1.0` logical coordinates 97 | that we covered in [the vertex input section of the guide](/guide/vertex-input). Any part of the 98 | shape that ends up outside of this rectangle will be discarded. 99 | 100 | The state `ViewportState::viewport_fixed_scissor_irrelevant()` configures the builder so that we 101 | use one specific viewport, and that the state of this viewport is *fixed*. This makes it not 102 | possible to change the viewport for each draw command, but adds more performance. Because we are 103 | drawing only one image and not changing the viewport between draws, this is the optimal approach. 104 | If you wanted to draw to another image of a different size, you would have to create a new pipeline 105 | object. Another approach would be to use a dynamic viewport, where you would pass your viewport in 106 | the command buffer instead. 107 | 108 | > **Note**: If you configure multiple viewports, you can use geometry shaders to choose which 109 | > viewport the shape is going to be drawn to. This topic isn't covered here. 110 | 111 | ## Drawing 112 | 113 | Now that we have all the ingredients, it is time to bind everything and insert a draw call inside 114 | of our render pass. 115 | 116 | To draw the triangle, we need to pass the pipeline, the vertex_buffer and the actual draw command: 117 | 118 | ```rust 119 | let mut builder = AutoCommandBufferBuilder::primary( 120 | device.clone(), 121 | queue.queue_family_index(), 122 | CommandBufferUsage::OneTimeSubmit, 123 | ) 124 | .unwrap(); 125 | 126 | builder 127 | .begin_render_pass( 128 | RenderPassBeginInfo { 129 | clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into())], 130 | ..RenderPassBeginInfo::framebuffer(framebuffer.clone()) 131 | }, 132 | SubpassContents::Inline, 133 | ) 134 | .unwrap() 135 | 136 | // new stuff 137 | .bind_pipeline_graphics(pipeline.clone()) 138 | .bind_vertex_buffers(0, vertex_buffer.clone()) 139 | .draw( 140 | 3, 1, 0, 0, // 3 is the number of vertices, 1 is the number of instances 141 | ) 142 | 143 | .unwrap() 144 | .end_render_pass() 145 | .unwrap() 146 | 147 | // (continued below) 148 | ``` 149 | 150 | The first parameter of the `.draw()` method is the number of vertices of our shape. All the other 151 | constants are in the case of drawing on multiple viewports or drawing multiple objects with 152 | instancing (we won't cover that here). 153 | 154 | > **Note**: If you wanted to draw multiple objects, the most straight-forward method is to call 155 | > `draw()` multiple time in a row. 156 | 157 | Once we have finished drawing, let's do the same thing as [in the mandelbrot 158 | example](/guide/mandelbrot) and write the image to a PNG file. 159 | 160 | To do that, as before, let's first create the buffer: 161 | 162 | ```rust 163 | // crop 164 | 165 | let buf = Buffer::from_iter( 166 | &memory_allocator, 167 | BufferCreateInfo { 168 | usage: BuferUsage::TRANSFER_DST, 169 | ..Default::default() 170 | }, 171 | AllocationCreateInfo { 172 | usage: MemoryUsage::Download, 173 | ..Default::default() 174 | }, 175 | (0..1024 * 1024 * 4).map(|_| 0u8), 176 | ) 177 | .expect("failed to create buffer"); 178 | 179 | // crop 180 | ``` 181 | 182 | And then write the rest of the operations: 183 | 184 | ```rust 185 | .copy_image_to_buffer(CopyImageToBufferInfo::image_buffer(image, buf.clone())) 186 | .unwrap(); 187 | 188 | let command_buffer = builder.build().unwrap(); 189 | 190 | let future = sync::now(device.clone()) 191 | .then_execute(queue.clone(), command_buffer) 192 | .unwrap() 193 | .then_signal_fence_and_flush() 194 | .unwrap(); 195 | future.wait(None).unwrap(); 196 | 197 | let buffer_content = buf.read().unwrap(); 198 | let image = ImageBuffer::, _>::from_raw(1024, 1024, &buffer_content[..]).unwrap(); 199 | image.save("image.png").unwrap(); 200 | 201 | println!("Everything succeeded!"); 202 | ``` 203 | 204 | And here is what you should get: 205 | 206 |
207 | 208 |
209 | 210 | Next: [Windowing](/guide/windowing/introduction) 211 | -------------------------------------------------------------------------------- /content/guide/compute_pipeline/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 guide 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](/guide/descriptor-sets). 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.33.0" 129 | ``` 130 | > **Note**: `vulkano-shaders` uses the crate `shaderc-sys` for the actual GLSL compilation step. 131 | > When you build your project, an attempt will be made to automatically install shaderc if you 132 | > don't already have it. shaderc also comes in [the Vulkan 133 | > SDK](https://www.vulkan.org/tools#download-these-essential-development-tools)). See [shaderc-sys 134 | > crate](https://lib.rs/crates/shaderc-sys) for installation instructions should the automatic 135 | > system fail. 136 | 137 | Here is the syntax: 138 | 139 | ```rust 140 | mod cs { 141 | vulkano_shaders::shader!{ 142 | ty: "compute", 143 | src: r" 144 | #version 460 145 | 146 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 147 | 148 | layout(set = 0, binding = 0) buffer Data { 149 | uint data[]; 150 | } buf; 151 | 152 | void main() { 153 | uint idx = gl_GlobalInvocationID.x; 154 | buf.data[idx] *= 12; 155 | } 156 | ", 157 | } 158 | } 159 | ``` 160 | 161 | As you can see, we specify some "fields" in the `vulkano_shaders::shader!` macro to specify our 162 | shader. The macro will then compile the GLSL code (outputting compilation errors if any) and 163 | generate several structs and methods, including one named `load`. This is the method that we have 164 | to use next: 165 | 166 | ```rust 167 | let shader = cs::load(device.clone()) 168 | .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. We won't cover the last three parameters, but 174 | you can search about them 175 | [here](https://docs.rs/vulkano/0.33.0/vulkano/pipeline/compute/struct.ComputePipeline.html). 176 | 177 | ```rust 178 | use vulkano::pipeline::ComputePipeline; 179 | 180 | let compute_pipeline = ComputePipeline::new( 181 | device.clone(), 182 | shader.entry_point("main").unwrap(), 183 | &(), 184 | None, 185 | |_| {}, 186 | ) 187 | .expect("failed to create compute pipeline"); 188 | ``` 189 | 190 | Before invoking that compute pipeline, we need to bind a buffer to it. This is covered by [the 191 | next section](/guide/descriptor-sets). 192 | -------------------------------------------------------------------------------- /content/guide/buffer_creation/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.33.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.33.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 = 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. 20 | 21 | # Creating a buffer 22 | 23 | When using Vulkan, you will very often need the GPU to read or write data in memory. In fact 24 | there isn't much point in using the GPU otherwise, as there is nothing you can do with the results 25 | of its work except write them to memory. 26 | 27 | In order for the GPU to be able to access some data (either for reading, writing or both), we 28 | first need to create a ***buffer*** object and put the data in it. 29 | 30 | ## Memory usage 31 | 32 | A Vulkan implementation might (and most often does) have multiple *memory types*, each being best 33 | suited to certain tasks. There are many possible arrangements of memory types a Vulkan 34 | implementation might have, and picking the right one is important to ensure most optimal performance. 35 | 36 | When allocating memory for a buffer in vulkano, you have to provide a *memory usage*, which tells 37 | the memory allocator which memory types it should prefer, and which ones it should avoid, when 38 | picking the right one. For example, if you want to continuously upload data to the GPU, you should 39 | use `MemoryUsage::Upload`; on the other hand, if you have some data that will largely remain 40 | visible only to the GPU, using `MemoryUsage::DeviceOnly` brings increased performance at the cost 41 | of more complicated data access from the CPU. 42 | 43 | The simplest way to create a buffer is to create it in CPU-accessible memory, by using 44 | `MemoryUsage::Upload` or `MemoryUsage::Download`: 45 | 46 | ```rust 47 | use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage}; 48 | use vulkano::memory::allocator::AllocationCreateInfo; 49 | 50 | let data: i32 = 12; 51 | let buffer = Buffer::from_data( 52 | &memory_allocator, 53 | BufferCreateInfo { 54 | usage: BufferUsage::UNIFORM_BUFFER, 55 | ..Default::default() 56 | }, 57 | AllocationCreateInfo { 58 | usage: MemoryUsage::Upload, 59 | ..Default::default() 60 | }, 61 | data, 62 | ) 63 | .expect("failed to create buffer"); 64 | ``` 65 | 66 | We have to indicate several things when creating the buffer. The first parameter is the memory 67 | allocator to use. 68 | 69 | The second parameter is the create info for the buffer. The only field that you have to override 70 | is [the usage for which we are creating the 71 | buffer](https://docs.rs/vulkano/0.33.0/vulkano/buffer/struct.BufferUsage.html) for, which can help 72 | the implementation perform some optimizations. Trying to use a buffer in a way that wasn't 73 | indicated when creating it will result in an error. For the sake of the example, we just create a 74 | buffer that supports being used as a uniform buffer. 75 | 76 | The third parameter is the create info for the allocation. The field of interest is similarly 77 | [the usage for which we are creating the 78 | allocation](https://docs.rs/vulkano/latest/vulkano/memory/allocator/enum.MemoryUsage.html). When 79 | creating a CPU-accessible buffer, you will most commonly use `MemoryUsage::Upload`, but in cases 80 | where the application is writing data through this buffer continuously, using 81 | `MemoryUsage::Download` is preferred as it may yield some performance gain. Using 82 | `MemoryUsage::DeviceOnly` will get you a buffer that is inaccessible from the CPU when such a 83 | memory type exists. Therefore, you can't use this memory usage together with `Buffer::from_data` 84 | directly, and instead have to create a *staging buffer* whose content is then copied to the 85 | device-local buffer. 86 | 87 | Finally, the fourth parameter is the content of the buffer. Here we create a buffer that contains 88 | a single integer with the value `12`. 89 | 90 | > **Note**: In a real application you shouldn't create buffers with only 4 bytes of data. Although 91 | > buffers aren't expensive, you should try to group as much related data as you can in the same 92 | > buffer. 93 | 94 | ## From_data and from_iter 95 | 96 | In the example above we create a buffer that contains the value `12`, which is of type `i32`. 97 | but you can put any type you want in a buffer, there is no restriction. In order to give our 98 | arbitrary types a representation that can be used in a generic way, we use the crate `bytemuck` 99 | and its "plain old data" trait, `AnyBitPattern`. Thus, any crate which exposes types with 100 | `bytemuck` support can be used in a buffer. You can also derive `AnyBitPattern` for you own types, 101 | or use the vulkano-provided `BufferContents` derive macro: 102 | 103 | ```rust 104 | use vulkano::buffer::BufferContents; 105 | 106 | #[derive(BufferContents)] 107 | #[repr(C)] 108 | struct MyStruct { 109 | a: u32, 110 | b: u32, 111 | } 112 | 113 | let data = MyStruct { a: 5, b: 69 }; 114 | 115 | let buffer = Buffer::from_data( 116 | &memory_allocator, 117 | BufferCreateInfo { 118 | usage: BufferUsage::UNIFORM_BUFFER, 119 | ..Default::default() 120 | }, 121 | AllocationCreateInfo { 122 | usage: MemoryUsage::Upload, 123 | ..Default::default() 124 | }, 125 | data, 126 | ) 127 | .unwrap(); 128 | ``` 129 | 130 | While it is sometimes useful to use a buffer that contains a single struct, in practice it is very 131 | common to put an array of values inside a buffer. You can, for example, put an array of fifty 132 | `i32`s in a buffer with the `Buffer::from_data` function. 133 | 134 | However, in practice it is also very common to not know the size of the array at compile-time. In 135 | order to handle this, `Buffer` provides a `from_iter` constructor that takes an iterator to the 136 | data as the last parameter, instead of the data itself. 137 | 138 | In the example below, we create a buffer that contains the value `5` of type `u8`, 128 times. The 139 | type of the content of the buffer is `[u8]`, which, in Rust, represents an array of `u8`s whose size 140 | is only known at runtime. 141 | 142 | ```rust 143 | let iter = (0..128).map(|_| 5u8); 144 | let buffer = Buffer::from_iter( 145 | &memory_allocator, 146 | BufferCreateInfo { 147 | usage: BufferUsage::UNIFORM_BUFFER, 148 | ..Default::default() 149 | }, 150 | AllocationCreateInfo { 151 | usage: MemoryUsage::Upload, 152 | ..Default::default() 153 | }, 154 | iter, 155 | ) 156 | .unwrap(); 157 | ``` 158 | 159 | ## Reading and writing the contents of a buffer 160 | 161 | Once a CPU-accessible buffer is created, you can access its content with the `read()` or `write()` 162 | methods. Using `read()` will grant you shared access to the content of the buffer, and using 163 | `write()` will grant you exclusive access. This is similar to using a `RwLock`. 164 | 165 | For example if `buffer` contains a `MyStruct` (see above): 166 | 167 | ```rust 168 | let mut content = buffer.write().unwrap(); 169 | // `content` implements `DerefMut` whose target is of type `MyStruct` (the content of the buffer) 170 | content.a *= 2; 171 | content.b = 9; 172 | ``` 173 | 174 | Alternatively, suppose that the content of `buffer` is of type `[u8]` (like with the example that 175 | uses `from_iter`): 176 | 177 | ```rust 178 | let mut content = buffer.write().unwrap(); 179 | // this time `content` derefs to `[u8]` 180 | content[12] = 83; 181 | content[7] = 3; 182 | ``` 183 | 184 | Just like the constructors, keep in mind that being able to read/write the content of the buffer 185 | like this is specific to buffer allocated in CPU-accessible memory. Device-local buffers cannot 186 | be accessed in this way. 187 | 188 | Next: [Example operation](/guide/example-operation) 189 | -------------------------------------------------------------------------------- /chapter_code/src/bin/graphics_pipeline.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 The vulkano developers 2 | // Licensed under the Apache License, Version 2.0 3 | // or the MIT 5 | // license , 6 | // at your option. All files in the project carrying such 7 | // notice may not be copied, modified, or distributed except 8 | // according to those terms. 9 | 10 | //! This is the source code of the "Graphics pipeline" chapter at http://vulkano.rs. 11 | //! 12 | //! It is not commented, as the explanations can be found in the guide itself. 13 | 14 | use image::{ImageBuffer, Rgba}; 15 | use vulkano::buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage}; 16 | use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; 17 | use vulkano::command_buffer::{ 18 | AutoCommandBufferBuilder, CommandBufferUsage, CopyImageToBufferInfo, RenderPassBeginInfo, 19 | SubpassContents, 20 | }; 21 | use vulkano::device::{Device, DeviceCreateInfo, QueueCreateInfo, QueueFlags}; 22 | use vulkano::format::Format; 23 | use vulkano::image::view::ImageView; 24 | use vulkano::image::{ImageDimensions, StorageImage}; 25 | use vulkano::instance::{Instance, InstanceCreateInfo}; 26 | use vulkano::memory::allocator::{AllocationCreateInfo, MemoryUsage, StandardMemoryAllocator}; 27 | use vulkano::pipeline::graphics::input_assembly::InputAssemblyState; 28 | use vulkano::pipeline::graphics::vertex_input::Vertex; 29 | use vulkano::pipeline::graphics::viewport::{Viewport, ViewportState}; 30 | use vulkano::pipeline::GraphicsPipeline; 31 | use vulkano::render_pass::{Framebuffer, FramebufferCreateInfo, Subpass}; 32 | use vulkano::sync::{self, GpuFuture}; 33 | 34 | fn main() { 35 | let library = vulkano::VulkanLibrary::new().expect("no local Vulkan library/DLL"); 36 | let instance = 37 | Instance::new(library, InstanceCreateInfo::default()).expect("failed to create instance"); 38 | 39 | let physical = instance 40 | .enumerate_physical_devices() 41 | .expect("could not enumerate devices") 42 | .next() 43 | .expect("no devices available"); 44 | 45 | let queue_family_index = physical 46 | .queue_family_properties() 47 | .iter() 48 | .enumerate() 49 | .position(|(_, q)| q.queue_flags.contains(QueueFlags::GRAPHICS)) 50 | .expect("couldn't find a graphical queue family") as u32; 51 | 52 | let (device, mut queues) = Device::new( 53 | physical, 54 | DeviceCreateInfo { 55 | queue_create_infos: vec![QueueCreateInfo { 56 | queue_family_index, 57 | ..Default::default() 58 | }], 59 | ..Default::default() 60 | }, 61 | ) 62 | .expect("failed to create device"); 63 | 64 | let queue = queues.next().unwrap(); 65 | 66 | let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); 67 | 68 | let image = StorageImage::new( 69 | &memory_allocator, 70 | ImageDimensions::Dim2d { 71 | width: 1024, 72 | height: 1024, 73 | array_layers: 1, 74 | }, 75 | Format::R8G8B8A8_UNORM, 76 | Some(queue.queue_family_index()), 77 | ) 78 | .unwrap(); 79 | 80 | let buf = Buffer::from_iter( 81 | &memory_allocator, 82 | BufferCreateInfo { 83 | usage: BufferUsage::TRANSFER_DST, 84 | ..Default::default() 85 | }, 86 | AllocationCreateInfo { 87 | usage: MemoryUsage::Upload, 88 | ..Default::default() 89 | }, 90 | (0..1024 * 1024 * 4).map(|_| 0u8), 91 | ) 92 | .expect("failed to create buffer"); 93 | 94 | #[derive(BufferContents, Vertex)] 95 | #[repr(C)] 96 | struct MyVertex { 97 | #[format(R32G32_SFLOAT)] 98 | position: [f32; 2], 99 | } 100 | 101 | let vertex1 = MyVertex { 102 | position: [-0.5, -0.5], 103 | }; 104 | let vertex2 = MyVertex { 105 | position: [0.0, 0.5], 106 | }; 107 | let vertex3 = MyVertex { 108 | position: [0.5, -0.25], 109 | }; 110 | let vertex_buffer = Buffer::from_iter( 111 | &memory_allocator, 112 | BufferCreateInfo { 113 | usage: BufferUsage::VERTEX_BUFFER, 114 | ..Default::default() 115 | }, 116 | AllocationCreateInfo { 117 | usage: MemoryUsage::Upload, 118 | ..Default::default() 119 | }, 120 | vec![vertex1, vertex2, vertex3].into_iter(), 121 | ) 122 | .unwrap(); 123 | 124 | let render_pass = vulkano::single_pass_renderpass!(device.clone(), 125 | attachments: { 126 | color: { 127 | load: Clear, 128 | store: Store, 129 | format: Format::R8G8B8A8_UNORM, 130 | samples: 1, 131 | }, 132 | }, 133 | pass: { 134 | color: [color], 135 | depth_stencil: {}, 136 | }, 137 | ) 138 | .unwrap(); 139 | 140 | let view = ImageView::new_default(image.clone()).unwrap(); 141 | let framebuffer = Framebuffer::new( 142 | render_pass.clone(), 143 | FramebufferCreateInfo { 144 | attachments: vec![view], 145 | ..Default::default() 146 | }, 147 | ) 148 | .unwrap(); 149 | 150 | mod vs { 151 | vulkano_shaders::shader! { 152 | ty: "vertex", 153 | src: r" 154 | #version 460 155 | 156 | layout(location = 0) in vec2 position; 157 | 158 | void main() { 159 | gl_Position = vec4(position, 0.0, 1.0); 160 | } 161 | ", 162 | } 163 | } 164 | 165 | mod fs { 166 | vulkano_shaders::shader! { 167 | ty: "fragment", 168 | src: r" 169 | #version 460 170 | 171 | layout(location = 0) out vec4 f_color; 172 | 173 | void main() { 174 | f_color = vec4(1.0, 0.0, 0.0, 1.0); 175 | } 176 | ", 177 | } 178 | } 179 | 180 | let vs = vs::load(device.clone()).expect("failed to create shader module"); 181 | let fs = fs::load(device.clone()).expect("failed to create shader module"); 182 | 183 | let viewport = Viewport { 184 | origin: [0.0, 0.0], 185 | dimensions: [1024.0, 1024.0], 186 | depth_range: 0.0..1.0, 187 | }; 188 | 189 | let pipeline = GraphicsPipeline::start() 190 | .vertex_input_state(MyVertex::per_vertex()) 191 | .vertex_shader(vs.entry_point("main").unwrap(), ()) 192 | .input_assembly_state(InputAssemblyState::new()) 193 | .viewport_state(ViewportState::viewport_fixed_scissor_irrelevant([viewport])) 194 | .fragment_shader(fs.entry_point("main").unwrap(), ()) 195 | .render_pass(Subpass::from(render_pass, 0).unwrap()) 196 | .build(device.clone()) 197 | .unwrap(); 198 | 199 | let command_buffer_allocator = 200 | StandardCommandBufferAllocator::new(device.clone(), Default::default()); 201 | 202 | let mut builder = AutoCommandBufferBuilder::primary( 203 | &command_buffer_allocator, 204 | queue.queue_family_index(), 205 | CommandBufferUsage::OneTimeSubmit, 206 | ) 207 | .unwrap(); 208 | 209 | builder 210 | .begin_render_pass( 211 | RenderPassBeginInfo { 212 | clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into())], 213 | ..RenderPassBeginInfo::framebuffer(framebuffer) 214 | }, 215 | SubpassContents::Inline, 216 | ) 217 | .unwrap() 218 | .bind_pipeline_graphics(pipeline) 219 | .bind_vertex_buffers(0, vertex_buffer) 220 | .draw( 221 | 3, 1, 0, 0, // 3 is the number of vertices, 1 is the number of instances 222 | ) 223 | .unwrap() 224 | .end_render_pass() 225 | .unwrap() 226 | .copy_image_to_buffer(CopyImageToBufferInfo::image_buffer(image, buf.clone())) 227 | .unwrap(); 228 | 229 | let command_buffer = builder.build().unwrap(); 230 | 231 | let future = sync::now(device) 232 | .then_execute(queue, command_buffer) 233 | .unwrap() 234 | .then_signal_fence_and_flush() 235 | .unwrap(); 236 | future.wait(None).unwrap(); 237 | 238 | let buffer_content = buf.read().unwrap(); 239 | let image = ImageBuffer::, _>::from_raw(1024, 1024, &buffer_content[..]).unwrap(); 240 | image.save("image.png").unwrap(); 241 | 242 | println!("Everything succeeded!"); 243 | } 244 | -------------------------------------------------------------------------------- /content/guide/buffer_creation/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-www/blob/master/chapter_code/src/bin/buffer_creation.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](/guide/buffer-creation). 16 | 17 | ```rust 18 | let source_content: Vec = (0..64).collect(); 19 | let source = Buffer::from_iter( 20 | &memory_allocator, 21 | BufferCreateInfo { 22 | usage: BufferUsage::TRANSFER_SRC, 23 | ..Default::default() 24 | }, 25 | AllocationCreateInfo { 26 | usage: MemoryUsage::Upload, 27 | ..Default::default() 28 | }, 29 | source_content, 30 | ) 31 | .expect("failed to create source buffer"); 32 | 33 | let destination_content: Vec = (0..64).map(|_| 0).collect(); 34 | let destination = Buffer::from_iter( 35 | &memory_allocator, 36 | BufferCreateInfo { 37 | usage: BufferUsage::TRANSFER_DST, 38 | ..Default::default() 39 | }, 40 | AllocationCreateInfo { 41 | usage: MemoryUsage::Download, 42 | ..Default::default() 43 | }, 44 | destination_content, 45 | ) 46 | .expect("failed to create destination buffer"); 47 | ``` 48 | 49 | The iterators might look a bit tricky. The `source_content` iterator produces 64 values ranging 50 | from 0 to 63. The `destination_content` iterator produces 64 values that are all equal to 0. 51 | In other words, once created the source buffer contains sixty-four values ranging from 0 to 63 52 | while the destination buffer contains sixty-four 0s. 53 | 54 | ## Creating a command buffer allocator 55 | 56 | Just like buffers, you need an allocator to allocate several command buffers, but you cannot use 57 | a memory allocator. You have to use a [command buffer 58 | allocator](https://docs.rs/vulkano/0.33.0/vulkano/command_buffer/allocator/trait.CommandBufferAllocator.html). 59 | In this case we just use the [standard 60 | one](https://docs.rs/vulkano/0.32.0/vulkano/command_buffer/allocator/struct.StandardCommandBufferAllocator.html). 61 | 62 | ```rust 63 | use vulkano::command_buffer::allocator::{ 64 | StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo, 65 | }; 66 | 67 | let command_buffer_allocator = StandardCommandBufferAllocator::new( 68 | device.clone(), 69 | StandardCommandBufferAllocatorCreateInfo::default(), 70 | ); 71 | ``` 72 | 73 | ## Creating command buffers 74 | 75 | In order to ask the GPU to perform an operation, we need to create a type of object that we 76 | haven't covered yet, the *command buffer*. 77 | 78 | With Vulkan and vulkano you can't just execute commands one by one, as it would be too inefficient. 79 | Instead, we need to build a command buffer that contains a list of commands that we want to 80 | execute. 81 | 82 | You can create many command buffers and use them at different times during the program. They can 83 | have different uses and can do many things. In this case, we are just going to create for the 84 | operation we are trying to achieve. 85 | 86 | Vulkan supports primary and secondary command buffers. Primary command buffers can be sent directly 87 | to the GPU while secondary command buffers allow you to store functionality that you can reuse 88 | multiple times in primary command buffers. We won't cover secondary command buffers here, but you 89 | can read [more about them](https://docs.rs/vulkano/0.33.0/vulkano/command_buffer/index.html). 90 | 91 | > **Note**: Submitting a command to the GPU can take up to several hundred microseconds, which is 92 | > why we submit as many things as we can at once. 93 | > OpenGL (Vulkan's predecessor) allows you to execute commands one by one, but in reality 94 | > implementations buffer commands internally into command buffers. In other words, OpenGL 95 | > automatically does what Vulkan requires us to do manually. In practice, OpenGL's automatic 96 | > buffering often causes more harm than good in performance-critical applications. 97 | 98 | We are going to submit the commands to the GPU, so let's create a primary command buffer: 99 | 100 | ```rust 101 | use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, CopyBufferInfo}; 102 | 103 | let mut builder = AutoCommandBufferBuilder::primary( 104 | &command_buffer_allocator, 105 | queue_family_index, 106 | CommandBufferUsage::OneTimeSubmit, 107 | ) 108 | .unwrap(); 109 | 110 | builder 111 | .copy_buffer(CopyBufferInfo::buffers(source.clone(), destination.clone())) 112 | .unwrap(); 113 | 114 | let command_buffer = builder.build().unwrap(); 115 | ``` 116 | 117 | As you can see, it is very straight-forward. We create a *builder*, add a copy command to it with 118 | `copy_buffer`, then turn that builder into an actual command buffer with `.build()`. Like we saw in 119 | [the buffers creation section](/guide/buffer-creation), we call `.clone()` multiple times, but we 120 | only clone `Arc`s. 121 | 122 | One thing to notice is that the `AutoCommandBufferBuilder::primary()` method takes as parameter a 123 | queue family index. This identifies the queue family that the command buffer is going to run on. 124 | In this example we don't have much choice anyway (as we only use one queue and thus one queue 125 | family), but when you design a real program you have to be aware of this requirement. 126 | 127 | ## Submission and synchronization 128 | 129 | The last step is to actually send the command buffer and execute it in the GPU. We can do that by 130 | synchronizing with the GPU, then executing the command buffer: 131 | 132 | ```rust 133 | use vulkano::sync::{self, GpuFuture}; 134 | 135 | sync::now(device.clone()) 136 | .then_execute(queue.clone(), command_buffer) 137 | .unwrap() 138 | .flush() 139 | .unwrap(); 140 | ``` 141 | 142 | No function in vulkano immediately sends an operation to the GPU (except some unsafe low-level 143 | functions). Instead, `sync::now()` creates a new type of object called a *future*, that keeps 144 | alive all the resources that will be used by the GPU and represents the execution in time of the 145 | actual operations. 146 | 147 | The future returned by `sync::now()` is in a pending state and makes it possible to append the 148 | execution of other command buffers and operations. Only by calling `.flush()` are these operations 149 | all submitted at once, and they actually start executing on the GPU. 150 | 151 | Using objects like this lets us build dependencies between operations and makes it possible to 152 | make an operation start only after a previous one is finished, while reducing the number of slow 153 | communication operations between the CPU and the GPU. 154 | 155 | After submitting the command buffer, we might be tempted to try to read the content of the 156 | `destination` buffer as demonstrated in [the previous section](/guide/buffer-creation). 157 | However, because the CPU and GPU are now executing in parallel, calling `destination.read()` 158 | now may sometimes return an error because the buffer could still be in use by the GPU. 159 | 160 | In order to read the content of `destination` and make sure that our copy succeeded, we need to 161 | wait until the operation is complete. To do that, we need to program the GPU to send back a special 162 | signal that will make us know it has finished. This kind of signal is called a *fence*, and it lets 163 | us know whenever the GPU has reached a certain point of execution. 164 | 165 | To do that, let's actually save the future from the above example and wait for the operations to 166 | finish: 167 | 168 | ```rust 169 | let future = sync::now(device.clone()) 170 | .then_execute(queue.clone(), command_buffer) 171 | .unwrap() 172 | .then_signal_fence_and_flush() // same as signal fence, and then flush 173 | .unwrap(); 174 | ``` 175 | 176 | Signaling a fence returns a future object called 177 | [`FenceSignalFuture`](https://docs.rs/vulkano/0.33.0/vulkano/sync/struct.FenceSignalFuture.html), 178 | that has a special method `.wait()`: 179 | 180 | ```rust 181 | future.wait(None).unwrap(); // None is an optional timeout 182 | ``` 183 | 184 | Only after this is done can we safely call `destination.read()` and check that our copy succeeded. 185 | 186 | ```rust 187 | let src_content = source.read().unwrap(); 188 | let destination_content = destination.read().unwrap(); 189 | assert_eq!(&*src_content, &*destination_content); 190 | 191 | println!("Everything succeeded!"); 192 | ``` 193 | 194 | Next: [Introduction to compute operations](/guide/compute-intro) 195 | -------------------------------------------------------------------------------- /chapter_code/src/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, MemoryUsage}; 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, 95 | BufferCreateInfo { 96 | usage: BufferUsage::VERTEX_BUFFER, 97 | ..Default::default() 98 | }, 99 | AllocationCreateInfo { 100 | usage: MemoryUsage::Upload, 101 | ..Default::default() 102 | }, 103 | M::get_vertices(), 104 | ) 105 | .unwrap() 106 | } 107 | 108 | fn create_device_local_vertex( 109 | allocators: &Allocators, 110 | queue: Arc, 111 | ) -> (Subbuffer<[V]>, CommandBufferExecFuture) 112 | where 113 | V: BufferContents, 114 | U: BufferContents, 115 | M: Model, 116 | { 117 | let vertices = M::get_vertices(); 118 | 119 | let buffer = Buffer::new_slice( 120 | &allocators.memory, 121 | BufferCreateInfo { 122 | usage: BufferUsage::VERTEX_BUFFER | BufferUsage::TRANSFER_DST, 123 | ..Default::default() 124 | }, 125 | AllocationCreateInfo { 126 | usage: MemoryUsage::DeviceOnly, 127 | ..Default::default() 128 | }, 129 | vertices.len() as DeviceSize, 130 | ) 131 | .unwrap(); 132 | 133 | let staging_buffer = Buffer::from_iter( 134 | &allocators.memory, 135 | BufferCreateInfo { 136 | usage: BufferUsage::TRANSFER_SRC, 137 | ..Default::default() 138 | }, 139 | AllocationCreateInfo { 140 | usage: MemoryUsage::Upload, 141 | ..Default::default() 142 | }, 143 | vertices, 144 | ) 145 | .unwrap(); 146 | 147 | let mut builder = AutoCommandBufferBuilder::primary( 148 | &allocators.command_buffer, 149 | queue.queue_family_index(), 150 | CommandBufferUsage::OneTimeSubmit, 151 | ) 152 | .unwrap(); 153 | builder 154 | .copy_buffer(CopyBufferInfo::buffers(staging_buffer, buffer.clone())) 155 | .unwrap(); 156 | 157 | let future = builder.build().unwrap().execute(queue).unwrap(); 158 | 159 | (buffer, future) 160 | } 161 | 162 | fn create_cpu_accessible_index(allocators: &Allocators) -> Subbuffer<[u16]> 163 | where 164 | V: BufferContents, 165 | U: BufferContents, 166 | M: Model, 167 | { 168 | Buffer::from_iter( 169 | &allocators.memory, 170 | BufferCreateInfo { 171 | usage: BufferUsage::INDEX_BUFFER, 172 | ..Default::default() 173 | }, 174 | AllocationCreateInfo { 175 | usage: MemoryUsage::Upload, 176 | ..Default::default() 177 | }, 178 | M::get_indices(), 179 | ) 180 | .unwrap() 181 | } 182 | 183 | fn create_device_local_index( 184 | allocators: &Allocators, 185 | queue: Arc, 186 | ) -> (Subbuffer<[u16]>, CommandBufferExecFuture) 187 | where 188 | V: BufferContents, 189 | U: BufferContents, 190 | M: Model, 191 | { 192 | let indices = M::get_indices(); 193 | 194 | let buffer = Buffer::new_slice( 195 | &allocators.memory, 196 | BufferCreateInfo { 197 | usage: BufferUsage::INDEX_BUFFER | BufferUsage::TRANSFER_DST, 198 | ..Default::default() 199 | }, 200 | AllocationCreateInfo { 201 | usage: MemoryUsage::DeviceOnly, 202 | ..Default::default() 203 | }, 204 | indices.len() as DeviceSize, 205 | ) 206 | .unwrap(); 207 | 208 | let staging_buffer = Buffer::from_iter( 209 | &allocators.memory, 210 | BufferCreateInfo { 211 | usage: BufferUsage::TRANSFER_SRC, 212 | ..Default::default() 213 | }, 214 | AllocationCreateInfo { 215 | usage: MemoryUsage::Upload, 216 | ..Default::default() 217 | }, 218 | indices, 219 | ) 220 | .unwrap(); 221 | 222 | let mut builder = AutoCommandBufferBuilder::primary( 223 | &allocators.command_buffer, 224 | queue.queue_family_index(), 225 | CommandBufferUsage::OneTimeSubmit, 226 | ) 227 | .unwrap(); 228 | builder 229 | .copy_buffer(CopyBufferInfo::buffers(staging_buffer, buffer.clone())) 230 | .unwrap(); 231 | 232 | let future = builder.build().unwrap().execute(queue).unwrap(); 233 | 234 | (buffer, future) 235 | } 236 | 237 | fn create_cpu_accessible_uniforms( 238 | allocators: &Allocators, 239 | descriptor_set_layout: Arc, 240 | buffer_count: usize, 241 | ) -> Vec> 242 | where 243 | V: BufferContents, 244 | U: BufferContents, 245 | M: Model, 246 | { 247 | (0..buffer_count) 248 | .map(|_| { 249 | let buffer = Buffer::from_data( 250 | &allocators.memory, 251 | BufferCreateInfo { 252 | usage: BufferUsage::INDEX_BUFFER, 253 | ..Default::default() 254 | }, 255 | AllocationCreateInfo { 256 | usage: MemoryUsage::Upload, 257 | ..Default::default() 258 | }, 259 | M::get_initial_uniform_data(), 260 | ) 261 | .unwrap(); 262 | 263 | let descriptor_set = PersistentDescriptorSet::new( 264 | &allocators.descriptor_set, 265 | descriptor_set_layout.clone(), 266 | [WriteDescriptorSet::buffer(0, buffer.clone())], 267 | ) 268 | .unwrap(); 269 | 270 | (buffer, descriptor_set) 271 | }) 272 | .collect() 273 | } 274 | -------------------------------------------------------------------------------- /static/prism.js: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=clike+glsl+rust */ 2 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={manual:_self.Prism&&_self.Prism.manual,util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(w instanceof s)){h.lastIndex=0;var _=h.exec(w),P=1;if(!_&&m&&b!=t.length-1){if(h.lastIndex=k,_=h.exec(e),!_)break;for(var A=_.index+(d?_[1].length:0),j=_.index+_[0].length,x=b,O=k,S=t.length;S>x&&(j>O||!t[x].type&&!t[x-1].greedy);++x)O+=t[x].length,A>=O&&(++b,k=O);if(t[b]instanceof s||t[x-1].greedy)continue;P=x-b,w=e.slice(k,O),_.index-=k}if(_){d&&(p=_[1].length);var A=_.index+p,_=_[0].slice(p),j=A+_.length,N=w.slice(0,A),C=w.slice(j),E=[b,P];N&&(++b,k+=N.length,E.push(N));var L=new s(u,f?n.tokenize(_,f):_,y,_,m);if(E.push(L),C&&E.push(C),Array.prototype.splice.apply(t,E),1!=P&&n.matchGrammar(e,t,a,b,k,!0,u),l)break}else if(l)break}}}}},tokenize:function(e,t){var a=[e],r=t.rest;if(r){for(var i in r)t[i]=r[i];delete t.rest}return n.matchGrammar(e,a,t,0,0,!1),a},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var i={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}n.hooks.run("wrap",i);var o=Object.keys(i.attributes).map(function(e){return e+'="'+(i.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+(o?" "+o:"")+">"+i.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,i=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),i&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,!document.addEventListener||n.manual||r.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 3 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; 4 | Prism.languages.glsl=Prism.languages.extend("clike",{comment:[/\/\*[\s\S]*?\*\//,/\/\/(?:\\(?:\r\n|[\s\S])|.)*/],number:/\b(?:0x[\da-f]+|(?:\.\d+|\d+\.?\d*)(?:e[+-]?\d+)?)[ulf]*\b/i,keyword:/\b(?:attribute|const|uniform|varying|buffer|shared|coherent|volatile|restrict|readonly|writeonly|atomic_uint|layout|centroid|flat|smooth|noperspective|patch|sample|break|continue|do|for|while|switch|case|default|if|else|subroutine|in|out|inout|float|double|int|void|bool|true|false|invariant|precise|discard|return|d?mat[234](?:x[234])?|[ibdu]?vec[234]|uint|lowp|mediump|highp|precision|[iu]?sampler[123]D|[iu]?samplerCube|sampler[12]DShadow|samplerCubeShadow|[iu]?sampler[12]DArray|sampler[12]DArrayShadow|[iu]?sampler2DRect|sampler2DRectShadow|[iu]?samplerBuffer|[iu]?sampler2DMS(?:Array)?|[iu]?samplerCubeArray|samplerCubeArrayShadow|[iu]?image[123]D|[iu]?image2DRect|[iu]?imageCube|[iu]?imageBuffer|[iu]?image[12]DArray|[iu]?imageCubeArray|[iu]?image2DMS(?:Array)?|struct|common|partition|active|asm|class|union|enum|typedef|template|this|resource|goto|inline|noinline|public|static|extern|external|interface|long|short|half|fixed|unsigned|superp|input|output|hvec[234]|fvec[234]|sampler3DRect|filter|sizeof|cast|namespace|using)\b/}),Prism.languages.insertBefore("glsl","comment",{preprocessor:{pattern:/(^[ \t]*)#(?:(?:define|undef|if|ifdef|ifndef|else|elif|endif|error|pragma|extension|version|line)\b)?/m,lookbehind:!0,alias:"builtin"}}); 5 | Prism.languages.rust={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:[{pattern:/b?r(#*)"(?:\\?.)*?"\1/,greedy:!0},{pattern:/b?("|')(?:\\?.)*?\1/,greedy:!0}],keyword:/\b(?:abstract|alignof|as|be|box|break|const|continue|crate|do|else|enum|extern|false|final|fn|for|if|impl|in|let|loop|match|mod|move|mut|offsetof|once|override|priv|pub|pure|ref|return|sizeof|static|self|struct|super|true|trait|type|typeof|unsafe|unsized|use|virtual|where|while|yield)\b/,attribute:{pattern:/#!?\[.+?\]/,greedy:!0,alias:"attr-name"},"function":[/[a-z0-9_]+(?=\s*\()/i,/[a-z0-9_]+!(?=\s*\(|\[)/i],"macro-rules":{pattern:/[a-z0-9_]+!/i,alias:"function"},number:/\b-?(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(\d(_?\d)*)?\.?\d(_?\d)*([Ee][+-]?\d+)?)(?:_?(?:[iu](?:8|16|32|64)?|f32|f64))?\b/,"closure-params":{pattern:/\|[^|]*\|(?=\s*[{-])/,inside:{punctuation:/[\|:,]/,operator:/[&*]/}},punctuation:/[{}[\];(),:]|\.+|->/,operator:/[-+*\/%!^=]=?|@|&[&=]?|\|[|=]?|<>?=?/}; 6 | -------------------------------------------------------------------------------- /content/guide/images/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 pipelines](/guide/compute-pipeline), we need to write 8 | some GLSL code and create a compute pipeline. This is done with the `vulkano_shader::shader!` 9 | macro, as explained in that section. Each invocation of the `main` function of the shader will 10 | write one pixel. 11 | 12 | > **Note**: You can find the [full source code of this section 13 | > here](https://github.com/vulkano-rs/vulkano-www/blob/master/chapter_code/src/bin/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](/guide/image_clear) 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 = StorageImage::new( 133 | device.clone(), 134 | ImageDimensions::Dim2d { 135 | width: 1024, 136 | height: 1024, 137 | array_layers: 1, 138 | }, 139 | Format::R8G8B8A8_UNORM, 140 | Some(queue.queue_family_index()), 141 | ) 142 | .unwrap(); 143 | ``` 144 | 145 | This time we can't just clear the image like we did earlier. To actually pass the image 146 | to the GPU shader, we first need to create a `ImageView` of it. An `ImageView` describes where 147 | and how the GPU should access or use the image. Here, we want a view of the entire image, 148 | so the creation isn't very difficult: 149 | 150 | ```rust 151 | use vulkano::image::view::ImageView; 152 | 153 | let view = ImageView::new_default(image.clone()).unwrap(); 154 | ``` 155 | 156 | Now, let's create the descriptor set by adding the image view, like we did 157 | [earlier](/guide/descriptor-sets): 158 | 159 | ```rust 160 | let layout = compute_pipeline.layout().set_layouts().get(0).unwrap(); 161 | let set = PersistentDescriptorSet::new( 162 | layout.clone(), 163 | [WriteDescriptorSet::image_view(0, view.clone())], // 0 is the binding 164 | ) 165 | .unwrap(); 166 | ``` 167 | 168 | Next, we can create a buffer for storing the image output: 169 | 170 | ```rust 171 | let buf = Buffer::from_iter( 172 | &memory_allocator, 173 | BufferCreateInfo { 174 | usage: BufferUsage::TRANSFER_DST, 175 | ..Default::default() 176 | }, 177 | AllocationCreateInfo { 178 | usage: MemoryUsage::Download, 179 | ..Default::default() 180 | }, 181 | (0..1024 * 1024 * 4).map(|_| 0u8), 182 | ) 183 | .expect("failed to create buffer"); 184 | ``` 185 | 186 | The command buffer contains a dispatch command followed with a copy-image-to-buffer command: 187 | 188 | ```rust 189 | let mut builder = AutoCommandBufferBuilder::primary( 190 | device.clone(), 191 | queue.queue_family_index(), 192 | CommandBufferUsage::OneTimeSubmit, 193 | ) 194 | .unwrap(); 195 | builder 196 | .bind_pipeline_compute(compute_pipeline.clone()) 197 | .bind_descriptor_sets( 198 | PipelineBindPoint::Compute, 199 | compute_pipeline.layout().clone(), 200 | 0, 201 | set, 202 | ) 203 | .dispatch([1024 / 8, 1024 / 8, 1]) 204 | .unwrap() 205 | .copy_image_to_buffer(CopyImageToBufferInfo::image_buffer( 206 | image.clone(), 207 | buf.clone(), 208 | )) 209 | .unwrap(); 210 | 211 | let command_buffer = builder.build().unwrap(); 212 | ``` 213 | 214 | And finally just like in [the previous section](/guide/image-export) we execute the command buffer 215 | and export the image as a PNG file: 216 | 217 | ```rust 218 | let future = sync::now(device.clone()) 219 | .then_execute(queue.clone(), command_buffer) 220 | .unwrap() 221 | .then_signal_fence_and_flush() 222 | .unwrap(); 223 | 224 | future.wait(None).unwrap(); 225 | 226 | let buffer_content = buf.read().unwrap(); 227 | let image = ImageBuffer::, _>::from_raw(1024, 1024, &buffer_content[..]).unwrap(); 228 | image.save("image.png").unwrap(); 229 | 230 | println!("Everything succeeded!"); 231 | ``` 232 | 233 | And here is what you should get: 234 | 235 |
236 | 237 |
238 | 239 | Next: [Graphics pipeline introduction](/guide/what-graphics-pipeline) 240 | -------------------------------------------------------------------------------- /chapter_code/src/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::SwapchainImage; 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, AcquireError, PresentFuture, Swapchain, SwapchainAcquireFuture, SwapchainCreateInfo, 19 | SwapchainCreationError, SwapchainPresentInfo, 20 | }; 21 | use vulkano::sync::future::{FenceSignalFuture, JoinFuture, NowFuture}; 22 | use vulkano::sync::{self, FlushError, GpuFuture}; 23 | use vulkano_win::VkSurfaceBuild; 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(); 53 | 54 | let surface = WindowBuilder::new() 55 | .build_vk_surface(event_loop, instance.clone()) 56 | .unwrap(); 57 | 58 | let window = surface 59 | .object() 60 | .unwrap() 61 | .clone() 62 | .downcast::() 63 | .unwrap(); 64 | 65 | window.set_title("Movable Square"); 66 | window.set_inner_size(LogicalSize::new(600.0f32, 600.0)); 67 | 68 | let device_extensions = DeviceExtensions { 69 | khr_swapchain: true, 70 | ..DeviceExtensions::empty() 71 | }; 72 | 73 | let (physical_device, queue_family_index) = 74 | vulkano_objects::physical_device::select_physical_device( 75 | &instance, 76 | surface.clone(), 77 | &device_extensions, 78 | ); 79 | 80 | let (device, mut queues) = Device::new( 81 | physical_device.clone(), 82 | DeviceCreateInfo { 83 | queue_create_infos: vec![QueueCreateInfo { 84 | queue_family_index, 85 | ..Default::default() 86 | }], 87 | enabled_extensions: device_extensions, // new 88 | ..Default::default() 89 | }, 90 | ) 91 | .expect("failed to create device"); 92 | 93 | let queue = queues.next().unwrap(); 94 | 95 | let (swapchain, images) = 96 | vulkano_objects::swapchain::create_swapchain(&physical_device, device.clone(), surface); 97 | 98 | let render_pass = 99 | vulkano_objects::render_pass::create_render_pass(device.clone(), swapchain.clone()); 100 | let framebuffers = vulkano_objects::swapchain::create_framebuffers_from_swapchain_images( 101 | &images, 102 | render_pass.clone(), 103 | ); 104 | 105 | let vertex_shader = 106 | movable_square::vs::load(device.clone()).expect("failed to create shader module"); 107 | let fragment_shader = 108 | movable_square::fs::load(device.clone()).expect("failed to create shader module"); 109 | 110 | let viewport = Viewport { 111 | origin: [0.0, 0.0], 112 | dimensions: window.inner_size().into(), 113 | depth_range: 0.0..1.0, 114 | }; 115 | 116 | let pipeline = vulkano_objects::pipeline::create_pipeline( 117 | device.clone(), 118 | vertex_shader.clone(), 119 | fragment_shader.clone(), 120 | render_pass.clone(), 121 | viewport.clone(), 122 | ); 123 | 124 | let allocators = Allocators::new(device.clone()); 125 | 126 | let buffers = Buffers::initialize_device_local::( 127 | &allocators, 128 | pipeline.layout().set_layouts().get(0).unwrap().clone(), 129 | images.len(), 130 | queue.clone(), 131 | ); 132 | 133 | let command_buffers = vulkano_objects::command_buffers::create_simple_command_buffers( 134 | &allocators, 135 | queue.clone(), 136 | pipeline.clone(), 137 | &framebuffers, 138 | &buffers, 139 | ); 140 | 141 | Self { 142 | _instance: instance, 143 | window, 144 | device, 145 | queue, 146 | swapchain, 147 | images, 148 | render_pass, 149 | framebuffers, 150 | allocators, 151 | buffers, 152 | vertex_shader, 153 | fragment_shader, 154 | viewport, 155 | pipeline, 156 | command_buffers, 157 | } 158 | } 159 | 160 | pub fn recreate_swapchain(&mut self) { 161 | let (new_swapchain, new_images) = match self.swapchain.recreate(SwapchainCreateInfo { 162 | image_extent: self.window.inner_size().into(), 163 | ..self.swapchain.create_info() 164 | }) { 165 | Ok(r) => r, 166 | Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, 167 | Err(e) => panic!("Failed to recreate swapchain: {:?}", e), 168 | }; 169 | 170 | self.swapchain = new_swapchain; 171 | self.framebuffers = vulkano_objects::swapchain::create_framebuffers_from_swapchain_images( 172 | &new_images, 173 | self.render_pass.clone(), 174 | ); 175 | } 176 | 177 | pub fn handle_window_resize(&mut self) { 178 | self.recreate_swapchain(); 179 | self.viewport.dimensions = self.window.inner_size().into(); 180 | 181 | self.pipeline = vulkano_objects::pipeline::create_pipeline( 182 | self.device.clone(), 183 | self.vertex_shader.clone(), 184 | self.fragment_shader.clone(), 185 | self.render_pass.clone(), 186 | self.viewport.clone(), 187 | ); 188 | 189 | self.command_buffers = vulkano_objects::command_buffers::create_simple_command_buffers( 190 | &self.allocators, 191 | self.queue.clone(), 192 | self.pipeline.clone(), 193 | &self.framebuffers, 194 | &self.buffers, 195 | ); 196 | } 197 | 198 | pub fn get_image_count(&self) -> usize { 199 | self.images.len() 200 | } 201 | 202 | pub fn acquire_swapchain_image( 203 | &self, 204 | ) -> Result<(u32, bool, SwapchainAcquireFuture), AcquireError> { 205 | swapchain::acquire_next_image(self.swapchain.clone(), None) 206 | } 207 | 208 | pub fn synchronize(&self) -> NowFuture { 209 | let mut now = sync::now(self.device.clone()); 210 | now.cleanup_finished(); 211 | 212 | now 213 | } 214 | 215 | pub fn flush_next_future( 216 | &self, 217 | previous_future: Box, 218 | swapchain_acquire_future: SwapchainAcquireFuture, 219 | image_i: u32, 220 | ) -> Result { 221 | previous_future 222 | .join(swapchain_acquire_future) 223 | .then_execute( 224 | self.queue.clone(), 225 | self.command_buffers[image_i as usize].clone(), 226 | ) 227 | .unwrap() 228 | .then_swapchain_present( 229 | self.queue.clone(), 230 | SwapchainPresentInfo::swapchain_image_index(self.swapchain.clone(), image_i), 231 | ) 232 | .then_signal_fence_and_flush() 233 | } 234 | 235 | pub fn update_uniform(&self, index: u32, square: &Square) { 236 | let mut uniform_content = self.buffers.uniforms[index as usize] 237 | .0 238 | .write() 239 | .unwrap_or_else(|e| panic!("Failed to write to uniform buffer\n{}", e)); 240 | 241 | uniform_content.color = square.color.into(); 242 | uniform_content.position = square.position; 243 | } 244 | } 245 | --------------------------------------------------------------------------------