From baf863ce335dce828a48be6e1c6cb511b36f9fbb Mon Sep 17 00:00:00 2001 From: marc0246 <40955683+marc0246@users.noreply.github.com> Date: Sun, 5 Mar 2023 19:56:35 +0100 Subject: [PATCH] Resolve restrictions of `BufferContents`, add support for allocating all types of buffers, and rework vulkano-shaders (#2132) * Add `BufferContentsLayout` * Rework `BufferContents` * Add `BufferContents` derive macro * Relax restrictions of shader code generation * Fix examples * Add additional invariant to `Subbuffer` * Shorten paths a bit * Remove a bit of bloat * Add `Sized` constraint to element traits * Fix an oopsie * Add `Aligned` * Add support for deriving `BufferContents` for sized types * Fix alignment and padding issues * Fix docs and add examples for `BufferContents` * Adjust shader macro * Add tests * Adjust `Vertex` example * Remove bytemuck re-export * Update examples * Workaround bytemuck's array elements that are `AnyBitPattern` limitation * Add more alignments * Fix an earlier oopsie * Rework vulkano-shaders * Fix examples * Fix some rogue tabs in examples * Add `AsRef` and `AsMut` implementation for `Padded` * Remove useless code duplication * Make the `BufferContents` derive macro the same for all types * Add example docs for `Padded` * Work around trivial bounds * More example docs * Minor consistency adjustment * Add `serde` to the list of cargo features * Make clippy happy again --- examples/Cargo.toml | 11 +- examples/src/bin/basic-compute-shader.rs | 87 +- examples/src/bin/buffer-allocator.rs | 44 +- examples/src/bin/clear_attachments.rs | 26 +- examples/src/bin/debug.rs | 36 +- .../deferred/frame/ambient_lighting_system.rs | 47 +- .../frame/directional_lighting_system.rs | 69 +- examples/src/bin/deferred/frame/mod.rs | 5 +- .../deferred/frame/point_lighting_system.rs | 104 +- examples/src/bin/deferred/frame/system.rs | 13 +- examples/src/bin/deferred/main.rs | 19 +- .../src/bin/deferred/triangle_draw_system.rs | 35 +- examples/src/bin/dynamic-buffers.rs | 52 +- examples/src/bin/dynamic-local-size.rs | 70 +- examples/src/bin/gl-interop.rs | 54 +- examples/src/bin/image-self-copy-blit/main.rs | 66 +- examples/src/bin/image/main.rs | 52 +- examples/src/bin/immutable-sampler/main.rs | 60 +- examples/src/bin/indirect.rs | 72 +- examples/src/bin/instancing.rs | 45 +- examples/src/bin/interactive_fractal/app.rs | 108 +- .../fractal_compute_pipeline.rs | 186 +- examples/src/bin/interactive_fractal/main.rs | 70 +- .../pixels_draw_pipeline.rs | 49 +- .../interactive_fractal/place_over_frame.rs | 32 +- examples/src/bin/msaa-renderpass.rs | 148 +- examples/src/bin/multi-window.rs | 51 +- .../src/bin/multi_window_game_of_life/app.rs | 19 +- .../multi_window_game_of_life/game_of_life.rs | 182 +- .../src/bin/multi_window_game_of_life/main.rs | 65 +- .../multi_window_game_of_life/pixels_draw.rs | 49 +- .../multi_window_game_of_life/render_pass.rs | 32 +- examples/src/bin/multiview.rs | 110 +- examples/src/bin/occlusion-query.rs | 112 +- examples/src/bin/pipeline-caching.rs | 82 +- examples/src/bin/push-constants.rs | 41 +- examples/src/bin/push-descriptors/main.rs | 50 +- examples/src/bin/runtime-shader/main.rs | 53 +- examples/src/bin/runtime-shader/vert.spv | Bin 1004 -> 1076 bytes examples/src/bin/runtime_array/main.rs | 66 +- examples/src/bin/self-copy-buffer.rs | 50 +- examples/src/bin/shader-include/main.rs | 30 +- examples/src/bin/shader-types-derive.rs | 104 +- examples/src/bin/shader-types-sharing.rs | 124 +- examples/src/bin/simple-particles.rs | 133 +- examples/src/bin/specialization-constants.rs | 19 +- examples/src/bin/teapot/main.rs | 23 +- examples/src/bin/tessellation.rs | 101 +- examples/src/bin/texture_array/main.rs | 64 +- examples/src/bin/triangle-v1_3.rs | 232 ++- examples/src/bin/triangle.rs | 252 +-- examples/src/lib.rs | 7 +- vulkano-shaders/Cargo.toml | 7 +- vulkano-shaders/src/codegen.rs | 651 +++--- vulkano-shaders/src/entry_point.rs | 34 +- vulkano-shaders/src/lib.rs | 1041 ++++------ vulkano-shaders/src/structs.rs | 1837 ++++++++++------- vulkano/Cargo.toml | 9 +- vulkano/macros/Cargo.toml | 3 + vulkano/macros/src/derive_buffer_contents.rs | 329 +++ vulkano/macros/src/derive_vertex.rs | 87 +- vulkano/macros/src/lib.rs | 41 +- vulkano/src/buffer/allocator.rs | 68 +- vulkano/src/buffer/mod.rs | 136 +- vulkano/src/buffer/subbuffer.rs | 744 ++++++- .../src/command_buffer/commands/bind_push.rs | 2 +- vulkano/src/command_buffer/commands/clear.rs | 36 +- vulkano/src/command_buffer/mod.rs | 5 +- .../standard/builder/bind_push.rs | 28 +- .../command_buffer/standard/builder/clear.rs | 24 +- vulkano/src/image/immutable.rs | 2 +- vulkano/src/lib.rs | 3 + vulkano/src/macros.rs | 13 +- vulkano/src/memory/allocator/layout.rs | 115 +- vulkano/src/memory/allocator/suballocator.rs | 22 - vulkano/src/padded.rs | 338 +++ .../src/pipeline/graphics/input_assembly.rs | 4 +- .../graphics/vertex_input/collection.rs | 2 +- .../graphics/vertex_input/impl_vertex.rs | 8 +- .../pipeline/graphics/vertex_input/vertex.rs | 16 +- vulkano/src/query.rs | 4 +- 81 files changed, 5187 insertions(+), 4033 deletions(-) create mode 100644 vulkano/macros/src/derive_buffer_contents.rs create mode 100644 vulkano/src/padded.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 92fe3473..c867b717 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] # The `vulkano` crate is the main crate that you must use to use Vulkan. -vulkano = { path = "../vulkano" } +vulkano = { path = "../vulkano", features = ["serde"] } # Provides the `shader!` macro that is used to generate code for using shaders. vulkano-shaders = { path = "../vulkano-shaders" } # The Vulkan library doesn't provide any functionality to create and handle windows, as @@ -18,10 +18,9 @@ winit = "0.27" vulkano-win = { path = "../vulkano-win" } vulkano-util = { path = "../vulkano-util" } -bytemuck = { version = "1.7", features = ["derive", "extern_crate_std", "min_const_generics"] } cgmath = "0.18" -png = "0.17" -serde = { version = "1.0", features = ["derive"] } -ron = "0.8" -rand = "0.8.4" glium = "0.32.1" +png = "0.17" +rand = "0.8.4" +ron = "0.8" +serde = { version = "1.0", features = ["derive"] } diff --git a/examples/src/bin/basic-compute-shader.rs b/examples/src/bin/basic-compute-shader.rs index e2df9353..cb58b64e 100644 --- a/examples/src/bin/basic-compute-shader.rs +++ b/examples/src/bin/basic-compute-shader.rs @@ -38,7 +38,6 @@ fn main() { let instance = Instance::new( library, InstanceCreateInfo { - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -55,8 +54,8 @@ fn main() { .unwrap() .filter(|p| p.supported_extensions().contains(&device_extensions)) .filter_map(|p| { - // The Vulkan specs guarantee that a compliant implementation must provide at least one queue - // that supports compute operations. + // The Vulkan specs guarantee that a compliant implementation must provide at least one + // queue that supports compute operations. p.queue_family_properties() .iter() .position(|q| q.queue_flags.intersects(QueueFlags::COMPUTE)) @@ -75,7 +74,7 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); // Now initializing the device. @@ -99,17 +98,17 @@ fn main() { // Now let's get to the actual example. // - // What we are going to do is very basic: we are going to fill a buffer with 64k integers - // and ask the GPU to multiply each of them by 12. + // What we are going to do is very basic: we are going to fill a buffer with 64k integers and + // ask the GPU to multiply each of them by 12. // // GPUs are very good at parallel computations (SIMD-like operations), and thus will do this // much more quickly than a CPU would do. While a CPU would typically multiply them one by one // or four by four, a GPU will do it by groups of 32 or 64. // - // Note however that in a real-life situation for such a simple operation the cost of - // accessing memory usually outweighs the benefits of a faster calculation. Since both the CPU - // and the GPU will need to access data, there is no other choice but to transfer the data - // through the slow PCI express bus. + // Note however that in a real-life situation for such a simple operation the cost of accessing + // memory usually outweighs the benefits of a faster calculation. Since both the CPU and the + // GPU will need to access data, there is no other choice but to transfer the data through the + // slow PCI express bus. // We need to create the compute pipeline that describes our operation. // @@ -119,23 +118,24 @@ fn main() { mod cs { vulkano_shaders::shader! { ty: "compute", - src: " + src: r" #version 450 layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) buffer Data { uint data[]; - } data; + }; void main() { uint idx = gl_GlobalInvocationID.x; - data.data[idx] *= 12; + data[idx] *= 12; } - " + ", } } let shader = cs::load(device.clone()).unwrap(); + ComputePipeline::new( device.clone(), shader.entry_point("main").unwrap(), @@ -152,20 +152,16 @@ fn main() { StandardCommandBufferAllocator::new(device.clone(), Default::default()); // We start by creating the buffer that will store the data. - let data_buffer = { + let data_buffer = Buffer::from_iter( + &memory_allocator, + BufferAllocateInfo { + buffer_usage: BufferUsage::STORAGE_BUFFER, + ..Default::default() + }, // Iterator that produces the data. - let data_iter = 0..65536u32; - // Builds the buffer and fills it with this iterator. - Buffer::from_iter( - &memory_allocator, - BufferAllocateInfo { - buffer_usage: BufferUsage::STORAGE_BUFFER, - ..Default::default() - }, - data_iter, - ) - .unwrap() - }; + 0..65536u32, + ) + .unwrap(); // In order to let the shader access the buffer, we need to build a *descriptor set* that // contains the buffer. @@ -191,13 +187,13 @@ fn main() { ) .unwrap(); builder - // The command buffer only does one thing: execute the compute pipeline. - // This is called a *dispatch* operation. + // The command buffer only does one thing: execute the compute pipeline. This is called a + // *dispatch* operation. // - // Note that we clone the pipeline and the set. Since they are both wrapped around an - // `Arc`, this only clones the `Arc` and not the whole pipeline or set (which aren't - // cloneable anyway). In this example we would avoid cloning them since this is the last - // time we use them, but in a real code you would probably need to clone them. + // Note that we clone the pipeline and the set. Since they are both wrapped in an `Arc`, + // this only clones the `Arc` and not the whole pipeline or set (which aren't cloneable + // anyway). In this example we would avoid cloning them since this is the last time we use + // them, but in real code you would probably need to clone them. .bind_pipeline_compute(pipeline.clone()) .bind_descriptor_sets( PipelineBindPoint::Compute, @@ -207,38 +203,37 @@ fn main() { ) .dispatch([1024, 1, 1]) .unwrap(); + // Finish building the command buffer by calling `build`. let command_buffer = builder.build().unwrap(); // Let's execute this command buffer now. - // To do so, we TODO: this is a bit clumsy, probably needs a shortcut let future = sync::now(device) .then_execute(queue, command_buffer) .unwrap() // This line instructs the GPU to signal a *fence* once the command buffer has finished // execution. A fence is a Vulkan object that allows the CPU to know when the GPU has - // reached a certain point. - // We need to signal a fence here because below we want to block the CPU until the GPU has - // reached that point in the execution. + // reached a certain point. We need to signal a fence here because below we want to block + // the CPU until the GPU has reached that point in the execution. .then_signal_fence_and_flush() .unwrap(); // Blocks execution until the GPU has finished the operation. This method only exists on the // future that corresponds to a signalled fence. In other words, this method wouldn't be - // available if we didn't call `.then_signal_fence_and_flush()` earlier. - // The `None` parameter is an optional timeout. + // available if we didn't call `.then_signal_fence_and_flush()` earlier. The `None` parameter + // is an optional timeout. // // Note however that dropping the `future` variable (with `drop(future)` for example) would // block execution as well, and this would be the case even if we didn't call - // `.then_signal_fence_and_flush()`. - // Therefore the actual point of calling `.then_signal_fence_and_flush()` and `.wait()` is to - // make things more explicit. In the future, if the Rust language gets linear types vulkano may - // get modified so that only fence-signalled futures can get destroyed like this. + // `.then_signal_fence_and_flush()`. Therefore the actual point of calling + // `.then_signal_fence_and_flush()` and `.wait()` is to make things more explicit. In the + // future, if the Rust language gets linear types vulkano may get modified so that only + // fence-signalled futures can get destroyed like this. future.wait(None).unwrap(); - // Now that the GPU is done, the content of the buffer should have been modified. Let's - // check it out. - // The call to `read()` would return an error if the buffer was still in use by the GPU. + // Now that the GPU is done, the content of the buffer should have been modified. Let's check + // it out. The call to `read()` would return an error if the buffer was still in use by the + // GPU. let data_buffer_content = data_buffer.read().unwrap(); for n in 0..65536u32 { assert_eq!(data_buffer_content[n as usize], n * 12); diff --git a/examples/src/bin/buffer-allocator.rs b/examples/src/bin/buffer-allocator.rs index 352a4aca..3207198c 100644 --- a/examples/src/bin/buffer-allocator.rs +++ b/examples/src/bin/buffer-allocator.rs @@ -9,7 +9,6 @@ // Modified triangle example to show `SubbufferAllocator`. -use bytemuck::{Pod, Zeroable}; use std::{ sync::Arc, time::{SystemTime, UNIX_EPOCH}, @@ -17,7 +16,7 @@ use std::{ use vulkano::{ buffer::{ allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, - BufferUsage, + BufferContents, BufferUsage, }, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, @@ -60,7 +59,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -103,7 +101,7 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); let (device, mut queues) = Device::new( @@ -156,8 +154,8 @@ fn main() { let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); + #[derive(Clone, Copy, BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32_SFLOAT)] position: [f32; 2], @@ -177,30 +175,30 @@ fn main() { mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " - #version 450 + src: r" + #version 450 - layout(location = 0) in vec2 position; + layout(location = 0) in vec2 position; - void main() { - gl_Position = vec4(position, 0.0, 1.0); - } - " + void main() { + gl_Position = vec4(position, 0.0, 1.0); + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " - #version 450 + src: r" + #version 450 - layout(location = 0) out vec4 f_color; + layout(location = 0) out vec4 f_color; - void main() { - f_color = vec4(1.0, 0.0, 0.0, 1.0); - } - " + void main() { + f_color = vec4(1.0, 0.0, 0.0, 1.0); + } + ", } } @@ -277,7 +275,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -296,7 +294,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -387,7 +385,7 @@ fn main() { previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box<_>); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box<_>); } } @@ -397,7 +395,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], render_pass: Arc, diff --git a/examples/src/bin/clear_attachments.rs b/examples/src/bin/clear_attachments.rs index 3f3ca6fc..52cb71c8 100644 --- a/examples/src/bin/clear_attachments.rs +++ b/examples/src/bin/clear_attachments.rs @@ -35,8 +35,8 @@ use winit::{ }; fn main() { - // The start of this example is exactly the same as `triangle`. You should read the - // `triangle` example if you haven't done so yet. + // The start of this example is exactly the same as `triangle`. You should read the `triangle` + // example if you haven't done so yet. let library = VulkanLibrary::new().unwrap(); let required_extensions = vulkano_win::required_extensions(&library); @@ -44,7 +44,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -192,7 +191,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -209,7 +208,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -233,29 +232,28 @@ fn main() { SubpassContents::Inline, ) .unwrap() - // Clear attachments with clear values and rects information, all the rects will be cleared by the same value - // Note that the ClearRect offsets and extents are not affected by the viewport, - // they are directly applied to the rendering image + // Clear attachments with clear values and rects information. All the rects will be + // cleared by the same value. Note that the ClearRect offsets and extents are not + // affected by the viewport, they are directly applied to the rendering image. .clear_attachments( [ClearAttachment::Color { color_attachment: 0, clear_value: [1.0, 0.0, 0.0, 1.0].into(), }], [ - // Fixed offset and extent + // Fixed offset and extent. ClearRect { offset: [0, 0], extent: [100, 100], array_layers: 0..1, }, - // Fixed offset - // Relative extent + // Fixed offset, relative extent. ClearRect { offset: [100, 150], extent: [width / 4, height / 4], array_layers: 0..1, }, - // Relative offset and extent + // Relative offset and extent. ClearRect { offset: [width / 2, height / 2], extent: [width / 3, height / 5], @@ -289,7 +287,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -298,7 +296,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], render_pass: Arc, diff --git a/examples/src/bin/debug.rs b/examples/src/bin/debug.rs index 2cbedb79..ea4d123d 100644 --- a/examples/src/bin/debug.rs +++ b/examples/src/bin/debug.rs @@ -37,7 +37,7 @@ fn main() { // https://vulkan.lunarg.com/doc/view/1.0.13.0/windows/layers.html // // .. but if you just want a template of code that has everything ready to go then follow - // this example. First, enable debugging using this extension: VK_EXT_debug_utils + // this example. First, enable debugging using this extension: `VK_EXT_debug_utils`. let extensions = InstanceExtensions { ext_debug_utils: true, ..InstanceExtensions::empty() @@ -45,12 +45,12 @@ fn main() { let library = VulkanLibrary::new().unwrap(); - // You also need to specify (unless you've used the methods linked above) which debugging layers - // your code should use. Each layer is a bunch of checks or messages that provide information of - // some sort. + // You also need to specify (unless you've used the methods linked above) which debugging + // layers your code should use. Each layer is a bunch of checks or messages that provide + // information of some sort. // - // The main layer you might want is: VK_LAYER_LUNARG_standard_validation - // This includes a number of the other layers for you and is quite detailed. + // The main layer you might want is `VK_LAYER_LUNARG_standard_validation`. This includes a + // number of the other layers for you and is quite detailed. // // Additional layers can be installed (gpu vendor provided, something you found on GitHub, etc) // and you should verify that list for safety - Vulkano will return an error if you specify @@ -61,28 +61,26 @@ fn main() { println!("\t{}", l.name()); } - // NOTE: To simplify the example code we won't verify these layer(s) are actually in the layers list: + // NOTE: To simplify the example code we won't verify these layer(s) are actually in the layers + // list. let layers = vec!["VK_LAYER_KHRONOS_validation".to_owned()]; - // Important: pass the extension(s) and layer(s) when creating the vulkano instance + // Important: pass the extension(s) and layer(s) when creating the vulkano instance. let instance = Instance::new( library, InstanceCreateInfo { enabled_extensions: extensions, enabled_layers: layers, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, ) .expect("failed to create Vulkan instance"); - /////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // After creating the instance we must register the debugging callback. // - /////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Note: If you let this debug_callback binding fall out of scope then the callback will stop providing events - + // After creating the instance we must register the debug callback. + // + // NOTE: If you let this debug_callback binding fall out of scope then the callback will stop + // providing events. let _debug_callback = unsafe { DebugUtilsMessenger::new( instance.clone(), @@ -130,10 +128,7 @@ fn main() { .ok() }; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Create Vulkan objects in the same way as the other examples // - /////////////////////////////////////////////////////////////////////////////////////////////////////////////// - + // Create Vulkan objects in the same way as the other examples. let device_extensions = DeviceExtensions { ..DeviceExtensions::empty() }; @@ -198,5 +193,6 @@ fn main() { ) .unwrap(); - // (At this point you should see a bunch of messages printed to the terminal window - have fun debugging!) + // (At this point you should see a bunch of messages printed to the terminal window - + // have fun debugging!) } diff --git a/examples/src/bin/deferred/frame/ambient_lighting_system.rs b/examples/src/bin/deferred/frame/ambient_lighting_system.rs index e85de44e..448a873f 100644 --- a/examples/src/bin/deferred/frame/ambient_lighting_system.rs +++ b/examples/src/bin/deferred/frame/ambient_lighting_system.rs @@ -122,14 +122,13 @@ impl AmbientLightingSystem { /// - `color_input` is an image containing the albedo of each object of the scene. It is the /// result of the deferred pass. /// - `ambient_color` is the color to apply. - /// pub fn draw( &self, viewport_dimensions: [u32; 2], color_input: Arc, ambient_color: [f32; 3], ) -> SecondaryAutoCommandBuffer { - let push_constants = fs::ty::PushConstants { + let push_constants = fs::PushConstants { color: [ambient_color[0], ambient_color[1], ambient_color[2], 1.0], }; @@ -177,38 +176,40 @@ impl AmbientLightingSystem { mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 position; + layout(location = 0) in vec2 position; -void main() { - gl_Position = vec4(position, 0.0, 1.0); -}" + void main() { + gl_Position = vec4(position, 0.0, 1.0); + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " -#version 450 + src: r" + #version 450 -// The `color_input` parameter of the `draw` method. -layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput u_diffuse; + // The `color_input` parameter of the `draw` method. + layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput u_diffuse; -layout(push_constant) uniform PushConstants { - // The `ambient_color` parameter of the `draw` method. - vec4 color; -} push_constants; + layout(push_constant) uniform PushConstants { + // The `ambient_color` parameter of the `draw` method. + vec4 color; + } push_constants; -layout(location = 0) out vec4 f_color; + layout(location = 0) out vec4 f_color; -void main() { - // Load the value at the current pixel. - vec3 in_diffuse = subpassLoad(u_diffuse).rgb; - f_color.rgb = push_constants.color.rgb * in_diffuse; - f_color.a = 1.0; -}", + void main() { + // Load the value at the current pixel. + vec3 in_diffuse = subpassLoad(u_diffuse).rgb; + f_color.rgb = push_constants.color.rgb * in_diffuse; + f_color.a = 1.0; + } + ", } } diff --git a/examples/src/bin/deferred/frame/directional_lighting_system.rs b/examples/src/bin/deferred/frame/directional_lighting_system.rs index d45f7c39..2b41a432 100644 --- a/examples/src/bin/deferred/frame/directional_lighting_system.rs +++ b/examples/src/bin/deferred/frame/directional_lighting_system.rs @@ -132,7 +132,6 @@ impl DirectionalLightingSystem { /// result of the deferred pass. /// - `direction` is the direction of the light in world coordinates. /// - `color` is the color to apply. - /// pub fn draw( &self, viewport_dimensions: [u32; 2], @@ -141,7 +140,7 @@ impl DirectionalLightingSystem { direction: Vector3, color: [f32; 3], ) -> SecondaryAutoCommandBuffer { - let push_constants = fs::ty::PushConstants { + let push_constants = fs::PushConstants { color: [color[0], color[1], color[2], 1.0], direction: direction.extend(0.0).into(), }; @@ -193,49 +192,53 @@ impl DirectionalLightingSystem { mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 position; + layout(location = 0) in vec2 position; -void main() { - gl_Position = vec4(position, 0.0, 1.0); -}" + void main() { + gl_Position = vec4(position, 0.0, 1.0); + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " -#version 450 + src: r" + #version 450 -// The `color_input` parameter of the `draw` method. -layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput u_diffuse; -// The `normals_input` parameter of the `draw` method. -layout(input_attachment_index = 1, set = 0, binding = 1) uniform subpassInput u_normals; + // The `color_input` parameter of the `draw` method. + layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput u_diffuse; + // The `normals_input` parameter of the `draw` method. + layout(input_attachment_index = 1, set = 0, binding = 1) uniform subpassInput u_normals; -layout(push_constant) uniform PushConstants { - // The `color` parameter of the `draw` method. - vec4 color; - // The `direction` parameter of the `draw` method. - vec4 direction; -} push_constants; + layout(push_constant) uniform PushConstants { + // The `color` parameter of the `draw` method. + vec4 color; + // The `direction` parameter of the `draw` method. + vec4 direction; + } push_constants; -layout(location = 0) out vec4 f_color; + layout(location = 0) out vec4 f_color; -void main() { - vec3 in_normal = normalize(subpassLoad(u_normals).rgb); - // If the normal is perpendicular to the direction of the lighting, then `light_percent` will - // be 0. If the normal is parallel to the direction of the lightin, then `light_percent` will - // be 1. Any other angle will yield an intermediate value. - float light_percent = -dot(push_constants.direction.xyz, in_normal); - // `light_percent` must not go below 0.0. There's no such thing as negative lighting. - light_percent = max(light_percent, 0.0); + void main() { + vec3 in_normal = normalize(subpassLoad(u_normals).rgb); - vec3 in_diffuse = subpassLoad(u_diffuse).rgb; - f_color.rgb = light_percent * push_constants.color.rgb * in_diffuse; - f_color.a = 1.0; -}", + // If the normal is perpendicular to the direction of the lighting, then + // `light_percent` will be 0. If the normal is parallel to the direction of the + // lightin, then `light_percent` will be 1. Any other angle will yield an + // intermediate value. + float light_percent = -dot(push_constants.direction.xyz, in_normal); + // `light_percent` must not go below 0.0. There's no such thing as negative lighting. + light_percent = max(light_percent, 0.0); + + vec3 in_diffuse = subpassLoad(u_diffuse).rgb; + f_color.rgb = light_percent * push_constants.color.rgb * in_diffuse; + f_color.a = 1.0; + } + ", } } diff --git a/examples/src/bin/deferred/frame/mod.rs b/examples/src/bin/deferred/frame/mod.rs index bd12c363..35f5317f 100644 --- a/examples/src/bin/deferred/frame/mod.rs +++ b/examples/src/bin/deferred/frame/mod.rs @@ -12,8 +12,7 @@ // The main code is in the `system` module, while the other modules implement the different kinds // of lighting sources. -use bytemuck::{Pod, Zeroable}; -use vulkano::pipeline::graphics::vertex_input::Vertex; +use vulkano::{buffer::BufferContents, pipeline::graphics::vertex_input::Vertex}; pub use self::system::{DrawPass, Frame, FrameSystem, LightingPass, Pass}; @@ -22,8 +21,8 @@ mod directional_lighting_system; mod point_lighting_system; mod system; +#[derive(BufferContents, Vertex)] #[repr(C)] -#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct LightingVertex { #[format(R32G32_SFLOAT)] position: [f32; 2], diff --git a/examples/src/bin/deferred/frame/point_lighting_system.rs b/examples/src/bin/deferred/frame/point_lighting_system.rs index 96a5ffab..34be828a 100644 --- a/examples/src/bin/deferred/frame/point_lighting_system.rs +++ b/examples/src/bin/deferred/frame/point_lighting_system.rs @@ -138,7 +138,6 @@ impl PointLightingSystem { /// coordinates of each pixel being processed. /// - `position` is the position of the spot light in world coordinates. /// - `color` is the color of the light. - /// #[allow(clippy::too_many_arguments)] pub fn draw( &self, @@ -150,7 +149,7 @@ impl PointLightingSystem { position: Vector3, color: [f32; 3], ) -> SecondaryAutoCommandBuffer { - let push_constants = fs::ty::PushConstants { + let push_constants = fs::PushConstants { screen_to_world: screen_to_world.into(), color: [color[0], color[1], color[2], 1.0], position: position.extend(0.0).into(), @@ -204,68 +203,73 @@ impl PointLightingSystem { mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 position; -layout(location = 0) out vec2 v_screen_coords; + layout(location = 0) in vec2 position; + layout(location = 0) out vec2 v_screen_coords; -void main() { - v_screen_coords = position; - gl_Position = vec4(position, 0.0, 1.0); -}" + void main() { + v_screen_coords = position; + gl_Position = vec4(position, 0.0, 1.0); + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " -#version 450 + src: r" + #version 450 -// The `color_input` parameter of the `draw` method. -layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput u_diffuse; -// The `normals_input` parameter of the `draw` method. -layout(input_attachment_index = 1, set = 0, binding = 1) uniform subpassInput u_normals; -// The `depth_input` parameter of the `draw` method. -layout(input_attachment_index = 2, set = 0, binding = 2) uniform subpassInput u_depth; + // The `color_input` parameter of the `draw` method. + layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput u_diffuse; + // The `normals_input` parameter of the `draw` method. + layout(input_attachment_index = 1, set = 0, binding = 1) uniform subpassInput u_normals; + // The `depth_input` parameter of the `draw` method. + layout(input_attachment_index = 2, set = 0, binding = 2) uniform subpassInput u_depth; -layout(push_constant) uniform PushConstants { - // The `screen_to_world` parameter of the `draw` method. - mat4 screen_to_world; - // The `color` parameter of the `draw` method. - vec4 color; - // The `position` parameter of the `draw` method. - vec4 position; -} push_constants; + layout(push_constant) uniform PushConstants { + // The `screen_to_world` parameter of the `draw` method. + mat4 screen_to_world; + // The `color` parameter of the `draw` method. + vec4 color; + // The `position` parameter of the `draw` method. + vec4 position; + } push_constants; -layout(location = 0) in vec2 v_screen_coords; -layout(location = 0) out vec4 f_color; + layout(location = 0) in vec2 v_screen_coords; + layout(location = 0) out vec4 f_color; -void main() { - float in_depth = subpassLoad(u_depth).x; - // Any depth superior or equal to 1.0 means that the pixel has been untouched by the deferred - // pass. We don't want to deal with them. - if (in_depth >= 1.0) { - discard; - } - // Find the world coordinates of the current pixel. - vec4 world = push_constants.screen_to_world * vec4(v_screen_coords, in_depth, 1.0); - world /= world.w; + void main() { + float in_depth = subpassLoad(u_depth).x; - vec3 in_normal = normalize(subpassLoad(u_normals).rgb); - vec3 light_direction = normalize(push_constants.position.xyz - world.xyz); - // Calculate the percent of lighting that is received based on the orientation of the normal - // and the direction of the light. - float light_percent = max(-dot(light_direction, in_normal), 0.0); + // Any depth superior or equal to 1.0 means that the pixel has been untouched by + // the deferred pass. We don't want to deal with them. + if (in_depth >= 1.0) { + discard; + } - float light_distance = length(push_constants.position.xyz - world.xyz); - // Further decrease light_percent based on the distance with the light position. - light_percent *= 1.0 / exp(light_distance); + // Find the world coordinates of the current pixel. + vec4 world = push_constants.screen_to_world * vec4(v_screen_coords, in_depth, 1.0); + world /= world.w; - vec3 in_diffuse = subpassLoad(u_diffuse).rgb; - f_color.rgb = push_constants.color.rgb * light_percent * in_diffuse; - f_color.a = 1.0; -}", + vec3 in_normal = normalize(subpassLoad(u_normals).rgb); + vec3 light_direction = normalize(push_constants.position.xyz - world.xyz); + + // Calculate the percent of lighting that is received based on the orientation of + // the normal and the direction of the light. + float light_percent = max(-dot(light_direction, in_normal), 0.0); + + float light_distance = length(push_constants.position.xyz - world.xyz); + // Further decrease light_percent based on the distance with the light position. + light_percent *= 1.0 / exp(light_distance); + + vec3 in_diffuse = subpassLoad(u_diffuse).rgb; + f_color.rgb = push_constants.color.rgb * light_percent * in_diffuse; + f_color.a = 1.0; + } + ", } } diff --git a/examples/src/bin/deferred/frame/system.rs b/examples/src/bin/deferred/frame/system.rs index bbc14149..266d2a67 100644 --- a/examples/src/bin/deferred/frame/system.rs +++ b/examples/src/bin/deferred/frame/system.rs @@ -69,7 +69,6 @@ impl FrameSystem { /// - `final_output_format` is the format of the image that will later be passed to the /// `frame()` method. We need to know that in advance. If that format ever changes, we have /// to create a new `FrameSystem`. - /// pub fn new( gfx_queue: Arc, final_output_format: Format, @@ -151,8 +150,8 @@ impl FrameSystem { ) .unwrap(); - // For now we create three temporary images with a dimension of 1 by 1 pixel. - // These images will be replaced the first time we call `frame()`. + // For now we create three temporary images with a dimension of 1 by 1 pixel. These images + // will be replaced the first time we call `frame()`. let diffuse_buffer = ImageView::new_default( AttachmentImage::with_usage( &memory_allocator, @@ -188,8 +187,8 @@ impl FrameSystem { gfx_queue.device().clone(), )); - // Initialize the three lighting systems. - // Note that we need to pass to them the subpass where they will be executed. + // Initialize the three lighting systems. Note that we need to pass to them the subpass + // where they will be executed. let lighting_subpass = Subpass::from(render_pass.clone(), 1).unwrap(); let ambient_lighting_system = AmbientLightingSystem::new( gfx_queue.clone(), @@ -245,7 +244,6 @@ impl FrameSystem { /// - `final_image` is the image we are going to draw to. /// - `world_to_framebuffer` is the matrix that will be used to convert from 3D coordinates in /// the world into 2D coordinates on the framebuffer. - /// pub fn frame( &mut self, before_future: F, @@ -454,7 +452,6 @@ pub struct DrawPass<'f, 's: 'f> { impl<'f, 's: 'f> DrawPass<'f, 's> { /// Appends a command that executes a secondary command buffer that performs drawing. - #[inline] pub fn execute(&mut self, command_buffer: C) where C: SecondaryCommandBufferAbstract + 'static, @@ -468,14 +465,12 @@ impl<'f, 's: 'f> DrawPass<'f, 's> { } /// Returns the dimensions in pixels of the viewport. - #[inline] pub fn viewport_dimensions(&self) -> [u32; 2] { self.frame.framebuffer.extent() } /// Returns the 4x4 matrix that turns world coordinates into 2D coordinates on the framebuffer. #[allow(dead_code)] - #[inline] pub fn world_to_framebuffer_matrix(&self) -> Matrix4 { self.frame.world_to_framebuffer } diff --git a/examples/src/bin/deferred/main.rs b/examples/src/bin/deferred/main.rs index 60ba0dfe..edfea12c 100644 --- a/examples/src/bin/deferred/main.rs +++ b/examples/src/bin/deferred/main.rs @@ -11,15 +11,15 @@ // // The idea behind deferred lighting is to render the scene in two steps. // -// First you draw all the objects of the scene. But instead of calculating the color they will -// have on the screen, you output their characteristics such as their diffuse color and their -// normals, and write this to images. +// First you draw all the objects of the scene. But instead of calculating the color they will have +// on the screen, you output their characteristics such as their diffuse color and their normals, +// and write this to images. // // After all the objects are drawn, you should obtain several images that contain the // characteristics of each pixel. // -// Then you apply lighting to the scene. In other words you draw to the final image by taking -// these intermediate images and the various lights of the scene as input. +// Then you apply lighting to the scene. In other words you draw to the final image by taking these +// intermediate images and the various lights of the scene as input. // // This technique allows you to apply tons of light sources to a scene, which would be too // expensive otherwise. It has some drawbacks, which are the fact that transparent objects must be @@ -66,7 +66,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -109,7 +108,7 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); let (device, mut queues) = Device::new( @@ -216,7 +215,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; let new_images = new_images .into_iter() @@ -235,7 +234,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -285,7 +284,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } diff --git a/examples/src/bin/deferred/triangle_draw_system.rs b/examples/src/bin/deferred/triangle_draw_system.rs index a7908a83..8d6efc41 100644 --- a/examples/src/bin/deferred/triangle_draw_system.rs +++ b/examples/src/bin/deferred/triangle_draw_system.rs @@ -7,10 +7,9 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use bytemuck::{Pod, Zeroable}; use std::sync::Arc; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage, Subbuffer}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage, Subbuffer}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferInheritanceInfo, CommandBufferUsage, SecondaryAutoCommandBuffer, @@ -120,8 +119,8 @@ impl TriangleDrawSystem { } } +#[derive(BufferContents, Vertex)] #[repr(C)] -#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct TriangleVertex { #[format(R32G32_SFLOAT)] position: [f32; 2], @@ -130,29 +129,31 @@ struct TriangleVertex { mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 position; + layout(location = 0) in vec2 position; -void main() { - gl_Position = vec4(position, 0.0, 1.0); -}" + void main() { + gl_Position = vec4(position, 0.0, 1.0); + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) out vec4 f_color; -layout(location = 1) out vec3 f_normal; + layout(location = 0) out vec4 f_color; + layout(location = 1) out vec3 f_normal; -void main() { - f_color = vec4(1.0, 1.0, 1.0, 1.0); - f_normal = vec3(0.0, 0.0, 1.0); -}" + void main() { + f_color = vec4(1.0, 1.0, 1.0, 1.0); + f_normal = vec3(0.0, 0.0, 1.0); + } + ", } } diff --git a/examples/src/bin/dynamic-buffers.rs b/examples/src/bin/dynamic-buffers.rs index 1c8dc4ed..a2cf6df5 100644 --- a/examples/src/bin/dynamic-buffers.rs +++ b/examples/src/bin/dynamic-buffers.rs @@ -9,10 +9,9 @@ // This example demonstrates how to use dynamic uniform buffers. // -// Dynamic uniform and storage buffers store buffer data for different -// calls in one large buffer. Each draw or dispatch call can specify an -// offset into the buffer to read object data from, without having to -// rebind descriptor sets. +// Dynamic uniform and storage buffers store buffer data for different calls in one large buffer. +// Each draw or dispatch call can specify an offset into the buffer to read object data from, +// without having to rebind descriptor sets. use std::{iter::repeat, mem::size_of}; use vulkano::{ @@ -40,7 +39,6 @@ fn main() { let instance = Instance::new( library, InstanceCreateInfo { - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -74,7 +72,7 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); let (device, mut queues) = Device::new( @@ -94,29 +92,29 @@ fn main() { mod shader { vulkano_shaders::shader! { ty: "compute", - src: " + src: r" #version 450 layout(local_size_x = 12) in; - // Uniform Buffer Object + // Uniform buffer. layout(set = 0, binding = 0) uniform InData { - uint data; - } ubo; + uint index; + } ub; - // Output Buffer + // Output buffer. layout(set = 0, binding = 1) buffer OutData { uint data[]; - } data; + }; - // Toy shader that only runs for the index specified in `ubo`. + // Toy shader that only runs for the index specified in `ub`. void main() { uint index = gl_GlobalInvocationID.x; - if(index == ubo.data) { - data.data[index] = index; + if (index == ub.index) { + data[index] = index; } } - " + ", } } @@ -138,29 +136,31 @@ fn main() { let command_buffer_allocator = StandardCommandBufferAllocator::new(device.clone(), Default::default()); - // Declare input buffer. - // Data in a dynamic buffer **MUST** be aligned to min_uniform_buffer_offset_align - // or min_storage_buffer_offset_align, depending on the type of buffer. - let data: Vec = vec![3, 11, 7]; + // Create the input buffer. Data in a dynamic buffer **MUST** be aligned to + // `min_uniform_buffer_offset_align` or `min_storage_buffer_offset_align`, depending on the + // type of buffer. + let data: Vec = vec![3, 11, 7]; let min_dynamic_align = device .physical_device() .properties() .min_uniform_buffer_offset_alignment as usize; + println!("Minimum uniform buffer offset alignment: {min_dynamic_align}"); println!("Input: {data:?}"); + // Round size up to the next multiple of align. let align = (size_of::() + min_dynamic_align - 1) & !(min_dynamic_align - 1); let aligned_data = { let mut aligned_data = Vec::with_capacity(align * data.len()); + for elem in data { let bytes = elem.to_ne_bytes(); - // Fill up the buffer with data - for b in bytes { - aligned_data.push(b); - } - // Zero out any padding needed for alignment + // Fill up the buffer with data. + aligned_data.extend(bytes); + // Zero out any padding needed for alignment. aligned_data.extend(repeat(0).take(align - bytes.len())); } + aligned_data }; @@ -196,7 +196,7 @@ fn main() { WriteDescriptorSet::buffer_with_range( 0, input_buffer, - 0..size_of::() as DeviceSize, + 0..size_of::() as DeviceSize, ), WriteDescriptorSet::buffer(1, output_buffer.clone()), ], diff --git a/examples/src/bin/dynamic-local-size.rs b/examples/src/bin/dynamic-local-size.rs index be59b751..d175d725 100644 --- a/examples/src/bin/dynamic-local-size.rs +++ b/examples/src/bin/dynamic-local-size.rs @@ -7,12 +7,11 @@ // notice may not be copied, modified, or distributed except // according to those terms. -// This example demonstrates how to compute and load Compute Shader local size -// layout in runtime through specialization constants using Physical Device metadata. +// This example demonstrates how to define the compute shader local size layout at runtime through +// specialization constants while considering the physical device properties. // -// Workgroup parallelism capabilities are varying between GPUs and setting them -// properly is important to achieve maximal performance that particular device -// can provide. +// Workgroup parallelism capabilities vary between GPUs and setting them properly is important to +// achieve the maximal performance that particular device can provide. use std::{fs::File, io::BufWriter, path::Path}; use vulkano::{ @@ -43,13 +42,11 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: InstanceExtensions { - // This extension is required to obtain physical device metadata - // about the device workgroup size limits + // This extension is required to obtain physical device metadata about the device + // workgroup size limits. khr_get_physical_device_properties2: true, - ..InstanceExtensions::empty() }, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -82,7 +79,7 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); let (device, mut queues) = Device::new( @@ -102,23 +99,20 @@ fn main() { mod cs { vulkano_shaders::shader! { ty: "compute", - src: " + src: r" #version 450 - // We set local_size_x and local_size_y to be variable configurable - // values through Specialization Constants. Values 1 and 2 define - // constant_id (1 and 2 correspondingly) and default values of - // the constants both. The `local_size_z = 1` here is an ordinary - // built-in value of the local size in Z axis. + // We set `local_size_x` and `local_size_y` to be variables configurable values + // through specialization constants. Values `1` and `2` both define a constant ID + // as well as a default value of 1 and 2 of the constants respecively. The + // `local_size_z = 1` here is an ordinary constant of the local size on the Z axis. // - // Unfortunately current GLSL language capabilities doesn't let us - // define exact names of the constants so we will have to use - // anonymous constants instead. See below on how to provide their - // values in run time. + // Unfortunately current GLSL language capabilities doesn't let us define exact + // names of the constants so we will have to use anonymous constants instead. See + // below for how to provide their values at runtime. // - // Please NOTE that the constant_id in local_size layout must be - // positive values. Zero value lead to runtime failure on nVidia - // devices due to a known bug in nVidia driver. + // NOTE: The constant ID in `local_size` layout must be positive values. Zeros lead + // to runtime failure on NVIDIA devices due to a known bug in the driver. layout(local_size_x_id = 1, local_size_y_id = 2, local_size_z = 1) in; // We can still define more constants in the Shader @@ -129,7 +123,7 @@ fn main() { layout(set = 0, binding = 0, rgba8) uniform writeonly image2D img; void main() { - // Colorful Mandelbrot fractal + // Colorful Mandelbrot fractal. vec2 norm_coordinates = (gl_GlobalInvocationID.xy + vec2(0.5)) / vec2(imageSize(img)); vec2 c = (norm_coordinates - vec2(0.5)) * 2.0 - vec2(1.0, 0.0); @@ -151,30 +145,30 @@ fn main() { imageStore(img, ivec2(gl_GlobalInvocationID.xy), to_write); } - " + ", } } let shader = cs::load(device.clone()).unwrap(); - // Fetching subgroup size from the Physical Device metadata to compute appropriate - // Compute Shader local size properties. + // Fetching subgroup size from the physical device properties to determine an appropriate + // compute shader local size. // - // Most of the drivers provide this metadata, but some of the drivers don't. - // In this case we can find appropriate value in this table: https://vulkan.gpuinfo.org/ - // or just use fallback constant for simplicity, but failure to set proper - // local size can lead to significant performance penalty. + // Most of the drivers provide this property, but some of the drivers don't. In that case we + // can find an appropriate value using this tool: https://vulkan.gpuinfo.org, or just use a + // fallback constant for simplicity, but failure to set a proper local size can lead to a + // significant performance penalty. let (local_size_x, local_size_y) = match device.physical_device().properties().subgroup_size { Some(subgroup_size) => { println!("Subgroup size is {subgroup_size}"); - // Most of the subgroup values are divisors of 8 + // Most of the subgroup values are divisors of 8. (8, subgroup_size / 8) } None => { println!("This Vulkan driver doesn't provide physical device Subgroup information"); - // Using fallback constant + // Using a fallback constant. (8, 8) } }; @@ -185,7 +179,8 @@ fn main() { red: 0.2, green: 0.5, blue: 1.0, - constant_1: local_size_x, // specifying local size constants + // Specify the local size constants. + constant_1: local_size_x, constant_2: local_size_y, }; let pipeline = ComputePipeline::new( @@ -247,11 +242,8 @@ fn main() { 0, set, ) - .dispatch([ - 1024 / local_size_x, // Note that dispatch dimensions must be - 1024 / local_size_y, // proportional to local size - 1, - ]) + // Note that dispatch dimensions must be proportional to the local size. + .dispatch([1024 / local_size_x, 1024 / local_size_y, 1]) .unwrap() .copy_image_to_buffer(CopyImageToBufferInfo::image_buffer(image, buf.clone())) .unwrap(); diff --git a/examples/src/bin/gl-interop.rs b/examples/src/bin/gl-interop.rs index 60d52e15..cd944138 100644 --- a/examples/src/bin/gl-interop.rs +++ b/examples/src/bin/gl-interop.rs @@ -8,14 +8,13 @@ fn main() { // TODO: Can this be demonstrated for other platforms as well? #[cfg(target_os = "linux")] mod linux { - use bytemuck::{Pod, Zeroable}; use glium::glutin::{self, platform::unix::HeadlessContextExt}; use std::{ sync::{Arc, Barrier}, time::Instant, }; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage, Subbuffer}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage, Subbuffer}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SemaphoreSubmitInfo, SubmitInfo, @@ -69,7 +68,7 @@ mod linux { pub fn main() { let event_loop = EventLoop::new(); - // For some reason, this must be created before the vulkan window + // For some reason, this must be created before the Vulkan window. let hrb = glutin::ContextBuilder::new() .with_gl_debug_flag(true) .with_gl(glutin::GlRequest::Latest) @@ -82,11 +81,13 @@ mod linux { .build_surfaceless(&event_loop) .unwrap(); + // Used for checking device and driver UUIDs. let display = glium::HeadlessRenderer::with_debug( hrb_vk, glium::debug::DebugCallbackBehavior::PrintAll, ) - .unwrap(); // Used for checking device and driver UUIDs + .unwrap(); + let ( device, _instance, @@ -293,7 +294,7 @@ mod linux { Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => { return } - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -312,7 +313,7 @@ mod linux { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -375,7 +376,7 @@ mod linux { previous_frame_end = Some(vulkano::sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(vulkano::sync::now(device.clone()).boxed()); } }; @@ -386,8 +387,8 @@ mod linux { }); } + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct MyVertex { #[format(R32G32_SFLOAT)] position: [f32; 2], @@ -427,7 +428,6 @@ mod linux { } .union(&required_extensions), - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() @@ -444,7 +444,7 @@ mod linux { msg.layer_prefix.unwrap_or("unknown"), msg.ty, msg.severity, - msg.description + msg.description, ); })), ) @@ -695,28 +695,30 @@ mod linux { mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " -#version 450 -layout(location = 0) in vec2 position; -layout(location = 0) out vec2 tex_coords; -void main() { - gl_Position = vec4(position, 0.0, 1.0); - tex_coords = position + vec2(0.5); -}" + src: r" + #version 450 + layout(location = 0) in vec2 position; + layout(location = 0) out vec2 tex_coords; + void main() { + gl_Position = vec4(position, 0.0, 1.0); + tex_coords = position + vec2(0.5); + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " -#version 450 -layout(location = 0) in vec2 tex_coords; -layout(location = 0) out vec4 f_color; -layout(set = 0, binding = 0) uniform sampler2D tex; -void main() { - f_color = texture(tex, tex_coords); -}" + src: r" + #version 450 + layout(location = 0) in vec2 tex_coords; + layout(location = 0) out vec4 f_color; + layout(set = 0, binding = 0) uniform sampler2D tex; + void main() { + f_color = texture(tex, tex_coords); + } + ", } } } diff --git a/examples/src/bin/image-self-copy-blit/main.rs b/examples/src/bin/image-self-copy-blit/main.rs index 2ce53682..515a8603 100644 --- a/examples/src/bin/image-self-copy-blit/main.rs +++ b/examples/src/bin/image-self-copy-blit/main.rs @@ -7,10 +7,9 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use bytemuck::{Pod, Zeroable}; use std::{io::Cursor, sync::Arc}; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, BlitImageInfo, BufferImageCopy, ClearColorImageInfo, CommandBufferUsage, CopyBufferToImageInfo, @@ -57,8 +56,8 @@ use winit::{ }; fn main() { - // The start of this example is exactly the same as `triangle`. You should read the - // `triangle` example if you haven't done so yet. + // The start of this example is exactly the same as `triangle`. You should read the `triangle` + // example if you haven't done so yet. let library = VulkanLibrary::new().unwrap(); let required_extensions = vulkano_win::required_extensions(&library); @@ -66,7 +65,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -161,8 +159,8 @@ fn main() { let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32_SFLOAT)] position: [f32; 2], @@ -255,12 +253,12 @@ fn main() { ) .unwrap(); - // here, we perform image copying and blitting on the same image + // Here, we perform image copying and blitting on the same image. uploads - // clear the image buffer + // Clear the image buffer. .clear_color_image(ClearColorImageInfo::image(image.clone())) .unwrap() - // put our image in the top left corner + // Put our image in the top left corner. .copy_buffer_to_image(CopyBufferToImageInfo { regions: [BufferImageCopy { image_subresource: image.subresource_layers(), @@ -271,7 +269,7 @@ fn main() { ..CopyBufferToImageInfo::buffer_image(buffer, image.clone()) }) .unwrap() - // copy from the top left corner to the bottom right corner + // Copy from the top left corner to the bottom right corner. .copy_image(CopyImageInfo { // Copying within the same image requires the General layout if the source and // destination subresources overlap. @@ -289,9 +287,9 @@ fn main() { ..CopyImageInfo::images(image.clone(), image.clone()) }) .unwrap() - // blit from the bottom right corner to the top right corner (flipped) + // Blit from the bottom right corner to the top right corner (flipped). .blit_image(BlitImageInfo { - // Same for blitting. + // Same as above applies for blitting. src_image_layout: ImageLayout::General, dst_image_layout: ImageLayout::General, regions: [ImageBlit { @@ -301,7 +299,7 @@ fn main() { [img_size[0] * 2, img_size[1] * 2, 1], ], dst_subresource: image.subresource_layers(), - // swapping the two corners results in flipped image + // Swapping the two corners results in flipped image. dst_offsets: [ [img_size[0] * 2 - 1, img_size[1] - 1, 0], [img_size[0], 0, 1], @@ -394,7 +392,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -410,7 +408,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -470,7 +468,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -479,7 +477,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], render_pass: Arc, @@ -507,32 +505,34 @@ fn window_size_dependent_setup( mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 position; -layout(location = 0) out vec2 tex_coords; + layout(location = 0) in vec2 position; + layout(location = 0) out vec2 tex_coords; -void main() { - gl_Position = vec4(position, 0.0, 1.0); - tex_coords = position + vec2(0.5); -}" + void main() { + gl_Position = vec4(position, 0.0, 1.0); + tex_coords = position + vec2(0.5); + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 tex_coords; -layout(location = 0) out vec4 f_color; + layout(location = 0) in vec2 tex_coords; + layout(location = 0) out vec4 f_color; -layout(set = 0, binding = 0) uniform sampler2D tex; + layout(set = 0, binding = 0) uniform sampler2D tex; -void main() { - f_color = texture(tex, tex_coords); -}" + void main() { + f_color = texture(tex, tex_coords); + } + ", } } diff --git a/examples/src/bin/image/main.rs b/examples/src/bin/image/main.rs index 857b70ff..44802a27 100644 --- a/examples/src/bin/image/main.rs +++ b/examples/src/bin/image/main.rs @@ -7,10 +7,9 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use bytemuck::{Pod, Zeroable}; use std::{io::Cursor, sync::Arc}; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents, @@ -55,8 +54,8 @@ use winit::{ }; fn main() { - // The start of this example is exactly the same as `triangle`. You should read the - // `triangle` example if you haven't done so yet. + // The start of this example is exactly the same as `triangle`. You should read the `triangle` + // example if you haven't done so yet. let library = VulkanLibrary::new().unwrap(); let required_extensions = vulkano_win::required_extensions(&library); @@ -64,7 +63,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -159,8 +157,8 @@ fn main() { let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32_SFLOAT)] position: [f32; 2], @@ -323,7 +321,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -339,7 +337,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -399,7 +397,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -408,7 +406,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], render_pass: Arc, @@ -436,32 +434,34 @@ fn window_size_dependent_setup( mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 position; -layout(location = 0) out vec2 tex_coords; + layout(location = 0) in vec2 position; + layout(location = 0) out vec2 tex_coords; -void main() { - gl_Position = vec4(position, 0.0, 1.0); - tex_coords = position + vec2(0.5); -}" + void main() { + gl_Position = vec4(position, 0.0, 1.0); + tex_coords = position + vec2(0.5); + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 tex_coords; -layout(location = 0) out vec4 f_color; + layout(location = 0) in vec2 tex_coords; + layout(location = 0) out vec4 f_color; -layout(set = 0, binding = 0) uniform sampler2D tex; + layout(set = 0, binding = 0) uniform sampler2D tex; -void main() { - f_color = texture(tex, tex_coords); -}" + void main() { + f_color = texture(tex, tex_coords); + } + ", } } diff --git a/examples/src/bin/immutable-sampler/main.rs b/examples/src/bin/immutable-sampler/main.rs index 8573ca6c..0422d5d0 100644 --- a/examples/src/bin/immutable-sampler/main.rs +++ b/examples/src/bin/immutable-sampler/main.rs @@ -7,19 +7,18 @@ // notice may not be copied, modified, or distributed except // according to those terms. -// An immutable sampler is a sampler that is integrated into the descriptor set layout -// (and thus pipeline layout), instead of being written to an individual descriptor set. -// Consequently, all descriptor sets with this layout will share the same sampler. +// An immutable sampler is a sampler that is integrated into the descriptor set layout (and thus +// pipeline layout), instead of being written to an individual descriptor set. Consequently, all +// descriptor sets with this layout will share the same sampler. // // This example is almost identical to the image example, but with two differences, which have // been commented: // - The sampler is added to the descriptor set layout at pipeline creation. // - No sampler is included when building a descriptor set. -use bytemuck::{Pod, Zeroable}; use std::{io::Cursor, sync::Arc}; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents, @@ -70,7 +69,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -165,8 +163,8 @@ fn main() { let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32_SFLOAT)] position: [f32; 2], @@ -273,8 +271,7 @@ fn main() { .color_blend_state(ColorBlendState::new(subpass.num_color_attachments()).blend_alpha()) .render_pass(subpass) .with_auto_layout(device.clone(), |layout_create_infos| { - // Modify the auto-generated layout by setting an immutable sampler to - // set 0 binding 0. + // Modify the auto-generated layout by setting an immutable sampler to set 0 binding 0. let binding = layout_create_infos[0].bindings.get_mut(&0).unwrap(); binding.immutable_samplers = vec![sampler]; }) @@ -282,7 +279,8 @@ fn main() { let layout = pipeline.layout().set_layouts().get(0).unwrap(); - // Use `image_view` instead of `image_view_sampler`, since the sampler is already in the layout. + // Use `image_view` instead of `image_view_sampler`, since the sampler is already in the + // layout. let set = PersistentDescriptorSet::new( &descriptor_set_allocator, layout.clone(), @@ -336,7 +334,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -352,7 +350,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -412,7 +410,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -421,7 +419,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], render_pass: Arc, @@ -449,32 +447,34 @@ fn window_size_dependent_setup( mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 position; -layout(location = 0) out vec2 tex_coords; + layout(location = 0) in vec2 position; + layout(location = 0) out vec2 tex_coords; -void main() { - gl_Position = vec4(position, 0.0, 1.0); - tex_coords = position + vec2(0.5); -}" + void main() { + gl_Position = vec4(position, 0.0, 1.0); + tex_coords = position + vec2(0.5); + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 tex_coords; -layout(location = 0) out vec4 f_color; + layout(location = 0) in vec2 tex_coords; + layout(location = 0) out vec4 f_color; -layout(set = 0, binding = 0) uniform sampler2D tex; + layout(set = 0, binding = 0) uniform sampler2D tex; -void main() { - f_color = texture(tex, tex_coords); -}" + void main() { + f_color = texture(tex, tex_coords); + } + ", } } diff --git a/examples/src/bin/indirect.rs b/examples/src/bin/indirect.rs index e8d1a8c5..3d3973bd 100644 --- a/examples/src/bin/indirect.rs +++ b/examples/src/bin/indirect.rs @@ -12,24 +12,22 @@ // Indirect draw calls allow us to issue a draw without needing to know the number of vertices // until later when the draw is executed by the GPU. // -// This is used in situations where vertices are being generated on the GPU, such as a GPU -// particle simulation, and the exact number of output vertices cannot be known until -// the compute shader has run. +// This is used in situations where vertices are being generated on the GPU, such as a GPU particle +// simulation, and the exact number of output vertices cannot be known until the compute shader has +// run. // // In this example the compute shader is trivial and the number of vertices does not change. -// However is does demonstrate that each compute instance atomically updates the vertex -// counter before filling the vertex buffer. +// However is does demonstrate that each compute instance atomically updates the vertex counter +// before filling the vertex buffer. // // For an explanation of how the rendering of the triangles takes place see the `triangle.rs` // example. -// -use bytemuck::{Pod, Zeroable}; use std::sync::Arc; use vulkano::{ buffer::{ allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, - BufferUsage, + BufferContents, BufferUsage, }, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, @@ -76,7 +74,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -174,7 +171,7 @@ fn main() { mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " + src: r" #version 450 // The triangle vertex positions. @@ -183,14 +180,14 @@ fn main() { void main() { gl_Position = vec4(position, 0.0, 1.0); } - " + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " + src: r#" #version 450 layout(location = 0) out vec4 f_color; @@ -198,16 +195,17 @@ fn main() { void main() { f_color = vec4(1.0, 0.0, 0.0, 1.0); } - " + "#, } } - // A simple compute shader that generates vertices. It has two buffers bound: the first is where we output the vertices, the second - // is the IndirectDrawArgs struct we passed the draw_indirect so we can set the number to vertices to draw + // A simple compute shader that generates vertices. It has two buffers bound: the first is + // where we output the vertices, the second is the `IndirectDrawArgs` struct we passed the + // `draw_indirect` so we can set the number to vertices to draw. mod cs { vulkano_shaders::shader! { ty: "compute", - src: " + src: r" #version 450 layout(local_size_x = 16, local_size_y = 1, local_size_z = 1) in; @@ -226,9 +224,10 @@ fn main() { void main() { uint idx = gl_GlobalInvocationID.x; - // each thread of compute shader is going to increment the counter, so we need to use atomic - // operations for safety. The previous value of the counter is returned so that gives us - // the offset into the vertex buffer this thread can write it's vertices into. + // Each invocation of the compute shader is going to increment the counter, so + // we need to use atomic operations for safety. The previous value of the + // counter is returned so that gives us the offset into the vertex buffer this + // thread can write it's vertices into. uint offset = atomicAdd(vertices, 6); vec2 center = vec2(-0.8, -0.8) + idx * vec2(0.1, 0.1); @@ -239,7 +238,7 @@ fn main() { triangles.pos[offset + 4] = center + vec2(0.025, 0.01725); triangles.pos[offset + 5] = center + vec2(-0.025, 0.01725); } - " + ", } } @@ -292,10 +291,10 @@ fn main() { ) .unwrap(); - // # Vertex Types - // `Vertex` is the vertex type that will be output from the compute shader and be input to the vertex shader. + // `Vertex` is the vertex type that will be output from the compute shader and be input to the + // vertex shader. + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32_SFLOAT)] position: [f32; 2], @@ -355,7 +354,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -374,15 +373,16 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { recreate_swapchain = true; } - // Allocate a GPU buffer to hold the arguments for this frames draw call. The compute - // shader will only update vertex_count, so set the other parameters correctly here. + // Allocate a buffer to hold the arguments for this frame's draw call. The compute + // shader will only update `vertex_count`, so set the other parameters correctly + // here. let indirect_commands = [DrawIndirectCommand { vertex_count: 0, instance_count: 1, @@ -397,15 +397,15 @@ fn main() { .unwrap() .copy_from_slice(&indirect_commands); - // Allocate a GPU buffer to hold this frames vertices. This needs to be large enough to hold - // the worst case number of vertices generated by the compute shader + // Allocate a buffer to hold this frame's vertices. This needs to be large enough + // to hold the worst case number of vertices generated by the compute shader. let iter = (0..(6 * 16)).map(|_| Vertex { position: [0.0; 2] }); let vertices = vertex_pool.allocate_slice(iter.len() as _).unwrap(); for (o, i) in vertices.write().unwrap().iter_mut().zip(iter) { *o = i; } - // Pass the two buffers to the compute shader + // Pass the two buffers to the compute shader. let layout = compute_pipeline.layout().set_layouts().get(0).unwrap(); let cs_desciptor_set = PersistentDescriptorSet::new( &descriptor_set_allocator, @@ -424,8 +424,8 @@ fn main() { ) .unwrap(); - // First in the command buffer we dispatch the compute shader to generate the vertices and fill out the draw - // call arguments + // First in the command buffer we dispatch the compute shader to generate the + // vertices and fill out the draw call arguments. builder .bind_pipeline_compute(compute_pipeline.clone()) .bind_descriptor_sets( @@ -446,11 +446,11 @@ fn main() { SubpassContents::Inline, ) .unwrap() - // The indirect draw call is placed in the command buffer with a reference to the GPU buffer that will - // contain the arguments when the draw is executed on the GPU .set_viewport(0, [viewport.clone()]) .bind_pipeline_graphics(render_pipeline.clone()) .bind_vertex_buffers(0, vertices) + // The indirect draw call is placed in the command buffer with a reference to + // the buffer that will contain the arguments for the draw. .draw_indirect(indirect_buffer) .unwrap() .end_render_pass() @@ -478,7 +478,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -488,7 +488,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], render_pass: Arc, diff --git a/examples/src/bin/instancing.rs b/examples/src/bin/instancing.rs index db50e977..9df961bc 100644 --- a/examples/src/bin/instancing.rs +++ b/examples/src/bin/instancing.rs @@ -12,10 +12,9 @@ // This is a simple, modified version of the `triangle.rs` example that demonstrates how we can use // the "instancing" technique with vulkano to draw many instances of the triangle. -use bytemuck::{Pod, Zeroable}; use std::sync::Arc; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SubpassContents, @@ -51,22 +50,17 @@ use winit::{ window::{Window, WindowBuilder}, }; -// # Vertex Types -// -// Seeing as we are going to use the `OneVertexOneInstanceDefinition` vertex definition for our -// graphics pipeline, we need to define two vertex types: -// -// 1. `TriangleVertex` is the vertex type that we will use to describe the triangle's geometry. +/// The vertex type that we will be used to describe the triangle's geometry. +#[derive(BufferContents, Vertex)] #[repr(C)] -#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct TriangleVertex { #[format(R32G32_SFLOAT)] position: [f32; 2], } -// 2. `InstanceData` is the vertex type that describes the unique data per instance. +/// The vertex type that describes the unique data per instance. +#[derive(BufferContents, Vertex)] #[repr(C)] -#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct InstanceData { #[format(R32G32_SFLOAT)] position_offset: [f32; 2], @@ -81,7 +75,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -177,8 +170,8 @@ fn main() { let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); - // We now create a buffer that will store the shape of our triangle. - // This triangle is identical to the one in the `triangle.rs` example. + // We now create a buffer that will store the shape of our triangle. This triangle is identical + // to the one in the `triangle.rs` example. let vertices = [ TriangleVertex { position: [-0.5, -0.25], @@ -202,8 +195,8 @@ fn main() { .unwrap() }; - // Now we create another buffer that will store the unique data per instance. - // For this example, we'll have the instances form a 10x10 grid that slowly gets larger. + // Now we create another buffer that will store the unique data per instance. For this example, + // we'll have the instances form a 10x10 grid that slowly gets larger. let instances = { let rows = 10; let cols = 10; @@ -238,7 +231,7 @@ fn main() { mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " + src: r" #version 450 // The triangle vertex positions. @@ -252,14 +245,14 @@ fn main() { // Apply the scale and offset for the instance. gl_Position = vec4(position * scale + position_offset, 0.0, 1.0); } - " + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " + src: r" #version 450 layout(location = 0) out vec4 f_color; @@ -267,7 +260,7 @@ fn main() { void main() { f_color = vec4(1.0, 0.0, 0.0, 1.0); } - " + ", } } @@ -292,8 +285,8 @@ fn main() { .unwrap(); let pipeline = GraphicsPipeline::start() - // Use the `BuffersDefinition` to describe to vulkano how the two vertex types - // are expected to be used. + // Use the implementations of the `Vertex` trait to describe to vulkano how the two vertex + // types are expected to be used. .vertex_input_state([TriangleVertex::per_vertex(), InstanceData::per_instance()]) .vertex_shader(vs.entry_point("main").unwrap(), ()) .input_assembly_state(InputAssemblyState::new()) @@ -346,7 +339,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -365,7 +358,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -425,7 +418,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -435,7 +428,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], render_pass: Arc, diff --git a/examples/src/bin/interactive_fractal/app.rs b/examples/src/bin/interactive_fractal/app.rs index e437094f..abb6715f 100644 --- a/examples/src/bin/interactive_fractal/app.rs +++ b/examples/src/bin/interactive_fractal/app.rs @@ -7,49 +7,52 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use crate::fractal_compute_pipeline::FractalComputePipeline; -use crate::place_over_frame::RenderPassPlaceOverFrame; +use crate::{ + fractal_compute_pipeline::FractalComputePipeline, place_over_frame::RenderPassPlaceOverFrame, +}; use cgmath::Vector2; -use std::sync::Arc; -use std::time::Instant; -use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; -use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; -use vulkano::device::Queue; -use vulkano::memory::allocator::StandardMemoryAllocator; -use vulkano::sync::GpuFuture; -use vulkano_util::renderer::{DeviceImageView, VulkanoWindowRenderer}; -use vulkano_util::window::WindowDescriptor; -use winit::window::Fullscreen; +use std::{sync::Arc, time::Instant}; +use vulkano::{ + command_buffer::allocator::StandardCommandBufferAllocator, + descriptor_set::allocator::StandardDescriptorSetAllocator, device::Queue, + memory::allocator::StandardMemoryAllocator, sync::GpuFuture, +}; +use vulkano_util::{ + renderer::{DeviceImageView, VulkanoWindowRenderer}, + window::WindowDescriptor, +}; use winit::{ dpi::PhysicalPosition, event::{ ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, VirtualKeyCode, WindowEvent, }, + window::Fullscreen, }; const MAX_ITERS_INIT: u32 = 200; const MOVE_SPEED: f32 = 0.5; -/// App for exploring Julia and Mandelbrot fractals +/// App for exploring Julia and Mandelbrot fractals. pub struct FractalApp { - /// Pipeline that computes Mandelbrot & Julia fractals and writes them to an image + /// Pipeline that computes Mandelbrot & Julia fractals and writes them to an image. fractal_pipeline: FractalComputePipeline, - /// Our render pipeline (pass) + /// Our render pipeline (pass). pub place_over_frame: RenderPassPlaceOverFrame, - /// Toggle that flips between julia and mandelbrot + /// Toggle that flips between Julia and Mandelbrot. pub is_julia: bool, - /// Togglet thats stops the movement on Julia + /// Toggle that stops the movement on Julia. is_c_paused: bool, /// C is a constant input to Julia escape time algorithm (mouse position). c: Vector2, - /// Our zoom level + /// Our zoom level. scale: Vector2, - /// Our translation on the complex plane + /// Our translation on the complex plane. translation: Vector2, - /// How far should the escape time algorithm run (higher = less performance, more accurate image) + /// How long the escape time algorithm should run (higher = less performance, more accurate + /// image). pub max_iters: u32, - /// Time tracking, useful for frame independent movement + /// Time tracking, useful for frame independent movement. time: Instant, dt: f32, dt_sum: f32, @@ -113,11 +116,11 @@ Usage: F: Toggle full-screen Right mouse: Stop movement in Julia (mouse position determines c) Esc: Quit\ - " + ", ); } - /// Run our compute pipeline and return a future of when the compute is finished + /// Runs our compute pipeline and return a future of when the compute is finished. pub fn compute(&self, image_target: DeviceImageView) -> Box { self.fractal_pipeline.compute( image_target, @@ -129,24 +132,24 @@ Usage: ) } - /// Should the app quit? (on esc) + /// Returns whether the app should quit. (Happens on when pressing ESC.) pub fn is_running(&self) -> bool { !self.input_state.should_quit } - /// Return average fps + /// Returns the average FPS. pub fn avg_fps(&self) -> f32 { self.avg_fps } - /// Delta time in milliseconds + /// Returns the delta time in milliseconds. pub fn dt(&self) -> f32 { self.dt * 1000.0 } - /// Update times and dt at the end of each frame + /// Updates times and dt at the end of each frame. pub fn update_time(&mut self) { - // Each second, update average fps & reset frame count & dt sum + // Each second, update average fps & reset frame count & dt sum. if self.dt_sum > 1.0 { self.avg_fps = self.frame_count / self.dt_sum; self.frame_count = 0.0; @@ -158,17 +161,19 @@ Usage: self.time = Instant::now(); } - /// Updates app state based on input state + /// Updates app state based on input state. pub fn update_state_after_inputs(&mut self, renderer: &mut VulkanoWindowRenderer) { - // Zoom in or out + // Zoom in or out. if self.input_state.scroll_delta > 0. { self.scale /= 1.05; } else if self.input_state.scroll_delta < 0. { self.scale *= 1.05; } - // Move speed scaled by zoom level + + // Move speed scaled by zoom level. let move_speed = MOVE_SPEED * self.dt * self.scale.x; - // Panning + + // Panning. if self.input_state.pan_up { self.translation += Vector2::new(0.0, move_speed); } @@ -181,22 +186,27 @@ Usage: if self.input_state.pan_left { self.translation += Vector2::new(-move_speed, 0.0); } - // Toggle between julia and mandelbrot + + // Toggle between Julia and Mandelbrot. if self.input_state.toggle_julia { self.is_julia = !self.is_julia; } - // Toggle c + + // Toggle c. if self.input_state.toggle_c { self.is_c_paused = !self.is_c_paused; } - // Update c + + // Update c. if !self.is_c_paused { - // Scale normalized mouse pos between -1.0 and 1.0; + // Scale normalized mouse pos between -1.0 and 1.0. let mouse_pos = self.input_state.normalized_mouse_pos() * 2.0 - Vector2::new(1.0, 1.0); - // Scale by our zoom (scale) level so when zooming in the movement on julia is not so drastic + // Scale by our zoom (scale) level so when zooming in the movement on Julia is not so + // drastic. self.c = mouse_pos * self.scale.x; } - // Update how many iterations we have + + // Update how many iterations we have. if self.input_state.increase_iterations { self.max_iters += 1; } @@ -207,11 +217,13 @@ Usage: self.max_iters -= 1; } } - // Randomize our palette + + // Randomize our palette. if self.input_state.randomize_palette { self.fractal_pipeline.randomize_palette(); } - // Toggle full-screen + + // Toggle full-screen. if self.input_state.toggle_full_screen { let is_full_screen = renderer.window().fullscreen().is_some(); renderer.window().set_fullscreen(if !is_full_screen { @@ -222,12 +234,12 @@ Usage: } } - /// Update input state + /// Update input state. pub fn handle_input(&mut self, window_size: [f32; 2], event: &Event<()>) { self.input_state.handle_input(window_size, event); } - /// reset input state at the end of frame + /// Reset input state at the end of the frame. pub fn reset_input_state(&mut self) { self.input_state.reset() } @@ -240,9 +252,9 @@ fn state_is_pressed(state: ElementState) -> bool { } } -/// Just a very simple input state (mappings). -/// Winit only has Pressed and Released events, thus continuous movement needs toggles. -/// Panning is one of those where continuous movement feels better. +/// Just a very simple input state (mappings). Winit only has `Pressed` and `Released` events, thus +/// continuous movement needs toggles. Panning is one of those things where continuous movement +/// feels better. struct InputState { pub window_size: [f32; 2], pub pan_up: bool, @@ -290,7 +302,7 @@ impl InputState { ) } - // Resets values that should be reset. All incremental mappings and toggles should be reset. + /// Resets values that should be reset. All incremental mappings and toggles should be reset. fn reset(&mut self) { *self = InputState { scroll_delta: 0.0, @@ -319,7 +331,7 @@ impl InputState { } } - /// Match keyboard event to our defined inputs + /// Matches keyboard events to our defined inputs. fn on_keyboard_event(&mut self, input: &KeyboardInput) { if let Some(key_code) = input.virtual_keycode { match key_code { @@ -338,7 +350,7 @@ impl InputState { } } - /// Update mouse scroll delta + /// Updates mouse scroll delta. fn on_mouse_wheel_event(&mut self, delta: &MouseScrollDelta) { let change = match delta { MouseScrollDelta::LineDelta(_x, y) => *y, diff --git a/examples/src/bin/interactive_fractal/fractal_compute_pipeline.rs b/examples/src/bin/interactive_fractal/fractal_compute_pipeline.rs index a8117c39..a3981dc4 100644 --- a/examples/src/bin/interactive_fractal/fractal_compute_pipeline.rs +++ b/examples/src/bin/interactive_fractal/fractal_compute_pipeline.rs @@ -45,7 +45,7 @@ impl FractalComputePipeline { command_buffer_allocator: Arc, descriptor_set_allocator: Arc, ) -> FractalComputePipeline { - // Initial colors + // Initial colors. let colors = vec![ [1.0, 0.0, 0.0, 1.0], [1.0, 1.0, 0.0, 1.0], @@ -90,7 +90,7 @@ impl FractalComputePipeline { } } - /// Randomizes our color palette + /// Randomizes our color palette. pub fn randomize_palette(&mut self) { let mut colors = vec![]; for _ in 0..self.palette_size { @@ -120,7 +120,7 @@ impl FractalComputePipeline { max_iters: u32, is_julia: bool, ) -> Box { - // Resize image if needed + // Resize image if needed. let img_dims = image.image().dimensions().width_height(); let pipeline_layout = self.pipeline.layout(); let desc_layout = pipeline_layout.set_layouts().get(0).unwrap(); @@ -140,15 +140,14 @@ impl FractalComputePipeline { ) .unwrap(); - let push_constants = cs::ty::PushConstants { + let push_constants = cs::PushConstants { + end_color: self.end_color, c: c.into(), scale: scale.into(), translation: translation.into(), - end_color: self.end_color, palette_size: self.palette_size, max_iters: max_iters as i32, is_julia: is_julia as u32, - _dummy0: [0u8; 8], // Required for alignment }; builder .bind_pipeline_compute(self.pipeline.clone()) @@ -165,101 +164,104 @@ impl FractalComputePipeline { mod cs { vulkano_shaders::shader! { ty: "compute", - src: " -#version 450 + src: r" + #version 450 -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; -// Image to which we'll write our fractal -layout(set = 0, binding = 0, rgba8) uniform writeonly image2D img; + // Image to which we'll write our fractal + layout(set = 0, binding = 0, rgba8) uniform writeonly image2D img; -// Our palette as a dynamic buffer -layout(set = 0, binding = 1) buffer Palette { - vec4 data[]; -} palette; + // Our palette as a dynamic buffer + layout(set = 0, binding = 1) buffer Palette { + vec4 data[]; + } palette; -// Our variable inputs as push constants -layout(push_constant) uniform PushConstants { - vec2 c; - vec2 scale; - vec2 translation; - vec4 end_color; - int palette_size; - int max_iters; - bool is_julia; -} push_constants; + // Our variable inputs as push constants + layout(push_constant) uniform PushConstants { + vec4 end_color; + vec2 c; + vec2 scale; + vec2 translation; + int palette_size; + int max_iters; + bool is_julia; + } push_constants; -// Gets smooth color between current color (determined by iterations) and the next color in the palette -// by linearly interpolating the colors based on: https://linas.org/art-gallery/escape/smooth.html -vec4 get_color( - int palette_size, - vec4 end_color, - int i, - int max_iters, - float len_z -) { - if (i < max_iters) { - float iters_float = float(i) + 1.0 - log(log(len_z)) / log(2.0f); - float iters_floor = floor(iters_float); - float remainder = iters_float - iters_floor; - vec4 color_start = palette.data[int(iters_floor) % push_constants.palette_size]; - vec4 color_end = palette.data[(int(iters_floor) + 1) % push_constants.palette_size]; - return mix(color_start, color_end, remainder); - } - return end_color; -} + // Gets smooth color between current color (determined by iterations) and the next + // color in the palette by linearly interpolating the colors based on: + // https://linas.org/art-gallery/escape/smooth.html + vec4 get_color( + int palette_size, + vec4 end_color, + int i, + int max_iters, + float len_z + ) { + if (i < max_iters) { + float iters_float = float(i) + 1.0 - log(log(len_z)) / log(2.0f); + float iters_floor = floor(iters_float); + float remainder = iters_float - iters_floor; + vec4 color_start = palette.data[int(iters_floor) % push_constants.palette_size]; + vec4 color_end = palette.data[(int(iters_floor) + 1) % push_constants.palette_size]; + return mix(color_start, color_end, remainder); + } + return end_color; + } -void main() { - // Scale image pixels to range - vec2 dims = vec2(imageSize(img)); - float ar = dims.x / dims.y; - float x_over_width = (gl_GlobalInvocationID.x / dims.x); - float y_over_height = (gl_GlobalInvocationID.y / dims.y); - float x0 = ar * (push_constants.translation.x + (x_over_width - 0.5) * push_constants.scale.x); - float y0 = push_constants.translation.y + (y_over_height - 0.5) * push_constants.scale.y; + void main() { + // Scale image pixels to range + vec2 dims = vec2(imageSize(img)); + float ar = dims.x / dims.y; + float x_over_width = (gl_GlobalInvocationID.x / dims.x); + float y_over_height = (gl_GlobalInvocationID.y / dims.y); + float x0 = ar * (push_constants.translation.x + (x_over_width - 0.5) * push_constants.scale.x); + float y0 = push_constants.translation.y + (y_over_height - 0.5) * push_constants.scale.y; - // Julia is like mandelbrot, but instead changing the constant `c` will change the shape - // you'll see. Thus we want to bind the c to mouse position. - // With mandelbrot, c = scaled xy position of the image. Z starts from zero. - // With julia, c = any value between the interesting range (-2.0 - 2.0), Z = scaled xy position of the image. - vec2 c; - vec2 z; - if (push_constants.is_julia) { - c = push_constants.c; - z = vec2(x0, y0); - } else { - c = vec2(x0, y0); - z = vec2(0.0, 0.0); - } + // Julia is like mandelbrot, but instead changing the constant `c` will change the + // shape you'll see. Thus we want to bind the c to mouse position. + // With mandelbrot, c = scaled xy position of the image. Z starts from zero. + // With julia, c = any value between the interesting range (-2.0 - 2.0), + // Z = scaled xy position of the image. + vec2 c; + vec2 z; + if (push_constants.is_julia) { + c = push_constants.c; + z = vec2(x0, y0); + } else { + c = vec2(x0, y0); + z = vec2(0.0, 0.0); + } - // Escape time algorithm: - // https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set - // It's an iterative algorithm where the bailout point (number of iterations) will determine - // the color we choose from the palette - int i; - float len_z; - for (i = 0; i < push_constants.max_iters; i += 1) { - z = vec2( - z.x * z.x - z.y * z.y + c.x, - z.y * z.x + z.x * z.y + c.y - ); + // Escape time algorithm: + // https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set + // It's an iterative algorithm where the bailout point (number of iterations) will + // determine the color we choose from the palette. + int i; + float len_z; + for (i = 0; i < push_constants.max_iters; i += 1) { + z = vec2( + z.x * z.x - z.y * z.y + c.x, + z.y * z.x + z.x * z.y + c.y + ); - len_z = length(z); - // Using 8.0 for bailout limit give a little nicer colors with smooth colors - // 2.0 is enough to 'determine' an escape will happen - if (len_z > 8.0) { - break; - } - } + len_z = length(z); + // Using 8.0 for bailout limit give a little nicer colors with smooth colors + // 2.0 is enough to 'determine' an escape will happen. + if (len_z > 8.0) { + break; + } + } - vec4 write_color = get_color( - push_constants.palette_size, - push_constants.end_color, - i, - push_constants.max_iters, - len_z - ); - imageStore(img, ivec2(gl_GlobalInvocationID.xy), write_color); -}", + vec4 write_color = get_color( + push_constants.palette_size, + push_constants.end_color, + i, + push_constants.max_iters, + len_z + ); + imageStore(img, ivec2(gl_GlobalInvocationID.xy), write_color); + } + ", } } diff --git a/examples/src/bin/interactive_fractal/main.rs b/examples/src/bin/interactive_fractal/main.rs index f04230f5..a74e4a8f 100644 --- a/examples/src/bin/interactive_fractal/main.rs +++ b/examples/src/bin/interactive_fractal/main.rs @@ -7,13 +7,25 @@ // notice may not be copied, modified, or distributed except // according to those terms. +// This is an example demonstrating an application with some more non-trivial functionality. +// It should get you more up to speed with how you can use Vulkano. +// +// It contains: +// +// - A compute pipeline to calculate Mandelbrot and Julia fractals writing them to an image. +// - A graphics pipeline to draw the fractal image over a quad that covers the whole screen. +// - A renderpass rendering that image on the swapchain image. +// - An organized renderer with functionality good enough to copy to other projects. +// - A simple `FractalApp` to handle runtime state. +// - A simple `InputState` to interact with the application. + use crate::app::FractalApp; -use vulkano::image::ImageUsage; -use vulkano::swapchain::PresentMode; -use vulkano::sync::GpuFuture; -use vulkano_util::context::{VulkanoConfig, VulkanoContext}; -use vulkano_util::renderer::{VulkanoWindowRenderer, DEFAULT_IMAGE_FORMAT}; -use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; +use vulkano::{image::ImageUsage, swapchain::PresentMode, sync::GpuFuture}; +use vulkano_util::{ + context::{VulkanoConfig, VulkanoContext}, + renderer::{VulkanoWindowRenderer, DEFAULT_IMAGE_FORMAT}, + window::{VulkanoWindows, WindowDescriptor}, +}; use winit::{ event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -25,17 +37,8 @@ mod fractal_compute_pipeline; mod pixels_draw_pipeline; mod place_over_frame; -/// This is an example demonstrating an application with some more non-trivial functionality. -/// It should get you more up to speed with how you can use Vulkano. -/// It contains -/// - Compute pipeline to calculate Mandelbrot and Julia fractals writing them to an image target -/// - Graphics pipeline to draw the fractal image over a quad that covers the whole screen -/// - Renderpass rendering that image over swapchain image -/// - An organized Renderer with functionality good enough to copy to other projects -/// - Simple FractalApp to handle runtime state -/// - Simple Input system to interact with the application fn main() { - // Create event loop + // Create the event loop. let mut event_loop = EventLoop::new(); let context = VulkanoContext::new(VulkanoConfig::default()); let mut windows = VulkanoWindows::default(); @@ -53,6 +56,7 @@ fn main() { // Add our render target image onto which we'll be rendering our fractals. let render_target_id = 0; let primary_window_renderer = windows.get_primary_renderer_mut().unwrap(); + // Make sure the image usage is correct (based on your pipeline). primary_window_renderer.add_additional_image_view( render_target_id, @@ -60,16 +64,18 @@ fn main() { ImageUsage::SAMPLED | ImageUsage::STORAGE | ImageUsage::TRANSFER_DST, ); - // Create app to hold the logic of our fractal explorer + // Create app to hold the logic of our fractal explorer. let gfx_queue = context.graphics_queue(); - // We intend to eventually render on our swapchain, thus we use that format when creating the app here. + + // We intend to eventually render on our swapchain, thus we use that format when creating the + // app here. let mut app = FractalApp::new( gfx_queue.clone(), primary_window_renderer.swapchain_format(), ); app.print_guide(); - // Basic loop for our runtime + // Basic loop for our runtime: // 1. Handle events // 2. Update state based on events // 3. Compute & Render @@ -82,7 +88,7 @@ fn main() { match primary_window_renderer.window_size() { [w, h] => { - // Skip this frame when minimized + // Skip this frame when minimized. if w == 0.0 || h == 0.0 { continue; } @@ -103,15 +109,17 @@ fn main() { } } -/// Handle events and return `bool` if we should quit +/// Handles events and returns a `bool` indicating if we should quit. fn handle_events( event_loop: &mut EventLoop<()>, renderer: &mut VulkanoWindowRenderer, app: &mut FractalApp, ) -> bool { let mut is_running = true; + event_loop.run_return(|event, _, control_flow| { *control_flow = ControlFlow::Wait; + match &event { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => is_running = false, @@ -123,19 +131,21 @@ fn handle_events( Event::MainEventsCleared => *control_flow = ControlFlow::Exit, _ => (), } - // Pass event for app to handle our inputs + + // Pass event for the app to handle our inputs. app.handle_input(renderer.window_size(), &event); }); + is_running && app.is_running() } -/// Orchestrate rendering here +/// Orchestrates rendering. fn compute_then_render( renderer: &mut VulkanoWindowRenderer, app: &mut FractalApp, target_image_id: usize, ) { - // Start frame + // Start the frame. let before_pipeline_future = match renderer.acquire() { Err(e) => { println!("{e}"); @@ -143,15 +153,19 @@ fn compute_then_render( } Ok(future) => future, }; - // Retrieve target image + + // Retrieve the target image. let image = renderer.get_additional_image_view(target_image_id); + // Compute our fractal (writes to target image). Join future with `before_pipeline_future`. let after_compute = app.compute(image.clone()).join(before_pipeline_future); - // Render image over frame. Input previous future. Draw on swapchain image + + // Render the image over the swapchain image, inputting the previous future. let after_renderpass_future = app.place_over_frame .render(after_compute, image, renderer.swapchain_image_view()); - // Finish frame (which presents the view). Input last future. Wait for the future so resources are not in use - // when we render + + // Finish the frame (which presents the view), inputting the last future. Wait for the future + // so resources are not in use when we render. renderer.present(after_renderpass_future, true); } diff --git a/examples/src/bin/interactive_fractal/pixels_draw_pipeline.rs b/examples/src/bin/interactive_fractal/pixels_draw_pipeline.rs index 22083c9e..a3ab2e54 100644 --- a/examples/src/bin/interactive_fractal/pixels_draw_pipeline.rs +++ b/examples/src/bin/interactive_fractal/pixels_draw_pipeline.rs @@ -7,10 +7,9 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use bytemuck::{Pod, Zeroable}; use std::sync::Arc; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage, Subbuffer}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage, Subbuffer}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferInheritanceInfo, CommandBufferUsage, SecondaryAutoCommandBuffer, @@ -33,9 +32,9 @@ use vulkano::{ sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerMipmapMode}, }; -/// Vertex for textured quads +/// Vertex for textured quads. +#[derive(BufferContents, Vertex)] #[repr(C)] -#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] pub struct TexturedVertex { #[format(R32G32_SFLOAT)] pub position: [f32; 2], @@ -67,7 +66,7 @@ pub fn textured_quad(width: f32, height: f32) -> (Vec, Vec) ) } -/// A subpass pipeline that fills a quad over frame +/// A subpass pipeline that fills a quad over frame. pub struct PixelsDrawPipeline { gfx_queue: Arc, subpass: Subpass, @@ -160,7 +159,7 @@ impl PixelsDrawPipeline { .unwrap() } - /// Draw input `image` over a quad of size -1.0 to 1.0 + /// Draws input `image` over a quad of size -1.0 to 1.0. pub fn draw( &self, viewport_dimensions: [u32; 2], @@ -204,35 +203,35 @@ impl PixelsDrawPipeline { mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " -#version 450 -layout(location=0) in vec2 position; -layout(location=1) in vec2 tex_coords; + src: r" + #version 450 + layout(location=0) in vec2 position; + layout(location=1) in vec2 tex_coords; -layout(location = 0) out vec2 f_tex_coords; + layout(location = 0) out vec2 f_tex_coords; -void main() { - gl_Position = vec4(position, 0.0, 1.0); - f_tex_coords = tex_coords; -} - " + void main() { + gl_Position = vec4(position, 0.0, 1.0); + f_tex_coords = tex_coords; + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " -#version 450 -layout(location = 0) in vec2 v_tex_coords; + src: r" + #version 450 + layout(location = 0) in vec2 v_tex_coords; -layout(location = 0) out vec4 f_color; + layout(location = 0) out vec4 f_color; -layout(set = 0, binding = 0) uniform sampler2D tex; + layout(set = 0, binding = 0) uniform sampler2D tex; -void main() { - f_color = texture(tex, v_tex_coords); -} -" + void main() { + f_color = texture(tex, v_tex_coords); + } + ", } } diff --git a/examples/src/bin/interactive_fractal/place_over_frame.rs b/examples/src/bin/interactive_fractal/place_over_frame.rs index 2b967682..94fed7c6 100644 --- a/examples/src/bin/interactive_fractal/place_over_frame.rs +++ b/examples/src/bin/interactive_fractal/place_over_frame.rs @@ -24,7 +24,7 @@ use vulkano::{ }; use vulkano_util::renderer::{DeviceImageView, SwapchainImageView}; -/// A render pass which places an incoming image over frame filling it +/// A render pass which places an incoming image over frame filling it. pub struct RenderPassPlaceOverFrame { gfx_queue: Arc, render_pass: Arc, @@ -72,8 +72,8 @@ impl RenderPassPlaceOverFrame { } } - /// Place view exactly over swapchain image target. - /// Texture draw pipeline uses a quad onto which it places the view. + /// Places the view exactly over the target swapchain image. The texture draw pipeline uses a + /// quad onto which it places the view. pub fn render( &self, before_future: F, @@ -83,9 +83,10 @@ impl RenderPassPlaceOverFrame { where F: GpuFuture + 'static, { - // Get dimensions + // Get dimensions. let img_dims = target.image().dimensions(); - // Create framebuffer (must be in same order as render pass description in `new` + + // Create framebuffer (must be in same order as render pass description in `new`. let framebuffer = Framebuffer::new( self.render_pass.clone(), FramebufferCreateInfo { @@ -94,14 +95,16 @@ impl RenderPassPlaceOverFrame { }, ) .unwrap(); - // Create primary command buffer builder + + // Create primary command buffer builder. let mut command_buffer_builder = AutoCommandBufferBuilder::primary( &self.command_buffer_allocator, self.gfx_queue.queue_family_index(), CommandBufferUsage::OneTimeSubmit, ) .unwrap(); - // Begin render pass + + // Begin render pass. command_buffer_builder .begin_render_pass( RenderPassBeginInfo { @@ -111,17 +114,22 @@ impl RenderPassPlaceOverFrame { SubpassContents::SecondaryCommandBuffers, ) .unwrap(); - // Create secondary command buffer from texture pipeline & send draw commands + + // Create secondary command buffer from texture pipeline & send draw commands. let cb = self .pixels_draw_pipeline .draw(img_dims.width_height(), view); - // Execute above commands (subpass) + + // Execute above commands (subpass). command_buffer_builder.execute_commands(cb).unwrap(); - // End render pass + + // End render pass. command_buffer_builder.end_render_pass().unwrap(); - // Build command buffer + + // Build command buffer. let command_buffer = command_buffer_builder.build().unwrap(); - // Execute primary command buffer + + // Execute primary command buffer. let after_future = before_future .then_execute(self.gfx_queue.clone(), command_buffer) .unwrap(); diff --git a/examples/src/bin/msaa-renderpass.rs b/examples/src/bin/msaa-renderpass.rs index deef7024..978306a4 100644 --- a/examples/src/bin/msaa-renderpass.rs +++ b/examples/src/bin/msaa-renderpass.rs @@ -7,67 +7,64 @@ // notice may not be copied, modified, or distributed except // according to those terms. -//! Multisampling anti-aliasing example, using a render pass resolve. -//! -//! # Introduction to multisampling -//! -//! When you draw an object on an image, this object occupies a certain set of pixels. Each pixel -//! of the image is either fully covered by the object, or not covered at all. There is no such -//! thing as a pixel that is half-covered by the object that you're drawing. What this means is -//! that you will sometimes see a "staircase effect" at the border of your object, also called -//! aliasing. -//! -//! The root cause of aliasing is that the resolution of the image is not high enough. If you -//! increase the size of the image you're drawing to, this effect will still exist but will be -//! much less visible. -//! -//! In order to decrease aliasing, some games and programs use what we call "Super-Sampling Anti -//! Aliasing" (SSAA). For example instead of drawing to an image of size 1024x1024, you draw to an -//! image of size 4096x4096. Then at the end, you scale down your image to 1024x1024 by merging -//! nearby pixels. Since the intermediate image is 4 times larger than the destination, this would -//! be x4 SSAA. -//! -//! However this technique is very expensive in terms of GPU power. The fragment shader and all -//! its calculations has to run four times more often. -//! -//! So instead of SSAA, a common alternative is MSAA (MultiSampling Anti Aliasing). The base -//! principle is more or less the same: you draw to an image of a larger dimension, and then at -//! the end you scale it down to the final size. The difference is that the fragment shader is -//! only run once per pixel of the final size, and its value is duplicated to fill to all the -//! pixels of the intermediate image that are covered by the object. -//! -//! For example, let's say that you use x4 MSAA, you draw to an intermediate image of size -//! 4096x4096, and your object covers the whole image. With MSAA, the fragment shader will only -//! be 1,048,576 times (1024 * 1024), compared to 16,777,216 times (4096 * 4096) with 4x SSAA. -//! Then the output of each fragment shader invocation is copied in each of the four pixels of the -//! intermediate image that correspond to each pixel of the final image. -//! -//! Now, let's say that your object doesn't cover the whole image. In this situation, only the -//! pixels of the intermediate image that are covered by the object will receive the output of the -//! fragment shader. -//! -//! Because of the way it works, this technique requires direct support from the hardware, -//! contrary to SSAA which can be done on any machine. -//! -//! # Multisampled images -//! -//! Using MSAA with Vulkan is done by creating a regular image, but with a number of samples per -//! pixel different from 1. For example if you want to use 4x MSAA, you should create an image with -//! 4 samples per pixel. Internally this image will have 4 times as many pixels as its dimensions -//! would normally require, but this is handled transparently for you. Drawing to a multisampled -//! image is exactly the same as drawing to a regular image. -//! -//! However multisampled images have some restrictions, for example you can't show them on the -//! screen (swapchain images are always single-sampled), and you can't copy them into a buffer. -//! Therefore when you have finished drawing, you have to blit your multisampled image to a -//! non-multisampled image. This operation is not a regular blit (blitting a multisampled image is -//! an error), instead it is called *resolving* the image. -//! +// Multisampling anti-aliasing example, using a render pass resolve. +// +// # Introduction to multisampling +// +// When you draw an object on an image, this object occupies a certain set of pixels. Each pixel of +// the image is either fully covered by the object, or not covered at all. There is no such thing +// as a pixel that is half-covered by the object that you're drawing. What this means is that you +// will sometimes see a "staircase effect" at the border of your object, also called aliasing. +// +// The root cause of aliasing is that the resolution of the image is not high enough. If you +// increase the size of the image you're drawing to, this effect will still exist but will be much +// less visible. +// +// In order to decrease aliasing, some games and programs use what we call "SuperSample Anti- +// Aliasing" (SSAA). For example instead of drawing to an image of size 1024x1024, you draw to an +// image of size 2048x2048. Then at the end, you scale down your image to 1024x1024 by merging +// nearby pixels. Since the intermediate image is 4 times larger than the destination, this would +// be 4x SSAA. +// +// However this technique is very expensive in terms of GPU power. The fragment shader and all its +// calculations has to run four times more often. +// +// So instead of SSAA, a common alternative is MSAA (MultiSample Anti-Aliasing). The base principle +// is more or less the same: you draw to an image of a larger dimension, and then at the end you +// scale it down to the final size. The difference is that the fragment shader is only run once per +// pixel of the final size, and its value is duplicated to fill to all the pixels of the +// intermediate image that are covered by the object. +// +// For example, let's say that you use 4x MSAA, you draw to an intermediate image of size +// 2048x2048, and your object covers the whole image. With MSAA, the fragment shader will only be +// run 1,048,576 times (1024 * 1024), compared to 4,194,304 times (2048 * 2048) with 4x SSAA. Then +// the output of each fragment shader invocation is copied in each of the four pixels of the +// intermediate image that correspond to each pixel of the final image. +// +// Now, let's say that your object doesn't cover the whole image. In this situation, only the +// pixels of the intermediate image that are covered by the object will receive the output of the +// fragment shader. +// +// Because of the way it works, this technique requires direct support from the hardware, contrary +// to SSAA which can be done on any machine. +// +// # Multisampled images +// +// Using MSAA with Vulkan is done by creating a regular image, but with a number of samples per +// pixel different from 1. For example if you want to use 4x MSAA, you should create an image with +// 4 samples per pixel. Internally this image will have 4 times as many pixels as its dimensions +// would normally require, but this is handled transparently for you. Drawing to a multisampled +// image is exactly the same as drawing to a regular image. +// +// However multisampled images have some restrictions, for example you can't show them on the +// screen (swapchain images are always single-sampled), and you can't copy them into a buffer. +// Therefore when you have finished drawing, you have to blit your multisampled image to a +// non-multisampled image. This operation is not a regular blit (blitting a multisampled image is +// an error), instead it is called *resolving* the image. -use bytemuck::{Pod, Zeroable}; use std::{fs::File, io::BufWriter, path::Path}; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, CopyImageToBufferInfo, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents, @@ -101,7 +98,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -135,7 +131,7 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); let (device, mut queues) = Device::new( @@ -195,14 +191,16 @@ fn main() { load: Clear, store: DontCare, format: Format::R8G8B8A8_UNORM, - samples: 4, // This has to match the image definition. + // This has to match the image definition. + samples: 4, }, // The second framebuffer attachment is the final image. color: { load: DontCare, store: Store, format: Format::R8G8B8A8_UNORM, - samples: 1, // Same here, this has to match. + // Same here, this has to match. + samples: 1, } }, pass: { @@ -230,30 +228,30 @@ fn main() { .unwrap(); // Here is the "end" of the multisampling example, as starting from here everything is the same - // as in any other example. - // The pipeline, vertex buffer, and command buffer are created in exactly the same way as - // without multisampling. - // At the end of the example, we copy the content of `image` (ie. the final image) to a buffer, - // then read the content of that buffer and save it to a PNG file. + // as in any other example. The pipeline, vertex buffer, and command buffer are created in + // exactly the same way as without multisampling. At the end of the example, we copy the + // content of `image` (ie. the final image) to a buffer, then read the content of that buffer + // and save it to a PNG file. mod vs { vulkano_shaders::shader! { - ty: "vertex", - src: " + ty: "vertex", + src: r" #version 450 layout(location = 0) in vec2 position; void main() { gl_Position = vec4(position, 0.0, 1.0); - }" - } + } + ", + } } mod fs { vulkano_shaders::shader! { - ty: "fragment", - src: " + ty: "fragment", + src: r" #version 450 layout(location = 0) out vec4 f_color; @@ -261,15 +259,15 @@ fn main() { void main() { f_color = vec4(1.0, 0.0, 0.0, 1.0); } - " + ", } } let vs = vs::load(device.clone()).unwrap(); let fs = fs::load(device.clone()).unwrap(); + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32_SFLOAT)] position: [f32; 2], diff --git a/examples/src/bin/multi-window.rs b/examples/src/bin/multi-window.rs index 981b10b5..e880e3dd 100644 --- a/examples/src/bin/multi-window.rs +++ b/examples/src/bin/multi-window.rs @@ -12,14 +12,13 @@ // This is the only example that is entirely detailed. All the other examples avoid code // duplication by using helper functions. // -// This example assumes that you are already more or less familiar with graphics programming -// and that you want to learn Vulkan. This means that for example it won't go into details about -// what a vertex or a shader is. +// This example assumes that you are already more or less familiar with graphics programming and +// that you want to learn Vulkan. This means that for example it won't go into details about what a +// vertex or a shader is. -use bytemuck::{Pod, Zeroable}; use std::{collections::HashMap, sync::Arc}; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SubpassContents, @@ -54,7 +53,7 @@ use winit::{ window::{Window, WindowBuilder}, }; -// A struct to contain resources related to a window +/// A struct to contain resources related to a window. struct WindowSurface { surface: Arc, swapchain: Arc, @@ -70,7 +69,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -78,18 +76,20 @@ fn main() { .unwrap(); let event_loop = EventLoop::new(); - // A hashmap that contains all of our created windows and their resources + // A hashmap that contains all of our created windows and their resources. let mut window_surfaces = HashMap::new(); let surface = WindowBuilder::new() .build_vk_surface(&event_loop, instance.clone()) .unwrap(); - // Use the window's id as a means to access it from the hashmap + + // Use the window's id as a means to access it from the hashmap. let window = surface.object().unwrap().downcast_ref::().unwrap(); let window_id = window.id(); // Find the device and a queue. - // TODO: it is assumed the device, queue, and surface surface_capabilities are the same for all windows + // TODO: it is assumed the device, queue, and surface surface_capabilities are the same for all + // windows. let (device, queue, surface_caps) = { let device_extensions = DeviceExtensions { @@ -123,7 +123,7 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); let (device, mut queues) = Device::new( @@ -147,8 +147,7 @@ fn main() { (device, queues.next().unwrap(), surface_capabilities) }; - // The swapchain and framebuffer images for this perticular window - + // The swapchain and framebuffer images for this particular window. let (swapchain, images) = { let image_format = Some( device @@ -180,8 +179,8 @@ fn main() { let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32_SFLOAT)] position: [f32; 2], @@ -211,7 +210,7 @@ fn main() { mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " + src: r" #version 450 layout(location = 0) in vec2 position; @@ -219,14 +218,14 @@ fn main() { void main() { gl_Position = vec4(position, 0.0, 1.0); } - " + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " + src: r" #version 450 layout(location = 0) out vec4 f_color; @@ -234,7 +233,7 @@ fn main() { void main() { f_color = vec4(1.0, 0.0, 0.0, 1.0); } - " + ", } } @@ -379,11 +378,11 @@ fn main() { } Event::RedrawRequested(window_id) => { let WindowSurface { - ref surface, - ref mut swapchain, - ref mut recreate_swapchain, - ref mut framebuffers, - ref mut previous_frame_end, + surface, + swapchain, + recreate_swapchain, + framebuffers, + previous_frame_end, } = window_surfaces.get_mut(&window_id).unwrap(); let window = surface.object().unwrap().downcast_ref::().unwrap(); @@ -401,7 +400,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; *swapchain = new_swapchain; @@ -417,7 +416,7 @@ fn main() { *recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -472,7 +471,7 @@ fn main() { *previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); *previous_frame_end = Some(sync::now(device.clone()).boxed()); } } diff --git a/examples/src/bin/multi_window_game_of_life/app.rs b/examples/src/bin/multi_window_game_of_life/app.rs index ac0624c7..454becbe 100644 --- a/examples/src/bin/multi_window_game_of_life/app.rs +++ b/examples/src/bin/multi_window_game_of_life/app.rs @@ -12,12 +12,15 @@ use crate::{ WINDOW2_HEIGHT, WINDOW2_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH, }; use std::{collections::HashMap, sync::Arc}; -use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; -use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; -use vulkano::memory::allocator::StandardMemoryAllocator; -use vulkano::{device::Queue, format::Format}; -use vulkano_util::context::{VulkanoConfig, VulkanoContext}; -use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; +use vulkano::{ + command_buffer::allocator::StandardCommandBufferAllocator, + descriptor_set::allocator::StandardDescriptorSetAllocator, device::Queue, format::Format, + memory::allocator::StandardMemoryAllocator, +}; +use vulkano_util::{ + context::{VulkanoConfig, VulkanoContext}, + window::{VulkanoWindows, WindowDescriptor}, +}; use winit::{event_loop::EventLoop, window::WindowId}; pub struct RenderPipeline { @@ -68,7 +71,7 @@ pub struct App { impl App { pub fn open(&mut self, event_loop: &EventLoop<()>) { - // Create windows & pipelines + // Create windows & pipelines. let id1 = self.windows.create_window( event_loop, &self.context, @@ -94,7 +97,7 @@ impl App { self.pipelines.insert( id1, RenderPipeline::new( - // Use same queue.. for synchronization + // Use same queue.. for synchronization. self.context.graphics_queue().clone(), self.context.graphics_queue().clone(), [ diff --git a/examples/src/bin/multi_window_game_of_life/game_of_life.rs b/examples/src/bin/multi_window_game_of_life/game_of_life.rs index c8fc4ac1..2e9f3239 100644 --- a/examples/src/bin/multi_window_game_of_life/game_of_life.rs +++ b/examples/src/bin/multi_window_game_of_life/game_of_life.rs @@ -10,28 +10,28 @@ use cgmath::Vector2; use rand::Rng; use std::sync::Arc; -use vulkano::buffer::{BufferAllocateInfo, Subbuffer}; -use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; -use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; -use vulkano::image::{ImageUsage, StorageImage}; -use vulkano::memory::allocator::MemoryAllocator; use vulkano::{ - buffer::{Buffer, BufferUsage}, - command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer}, - descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet}, + buffer::{Buffer, BufferAllocateInfo, BufferUsage, Subbuffer}, + command_buffer::{ + allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, + PrimaryAutoCommandBuffer, + }, + descriptor_set::{ + allocator::StandardDescriptorSetAllocator, PersistentDescriptorSet, WriteDescriptorSet, + }, device::Queue, format::Format, - image::ImageAccess, + image::{ImageAccess, ImageUsage, StorageImage}, + memory::allocator::MemoryAllocator, pipeline::{ComputePipeline, Pipeline, PipelineBindPoint}, sync::GpuFuture, }; use vulkano_util::renderer::DeviceImageView; -/// Pipeline holding double buffered grid & color image. -/// Grids are used to calculate the state, and color image is used to show the output. -/// Because each step we determine state in parallel, we need to write the output to -/// another grid. Otherwise the state would not be correctly determined as one thread might read -/// data that was just written by another thread +/// Pipeline holding double buffered grid & color image. Grids are used to calculate the state, and +/// color image is used to show the output. Because on each step we determine state in parallel, we +/// need to write the output to another grid. Otherwise the state would not be correctly determined +/// as one shader invocation might read data that was just written by another shader invocation. pub struct GameOfLifeComputePipeline { compute_queue: Arc, compute_life_pipeline: Arc, @@ -126,13 +126,15 @@ impl GameOfLifeComputePipeline { ) .unwrap(); - // Dispatch will mutate the builder adding commands which won't be sent before we build the command buffer - // after dispatches. This will minimize the commands we send to the GPU. For example, we could be doing - // tens of dispatches here depending on our needs. Maybe we wanted to simulate 10 steps at a time... + // Dispatch will mutate the builder adding commands which won't be sent before we build the + // command buffer after dispatches. This will minimize the commands we send to the GPU. For + // example, we could be doing tens of dispatches here depending on our needs. Maybe we + // wanted to simulate 10 steps at a time... - // First compute the next state + // First compute the next state. self.dispatch(&mut builder, life_color, dead_color, 0); - // Then color based on the next state + + // Then color based on the next state. self.dispatch(&mut builder, life_color, dead_color, 1); let command_buffer = builder.build().unwrap(); @@ -141,13 +143,13 @@ impl GameOfLifeComputePipeline { .unwrap(); let after_pipeline = finished.then_signal_fence_and_flush().unwrap().boxed(); - // Swap input and output so the output becomes the input for next frame + // Swap input and output so the output becomes the input for next frame. std::mem::swap(&mut self.life_in, &mut self.life_out); after_pipeline } - /// Build the command for a dispatch. + /// Builds the command for a dispatch. fn dispatch( &self, builder: &mut AutoCommandBufferBuilder< @@ -156,10 +158,10 @@ impl GameOfLifeComputePipeline { >, life_color: [f32; 4], dead_color: [f32; 4], - // Step determines whether we color or compute life (see branch in the shader)s + // Step determines whether we color or compute life (see branch in the shader)s. step: i32, ) { - // Resize image if needed + // Resize image if needed. let img_dims = self.image.image().dimensions().width_height(); let pipeline_layout = self.compute_life_pipeline.layout(); let desc_layout = pipeline_layout.set_layouts().get(0).unwrap(); @@ -174,7 +176,7 @@ impl GameOfLifeComputePipeline { ) .unwrap(); - let push_constants = compute_life_cs::ty::PushConstants { + let push_constants = compute_life_cs::PushConstants { life_color, dead_color, step, @@ -191,79 +193,81 @@ impl GameOfLifeComputePipeline { mod compute_life_cs { vulkano_shaders::shader! { ty: "compute", - src: " -#version 450 + src: r" + #version 450 -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; -layout(set = 0, binding = 0, rgba8) uniform writeonly image2D img; -layout(set = 0, binding = 1) buffer LifeInBuffer { uint life_in[]; }; -layout(set = 0, binding = 2) buffer LifeOutBuffer { uint life_out[]; }; + layout(set = 0, binding = 0, rgba8) uniform writeonly image2D img; + layout(set = 0, binding = 1) buffer LifeInBuffer { uint life_in[]; }; + layout(set = 0, binding = 2) buffer LifeOutBuffer { uint life_out[]; }; -layout(push_constant) uniform PushConstants { - vec4 life_color; - vec4 dead_color; - int step; -} push_constants; + layout(push_constant) uniform PushConstants { + vec4 life_color; + vec4 dead_color; + int step; + } push_constants; -int get_index(ivec2 pos) { - ivec2 dims = ivec2(imageSize(img)); - return pos.y * dims.x + pos.x; -} + int get_index(ivec2 pos) { + ivec2 dims = ivec2(imageSize(img)); + return pos.y * dims.x + pos.x; + } -// https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life -void compute_life() { - ivec2 pos = ivec2(gl_GlobalInvocationID.xy); - int index = get_index(pos); + // https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life + void compute_life() { + ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + int index = get_index(pos); - ivec2 up_left = pos + ivec2(-1, 1); - ivec2 up = pos + ivec2(0, 1); - ivec2 up_right = pos + ivec2(1, 1); - ivec2 right = pos + ivec2(1, 0); - ivec2 down_right = pos + ivec2(1, -1); - ivec2 down = pos + ivec2(0, -1); - ivec2 down_left = pos + ivec2(-1, -1); - ivec2 left = pos + ivec2(-1, 0); + ivec2 up_left = pos + ivec2(-1, 1); + ivec2 up = pos + ivec2(0, 1); + ivec2 up_right = pos + ivec2(1, 1); + ivec2 right = pos + ivec2(1, 0); + ivec2 down_right = pos + ivec2(1, -1); + ivec2 down = pos + ivec2(0, -1); + ivec2 down_left = pos + ivec2(-1, -1); + ivec2 left = pos + ivec2(-1, 0); - int alive_count = 0; - if (life_out[get_index(up_left)] == 1) { alive_count += 1; } - if (life_out[get_index(up)] == 1) { alive_count += 1; } - if (life_out[get_index(up_right)] == 1) { alive_count += 1; } - if (life_out[get_index(right)] == 1) { alive_count += 1; } - if (life_out[get_index(down_right)] == 1) { alive_count += 1; } - if (life_out[get_index(down)] == 1) { alive_count += 1; } - if (life_out[get_index(down_left)] == 1) { alive_count += 1; } - if (life_out[get_index(left)] == 1) { alive_count += 1; } + int alive_count = 0; + if (life_out[get_index(up_left)] == 1) { alive_count += 1; } + if (life_out[get_index(up)] == 1) { alive_count += 1; } + if (life_out[get_index(up_right)] == 1) { alive_count += 1; } + if (life_out[get_index(right)] == 1) { alive_count += 1; } + if (life_out[get_index(down_right)] == 1) { alive_count += 1; } + if (life_out[get_index(down)] == 1) { alive_count += 1; } + if (life_out[get_index(down_left)] == 1) { alive_count += 1; } + if (life_out[get_index(left)] == 1) { alive_count += 1; } - // Dead becomes alive - if (life_out[index] == 0 && alive_count == 3) { - life_out[index] = 1; - } // Becomes dead - else if (life_out[index] == 1 && alive_count < 2 || alive_count > 3) { - life_out[index] = 0; - } // Else Do nothing - else { + // Dead becomes alive. + if (life_out[index] == 0 && alive_count == 3) { + life_out[index] = 1; + } + // Becomes dead. + else if (life_out[index] == 1 && alive_count < 2 || alive_count > 3) { + life_out[index] = 0; + } + // Else do nothing. + else { + life_out[index] = life_in[index]; + } + } - life_out[index] = life_in[index]; - } -} - -void compute_color() { - ivec2 pos = ivec2(gl_GlobalInvocationID.xy); - int index = get_index(pos); - if (life_out[index] == 1) { - imageStore(img, pos, push_constants.life_color); - } else { - imageStore(img, pos, push_constants.dead_color); - } -} - -void main() { - if (push_constants.step == 0) { - compute_life(); - } else { - compute_color(); - } -}", + void compute_color() { + ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + int index = get_index(pos); + if (life_out[index] == 1) { + imageStore(img, pos, push_constants.life_color); + } else { + imageStore(img, pos, push_constants.dead_color); + } + } + + void main() { + if (push_constants.step == 0) { + compute_life(); + } else { + compute_color(); + } + } + ", } } diff --git a/examples/src/bin/multi_window_game_of_life/main.rs b/examples/src/bin/multi_window_game_of_life/main.rs index 03b9987f..3b90d206 100644 --- a/examples/src/bin/multi_window_game_of_life/main.rs +++ b/examples/src/bin/multi_window_game_of_life/main.rs @@ -7,6 +7,15 @@ // notice may not be copied, modified, or distributed except // according to those terms. +// A multi windowed game of life application. You could use this to learn: +// +// - how to handle multiple window inputs, +// - how to draw on a canvas, +// - how to organize compute shader with graphics, +// - how to do a cellular automata simulation using compute shaders. +// +// The possibilities are limitless. ;) + mod app; mod game_of_life; mod pixels_draw; @@ -23,13 +32,6 @@ use winit::{ platform::run_return::EventLoopExtRunReturn, }; -// A multi windowed game of life application. You could use this to learn: -// - how to handle multiple window inputs, -// - how to draw on a canvas -// - how to organize compute shader with graphics -// - how to do a cellular automata simulation using compute shaders -// The possibilities are limitless ;) - pub const WINDOW_WIDTH: f32 = 1024.0; pub const WINDOW_HEIGHT: f32 = 1024.0; pub const WINDOW2_WIDTH: f32 = 512.0; @@ -37,21 +39,25 @@ pub const WINDOW2_HEIGHT: f32 = 512.0; pub const SCALING: f32 = 2.0; fn main() { - println!("Welcome to Vulkano Game of Life\n Use Mouse to draw life on the grid(s)\n"); - // Create event loop + println!("Welcome to Vulkano Game of Life\nUse the mouse to draw life on the grid(s)\n"); + + // Create event loop. let mut event_loop = EventLoop::new(); - // Create app with vulkano context + + // Create app with vulkano context. let mut app = App::default(); app.open(&event_loop); // Time & inputs... let mut time = Instant::now(); let mut cursor_pos = Vector2::new(0.0, 0.0); - // An extremely crude way to handle input state... But works for this example. + + // An extremely crude way to handle input state... but works for this example. let mut mouse_is_pressed_w1 = false; let mut mouse_is_pressed_w2 = false; + loop { - // Event handling + // Event handling. if !handle_events( &mut event_loop, &mut app, @@ -61,14 +67,16 @@ fn main() { ) { break; } - // Draw life on windows if mouse is down + + // Draw life on windows if mouse is down. draw_life( &mut app, cursor_pos, mouse_is_pressed_w1, mouse_is_pressed_w2, ); - // Compute life & render 60fps + + // Compute life & render 60fps. if (Instant::now() - time).as_secs_f64() > 1.0 / 60.0 { compute_then_render_per_window(&mut app); time = Instant::now(); @@ -76,7 +84,7 @@ fn main() { } } -/// Handle events and return `bool` if we should quit +/// Handles events and returns a `bool` indicating if we should quit. fn handle_events( event_loop: &mut EventLoop<()>, app: &mut App, @@ -85,6 +93,7 @@ fn handle_events( mouse_pressed_w2: &mut bool, ) -> bool { let mut is_running = true; + event_loop.run_return(|event, _, control_flow| { *control_flow = ControlFlow::Poll; match &event { @@ -95,20 +104,21 @@ fn handle_events( if *window_id == app.windows.primary_window_id().unwrap() { is_running = false; } else { - // Destroy window by removing its renderer... + // Destroy window by removing its renderer. app.windows.remove_renderer(*window_id); app.pipelines.remove(window_id); } } - // Resize window and its images... + // Resize window and its images. WindowEvent::Resized(..) | WindowEvent::ScaleFactorChanged { .. } => { let vulkano_window = app.windows.get_renderer_mut(*window_id).unwrap(); vulkano_window.resize(); } + // Handle mouse position events. WindowEvent::CursorMoved { position, .. } => { *cursor_pos = Vector2::new(position.x as f32, position.y as f32) } - // Mouse button event + // Handle mouse button events. WindowEvent::MouseInput { state, button, .. } => { let mut mouse_pressed = false; if button == &MouseButton::Left && state == &ElementState::Pressed { @@ -129,6 +139,7 @@ fn handle_events( _ => (), } }); + is_running } @@ -146,13 +157,15 @@ fn draw_life( if id != &primary_window_id && !mouse_is_pressed_w2 { continue; } + let window_size = window.window_size(); let compute_pipeline = &mut app.pipelines.get_mut(id).unwrap().compute; let mut normalized_pos = Vector2::new( (cursor_pos.x / window_size[0]).clamp(0.0, 1.0), (cursor_pos.y / window_size[1]).clamp(0.0, 1.0), ); - // flip y + + // Flip y. normalized_pos.y = 1.0 - normalized_pos.y; let image_size = compute_pipeline .color_image() @@ -166,7 +179,7 @@ fn draw_life( } } -/// Compute and render per window +/// Compute and render per window. fn compute_then_render_per_window(app: &mut App) { let primary_window_id = app.windows.primary_window_id().unwrap(); for (window_id, window_renderer) in app.windows.iter_mut() { @@ -179,14 +192,14 @@ fn compute_then_render_per_window(app: &mut App) { } } -/// Compute game of life, then display result on target image +/// Compute game of life, then display result on target image. fn compute_then_render( window_renderer: &mut VulkanoWindowRenderer, pipeline: &mut RenderPipeline, life_color: [f32; 4], dead_color: [f32; 4], ) { - // Skip this window when minimized + // Skip this window when minimized. match window_renderer.window_size() { [w, h] => { if w == 0.0 || h == 0.0 { @@ -195,7 +208,7 @@ fn compute_then_render( } } - // Start frame + // Start the frame. let before_pipeline_future = match window_renderer.acquire() { Err(e) => { println!("{e}"); @@ -204,12 +217,12 @@ fn compute_then_render( Ok(future) => future, }; - // Compute + // Compute. let after_compute = pipeline .compute .compute(before_pipeline_future, life_color, dead_color); - // Render + // Render. let color_image = pipeline.compute.color_image(); let target_image = window_renderer.swapchain_image_view(); @@ -217,6 +230,6 @@ fn compute_then_render( .place_over_frame .render(after_compute, color_image, target_image); - // Finish frame. Wait for the future so resources are not in use when we render + // Finish the frame. Wait for the future so resources are not in use when we render. window_renderer.present(after_render, true); } diff --git a/examples/src/bin/multi_window_game_of_life/pixels_draw.rs b/examples/src/bin/multi_window_game_of_life/pixels_draw.rs index 246c7115..12e257dc 100644 --- a/examples/src/bin/multi_window_game_of_life/pixels_draw.rs +++ b/examples/src/bin/multi_window_game_of_life/pixels_draw.rs @@ -7,10 +7,9 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use bytemuck::{Pod, Zeroable}; use std::sync::Arc; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage, Subbuffer}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage, Subbuffer}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferInheritanceInfo, CommandBufferUsage, SecondaryAutoCommandBuffer, @@ -33,9 +32,9 @@ use vulkano::{ sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerMipmapMode}, }; -/// Vertex for textured quads +/// Vertex for textured quads. +#[derive(BufferContents, Vertex)] #[repr(C)] -#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] pub struct TexturedVertex { #[format(R32G32_SFLOAT)] pub position: [f32; 2], @@ -67,7 +66,7 @@ pub fn textured_quad(width: f32, height: f32) -> (Vec, Vec) ) } -/// A subpass pipeline that fills a quad over frame +/// A subpass pipeline that fills a quad over the frame. pub struct PixelsDrawPipeline { gfx_queue: Arc, subpass: Subpass, @@ -160,7 +159,7 @@ impl PixelsDrawPipeline { .unwrap() } - /// Draw input `image` over a quad of size -1.0 to 1.0 + /// Draws input `image` over a quad of size -1.0 to 1.0. pub fn draw( &self, viewport_dimensions: [u32; 2], @@ -204,35 +203,35 @@ impl PixelsDrawPipeline { mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " -#version 450 -layout(location=0) in vec2 position; -layout(location=1) in vec2 tex_coords; + src: r" + #version 450 + layout(location=0) in vec2 position; + layout(location=1) in vec2 tex_coords; -layout(location = 0) out vec2 f_tex_coords; + layout(location = 0) out vec2 f_tex_coords; -void main() { - gl_Position = vec4(position, 0.0, 1.0); - f_tex_coords = tex_coords; -} - " + void main() { + gl_Position = vec4(position, 0.0, 1.0); + f_tex_coords = tex_coords; + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " -#version 450 -layout(location = 0) in vec2 v_tex_coords; + src: r" + #version 450 + layout(location = 0) in vec2 v_tex_coords; -layout(location = 0) out vec4 f_color; + layout(location = 0) out vec4 f_color; -layout(set = 0, binding = 0) uniform sampler2D tex; + layout(set = 0, binding = 0) uniform sampler2D tex; -void main() { - f_color = texture(tex, v_tex_coords); -} -" + void main() { + f_color = texture(tex, v_tex_coords); + } + ", } } diff --git a/examples/src/bin/multi_window_game_of_life/render_pass.rs b/examples/src/bin/multi_window_game_of_life/render_pass.rs index b7724f66..cd14943c 100644 --- a/examples/src/bin/multi_window_game_of_life/render_pass.rs +++ b/examples/src/bin/multi_window_game_of_life/render_pass.rs @@ -24,7 +24,7 @@ use vulkano::{ }; use vulkano_util::renderer::{DeviceImageView, SwapchainImageView}; -/// A render pass which places an incoming image over frame filling it +/// A render pass which places an incoming image over the frame, filling it. pub struct RenderPassPlaceOverFrame { gfx_queue: Arc, render_pass: Arc, @@ -72,8 +72,8 @@ impl RenderPassPlaceOverFrame { } } - /// Place view exactly over swapchain image target. - /// Texture draw pipeline uses a quad onto which it places the view. + /// Places the view exactly over the target swapchain image. The texture draw pipeline uses a + /// quad onto which it places the view. pub fn render( &self, before_future: F, @@ -83,9 +83,10 @@ impl RenderPassPlaceOverFrame { where F: GpuFuture + 'static, { - // Get dimensions + // Get the dimensions. let img_dims = target.image().dimensions(); - // Create framebuffer (must be in same order as render pass description in `new` + + // Create the framebuffer. let framebuffer = Framebuffer::new( self.render_pass.clone(), FramebufferCreateInfo { @@ -94,14 +95,16 @@ impl RenderPassPlaceOverFrame { }, ) .unwrap(); - // Create primary command buffer builder + + // Create a primary command buffer builder. let mut command_buffer_builder = AutoCommandBufferBuilder::primary( &self.command_buffer_allocator, self.gfx_queue.queue_family_index(), CommandBufferUsage::OneTimeSubmit, ) .unwrap(); - // Begin render pass + + // Begin the render pass. command_buffer_builder .begin_render_pass( RenderPassBeginInfo { @@ -111,17 +114,22 @@ impl RenderPassPlaceOverFrame { SubpassContents::SecondaryCommandBuffers, ) .unwrap(); - // Create secondary command buffer from texture pipeline & send draw commands + + // Create a secondary command buffer from the texture pipeline & send draw commands. let cb = self .pixels_draw_pipeline .draw(img_dims.width_height(), view); - // Execute above commands (subpass) + + // Execute above commands (subpass). command_buffer_builder.execute_commands(cb).unwrap(); - // End render pass + + // End the render pass. command_buffer_builder.end_render_pass().unwrap(); - // Build command buffer + + // Build the command buffer. let command_buffer = command_buffer_builder.build().unwrap(); - // Execute primary command buffer + + // Execute primary command buffer. let after_future = before_future .then_execute(self.gfx_queue.clone(), command_buffer) .unwrap(); diff --git a/examples/src/bin/multiview.rs b/examples/src/bin/multiview.rs index 3df2a796..926c8b18 100644 --- a/examples/src/bin/multiview.rs +++ b/examples/src/bin/multiview.rs @@ -7,16 +7,14 @@ // notice may not be copied, modified, or distributed except // according to those terms. -//! This example demonstrates using the `VK_KHR_multiview` extension to render to multiple -//! layers of the framebuffer in one render pass. This can significantly improve performance -//! in cases where multiple perspectives or cameras are very similar like in virtual reality -//! or other types of stereoscopic rendering where the left and right eye only differ -//! in a small position offset. +// This example demonstrates using the `VK_KHR_multiview` extension to render to multiple layers of +// the framebuffer in one render pass. This can significantly improve performance in cases where +// multiple perspectives or cameras are very similar like in virtual reality or other types of +// stereoscopic rendering where the left and right eye only differ in a small position offset. -use bytemuck::{Pod, Zeroable}; use std::{fs::File, io::BufWriter, path::Path}; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage, Subbuffer}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage, Subbuffer}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, BufferImageCopy, CommandBufferUsage, CopyImageToBufferInfo, RenderPassBeginInfo, SubpassContents, @@ -54,10 +52,10 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: InstanceExtensions { - khr_get_physical_device_properties2: true, // required to get multiview limits + // Required to get multiview limits. + khr_get_physical_device_properties2: true, ..InstanceExtensions::empty() }, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -68,25 +66,23 @@ fn main() { ..DeviceExtensions::empty() }; let features = Features { - // enabling the `multiview` feature will use the `VK_KHR_multiview` extension on - // Vulkan 1.0 and the device feature on Vulkan 1.1+ + // enabling the `multiview` feature will use the `VK_KHR_multiview` extension on Vulkan 1.0 + // and the device feature on Vulkan 1.1+. multiview: true, ..Features::empty() }; - let (physical_device, queue_family_index) = instance.enumerate_physical_devices().unwrap() + let (physical_device, queue_family_index) = instance + .enumerate_physical_devices() + .unwrap() + .filter(|p| p.supported_extensions().contains(&device_extensions)) + .filter(|p| p.supported_features().contains(&features)) .filter(|p| { - p.supported_extensions().contains(&device_extensions) - }) - .filter(|p| { - p.supported_features().contains(&features) - }) - .filter(|p| { - // This example renders to two layers of the framebuffer using the multiview - // extension so we check that at least two views are supported by the device. - // Not checking this on a device that doesn't support two views - // will lead to a runtime error when creating the `RenderPass`. - // The `max_multiview_view_count` function will return `None` when the - // `VK_KHR_get_physical_device_properties2` instance extension has not been enabled. + // This example renders to two layers of the framebuffer using the multiview extension + // so we check that at least two views are supported by the device. Not checking this + // on a device that doesn't support two views will lead to a runtime error when + // creating the `RenderPass`. The `max_multiview_view_count` function will return + // `None` when the `VK_KHR_get_physical_device_properties2` instance extension has not + // been enabled. p.properties().max_multiview_view_count.unwrap_or(0) >= 2 }) .filter_map(|p| { @@ -103,14 +99,17 @@ fn main() { PhysicalDeviceType::Other => 4, _ => 5, }) - // A real application should probably fall back to rendering the framebuffer layers - // in multiple passes when multiview isn't supported. - .expect("No device supports two multiview views or the VK_KHR_get_physical_device_properties2 instance extension has not been loaded"); + // A real application should probably fall back to rendering the framebuffer layers in + // multiple passes when multiview isn't supported. + .expect( + "no device supports two multiview views or the \ + `VK_KHR_get_physical_device_properties2` instance extension has not been loaded", + ); println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); let (device, mut queues) = Device::new( @@ -147,8 +146,8 @@ fn main() { let image_view = ImageView::new_default(image.clone()).unwrap(); + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32_SFLOAT)] position: [f32; 2], @@ -175,40 +174,39 @@ fn main() { ) .unwrap(); - // Note the `#extension GL_EXT_multiview : enable` that enables the multiview extension - // for the shader and the use of `gl_ViewIndex` which contains a value based on which - // view the shader is being invoked for. - // In this example `gl_ViewIndex` is used toggle a hardcoded offset for vertex positions - // but in a VR application you could easily use it as an index to a uniform array - // that contains the transformation matrices for the left and right eye. + // Note the `#extension GL_EXT_multiview : enable` that enables the multiview extension for the + // shader and the use of `gl_ViewIndex` which contains a value based on which view the shader + // is being invoked for. In this example `gl_ViewIndex` is used to toggle a hardcoded offset + // for vertex positions but in a VR application you could easily use it as an index to a + // uniform array that contains the transformation matrices for the left and right eye. mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " - #version 450 + src: r" + #version 450 #extension GL_EXT_multiview : enable - layout(location = 0) in vec2 position; + layout(location = 0) in vec2 position; - void main() { + void main() { gl_Position = vec4(position, 0.0, 1.0) + gl_ViewIndex * vec4(0.25, 0.25, 0.0, 0.0); - } - " + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " - #version 450 + src: r" + #version 450 - layout(location = 0) out vec4 f_color; + layout(location = 0) out vec4 f_color; - void main() { - f_color = vec4(1.0, 0.0, 0.0, 1.0); - } - " + void main() { + f_color = vec4(1.0, 0.0, 0.0, 1.0); + } + ", } } @@ -228,8 +226,8 @@ fn main() { ..Default::default() }], subpasses: vec![SubpassDescription { - // the view mask indicates which layers of the framebuffer should be rendered for each - // subpass + // The view mask indicates which layers of the framebuffer should be rendered for each + // subpass. view_mask: 0b11, color_attachments: vec![Some(AttachmentReference { attachment: 0, @@ -238,8 +236,8 @@ fn main() { })], ..Default::default() }], - // the correlated view masks indicate sets of views that may be more efficient to render - // concurrently + // The correlated view masks indicate sets of views that may be more efficient to render + // concurrently. correlated_view_masks: vec![0b11], ..Default::default() }; @@ -299,8 +297,8 @@ fn main() { ) .unwrap(); - // drawing commands are broadcast to each view in the view mask of the active renderpass - // which means only a single draw call is needed to draw to multiple layers of the framebuffer + // Drawing commands are broadcast to each view in the view mask of the active renderpass which + // means only a single draw call is needed to draw to multiple layers of the framebuffer. builder .begin_render_pass( RenderPassBeginInfo { @@ -317,7 +315,7 @@ fn main() { .end_render_pass() .unwrap(); - // copy the image layers to different buffers to save them as individual images to disk + // Copy the image layers to different buffers to save them as individual images to disk. builder .copy_image_to_buffer(CopyImageToBufferInfo { regions: [BufferImageCopy { @@ -356,7 +354,7 @@ fn main() { future.wait(None).unwrap(); - // write each layer to its own file + // Write each layer to its own file. write_image_buffer_to_file( buffer1, "multiview1.png", diff --git a/examples/src/bin/occlusion-query.rs b/examples/src/bin/occlusion-query.rs index 7de41cc9..f8f29ca1 100644 --- a/examples/src/bin/occlusion-query.rs +++ b/examples/src/bin/occlusion-query.rs @@ -7,14 +7,13 @@ // notice may not be copied, modified, or distributed except // according to those terms. -// This is a modification of the triangle example, that demonstrates the basics of occlusion queries. -// Occlusion queries allow you to query whether, and sometimes how many, pixels pass the depth test -// in a range of draw calls. +// This is a modification of the triangle example, that demonstrates the basics of occlusion +// queries. Occlusion queries allow you to query whether, and sometimes how many, pixels pass the +// depth test in a range of draw calls. -use bytemuck::{Pod, Zeroable}; use std::sync::Arc; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SubpassContents, @@ -59,7 +58,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -154,8 +152,8 @@ fn main() { let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32B32_SFLOAT)] position: [f32; 3], @@ -177,10 +175,9 @@ fn main() { position: [0.25, -0.1, 0.5], color: [1.0, 0.0, 0.0], }, - // The second triangle (cyan) is the same shape and position as the first, - // but smaller, and moved behind a bit. - // It should be completely occluded by the first triangle. - // (You can lower its z value to put it in front) + // The second triangle (cyan) is the same shape and position as the first, but smaller, and + // moved behind a bit. It should be completely occluded by the first triangle. (You can + // lower its z value to put it in front.) Vertex { position: [-0.25, -0.125, 0.6], color: [0.0, 1.0, 1.0], @@ -193,9 +190,8 @@ fn main() { position: [0.125, -0.05, 0.6], color: [0.0, 1.0, 1.0], }, - // The third triangle (green) is the same shape and size as the first, - // but moved to the left and behind the second. - // It is partially occluded by the first two. + // The third triangle (green) is the same shape and size as the first, but moved to the + // left and behind the second. It is partially occluded by the first two. Vertex { position: [-0.25, -0.25, 0.7], color: [0.0, 1.0, 0.0], @@ -234,46 +230,45 @@ fn main() { ) .unwrap(); - // Create a buffer on the CPU to hold the results of the three queries. - // Query results are always represented as either `u32` or `u64`. - // For occlusion queries, you always need one element per query. You can ask for the number of - // elements needed at runtime by calling `QueryType::result_len`. - // If you retrieve query results with `with_availability` enabled, then this array needs to - // be 6 elements long instead of 3. + // Create a buffer on the CPU to hold the results of the three queries. Query results are + // always represented as either `u32` or `u64`. For occlusion queries, you always need one + // element per query. You can ask for the number of elements needed at runtime by calling + // `QueryType::result_len`. If you retrieve query results with `with_availability` enabled, + // then this array needs to be 6 elements long instead of 3. let mut query_results = [0u32; 3]; mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " - #version 450 + src: r" + #version 450 - layout(location = 0) in vec3 position; + layout(location = 0) in vec3 position; layout(location = 1) in vec3 color; layout(location = 0) out vec3 v_color; - void main() { + void main() { v_color = color; - gl_Position = vec4(position, 1.0); - } - " + gl_Position = vec4(position, 1.0); + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " - #version 450 + src: r" + #version 450 layout(location = 0) in vec3 v_color; - layout(location = 0) out vec4 f_color; + layout(location = 0) out vec4 f_color; - void main() { - f_color = vec4(v_color, 1.0); - } - " + void main() { + f_color = vec4(v_color, 1.0); + } + ", } } @@ -310,9 +305,9 @@ fn main() { .viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant()) .fragment_shader(fs.entry_point("main").unwrap(), ()) .render_pass(Subpass::from(render_pass.clone(), 0).unwrap()) - // Enable depth testing, which is needed for occlusion queries to make sense at all. - // If you disable depth testing, every pixel is considered to pass the depth test, so - // every query will return a nonzero result. + // Enable depth testing, which is needed for occlusion queries to make sense at all. If you + // disable depth testing, every pixel is considered to pass the depth test, so every query + // will return a nonzero result. .depth_stencil_state(DepthStencilState::simple_depth_test()) .build(device.clone()) .unwrap(); @@ -365,7 +360,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -385,7 +380,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -402,8 +397,8 @@ fn main() { // Beginning or resetting a query is unsafe for now. unsafe { builder - // A query must be reset before each use, including the first use. - // This must be done outside a render pass. + // A query must be reset before each use, including the first use. This must be + // done outside a render pass. .reset_query_pool(query_pool.clone(), 0..3) .unwrap() .set_viewport(0, [viewport.clone()]) @@ -418,14 +413,14 @@ fn main() { SubpassContents::Inline, ) .unwrap() - // Begin query 0, then draw the red triangle. - // Enabling the `QueryControlFlags::PRECISE` flag would give exact numeric - // results. This needs the `occlusion_query_precise` feature to be enabled on - // the device. + // Begin query 0, then draw the red triangle. Enabling the + // `QueryControlFlags::PRECISE` flag would give exact numeric results. This + // needs the `occlusion_query_precise` feature to be enabled on the device. .begin_query( query_pool.clone(), 0, - QueryControlFlags::empty(), // QueryControlFlags::PRECISE + QueryControlFlags::empty(), + // QueryControlFlags::PRECISE, ) .unwrap() .bind_vertex_buffers(0, triangle1.clone()) @@ -477,16 +472,15 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } - // Retrieve the query results. - // This copies the results to a variable on the CPU. You can also use the - // `copy_query_pool_results` function on a command buffer to write results to a - // Vulkano buffer. This could then be used to influence draw operations further down - // the line, either in the same frame or a future frame. + // Retrieve the query results. This copies the results to a variable on the CPU. You + // can also use the `copy_query_pool_results` function on a command buffer to write + // results to a Vulkano buffer. This could then be used to influence draw operations + // further down the line, either in the same frame or a future frame. #[rustfmt::skip] query_pool .queries_range(0..3) @@ -494,21 +488,21 @@ fn main() { .get_results( &mut query_results, // Block the function call until the results are available. - // Note: if not all the queries have actually been executed, then this - // will wait forever for something that never happens! + // NOTE: If not all the queries have actually been executed, then this will + // wait forever for something that never happens! QueryResultFlags::WAIT // Enable this flag to give partial results if available, instead of waiting // for the full results. // | QueryResultFlags::PARTIAL - // Blocking and waiting will ensure the results are always available after - // the function returns. + // Blocking and waiting will ensure the results are always available after the + // function returns. // // If you disable waiting, then this flag can be enabled to include the - // availability of each query's results. You need one extra element per - // query in your `query_results` buffer for this. This element will - // be filled with a zero/nonzero value indicating availability. + // availability of each query's results. You need one extra element per query + // in your `query_results` buffer for this. This element will be filled with a + // zero/nonzero value indicating availability. // | QueryResultFlags::WITH_AVAILABILITY ) .unwrap(); diff --git a/examples/src/bin/pipeline-caching.rs b/examples/src/bin/pipeline-caching.rs index b8c41346..f89b23de 100644 --- a/examples/src/bin/pipeline-caching.rs +++ b/examples/src/bin/pipeline-caching.rs @@ -9,23 +9,20 @@ // This example demonstrates how to use pipeline caching. // -// Using a PipelineCache can improve performance significantly, -// by checking if the requested pipeline exists in the cache and if so, -// return that pipeline directly or insert that new pipeline into the -// cache. +// Using a `PipelineCache` can improve performance significantly, by checking if the requested +// pipeline exists in the cache and if so, return that pipeline directly or insert that new +// pipeline into the cache. // -// You can retrieve the data in the cache as a `Vec` and -// save that to a binary file. Later you can load that file and build a -// PipelineCache with the given data. Be aware that the Vulkan -// implementation does not check if the data is valid and vulkano -// currently does not either. Invalid data can lead to driver crashes -// or worse. Using the same cache data with a different GPU probably -// won't work, a simple driver update can lead to invalid data as well. -// To check if your data is valid you can find inspiration here: -// https://zeux.io/2019/07/17/serializing-pipeline-cache/ +// You can retrieve the data in the cache as a `Vec` and save that to a binary file. Later you +// can load that file and build a PipelineCache with the given data. Be aware that the Vulkan +// implementation does not check if the data is valid and vulkano currently does not either. +// Invalid data can lead to driver crashes or worse. Using the same cache data with a different GPU +// probably won't work, a simple driver update can lead to invalid data as well. To check if your +// data is valid you can find inspiration here: +// https://zeux.io/2019/07/17/serializing-pipeline-cache/ // -// In the future, vulkano might implement those safety checks, but for -// now, you would have to do that yourself or trust the data and the user. +// In the future, vulkano might implement those safety checks, but for now, you would have to do +// that yourself or trust the data and the user. use std::{ fs::{remove_file, rename, File}, @@ -47,7 +44,6 @@ fn main() { let instance = Instance::new( library, InstanceCreateInfo { - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -82,7 +78,7 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); // Now initializing the device. @@ -102,33 +98,33 @@ fn main() { // We are creating an empty PipelineCache to start somewhere. let pipeline_cache = PipelineCache::empty(device.clone()).unwrap(); - // We need to create the compute pipeline that describes our operation. We are using the - // shader from the basic-compute-shader example. + // We need to create the compute pipeline that describes our operation. We are using the shader + // from the basic-compute-shader example. // // If you are familiar with graphics pipeline, the principle is the same except that compute // pipelines are much simpler to create. // - // Pass the PipelineCache as an optional parameter to the ComputePipeline constructor. - // For GraphicPipelines you can use the GraphicPipelineBuilder that has a method - // `build_with_cache(cache: Arc)` + // Pass the `PipelineCache` as an optional parameter to the `ComputePipeline` constructor. For + // `GraphicPipeline`s you can use the `GraphicPipelineBuilder` that has a method + // `build_with_cache(cache: Arc)`. let _pipeline = { mod cs { vulkano_shaders::shader! { ty: "compute", - src: " + src: r" #version 450 layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) buffer Data { uint data[]; - } data; + }; void main() { uint idx = gl_GlobalInvocationID.x; - data.data[idx] *= 12; + data[idx] *= 12; } - " + ", } } let shader = cs::load(device.clone()).unwrap(); @@ -142,13 +138,12 @@ fn main() { .unwrap() }; - // Normally you would use your pipeline for computing, but we just want to focus on the - // cache functionality. - // The cache works the same for a GraphicsPipeline, a ComputePipeline is just simpler to - // build. + // Normally you would use your pipeline for computing, but we just want to focus on the cache + // functionality. The cache works the same for a `GraphicsPipeline`, a `ComputePipeline` is + // just simpler to build. // - // We are now going to retrieve the cache data into a Vec and save that to a file on - // our disk. + // We are now going to retrieve the cache data into a Vec and save that to a file on our + // disk. if let Ok(data) = pipeline_cache.get_data() { if let Ok(mut file) = File::create("pipeline_cache.bin.tmp") { @@ -160,13 +155,13 @@ fn main() { } } - // The PipelineCache is now saved to disk and can be loaded the next time the application - // is started. This way, the pipelines do not have to be rebuild and pipelines that might - // exist in the cache can be build far quicker. + // The `PipelineCache` is now saved to disk and can be loaded the next time the application is + // started. This way, the pipelines do not have to be rebuild and pipelines that might exist in + // the cache can be build far quicker. // - // To load the cache from the file, we just need to load the data into a Vec and build - // the PipelineCache from that. Note that this function is currently unsafe as there are - // no checks, as it was mentioned at the start of this example. + // To load the cache from the file, we just need to load the data into a Vec and build the + // `PipelineCache` from that. Note that this function is currently unsafe as there are no + // checks, as it was mentioned at the start of this example. let data = { if let Ok(mut file) = File::open("pipeline_cache.bin") { let mut data = Vec::new(); @@ -187,14 +182,13 @@ fn main() { PipelineCache::empty(device).unwrap() }; - // As the PipelineCache of the Vulkan implementation saves an opaque blob of data, - // there is no real way to know if the data is correct. There might be differences - // in the byte blob here, but it should still work. - // If it doesn't, please check if there is an issue describing this problem, and if - // not open a new one, on the GitHub page. + // As the `PipelineCache` of the Vulkan implementation saves an opaque blob of data, there is + // no real way to know if the data is correct. There might be differences in the byte blob + // here, but it should still work. If it doesn't, please check if there is an issue describing + // this problem, and if not open a new one, on the GitHub page. assert_eq!( pipeline_cache.get_data().unwrap(), - second_cache.get_data().unwrap() + second_cache.get_data().unwrap(), ); println!("Success"); } diff --git a/examples/src/bin/push-constants.rs b/examples/src/bin/push-constants.rs index 46ee0ddf..bef8547e 100644 --- a/examples/src/bin/push-constants.rs +++ b/examples/src/bin/push-constants.rs @@ -7,10 +7,10 @@ // notice may not be copied, modified, or distributed except // according to those terms. -// Push constants are a small bank of values written directly to the command buffer -// and accessible in shaders. They allow the application to set values used in shaders -// without creating buffers or modifying and binding descriptor sets for each update. -// As a result, they are expected to outperform such memory-backed resource updates. +// Push constants are a small bank of values written directly to the command buffer and accessible +// in shaders. They allow the application to set values used in shaders without creating buffers or +// modifying and binding descriptor sets for each update. As a result, they are expected to +// outperform such memory-backed resource updates. use vulkano::{ buffer::{Buffer, BufferAllocateInfo, BufferUsage}, @@ -36,7 +36,6 @@ fn main() { let instance = Instance::new( library, InstanceCreateInfo { - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -70,7 +69,7 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); let (device, mut queues) = Device::new( @@ -90,26 +89,26 @@ fn main() { mod cs { vulkano_shaders::shader! { ty: "compute", - src: " + src: r" #version 450 layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(push_constant) uniform PushConstantData { - int multiple; - float addend; - bool enable; + int multiple; + float addend; + bool enable; } pc; layout(set = 0, binding = 0) buffer Data { uint data[]; - } data; + }; void main() { uint idx = gl_GlobalInvocationID.x; if (pc.enable) { - data.data[idx] *= pc.multiple; - data.data[idx] += uint(pc.addend); + data[idx] *= pc.multiple; + data[idx] += uint(pc.addend); } } ", @@ -152,18 +151,20 @@ fn main() { ) .unwrap(); - // The `vulkano_shaders::shaders!` macro generates a struct with the correct representation of the push constants struct specified in the shader. - // Here we create an instance of the generated struct. - let push_constants = cs::ty::PushConstantData { + // The `vulkano_shaders::shaders!` macro generates a struct with the correct representation of + // the push constants struct specified in the shader. Here we create an instance of the + // generated struct. + let push_constants = cs::PushConstantData { multiple: 1, addend: 1.0, enable: 1, }; - // For a compute pipeline, push constants are passed to the `dispatch` method. - // For a graphics pipeline, push constants are passed to the `draw` and `draw_indexed` methods. - // Note that there is no type safety for the push constants argument. - // So be careful to only pass an instance of the struct generated by the `vulkano_shaders::shaders!` macro. + // For a compute pipeline, push constants are passed to the `dispatch` method. For a graphics + // pipeline, push constants are passed to the `draw` and `draw_indexed` methods. + // + // Note that there is no type safety for the push constant data, so be careful to only pass an + // instance of the struct generated by the `vulkano_shaders::shaders!` macro. let mut builder = AutoCommandBufferBuilder::primary( &command_buffer_allocator, queue.queue_family_index(), diff --git a/examples/src/bin/push-descriptors/main.rs b/examples/src/bin/push-descriptors/main.rs index ebcdbd49..2e15a885 100644 --- a/examples/src/bin/push-descriptors/main.rs +++ b/examples/src/bin/push-descriptors/main.rs @@ -7,10 +7,9 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use bytemuck::{Pod, Zeroable}; use std::{io::Cursor, sync::Arc}; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents, @@ -59,7 +58,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -98,7 +96,7 @@ fn main() { PhysicalDeviceType::Other => 4, _ => 5, }) - .expect("No suitable physical device found"); + .expect("no suitable physical device found"); println!( "Using device: {} (type: {:?})", @@ -155,8 +153,8 @@ fn main() { let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32_SFLOAT)] position: [f32; 2], @@ -315,7 +313,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -331,7 +329,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -391,7 +389,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -400,7 +398,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], render_pass: Arc, @@ -428,32 +426,34 @@ fn window_size_dependent_setup( mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 position; -layout(location = 0) out vec2 tex_coords; + layout(location = 0) in vec2 position; + layout(location = 0) out vec2 tex_coords; -void main() { - gl_Position = vec4(position, 0.0, 1.0); - tex_coords = position + vec2(0.5); -}" + void main() { + gl_Position = vec4(position, 0.0, 1.0); + tex_coords = position + vec2(0.5); + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 tex_coords; -layout(location = 0) out vec4 f_color; + layout(location = 0) in vec2 tex_coords; + layout(location = 0) out vec4 f_color; -layout(set = 0, binding = 0) uniform sampler2D tex; + layout(set = 0, binding = 0) uniform sampler2D tex; -void main() { - f_color = texture(tex, tex_coords); -}" + void main() { + f_color = texture(tex, tex_coords); + } + ", } } diff --git a/examples/src/bin/runtime-shader/main.rs b/examples/src/bin/runtime-shader/main.rs index 1a4e4f26..993184e3 100644 --- a/examples/src/bin/runtime-shader/main.rs +++ b/examples/src/bin/runtime-shader/main.rs @@ -6,23 +6,24 @@ // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. -// -// This example demonstrates one way of preparing data structures and loading -// SPIRV shaders from external source (file system). -// -// Note that you will need to do all correctness checking by yourself. -// -// vert.glsl and frag.glsl must be built by yourself. -// One way of building them is to build Khronos' glslang and use -// glslangValidator tool: -// $ glslangValidator vert.glsl -V -S vert -o vert.spv -// $ glslangValidator frag.glsl -V -S frag -o frag.spv -// Vulkano uses glslangValidator to build your shaders internally. -use bytemuck::{Pod, Zeroable}; +// This example demonstrates one way of preparing data structures and loading SPIRV shaders from +// external source (file system). +// +// Note that you will need to do all correctness checking yourself. +// +// `vert.glsl` and `frag.glsl` must be built by you. One way of building them is to use `shaderc`: +// +// ```bash +// glslc -fshader-stage=vert vert.glsl -o vert.spv +// glslc -fshader-stage=frag frag.glsl -o frag.spv +// ``` +// +// Vulkano uses shaderc to build your shaders internally. + use std::{fs::File, io::Read, sync::Arc}; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SubpassContents, @@ -66,7 +67,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -109,7 +109,7 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); let (device, mut queues) = Device::new( @@ -177,10 +177,13 @@ fn main() { .unwrap(); let vs = { - let mut f = File::open("src/bin/runtime-shader/vert.spv") - .expect("Can't find file src/bin/runtime-shader/vert.spv This example needs to be run from the root of the example crate."); + let mut f = File::open("src/bin/runtime-shader/vert.spv").expect( + "can't find file `src/bin/runtime-shader/vert.spv`, this example needs to be run from \ + the root of the example crate", + ); let mut v = vec![]; f.read_to_end(&mut v).unwrap(); + // Create a ShaderModule on a device the same Shader::load does it. // NOTE: You will have to verify correctness of the data by yourself! unsafe { ShaderModule::from_bytes(device.clone(), &v) }.unwrap() @@ -188,9 +191,10 @@ fn main() { let fs = { let mut f = File::open("src/bin/runtime-shader/frag.spv") - .expect("Can't find file src/bin/runtime-shader/frag.spv"); + .expect("can't find file `src/bin/runtime-shader/frag.spv`"); let mut v = vec![]; f.read_to_end(&mut v).unwrap(); + unsafe { ShaderModule::from_bytes(device.clone(), &v) }.unwrap() }; @@ -213,8 +217,8 @@ fn main() { let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] pub struct Vertex { #[format(R32G32_SFLOAT)] pub position: [f32; 2], @@ -249,6 +253,7 @@ fn main() { // NOTE: We don't create any descriptor sets in this example, but you should // note that passing wrong types, providing sets at wrong indexes will cause // descriptor set builder to return Err! + // TODO: Outdated ^ let mut viewport = Viewport { origin: [0.0, 0.0], @@ -290,7 +295,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -306,7 +311,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -360,7 +365,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -369,7 +374,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], render_pass: Arc, diff --git a/examples/src/bin/runtime-shader/vert.spv b/examples/src/bin/runtime-shader/vert.spv index bd5217492c53f48c159b51e46409b5ecee6f542b..da3f88a6fa746a2409bfaa9b7567a7d81d555b63 100644 GIT binary patch delta 95 zcmaFEzJ-IEnMs+Qfq{{Mn}K&Ccd9!J0~dq4PrSRozq^lXd~!iSd~r!-PHKEkW?pK1 gN@h`Na!F=cDgy%x0|%12%)I2B(i9{G8}n r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -473,7 +471,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -533,7 +531,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -542,7 +540,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], render_pass: Arc, @@ -572,21 +570,22 @@ mod vs { ty: "vertex", vulkan_version: "1.2", spirv_version: "1.5", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 position; -layout(location = 1) in uint tex_i; -layout(location = 2) in vec2 coords; + layout(location = 0) in vec2 position; + layout(location = 1) in uint tex_i; + layout(location = 2) in vec2 coords; -layout(location = 0) out flat uint out_tex_i; -layout(location = 1) out vec2 out_coords; + layout(location = 0) out flat uint out_tex_i; + layout(location = 1) out vec2 out_coords; -void main() { - gl_Position = vec4(position, 0.0, 1.0); - out_tex_i = tex_i; - out_coords = coords; -}" + void main() { + gl_Position = vec4(position, 0.0, 1.0); + out_tex_i = tex_i; + out_coords = coords; + } + ", } } @@ -595,20 +594,21 @@ mod fs { ty: "fragment", vulkan_version: "1.2", spirv_version: "1.5", - src: " -#version 450 + src: r" + #version 450 -#extension GL_EXT_nonuniform_qualifier : enable + #extension GL_EXT_nonuniform_qualifier : enable -layout(location = 0) in flat uint tex_i; -layout(location = 1) in vec2 coords; + layout(location = 0) in flat uint tex_i; + layout(location = 1) in vec2 coords; -layout(location = 0) out vec4 f_color; + layout(location = 0) out vec4 f_color; -layout(set = 0, binding = 0) uniform sampler2D tex[]; + layout(set = 0, binding = 0) uniform sampler2D tex[]; -void main() { - f_color = texture(nonuniformEXT(tex[tex_i]), coords); -}" + void main() { + f_color = texture(nonuniformEXT(tex[tex_i]), coords); + } + ", } } diff --git a/examples/src/bin/self-copy-buffer.rs b/examples/src/bin/self-copy-buffer.rs index 06934e57..a81c4e03 100644 --- a/examples/src/bin/self-copy-buffer.rs +++ b/examples/src/bin/self-copy-buffer.rs @@ -8,7 +8,8 @@ // according to those terms. // This example is a copy of `basic-compute-shaders.rs`, but initalizes half of the input buffer -// and then we use `copy_buffer_dimensions` to copy the first half of the input buffer to the second half. +// and then we use `copy_buffer_dimensions` to copy the first half of the input buffer to the +// second half. use vulkano::{ buffer::{Buffer, BufferAllocateInfo, BufferUsage}, @@ -35,7 +36,6 @@ fn main() { let instance = Instance::new( library, InstanceCreateInfo { - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -69,7 +69,7 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); let (device, mut queues) = Device::new( @@ -91,23 +91,24 @@ fn main() { mod cs { vulkano_shaders::shader! { ty: "compute", - src: " + src: r" #version 450 layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) buffer Data { uint data[]; - } data; + }; void main() { uint idx = gl_GlobalInvocationID.x; - data.data[idx] *= 12; + data[idx] *= 12; } - " + ", } } let shader = cs::load(device.clone()).unwrap(); + ComputePipeline::new( device.clone(), shader.entry_point("main").unwrap(), @@ -123,21 +124,19 @@ fn main() { let command_buffer_allocator = StandardCommandBufferAllocator::new(device.clone(), Default::default()); - let data_buffer = { - // we intitialize half of the array and leave the other half to 0, we will use copy later to fill it - let data_iter = (0..65536u32).map(|n| if n < 65536 / 2 { n } else { 0 }); - Buffer::from_iter( - &memory_allocator, - BufferAllocateInfo { - buffer_usage: BufferUsage::STORAGE_BUFFER - | BufferUsage::TRANSFER_SRC - | BufferUsage::TRANSFER_DST, - ..Default::default() - }, - data_iter, - ) - .unwrap() - }; + let data_buffer = Buffer::from_iter( + &memory_allocator, + BufferAllocateInfo { + buffer_usage: BufferUsage::STORAGE_BUFFER + | BufferUsage::TRANSFER_SRC + | BufferUsage::TRANSFER_DST, + ..Default::default() + }, + // We intitialize half of the array and leave the other half at 0, we will use the copy + // command later to fill it. + (0..65536u32).map(|n| if n < 65536 / 2 { n } else { 0 }), + ) + .unwrap(); let layout = pipeline.layout().set_layouts().get(0).unwrap(); let set = PersistentDescriptorSet::new( @@ -154,7 +153,8 @@ fn main() { ) .unwrap(); builder - // copy from the first half to the second half (inside the same buffer) before we run the computation + // Copy from the first half to the second half (inside the same buffer) before we run the + // computation. .copy_buffer(CopyBufferInfoTyped { regions: [BufferCopy { src_offset: 0, @@ -187,9 +187,9 @@ fn main() { let data_buffer_content = data_buffer.read().unwrap(); - // here we have the same data in the two halfs of the buffer + // Here we have the same data in the two halfs of the buffer. for n in 0..65536 / 2 { - // the two halfs should have the same data + // The two halfs should have the same data. assert_eq!(data_buffer_content[n as usize], n * 12); assert_eq!(data_buffer_content[n as usize + 65536 / 2], n * 12); } diff --git a/examples/src/bin/shader-include/main.rs b/examples/src/bin/shader-include/main.rs index 6c825290..980c67b1 100644 --- a/examples/src/bin/shader-include/main.rs +++ b/examples/src/bin/shader-include/main.rs @@ -7,9 +7,9 @@ // notice may not be copied, modified, or distributed except // according to those terms. -// This example demonstrates how to use the standard and relative include directives within -// shader source code. The boilerplate is taken from the "basic-compute-shader.rs" example, where -// most of the boilerplate is explained. +// This example demonstrates how to use the standard and relative include directives within shader +// source code. The boilerplate is taken from the "basic-compute-shader.rs" example, where most of +// the boilerplate is explained. use vulkano::{ buffer::{Buffer, BufferAllocateInfo, BufferUsage}, @@ -35,7 +35,6 @@ fn main() { let instance = Instance::new( library, InstanceCreateInfo { - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -69,7 +68,7 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); let (device, mut queues) = Device::new( @@ -92,27 +91,28 @@ fn main() { ty: "compute", // We declare what directories to search for when using the `#include <...>` // syntax. Specified directories have descending priorities based on their order. - include: [ "src/bin/shader-include/standard-shaders" ], - src: " + include: ["src/bin/shader-include/standard-shaders"], + src: r#" #version 450 - // Substitutes this line with the contents of the file `common.glsl` found in one of the standard - // `include` directories specified above. - // Note, that relative inclusion (`#include \"...\"`), although it falls back to standard - // inclusion, should not be used for **embedded** shader source, as it may be misleading and/or - // confusing. + + // Substitutes this line with the contents of the file `common.glsl` found in + // one of the standard `include` directories specified above. + // Note that relative inclusion (`#include "..."`), although it falls back to + // standard inclusion, should not be used for **embedded** shader source, as it + // may be misleading and/or confusing. #include layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0) buffer Data { uint data[]; - } data; + }; void main() { uint idx = gl_GlobalInvocationID.x; - data.data[idx] = multiply_by_12(data.data[idx]); + data[idx] = multiply_by_12(data[idx]); } - " + "#, } } let shader = cs::load(device.clone()).unwrap(); diff --git a/examples/src/bin/shader-types-derive.rs b/examples/src/bin/shader-types-derive.rs index fcbff59c..ffc95a19 100644 --- a/examples/src/bin/shader-types-derive.rs +++ b/examples/src/bin/shader-types-derive.rs @@ -7,38 +7,26 @@ // notice may not be copied, modified, or distributed except // according to those terms. -// This example demonstrates how to put derives onto generated Rust structs from -// the Shader types through the "types-meta" options of -// `shader!` macro. +// This example demonstrates how to put derives onto Rust structs generated from the shader types +// through the `custom_derives` option of the `shader!` macro. -// Vulkano Shader macro is capable to generate Rust structs representing each -// type found in the shader source. These structs appear in the `ty` module -// generated in the same module where the macro was called. +// The `shader!` macro is capable of generating Rust structs representing each type found in the +// shader source. These structs are generated in the same module where the macro was called. // -// By default each type has only `Clone` and `Copy` implementations. For -// ergonomic purposes developer may want to implement more traits on top of each -// type. For example "standard" traits such as `Default` or `Debug`. +// By default each type only has `Clone` and `Copy` derives. For ergonomic purposes you may want to +// add more derives for each type. For example built-in derive macros such as `Default` or `Debug`. // -// One way to do so is implementing them manually, but it would be hard to do, -// and complicates code maintenances. -// -// Another way is to specify a macro option to automatically put derives and -// blanket impls onto each generated type by the Macro itself. -// -// The macro is capable to implement standard trait derives in smart way ignoring -// `_dummyX` fields whenever these fields make no sense. And in addition to that -// developer can also specify derives of traits from external modules/crates -// whenever such traits provide custom derive feature. +// The only way we can add derive macros to these generated types is if the `shader!` macro +// generates the derive attribute with the wanted derives, hence there's a macro option for it. -use ron::{ - from_str, - ser::{to_string_pretty, PrettyConfig}, -}; -use std::fmt::{Debug, Display, Error, Formatter}; +use ron::ser::PrettyConfig; +use serde::{Deserialize, Serialize}; +use std::fmt::{Debug, Display, Error as FmtError, Formatter}; +use vulkano::padded::Padded; vulkano_shaders::shader! { ty: "compute", - src: " + src: r" #version 450 struct Foo { @@ -61,62 +49,43 @@ vulkano_shaders::shader! { void main() {} ", - types_meta: { - use serde::{Deserialize, Serialize}; - - #[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize)] - - impl Eq - } + custom_derives: [Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize], } -// In the example above the macro generated `Clone`, `Copy`, `PartialEq`, -// `Debug` and `Default` implementations for each declared -// type(`PushConstantData`, `Foo` and `Bar`) in the shader, and applied -// `impl Eq` for each of them too. And it also applied derives of -// `Serialize` and `Deserialize` traits from Serde crate, but it didn't apply -// these things to `Bars` since the `Bars` type does not have size known in -// compile time. -// -// The macro also didn't generate `Display` implementation since we didn't -// specify it. As such we still can implement this trait manually for some -// selected types. +// In the example above the macro generates `Clone`, `Copy`, `Debug`, `Default`, `PartialEq`, +// derives for each declared type (`PushConstantData`, `Foo` and `Bar`) in the shader, and it also +// applies derives of the `Serialize` and `Deserialize` traits from serde. However, it doesn't +// apply any of these to `Bars` since that type does not a have size known at compile time. -impl Display for crate::ty::Foo { - fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), Error> { +// Some traits are not meant to be derived, such as `Display`, but we can still implement them +// manually. +impl Display for Foo { + fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), FmtError> { Debug::fmt(self, formatter) } } fn main() { - use crate::ty::*; - - // Prints "Foo { x: 0.0, z: [100.0, 200.0, 300.0] }" skipping "_dummyX" fields. + // Prints "Foo { x: 0.0, z: [100.0, 200.0, 300.0] }". println!( - "{}", + "{:?}", Foo { z: [100.0, 200.0, 300.0], - ..Default::default() - } + }, ); let mut bar = Bar { - y: [5.1, 6.2], - - // Fills all fields with zeroes including "_dummyX" fields, so we don't - // have to maintain them manually anymore. + // The `Padded` wrapper here is padding the following field, `foo`. + y: Padded([5.1, 6.2]), + // Fills all fields with zeroes. ..Default::default() }; - // The data inside "_dummyX" has no use, but we still can fill it with - // something different from zeroes. - bar._dummy0 = [5; 8]; - - // Objects are equal since "_dummyX" fields ignoring during comparison assert_eq!( Bar { - y: [5.1, 6.2], + // `Padded` implementes `From`, so you can construct it this way as well. + y: [5.1, 6.2].into(), ..Default::default() }, bar, @@ -124,16 +93,17 @@ fn main() { assert_ne!(Bar::default(), bar); - bar.foo.x = 125.0; + // `Padded` dereferences into the wrapped type, so we can easily access the underlying data. + *bar.foo.x = 125.0; - // Since we put `Serialize` and `Deserialize` traits to derives list we can - // serialize and deserialize Shader data + // Since we put `Serialize` and `Deserialize` traits to the derives list we can serialize and + // deserialize shader data. - let serialized = to_string_pretty(&bar, PrettyConfig::default()).unwrap(); + let serialized = ron::ser::to_string_pretty(&bar, PrettyConfig::default()).unwrap(); println!("Serialized Bar: {serialized}"); - let deserialized = from_str::(&serialized).unwrap(); + let deserialized = ron::from_str::(&serialized).unwrap(); - assert_eq!(deserialized.foo.x, 125.0); + assert_eq!(*deserialized.foo.x, 125.0); } diff --git a/examples/src/bin/shader-types-sharing.rs b/examples/src/bin/shader-types-sharing.rs index 8b0f9a35..6e23b50a 100644 --- a/examples/src/bin/shader-types-sharing.rs +++ b/examples/src/bin/shader-types-sharing.rs @@ -7,25 +7,23 @@ // notice may not be copied, modified, or distributed except // according to those terms. -// This example demonstrates how to compile several shaders together using vulkano-shaders macro, -// such that the macro generates unique Shader types per each compiled shader, but generates common -// shareable set of Rust structs representing corresponding structs in the source glsl code. +// This example demonstrates how to compile several shaders together using the `shader!` macro, +// such that the macro doesn't generate unique shader types for each compiled shader, but generates +// a common shareable set of Rust structs for the corresponding structs in the shaders. // -// Normally, each vulkano-shaders macro invocation among other things generates a `ty` submodule -// containing all Rust types per each "struct" declaration of glsl code. Using this submodule -// the user can organize type-safe interoperability between Rust code and the shader interface -// input/output data tied to these structs. However, if the user compiles several shaders in -// independent Rust modules, each of these modules would contain independent `ty` submodule with -// each own set of Rust types. So, even if both shaders contain the same(or partially intersecting) -// glsl structs they will be duplicated in each generated `ty` submodule and treated by Rust as -// independent types. As such it would be tricky to organize interoperability between shader -// interfaces in Rust. +// Normally, each `shader!` macro invocation among other things generates all Rust types for each +// `struct` declaration of the GLSL code. Using these the user can organize type-safe +// interoperability between Rust code and the shader input/output interface tied to these structs. +// However, if the user compiles several shaders in independent Rust modules, each of these modules +// would contain an independent set of Rust types. So, even if both shaders contain the same (or +// partially intersecting) GLSL structs they will be duplicated by each macro invocation and +// treated by Rust as independent types. As such it would be tricky to organize interoperability +// between shader interfaces in Rust. // // To solve this problem the user can use "shared" generation mode of the macro. In this mode the // user declares all shaders that possibly share common layout interfaces in a single macro -// invocation. The macro will check that there is no inconsistency between declared glsl structs -// with the same names, and it will put all generated Rust structs for all shaders in just a single -// `ty` submodule. +// invocation. The macro will check that there is no inconsistency between declared GLSL structs +// with the same names, and it will not generate duplicates. use std::sync::Arc; use vulkano::{ @@ -52,7 +50,6 @@ fn main() { let instance = Instance::new( library, InstanceCreateInfo { - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -109,91 +106,91 @@ fn main() { // their layout interfaces. // // First one is just multiplying each value from the input array of ints to provided - // value in push constants struct. And the second one in turn adds this value instead of - // multiplying. + // value in push constants struct. And the second one in turn adds this value instead + // of multiplying. // // However both shaders declare glsl struct `Parameters` for push constants in each // shader. Since each of the struct has exactly the same interface, they will be // treated by the macro as "shared". // - // Also, note that glsl code duplications between shader sources is not necessary too. - // In more complex system the user may want to declare independent glsl file with - // such types, and include it in each shader entry-point files using "#include" + // Also, note that GLSL code duplications between shader sources is not necessary too. + // In a more complex system the user may want to declare an independent GLSL file with + // such types, and include it in each shader entry-point file using the `#include` // directive. shaders: { - // Generate single unique `SpecializationConstants` struct for all shaders since - // their specialization interfaces are the same. This option is turned off - // by default and the macro by default producing unique - // structs(`MultSpecializationConstants`, `AddSpecializationConstants`) + // Generate single unique `SpecializationConstants` struct for all shaders, since + // their specialization interfaces are the same. This option is turned off by + // default and the macro by default produces unique structs + // (`MultSpecializationConstants` and `AddSpecializationConstants` in this case). shared_constants: true, mult: { ty: "compute", - src: " + src: r" #version 450 layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(constant_id = 0) const bool enabled = true; layout(push_constant) uniform Parameters { - int value; + int value; } pc; layout(set = 0, binding = 0) buffer Data { uint data[]; - } data; + }; void main() { if (!enabled) { return; } uint idx = gl_GlobalInvocationID.x; - data.data[idx] *= pc.value; + data[idx] *= pc.value; } - " + ", }, add: { ty: "compute", - src: " + src: r" #version 450 layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(constant_id = 0) const bool enabled = true; layout(push_constant) uniform Parameters { - int value; + int value; } pc; layout(set = 0, binding = 0) buffer Data { uint data[]; - } data; + }; void main() { if (!enabled) { return; } uint idx = gl_GlobalInvocationID.x; - data.data[idx] += pc.value; + data[idx] += pc.value; } - " - } + ", + }, }, } // The macro will create the following things in this module: - // - `ShaderMult` for the first shader loader/entry-point. - // - `ShaderAdd` for the second shader loader/entry-point. - // `SpecializationConstants` Rust struct for both shader's specialization constants. - // `ty` submodule with `Parameters` Rust struct common for both shaders. + // - `load_mult` for the first shader loader/entry-point. + // - `load_add` for the second shader loader/entry-point. + // - `SpecializationConstants` struct for both shaders' specialization constants. + // - `Parameters` struct common for both shaders. } - // We introducing generic function responsible for running any of the shaders above with - // provided Push Constants parameter. - // Note that shader's interface `parameter` here is shader-independent. + /// We are introducing a generic function responsible for running any of the shaders above with + /// the provided push constants parameter. Note that the shaders' interface `parameters` here + /// are shader-independent. fn run_shader( pipeline: Arc, queue: Arc, data_buffer: Subbuffer<[u32]>, - parameters: shaders::ty::Parameters, + parameters: shaders::Parameters, command_buffer_allocator: &StandardCommandBufferAllocator, descriptor_set_allocator: &StandardDescriptorSetAllocator, ) { @@ -238,21 +235,18 @@ fn main() { StandardCommandBufferAllocator::new(device.clone(), Default::default()); let descriptor_set_allocator = StandardDescriptorSetAllocator::new(device.clone()); - // Preparing test data array `[0, 1, 2, 3....]` - let data_buffer = { - let data_iter = 0..65536u32; - Buffer::from_iter( - &memory_allocator, - BufferAllocateInfo { - buffer_usage: BufferUsage::STORAGE_BUFFER, - ..Default::default() - }, - data_iter, - ) - .unwrap() - }; + // Prepare test array `[0, 1, 2, 3....]`. + let data_buffer = Buffer::from_iter( + &memory_allocator, + BufferAllocateInfo { + buffer_usage: BufferUsage::STORAGE_BUFFER, + ..Default::default() + }, + 0..65536u32, + ) + .unwrap(); - // Loading the first shader, and creating a Pipeline for the shader + // Load the first shader, and create a pipeline for the shader. let mult_pipeline = ComputePipeline::new( device.clone(), shaders::load_mult(device.clone()) @@ -265,7 +259,7 @@ fn main() { ) .unwrap(); - // Loading the second shader, and creating a Pipeline for the shader + // Load the second shader, and create a pipeline for the shader. let add_pipeline = ComputePipeline::new( device.clone(), shaders::load_add(device) @@ -278,32 +272,32 @@ fn main() { ) .unwrap(); - // Multiply each value by 2 + // Multiply each value by 2. run_shader( mult_pipeline.clone(), queue.clone(), data_buffer.clone(), - shaders::ty::Parameters { value: 2 }, + shaders::Parameters { value: 2 }, &command_buffer_allocator, &descriptor_set_allocator, ); - // Then add 1 to each value + // Then add 1 to each value. run_shader( add_pipeline, queue.clone(), data_buffer.clone(), - shaders::ty::Parameters { value: 1 }, + shaders::Parameters { value: 1 }, &command_buffer_allocator, &descriptor_set_allocator, ); - // Then multiply each value by 3 + // Then multiply each value by 3. run_shader( mult_pipeline, queue, data_buffer.clone(), - shaders::ty::Parameters { value: 3 }, + shaders::Parameters { value: 3 }, &command_buffer_allocator, &descriptor_set_allocator, ); diff --git a/examples/src/bin/simple-particles.rs b/examples/src/bin/simple-particles.rs index 2eff2388..c3aa4a2b 100644 --- a/examples/src/bin/simple-particles.rs +++ b/examples/src/bin/simple-particles.rs @@ -7,15 +7,14 @@ // notice may not be copied, modified, or distributed except // according to those terms. -//! A minimal particle-sandbox to demonstrate a reasonable use-case for a device-local buffer. -//! We gain significant runtime performance by writing the inital vertex values to the GPU using -//! a staging buffer and then copying the data to a device-local buffer to be accessed solely -//! by the GPU through the compute shader and as a vertex array. +// A minimal particle-sandbox to demonstrate a reasonable use-case for a device-local buffer. We +// gain significant runtime performance by writing the inital vertex values to the GPU using a +// staging buffer and then copying the data to a device-local buffer to be accessed solely by the +// GPU through the compute shader and as a vertex array. -use bytemuck::{Pod, Zeroable}; use std::{sync::Arc, time::SystemTime}; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, CopyBufferInfo, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents, @@ -39,8 +38,10 @@ use vulkano::{ GraphicsPipeline, PipelineBindPoint, }, render_pass::{Framebuffer, FramebufferCreateInfo, Subpass}, - swapchain::{PresentMode, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo}, - sync::{future::FenceSignalFuture, GpuFuture}, + swapchain::{ + acquire_next_image, PresentMode, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo, + }, + sync::{self, future::FenceSignalFuture, GpuFuture}, VulkanLibrary, }; use vulkano_win::VkSurfaceBuild; @@ -56,15 +57,14 @@ const WINDOW_HEIGHT: u32 = 600; const PARTICLE_COUNT: usize = 100_000; fn main() { - // The usual Vulkan initialization. - // Largely the same as example `triangle.rs` until further commentation is provided. + // The usual Vulkan initialization. Largely the same as example `triangle.rs` until further + // commentation is provided. let library = VulkanLibrary::new().unwrap(); let required_extensions = vulkano_win::required_extensions(&library); let instance = Instance::new( library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -73,7 +73,8 @@ fn main() { let event_loop = EventLoop::new(); let surface = WindowBuilder::new() - .with_resizable(false) // For simplicity, we are going to assert that the window size is static + // For simplicity, we are going to assert that the window size is static. + .with_resizable(false) .with_title("simple particles") .with_inner_size(winit::dpi::PhysicalSize::new(WINDOW_WIDTH, WINDOW_HEIGHT)) .build_vk_surface(&event_loop, instance.clone()) @@ -110,8 +111,9 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); + let (device, mut queues) = Device::new( physical_device, DeviceCreateInfo { @@ -196,7 +198,7 @@ fn main() { mod cs { vulkano_shaders::shader! { ty: "compute", - src: " + src: r" #version 450 layout(local_size_x = 128, local_size_y = 1, local_size_z = 1) in; @@ -212,14 +214,15 @@ fn main() { }; // Allow push constants to define a parameters of compute. - layout (push_constant) uniform PushConstants - { + layout (push_constant) uniform PushConstants { vec2 attractor; float attractor_strength; float delta_time; } push; - const float maxSpeed = 10.0; // Keep this value in sync with the `maxSpeed` const in the vertex shader. + // Keep this value in sync with the `maxSpeed` const in the vertex shader. + const float maxSpeed = 10.0; + const float minLength = 0.02; const float friction = -2.0; @@ -232,15 +235,15 @@ fn main() { vec2 pos = verticies[index].pos + push.delta_time * vel; // Bounce particle off screen-border. - if(abs(pos.x) > 1.0) { + if (abs(pos.x) > 1.0) { vel.x = sign(pos.x) * (-0.95 * abs(vel.x) - 0.0001); - if(abs(pos.x) >= 1.05) { + if (abs(pos.x) >= 1.05) { pos.x = sign(pos.x); } } - if(abs(pos.y) > 1.0) { + if (abs(pos.y) > 1.0) { vel.y = sign(pos.y) * (-0.95 * abs(vel.y) - 0.0001); - if(abs(pos.y) >= 1.05) { + if (abs(pos.y) >= 1.05) { pos.y = sign(pos.y); } } @@ -248,11 +251,11 @@ fn main() { // Simple inverse-square force. vec2 t = push.attractor - pos; float r = max(length(t), minLength); - vec2 force = push.attractor_strength * (t/r) / (r*r); + vec2 force = push.attractor_strength * (t / r) / (r * r); // Update velocity, enforcing a maximum speed. vel += push.delta_time * force; - if(length(vel) > maxSpeed) { + if (length(vel) > maxSpeed) { vel = maxSpeed*normalize(vel); } @@ -263,12 +266,13 @@ fn main() { ", } } - // Vertex shader determines color and is run once per particle. - // The vertices will be updates by the compute shader each frame. + + // The vertex shader determines color and is run once per particle. The vertices will be + // updated by the compute shader each frame. mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " + src: r" #version 450 layout(location = 0) in vec2 pos; @@ -276,23 +280,30 @@ fn main() { layout(location = 0) out vec4 outColor; - const float maxSpeed = 10.0; // Keep this value in sync with the `maxSpeed` const in the compute shader. + // Keep this value in sync with the `maxSpeed` const in the compute shader. + const float maxSpeed = 10.0; void main() { gl_Position = vec4(pos, 0.0, 1.0); gl_PointSize = 1.0; // Mix colors based on position and velocity. - outColor = mix(0.2*vec4(pos, abs(vel.x)+abs(vel.y), 1.0), vec4(1.0, 0.5, 0.8, 1.0), sqrt(length(vel)/maxSpeed)); - }" + outColor = mix( + 0.2 * vec4(pos, abs(vel.x) + abs(vel.y), 1.0), + vec4(1.0, 0.5, 0.8, 1.0), + sqrt(length(vel) / maxSpeed) + ); + } + ", } } - // Fragment shader will only need to apply the color forwarded by the vertex shader. - // This is because the color of a particle should be identical over all pixels. + + // The fragment shader will only need to apply the color forwarded by the vertex shader, + // because the color of a particle should be identical over all pixels. mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " + src: r" #version 450 layout(location = 0) in vec4 outColor; @@ -302,7 +313,7 @@ fn main() { void main() { fragColor = outColor; } - " + ", } } @@ -315,8 +326,8 @@ fn main() { let command_buffer_allocator = StandardCommandBufferAllocator::new(device.clone(), Default::default()); + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32_SFLOAT)] pos: [f32; 2], @@ -349,7 +360,8 @@ fn main() { ) .unwrap(); - // Create a buffer in device-local memory with enough space for `PARTICLE_COUNT` number of `Vertex`. + // Create a buffer in device-local memory with enough space for `PARTICLE_COUNT` number of + // `Vertex`. let device_local_buffer = Buffer::new_slice::( &memory_allocator, BufferAllocateInfo { @@ -390,7 +402,7 @@ fn main() { device_local_buffer }; - // Create compute-pipeline for applying compute shader to vertices. + // Create a compute-pipeline for applying the compute shader to vertices. let compute_pipeline = vulkano::pipeline::ComputePipeline::new( device.clone(), cs.entry_point("main").unwrap(), @@ -398,20 +410,21 @@ fn main() { None, |_| {}, ) - .expect("Failed to create compute shader"); + .expect("failed to create compute shader"); - // Create a new descriptor set for binding vertices as a Storage Buffer. - use vulkano::pipeline::Pipeline; // Required to access layout() method of pipeline. + // Create a new descriptor set for binding vertices as a storage buffer. + use vulkano::pipeline::Pipeline; // Required to access the `layout` method of pipeline. let descriptor_set = PersistentDescriptorSet::new( &descriptor_set_allocator, compute_pipeline .layout() .set_layouts() - .get(0) // 0 is the index of the descriptor set. + // 0 is the index of the descriptor set. + .get(0) .unwrap() .clone(), [ - // 0 is the binding of the data in this set. We bind the `DeviceLocalBuffer` of vertices here. + // 0 is the binding of the data in this set. We bind the `Buffer` of vertices here. WriteDescriptorSet::buffer(0, vertex_buffer.clone()), ], ) @@ -428,7 +441,8 @@ fn main() { let graphics_pipeline = GraphicsPipeline::start() .vertex_input_state(Vertex::per_vertex()) .vertex_shader(vs.entry_point("main").unwrap(), ()) - .input_assembly_state(InputAssemblyState::new().topology(PrimitiveTopology::PointList)) // Vertices will be rendered as a list of points. + // Vertices will be rendered as a list of points. + .input_assembly_state(InputAssemblyState::new().topology(PrimitiveTopology::PointList)) .viewport_state(ViewportState::viewport_fixed_scissor_irrelevant([viewport])) .fragment_shader(fs.entry_point("main").unwrap(), ()) .render_pass(Subpass::from(render_pass, 0).unwrap()) @@ -462,41 +476,42 @@ fn main() { last_frame_time = now; // Create push contants to be passed to compute shader. - let push_constants = cs::ty::PushConstants { + let push_constants = cs::PushConstants { attractor: [0.75 * (3. * time).cos(), 0.6 * (0.75 * time).sin()], attractor_strength: 1.2 * (2. * time).cos(), delta_time, }; // Acquire information on the next swapchain target. - let (image_index, suboptimal, acquire_future) = - match vulkano::swapchain::acquire_next_image( - swapchain.clone(), - None, /*timeout*/ - ) { - Ok(tuple) => tuple, - Err(e) => panic!("Failed to acquire next image: {e:?}"), - }; + let (image_index, suboptimal, acquire_future) = match acquire_next_image( + swapchain.clone(), + None, // timeout + ) { + Ok(tuple) => tuple, + Err(e) => panic!("failed to acquire next image: {e}"), + }; - // Since we disallow resizing, assert the swapchain and surface are optimally configured. + // Since we disallow resizing, assert that the swapchain and surface are optimally + // configured. assert!( !suboptimal, - "Not handling sub-optimal swapchains in this sample code" + "not handling sub-optimal swapchains in this sample code", ); - // If this image buffer already has a future then attempt to cleanup fence resources. - // Usually the future for this index will have completed by the time we are rendering it again. + // If this image buffer already has a future then attempt to cleanup fence + // resources. Usually the future for this index will have completed by the time we + // are rendering it again. if let Some(image_fence) = &mut fences[image_index as usize] { image_fence.cleanup_finished() } - // If the previous image has a fence then use it for synchronization, else create a new one. + // If the previous image has a fence then use it for synchronization, else create + // a new one. let previous_future = match fences[previous_fence_index as usize].clone() { // Ensure current frame is synchronized with previous. Some(fence) => fence.boxed(), - // Create new future to guarentee synchronization with (fake) previous frame. - None => vulkano::sync::now(device.clone()).boxed(), + None => sync::now(device.clone()).boxed(), }; let mut builder = AutoCommandBufferBuilder::primary( @@ -553,7 +568,7 @@ fn main() { Ok(future) => Some(Arc::new(future)), // Unknown failure. - Err(e) => panic!("Failed to flush future: {e:?}"), + Err(e) => panic!("failed to flush future: {e}"), }; previous_fence_index = image_index; } diff --git a/examples/src/bin/specialization-constants.rs b/examples/src/bin/specialization-constants.rs index bf20bc73..9890c80b 100644 --- a/examples/src/bin/specialization-constants.rs +++ b/examples/src/bin/specialization-constants.rs @@ -7,7 +7,7 @@ // notice may not be copied, modified, or distributed except // according to those terms. -// TODO: Give a paragraph about what specialization are and what problems they solve +// TODO: Give a paragraph about what specialization are and what problems they solve. use vulkano::{ buffer::{Buffer, BufferAllocateInfo, BufferUsage}, @@ -33,7 +33,6 @@ fn main() { let instance = Instance::new( library, InstanceCreateInfo { - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -65,9 +64,9 @@ fn main() { .unwrap(); println!( - "Using device: {} (type: {:?})", + "using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); let (device, mut queues) = Device::new( @@ -87,27 +86,27 @@ fn main() { mod cs { vulkano_shaders::shader! { ty: "compute", - src: " + src: r" #version 450 layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; + layout(constant_id = 0) const int multiple = 64; layout(constant_id = 1) const float addend = 64; layout(constant_id = 2) const bool enable = true; - const vec2 foo = vec2(0, 0); // TODO: How do I hit Instruction::SpecConstantComposite layout(set = 0, binding = 0) buffer Data { uint data[]; - } data; + }; void main() { uint idx = gl_GlobalInvocationID.x; if (enable) { - data.data[idx] *= multiple; - data.data[idx] += uint(addend); + data[idx] *= multiple; + data[idx] += uint(addend); } } - " + ", } } diff --git a/examples/src/bin/teapot/main.rs b/examples/src/bin/teapot/main.rs index e3415dc1..64cdca03 100644 --- a/examples/src/bin/teapot/main.rs +++ b/examples/src/bin/teapot/main.rs @@ -56,8 +56,8 @@ use winit::{ }; fn main() { - // The start of this example is exactly the same as `triangle`. You should read the - // `triangle` example if you haven't done so yet. + // The start of this example is exactly the same as `triangle`. You should read the `triangle` + // example if you haven't done so yet. let library = VulkanLibrary::new().unwrap(); let required_extensions = vulkano_win::required_extensions(&library); @@ -65,7 +65,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -264,7 +263,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -303,7 +302,7 @@ fn main() { ); let scale = Matrix4::from_scale(0.01); - let uniform_data = vs::ty::Data { + let uniform_data = vs::Data { world: Matrix4::from(rotation).into(), view: (view * scale).into(), proj: proj.into(), @@ -330,7 +329,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -393,7 +392,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -403,7 +402,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( memory_allocator: &StandardMemoryAllocator, vs: &ShaderModule, @@ -433,9 +432,9 @@ fn window_size_dependent_setup( }) .collect::>(); - // In the triangle example we use a dynamic viewport, as its a simple example. - // However in the teapot example, we recreate the pipelines with a hardcoded viewport instead. - // This allows the driver to optimize things, at the cost of slower window resizes. + // In the triangle example we use a dynamic viewport, as its a simple example. However in the + // teapot example, we recreate the pipelines with a hardcoded viewport instead. This allows the + // driver to optimize things, at the cost of slower window resizes. // https://computergraphics.stackexchange.com/questions/5742/vulkan-best-way-of-updating-pipeline-viewport let pipeline = GraphicsPipeline::start() .vertex_input_state([Position::per_vertex(), Normal::per_vertex()]) @@ -467,6 +466,6 @@ mod vs { mod fs { vulkano_shaders::shader! { ty: "fragment", - path: "src/bin/teapot/frag.glsl" + path: "src/bin/teapot/frag.glsl", } } diff --git a/examples/src/bin/tessellation.rs b/examples/src/bin/tessellation.rs index d0924444..4e9ec0da 100644 --- a/examples/src/bin/tessellation.rs +++ b/examples/src/bin/tessellation.rs @@ -8,20 +8,22 @@ // according to those terms. // Some relevant documentation: -// * Tessellation overview https://www.khronos.org/opengl/wiki/Tessellation -// * Tessellation Control Shader https://www.khronos.org/opengl/wiki/Tessellation_Control_Shader -// * Tessellation Evaluation Shader https://www.khronos.org/opengl/wiki/Tessellation_Evaluation_Shader -// * Tessellation real-world usage 1 http://ogldev.atspace.co.uk/www/tutorial30/tutorial30.html -// * Tessellation real-world usage 2 https://prideout.net/blog/?p=48 +// +// - Tessellation overview https://www.khronos.org/opengl/wiki/Tessellation +// - Tessellation Control Shader https://www.khronos.org/opengl/wiki/Tessellation_Control_Shader +// - Tessellation Evaluation Shader https://www.khronos.org/opengl/wiki/Tessellation_Evaluation_Shader +// - Tessellation real-world usage 1 http://ogldev.atspace.co.uk/www/tutorial30/tutorial30.html +// - Tessellation real-world usage 2 https://prideout.net/blog/?p=48 // Notable elements of this example: -// * tessellation control shader and a tessellation evaluation shader -// * tessellation_shaders(..), patch_list(3) and polygon_mode_line() are called on the pipeline builder +// +// - Usage of a tessellation control shader and a tessellation evaluation shader. +// - `tessellation_shaders` and `tessellation_state` are called on the pipeline builder. +// - The use of `PrimitiveTopology::PatchList`. -use bytemuck::{Pod, Zeroable}; use std::sync::Arc; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SubpassContents, @@ -61,7 +63,7 @@ use winit::{ mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " + src: r" #version 450 layout(location = 0) in vec2 position; @@ -69,64 +71,66 @@ mod vs { void main() { gl_Position = vec4(position, 0.0, 1.0); } - " + ", } } mod tcs { vulkano_shaders::shader! { ty: "tess_ctrl", - src: " + src: r" #version 450 - layout (vertices = 3) out; // a value of 3 means a patch consists of a single triangle + // A value of 3 means a patch consists of a single triangle. + layout(vertices = 3) out; - void main(void) - { - // save the position of the patch, so the tes can access it - // We could define our own output variables for this, - // but gl_out is handily provided. + void main(void) { + // Save the position of the patch, so the TES can access it. We could define our + // own output variables for this, but `gl_out` is handily provided. gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; - gl_TessLevelInner[0] = 10; // many triangles are generated in the center - gl_TessLevelOuter[0] = 1; // no triangles are generated for this edge - gl_TessLevelOuter[1] = 10; // many triangles are generated for this edge - gl_TessLevelOuter[2] = 10; // many triangles are generated for this edge - // gl_TessLevelInner[1] = only used when tes uses layout(quads) - // gl_TessLevelOuter[3] = only used when tes uses layout(quads) + // Many triangles are generated in the center. + gl_TessLevelInner[0] = 10; + // No triangles are generated for this edge. + gl_TessLevelOuter[0] = 1; + // Many triangles are generated for this edge. + gl_TessLevelOuter[1] = 10; + // Many triangles are generated for this edge. + gl_TessLevelOuter[2] = 10; + + // These are only used when TES uses `layout(quads)`. + // gl_TessLevelInner[1] = ...; + // gl_TessLevelOuter[3] = ...; } - " + ", } } -// PG -// There is a stage in between tcs and tes called Primitive Generation (PG) -// Shaders cannot be defined for it. -// It takes gl_TessLevelInner and gl_TessLevelOuter and uses them to generate positions within -// the patch and pass them to tes via gl_TessCoord. +// There is a stage in between TCS and TES called Primitive Generation (PG). Shaders cannot be +// defined for it. It takes `gl_TessLevelInner` and `gl_TessLevelOuter` and uses them to generate +// positions within the patch and pass them to TES via `gl_TessCoord`. // -// When tes uses layout(triangles) then gl_TessCoord is in barrycentric coordinates. -// if layout(quads) is used then gl_TessCoord is in cartesian coordinates. -// Barrycentric coordinates are of the form (x, y, z) where x + y + z = 1 -// and the values x, y and z represent the distance from a vertex of the triangle. +// When TES uses `layout(triangles)` then `gl_TessCoord` is in Barycentric coordinates. If +// `layout(quads)` is used then `gl_TessCoord` is in Cartesian coordinates. Barycentric coordinates +// are of the form (x, y, z) where x + y + z = 1 and the values x, y and z represent the distance +// from a vertex of the triangle. // https://mathworld.wolfram.com/BarycentricCoordinates.html mod tes { vulkano_shaders::shader! { ty: "tess_eval", - src: " + src: r" #version 450 layout(triangles, equal_spacing, cw) in; - void main(void) - { - // retrieve the vertex positions set by the tcs + void main(void) { + // Retrieve the vertex positions set by the TCS. vec4 vert_x = gl_in[0].gl_Position; vec4 vert_y = gl_in[1].gl_Position; vec4 vert_z = gl_in[2].gl_Position; - // convert gl_TessCoord from barycentric coordinates to cartesian coordinates + // Convert `gl_TessCoord` from Barycentric coordinates to Cartesian coordinates. gl_Position = vec4( gl_TessCoord.x * vert_x.x + gl_TessCoord.y * vert_y.x + gl_TessCoord.z * vert_z.x, gl_TessCoord.x * vert_x.y + gl_TessCoord.y * vert_y.y + gl_TessCoord.z * vert_z.y, @@ -134,14 +138,14 @@ mod tes { 1.0 ); } - " + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " + src: r" #version 450 layout(location = 0) out vec4 f_color; @@ -149,7 +153,7 @@ mod fs { void main() { f_color = vec4(1.0, 1.0, 1.0, 1.0); } - " + ", } } @@ -160,7 +164,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -209,7 +212,7 @@ fn main() { println!( "Using device: {} (type: {:?})", physical_device.properties().device_name, - physical_device.properties().device_type + physical_device.properties().device_type, ); let (device, mut queues) = Device::new( @@ -262,7 +265,7 @@ fn main() { let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] + #[derive(BufferContents, Vertex)] #[repr(C)] struct Vertex { #[format(R32G32_SFLOAT)] @@ -396,7 +399,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -412,7 +415,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -466,7 +469,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -475,7 +478,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], render_pass: Arc, diff --git a/examples/src/bin/texture_array/main.rs b/examples/src/bin/texture_array/main.rs index 641c6270..a460ada1 100644 --- a/examples/src/bin/texture_array/main.rs +++ b/examples/src/bin/texture_array/main.rs @@ -7,10 +7,9 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use bytemuck::{Pod, Zeroable}; use std::{io::Cursor, sync::Arc}; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents, @@ -66,7 +65,6 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -161,8 +159,8 @@ fn main() { let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32_SFLOAT)] position: [f32; 2], @@ -239,11 +237,14 @@ fn main() { image_data }) .collect(); + + // Replace with your actual image array dimensions. let dimensions = ImageDimensions::Dim2d { width: 128, height: 128, array_layers: 3, - }; // Replace with your actual image array dimensions + }; + let image = ImmutableImage::from_iter( &memory_allocator, image_array_data, @@ -253,6 +254,7 @@ fn main() { &mut uploads, ) .unwrap(); + ImageView::new_default(image).unwrap() }; @@ -324,7 +326,7 @@ fn main() { }) { Ok(r) => r, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; @@ -340,7 +342,7 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; if suboptimal { @@ -400,7 +402,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -409,7 +411,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], render_pass: Arc, @@ -437,38 +439,40 @@ fn window_size_dependent_setup( mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 position; -layout(location = 0) out vec2 tex_coords; -layout(location = 1) out uint layer; + layout(location = 0) in vec2 position; + layout(location = 0) out vec2 tex_coords; + layout(location = 1) out uint layer; -const float x[4] = float[](0.0, 0.0, 1.0, 1.0); -const float y[4] = float[](0.0, 1.0, 0.0, 1.0); + const float x[4] = float[](0.0, 0.0, 1.0, 1.0); + const float y[4] = float[](0.0, 1.0, 0.0, 1.0); -void main() { - gl_Position = vec4(position, 0.0, 1.0); - tex_coords = vec2(x[gl_VertexIndex], y[gl_VertexIndex]); - layer = gl_InstanceIndex; -}" + void main() { + gl_Position = vec4(position, 0.0, 1.0); + tex_coords = vec2(x[gl_VertexIndex], y[gl_VertexIndex]); + layer = gl_InstanceIndex; + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " -#version 450 + src: r" + #version 450 -layout(location = 0) in vec2 tex_coords; -layout(location = 1) flat in uint layer; -layout(location = 0) out vec4 f_color; + layout(location = 0) in vec2 tex_coords; + layout(location = 1) flat in uint layer; + layout(location = 0) out vec4 f_color; -layout(set = 0, binding = 0) uniform sampler2DArray tex; + layout(set = 0, binding = 0) uniform sampler2DArray tex; -void main() { - f_color = texture(tex, vec3(tex_coords, layer)); -}" + void main() { + f_color = texture(tex, vec3(tex_coords, layer)); + } + ", } } diff --git a/examples/src/bin/triangle-v1_3.rs b/examples/src/bin/triangle-v1_3.rs index 26b1dd18..23217e76 100644 --- a/examples/src/bin/triangle-v1_3.rs +++ b/examples/src/bin/triangle-v1_3.rs @@ -12,19 +12,18 @@ // This is the only example that is entirely detailed. All the other examples avoid code // duplication by using helper functions. // -// This example assumes that you are already more or less familiar with graphics programming -// and that you want to learn Vulkan. This means that for example it won't go into details about -// what a vertex or a shader is. +// This example assumes that you are already more or less familiar with graphics programming and +// that you want to learn Vulkan. This means that for example it won't go into details about what a +// vertex or a shader is. // // This version of the triangle example is written using dynamic rendering instead of render pass // and framebuffer objects. If your device does not support Vulkan 1.3 or the // `khr_dynamic_rendering` extension, or if you want to see how to support older versions, see the // original triangle example. -use bytemuck::{Pod, Zeroable}; use std::sync::Arc; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, RenderingAttachmentInfo, RenderingInfo, @@ -61,14 +60,15 @@ use winit::{ }; fn main() { + let library = VulkanLibrary::new().unwrap(); + // The first step of any Vulkan program is to create an instance. // // When we create an instance, we have to pass a list of extensions that we want to enable. // - // All the window-drawing functionalities are part of non-core extensions that we need - // to enable manually. To do so, we ask the `vulkano_win` crate for the list of extensions + // All the window-drawing functionalities are part of non-core extensions that we need to + // enable manually. To do so, we ask the `vulkano_win` crate for the list of extensions // required to draw to a window. - let library = VulkanLibrary::new().unwrap(); let required_extensions = vulkano_win::required_extensions(&library); // Now creating the instance. @@ -76,7 +76,8 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) + // Enable enumerating devices that use non-conformant Vulkan implementations. (e.g. + // MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -91,16 +92,15 @@ fn main() { // ever get an error about `build_vk_surface` being undefined in one of your projects, this // probably means that you forgot to import this trait. // - // This returns a `vulkano::swapchain::Surface` object that contains both a cross-platform winit - // window and a cross-platform Vulkan surface that represents the surface of the window. + // This returns a `vulkano::swapchain::Surface` object that contains both a cross-platform + // winit window and a cross-platform Vulkan surface that represents the surface of the window. let event_loop = EventLoop::new(); let surface = WindowBuilder::new() .build_vk_surface(&event_loop, instance.clone()) .unwrap(); - // Choose device extensions that we're going to use. - // In order to present images to a surface, we need a `Swapchain`, which is provided by the - // `khr_swapchain` extension. + // Choose device extensions that we're going to use. In order to present images to a surface, + // we need a `Swapchain`, which is provided by the `khr_swapchain` extension. let mut device_extensions = DeviceExtensions { khr_swapchain: true, ..DeviceExtensions::empty() @@ -128,11 +128,11 @@ fn main() { // // Devices can provide multiple queues to run commands in parallel (for example a draw // queue and a compute queue), similar to CPU threads. This is something you have to - // have to manage manually in Vulkan. Queues of the same type belong to the same - // queue family. + // have to manage manually in Vulkan. Queues of the same type belong to the same queue + // family. // // Here, we look for a single queue family that is suitable for our purposes. In a - // real-life application, you may want to use a separate dedicated transfer queue to + // real-world application, you may want to use a separate dedicated transfer queue to // handle data transfers in parallel with graphics operations. You may also need a // separate queue for compute operations, if your application uses those. p.queue_family_properties() @@ -140,8 +140,8 @@ fn main() { .enumerate() .position(|(i, q)| { // We select a queue family that supports graphics operations. When drawing to - // a window surface, as we do in this example, we also need to check that queues - // in this queue family are capable of presenting images to the surface. + // a window surface, as we do in this example, we also need to check that + // queues in this queue family are capable of presenting images to the surface. q.queue_flags.intersects(QueueFlags::GRAPHICS) && p.surface_support(i as u32, &surface).unwrap_or(false) }) @@ -151,13 +151,12 @@ fn main() { .map(|i| (p, i as u32)) }) // All the physical devices that pass the filters above are suitable for the application. - // However, not every device is equal, some are preferred over others. Now, we assign - // each physical device a score, and pick the device with the - // lowest ("best") score. + // However, not every device is equal, some are preferred over others. Now, we assign each + // physical device a score, and pick the device with the lowest ("best") score. // // In this example, we simply select the best-scoring device to use in the application. - // In a real-life setting, you may want to use the best-scoring device only as a - // "default" or "recommended" device, and let the user choose the device themselves. + // In a real-world setting, you may want to use the best-scoring device only as a "default" + // or "recommended" device, and let the user choose the device themself. .min_by_key(|(p, _)| { // We assign a lower score to device types that are likely to be faster/better. match p.properties().device_type { @@ -169,7 +168,7 @@ fn main() { _ => 5, } }) - .expect("No suitable physical device found"); + .expect("no suitable physical device found"); // Some little debug infos. println!( @@ -189,7 +188,7 @@ fn main() { // Now initializing the device. This is probably the most important object of Vulkan. // - // The iterator of created queues is returned by the function alongside the device. + // An iterator of created queues is returned by the function alongside the device. let (device, mut queues) = Device::new( // Which physical device to connect to. physical_device, @@ -223,17 +222,17 @@ fn main() { ) .unwrap(); - // Since we can request multiple queues, the `queues` variable is in fact an iterator. We - // only use one queue in this example, so we just retrieve the first and only element of the + // Since we can request multiple queues, the `queues` variable is in fact an iterator. We only + // use one queue in this example, so we just retrieve the first and only element of the // iterator. let queue = queues.next().unwrap(); - // Before we can draw on the surface, we have to create what is called a swapchain. Creating - // a swapchain allocates the color buffers that will contain the image that will ultimately - // be visible on the screen. These images are returned alongside the swapchain. + // Before we can draw on the surface, we have to create what is called a swapchain. Creating a + // swapchain allocates the color buffers that will contain the image that will ultimately be + // visible on the screen. These images are returned alongside the swapchain. let (mut swapchain, images) = { - // Querying the capabilities of the surface. When we create the swapchain we can only - // pass values that are allowed by the capabilities. + // Querying the capabilities of the surface. When we create the swapchain we can only pass + // values that are allowed by the capabilities. let surface_capabilities = device .physical_device() .surface_capabilities(&surface, Default::default()) @@ -257,17 +256,17 @@ fn main() { min_image_count: surface_capabilities.min_image_count, image_format, + // The dimensions of the window, only used to initially setup the swapchain. + // // NOTE: // On some drivers the swapchain dimensions are specified by // `surface_capabilities.current_extent` and the swapchain size must use these - // dimensions. - // These dimensions are always the same as the window dimensions. + // dimensions. These dimensions are always the same as the window dimensions. // // However, other drivers don't specify a value, i.e. // `surface_capabilities.current_extent` is `None`. These drivers will allow - // anything, but the only sensible value is the window - // dimensions. + // anything, but the only sensible value is the window dimensions. // // Both of these cases need the swapchain to use the window dimensions, so we just // use that. @@ -291,11 +290,11 @@ fn main() { let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); - // We now create a buffer that will store the shape of our triangle. - // We use #[repr(C)] here to force rustc to not do anything funky with our data, although for this - // particular example, it doesn't actually change the in-memory representation. + // We now create a buffer that will store the shape of our triangle. We use `#[repr(C)]` here + // to force rustc to use a defined layout for our data, as the default representation has *no + // guarantees*. + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32_SFLOAT)] position: [f32; 2], @@ -324,47 +323,45 @@ fn main() { // The next step is to create the shaders. // - // The raw shader creation API provided by the vulkano library is unsafe for various - // reasons, so The `shader!` macro provides a way to generate a Rust module from GLSL - // source - in the example below, the source is provided as a string input directly to - // the shader, but a path to a source file can be provided as well. Note that the user - // must specify the type of shader (e.g., "vertex," "fragment, etc.") using the `ty` - // option of the macro. + // The raw shader creation API provided by the vulkano library is unsafe for various reasons, + // so The `shader!` macro provides a way to generate a Rust module from GLSL source - in the + // example below, the source is provided as a string input directly to the shader, but a path + // to a source file can be provided as well. Note that the user must specify the type of shader + // (e.g. "vertex", "fragment", etc.) using the `ty` option of the macro. // - // The module generated by the `shader!` macro includes a `load` function which loads - // the shader using an input logical device. The module also includes type definitions - // for layout structures defined in the shader source, for example, uniforms and push - // constants. + // The items generated by the `shader!` macro include a `load` function which loads the shader + // using an input logical device. The module also includes type definitions for layout + // structures defined in the shader source, for example uniforms and push constants. // // A more detailed overview of what the `shader!` macro generates can be found in the - // `vulkano-shaders` crate docs. You can view them at https://docs.rs/vulkano-shaders/ + // vulkano-shaders crate docs. You can view them at https://docs.rs/vulkano-shaders/ mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " - #version 450 + src: r" + #version 450 - layout(location = 0) in vec2 position; + layout(location = 0) in vec2 position; - void main() { - gl_Position = vec4(position, 0.0, 1.0); - } - " + void main() { + gl_Position = vec4(position, 0.0, 1.0); + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " - #version 450 + src: r" + #version 450 - layout(location = 0) out vec4 f_color; + layout(location = 0) out vec4 f_color; - void main() { - f_color = vec4(1.0, 0.0, 0.0, 1.0); - } - " + void main() { + f_color = vec4(1.0, 0.0, 0.0, 1.0); + } + ", } } @@ -392,8 +389,8 @@ fn main() { .vertex_input_state(Vertex::per_vertex()) // The content of the vertex buffer describes a list of triangles. .input_assembly_state(InputAssemblyState::new()) - // A Vulkan shader can in theory contain multiple entry points, so we have to specify - // which one. + // A Vulkan shader can in theory contain multiple entry points, so we have to specify which + // one. .vertex_shader(vs.entry_point("main").unwrap(), ()) // Use a resizable viewport set to draw over the entire window .viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant()) @@ -403,7 +400,7 @@ fn main() { .build(device.clone()) .unwrap(); - // Dynamic viewports allow us to recreate just the viewport when the window is resized + // Dynamic viewports allow us to recreate just the viewport when the window is resized. // Otherwise we would have to recreate the whole pipeline. let mut viewport = Viewport { origin: [0.0, 0.0], @@ -433,8 +430,8 @@ fn main() { // // In this situation, acquiring a swapchain image or presenting it will return an error. // Rendering to an image of that swapchain will not produce any error, but may or may not work. - // To continue rendering, we need to recreate the swapchain by creating a new swapchain. - // Here, we remember that we need to do this for the next loop iteration. + // To continue rendering, we need to recreate the swapchain by creating a new swapchain. Here, + // we remember that we need to do this for the next loop iteration. let mut recreate_swapchain = false; // In the loop below we are going to submit commands to the GPU. Submitting a command produces @@ -460,14 +457,15 @@ fn main() { recreate_swapchain = true; } Event::RedrawEventsCleared => { - // It is important to call this function from time to time, otherwise resources will keep - // accumulating and you will eventually reach an out of memory error. - // Calling this function polls various fences in order to determine what the GPU has - // already processed, and frees the resources that are no longer needed. + // It is important to call this function from time to time, otherwise resources + // will keep accumulating and you will eventually reach an out of memory error. + // Calling this function polls various fences in order to determine what the GPU + // has already processed, and frees the resources that are no longer needed. previous_frame_end.as_mut().unwrap().cleanup_finished(); - // Whenever the window resizes we need to recreate everything dependent on the window size. - // In this example that includes the swapchain, the framebuffers and the dynamic state viewport. + // Whenever the window resizes we need to recreate everything dependent on the + // window size. In this example that includes the swapchain, the framebuffers and + // the dynamic state viewport. if recreate_swapchain { // Get the new dimensions of the window. let window = surface.object().unwrap().downcast_ref::().unwrap(); @@ -478,27 +476,30 @@ fn main() { ..swapchain.create_info() }) { Ok(r) => r, - // This error tends to happen when the user is manually resizing the window. - // Simply restarting the loop is the easiest way to fix this issue. + // This error tends to happen when the user is manually resizing the + // window. Simply restarting the loop is the easiest way to fix this + // issue. Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; + // Now that we have new swapchain images, we must create new image views from // them as well. attachment_image_views = window_size_dependent_setup(&new_images, &mut viewport); + recreate_swapchain = false; } - // Before we can draw on the output, we have to *acquire* an image from the swapchain. If - // no image is available (which happens if you submit draw commands too quickly), then the - // function will block. - // This operation returns the index of the image that we are allowed to draw upon. + // Before we can draw on the output, we have to *acquire* an image from the + // swapchain. If no image is available (which happens if you submit draw commands + // too quickly), then the function will block. This operation returns the index of + // the image that we are allowed to draw upon. // - // This function can block if no image is available. The parameter is an optional timeout - // after which the function call will return an error. + // This function can block if no image is available. The parameter is an optional + // timeout after which the function call will return an error. let (image_index, suboptimal, acquire_future) = match acquire_next_image(swapchain.clone(), None) { Ok(r) => r, @@ -506,25 +507,26 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; - // acquire_next_image can be successful, but suboptimal. This means that the swapchain image - // will still work, but it may not display correctly. With some drivers this can be when - // the window resizes, but it may not cause the swapchain to become out of date. + // `acquire_next_image` can be successful, but suboptimal. This means that the + // swapchain image will still work, but it may not display correctly. With some + // drivers this can be when the window resizes, but it may not cause the swapchain + // to become out of date. if suboptimal { recreate_swapchain = true; } - // In order to draw, we have to build a *command buffer*. The command buffer object holds - // the list of commands that are going to be executed. + // In order to draw, we have to build a *command buffer*. The command buffer object + // holds the list of commands that are going to be executed. // // Building a command buffer is an expensive operation (usually a few hundred - // microseconds), but it is known to be a hot path in the driver and is expected to be - // optimized. + // microseconds), but it is known to be a hot path in the driver and is expected to + // be optimized. // - // Note that we have to pass a queue family when we create the command buffer. The command - // buffer will only be executable on that given queue family. + // Note that we have to pass a queue family when we create the command buffer. The + // command buffer will only be executable on that given queue family. let mut builder = AutoCommandBufferBuilder::primary( &command_buffer_allocator, queue.queue_family_index(), @@ -537,20 +539,20 @@ fn main() { // attachments we are going to use for rendering here, which needs to match // what was previously specified when creating the pipeline. .begin_rendering(RenderingInfo { - // As before, we specify one color attachment, but now we specify - // the image view to use as well as how it should be used. + // As before, we specify one color attachment, but now we specify the image + // view to use as well as how it should be used. color_attachments: vec![Some(RenderingAttachmentInfo { // `Clear` means that we ask the GPU to clear the content of this // attachment at the start of rendering. load_op: LoadOp::Clear, - // `Store` means that we ask the GPU to store the rendered output - // in the attachment image. We could also ask it to discard the result. + // `Store` means that we ask the GPU to store the rendered output in + // the attachment image. We could also ask it to discard the result. store_op: StoreOp::Store, - // The value to clear the attachment with. Here we clear it with a - // blue color. + // The value to clear the attachment with. Here we clear it with a blue + // color. // - // Only attachments that have `LoadOp::Clear` are provided with - // clear values, any others should use `None` as the clear value. + // Only attachments that have `LoadOp::Clear` are provided with clear + // values, any others should use `None` as the clear value. clear_value: Some([0.0, 0.0, 1.0, 1.0].into()), ..RenderingAttachmentInfo::image_view( // We specify image view corresponding to the currently acquired @@ -561,13 +563,13 @@ fn main() { ..Default::default() }) .unwrap() - // We are now inside the first subpass of the render pass. We add a draw command. + // We are now inside the first subpass of the render pass. // - // The last two parameters contain the list of resources to pass to the shaders. - // Since we used an `EmptyPipeline` object, the objects have to be `()`. + // TODO: Document state setting and how it affects subsequent draw commands. .set_viewport(0, [viewport.clone()]) .bind_pipeline_graphics(pipeline.clone()) .bind_vertex_buffers(0, vertex_buffer.clone()) + // We add a draw command. .draw(vertex_buffer.len() as u32, 1, 0, 0) .unwrap() // We leave the render pass. @@ -583,12 +585,14 @@ fn main() { .join(acquire_future) .then_execute(queue.clone(), command_buffer) .unwrap() - // The color output is now expected to contain our triangle. But in order to show it on - // the screen, we have to *present* the image by calling `present`. + // The color output is now expected to contain our triangle. But in order to + // show it on the screen, we have to *present* the image by calling + // `then_swapchain_present`. // - // This function does not actually present the image immediately. Instead it submits a - // present command at the end of the queue. This means that it will only be presented once - // the GPU has finished executing the command buffer that draws the triangle. + // This function does not actually present the image immediately. Instead it + // submits a present command at the end of the queue. This means that it will + // only be presented once the GPU has finished executing the command buffer + // that draws the triangle. .then_swapchain_present( queue.clone(), SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index), @@ -604,7 +608,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("failed to flush future: {e}"); previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -614,7 +618,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], viewport: &mut Viewport, diff --git a/examples/src/bin/triangle.rs b/examples/src/bin/triangle.rs index 70d3d604..cf6b40ec 100644 --- a/examples/src/bin/triangle.rs +++ b/examples/src/bin/triangle.rs @@ -12,14 +12,13 @@ // This is the only example that is entirely detailed. All the other examples avoid code // duplication by using helper functions. // -// This example assumes that you are already more or less familiar with graphics programming -// and that you want to learn Vulkan. This means that for example it won't go into details about -// what a vertex or a shader is. +// This example assumes that you are already more or less familiar with graphics programming and +// that you want to learn Vulkan. This means that for example it won't go into details about what a +// vertex or a shader is. -use bytemuck::{Pod, Zeroable}; use std::sync::Arc; use vulkano::{ - buffer::{Buffer, BufferAllocateInfo, BufferUsage}, + buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, RenderPassBeginInfo, SubpassContents, @@ -55,14 +54,15 @@ use winit::{ }; fn main() { + let library = VulkanLibrary::new().unwrap(); + // The first step of any Vulkan program is to create an instance. // // When we create an instance, we have to pass a list of extensions that we want to enable. // - // All the window-drawing functionalities are part of non-core extensions that we need - // to enable manually. To do so, we ask the `vulkano_win` crate for the list of extensions + // All the window-drawing functionalities are part of non-core extensions that we need to + // enable manually. To do so, we ask the `vulkano_win` crate for the list of extensions // required to draw to a window. - let library = VulkanLibrary::new().unwrap(); let required_extensions = vulkano_win::required_extensions(&library); // Now creating the instance. @@ -70,7 +70,8 @@ fn main() { library, InstanceCreateInfo { enabled_extensions: required_extensions, - // Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK) + // Enable enumerating devices that use non-conformant Vulkan implementations. (e.g. + // MoltenVK) enumerate_portability: true, ..Default::default() }, @@ -85,16 +86,15 @@ fn main() { // ever get an error about `build_vk_surface` being undefined in one of your projects, this // probably means that you forgot to import this trait. // - // This returns a `vulkano::swapchain::Surface` object that contains both a cross-platform winit - // window and a cross-platform Vulkan surface that represents the surface of the window. + // This returns a `vulkano::swapchain::Surface` object that contains both a cross-platform + // winit window and a cross-platform Vulkan surface that represents the surface of the window. let event_loop = EventLoop::new(); let surface = WindowBuilder::new() .build_vk_surface(&event_loop, instance.clone()) .unwrap(); - // Choose device extensions that we're going to use. - // In order to present images to a surface, we need a `Swapchain`, which is provided by the - // `khr_swapchain` extension. + // Choose device extensions that we're going to use. In order to present images to a surface, + // we need a `Swapchain`, which is provided by the `khr_swapchain` extension. let device_extensions = DeviceExtensions { khr_swapchain: true, ..DeviceExtensions::empty() @@ -117,11 +117,11 @@ fn main() { // // Devices can provide multiple queues to run commands in parallel (for example a draw // queue and a compute queue), similar to CPU threads. This is something you have to - // have to manage manually in Vulkan. Queues of the same type belong to the same - // queue family. + // have to manage manually in Vulkan. Queues of the same type belong to the same queue + // family. // // Here, we look for a single queue family that is suitable for our purposes. In a - // real-life application, you may want to use a separate dedicated transfer queue to + // real-world application, you may want to use a separate dedicated transfer queue to // handle data transfers in parallel with graphics operations. You may also need a // separate queue for compute operations, if your application uses those. p.queue_family_properties() @@ -129,8 +129,8 @@ fn main() { .enumerate() .position(|(i, q)| { // We select a queue family that supports graphics operations. When drawing to - // a window surface, as we do in this example, we also need to check that queues - // in this queue family are capable of presenting images to the surface. + // a window surface, as we do in this example, we also need to check that + // queues in this queue family are capable of presenting images to the surface. q.queue_flags.intersects(QueueFlags::GRAPHICS) && p.surface_support(i as u32, &surface).unwrap_or(false) }) @@ -140,13 +140,12 @@ fn main() { .map(|i| (p, i as u32)) }) // All the physical devices that pass the filters above are suitable for the application. - // However, not every device is equal, some are preferred over others. Now, we assign - // each physical device a score, and pick the device with the - // lowest ("best") score. + // However, not every device is equal, some are preferred over others. Now, we assign each + // physical device a score, and pick the device with the lowest ("best") score. // // In this example, we simply select the best-scoring device to use in the application. - // In a real-life setting, you may want to use the best-scoring device only as a - // "default" or "recommended" device, and let the user choose the device themselves. + // In a real-world setting, you may want to use the best-scoring device only as a "default" + // or "recommended" device, and let the user choose the device themself. .min_by_key(|(p, _)| { // We assign a lower score to device types that are likely to be faster/better. match p.properties().device_type { @@ -158,7 +157,7 @@ fn main() { _ => 5, } }) - .expect("No suitable physical device found"); + .expect("no suitable physical device found"); // Some little debug infos. println!( @@ -169,7 +168,7 @@ fn main() { // Now initializing the device. This is probably the most important object of Vulkan. // - // The iterator of created queues is returned by the function alongside the device. + // An iterator of created queues is returned by the function alongside the device. let (device, mut queues) = Device::new( // Which physical device to connect to. physical_device, @@ -192,17 +191,17 @@ fn main() { ) .unwrap(); - // Since we can request multiple queues, the `queues` variable is in fact an iterator. We - // only use one queue in this example, so we just retrieve the first and only element of the + // Since we can request multiple queues, the `queues` variable is in fact an iterator. We only + // use one queue in this example, so we just retrieve the first and only element of the // iterator. let queue = queues.next().unwrap(); - // Before we can draw on the surface, we have to create what is called a swapchain. Creating - // a swapchain allocates the color buffers that will contain the image that will ultimately - // be visible on the screen. These images are returned alongside the swapchain. + // Before we can draw on the surface, we have to create what is called a swapchain. Creating a + // swapchain allocates the color buffers that will contain the image that will ultimately be + // visible on the screen. These images are returned alongside the swapchain. let (mut swapchain, images) = { - // Querying the capabilities of the surface. When we create the swapchain we can only - // pass values that are allowed by the capabilities. + // Querying the capabilities of the surface. When we create the swapchain we can only pass + // values that are allowed by the capabilities. let surface_capabilities = device .physical_device() .surface_capabilities(&surface, Default::default()) @@ -226,17 +225,17 @@ fn main() { min_image_count: surface_capabilities.min_image_count, image_format, + // The dimensions of the window, only used to initially setup the swapchain. + // // NOTE: // On some drivers the swapchain dimensions are specified by // `surface_capabilities.current_extent` and the swapchain size must use these - // dimensions. - // These dimensions are always the same as the window dimensions. + // dimensions. These dimensions are always the same as the window dimensions. // // However, other drivers don't specify a value, i.e. // `surface_capabilities.current_extent` is `None`. These drivers will allow - // anything, but the only sensible value is the window - // dimensions. + // anything, but the only sensible value is the window dimensions. // // Both of these cases need the swapchain to use the window dimensions, so we just // use that. @@ -260,11 +259,11 @@ fn main() { let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); - // We now create a buffer that will store the shape of our triangle. - // We use #[repr(C)] here to force rustc to not do anything funky with our data, although for this - // particular example, it doesn't actually change the in-memory representation. + // We now create a buffer that will store the shape of our triangle. We use `#[repr(C)]` here + // to force rustc to use a defined layout for our data, as the default representation has *no + // guarantees*. + #[derive(BufferContents, Vertex)] #[repr(C)] - #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] struct Vertex { #[format(R32G32_SFLOAT)] position: [f32; 2], @@ -293,47 +292,45 @@ fn main() { // The next step is to create the shaders. // - // The raw shader creation API provided by the vulkano library is unsafe for various - // reasons, so The `shader!` macro provides a way to generate a Rust module from GLSL - // source - in the example below, the source is provided as a string input directly to - // the shader, but a path to a source file can be provided as well. Note that the user - // must specify the type of shader (e.g., "vertex," "fragment, etc.") using the `ty` - // option of the macro. + // The raw shader creation API provided by the vulkano library is unsafe for various reasons, + // so The `shader!` macro provides a way to generate a Rust module from GLSL source - in the + // example below, the source is provided as a string input directly to the shader, but a path + // to a source file can be provided as well. Note that the user must specify the type of shader + // (e.g. "vertex", "fragment", etc.) using the `ty` option of the macro. // - // The module generated by the `shader!` macro includes a `load` function which loads - // the shader using an input logical device. The module also includes type definitions - // for layout structures defined in the shader source, for example, uniforms and push - // constants. + // The items generated by the `shader!` macro include a `load` function which loads the shader + // using an input logical device. The module also includes type definitions for layout + // structures defined in the shader source, for example uniforms and push constants. // // A more detailed overview of what the `shader!` macro generates can be found in the - // `vulkano-shaders` crate docs. You can view them at https://docs.rs/vulkano-shaders/ + // vulkano-shaders crate docs. You can view them at https://docs.rs/vulkano-shaders/ mod vs { vulkano_shaders::shader! { ty: "vertex", - src: " - #version 450 + src: r" + #version 450 - layout(location = 0) in vec2 position; + layout(location = 0) in vec2 position; - void main() { - gl_Position = vec4(position, 0.0, 1.0); - } - " + void main() { + gl_Position = vec4(position, 0.0, 1.0); + } + ", } } mod fs { vulkano_shaders::shader! { ty: "fragment", - src: " - #version 450 + src: r" + #version 450 - layout(location = 0) out vec4 f_color; + layout(location = 0) out vec4 f_color; - void main() { - f_color = vec4(1.0, 0.0, 0.0, 1.0); - } - " + void main() { + f_color = vec4(1.0, 0.0, 0.0, 1.0); + } + ", } } @@ -345,27 +342,28 @@ fn main() { // manually. // The next step is to create a *render pass*, which is an object that describes where the - // output of the graphics pipeline will go. It describes the layout of the images - // where the colors, depth and/or stencil information will be written. + // output of the graphics pipeline will go. It describes the layout of the images where the + // colors, depth and/or stencil information will be written. let render_pass = vulkano::single_pass_renderpass!( device.clone(), attachments: { // `color` is a custom name we give to the first and only attachment. color: { - // `load: Clear` means that we ask the GPU to clear the content of this - // attachment at the start of the drawing. + // `load: Clear` means that we ask the GPU to clear the content of this attachment + // at the start of the drawing. load: Clear, - // `store: Store` means that we ask the GPU to store the output of the draw - // in the actual image. We could also ask it to discard the result. + // `store: Store` means that we ask the GPU to store the output of the draw in the + // actual image. We could also ask it to discard the result. store: Store, - // `format: ` indicates the type of the format of the image. This has to - // be one of the types of the `vulkano::format` module (or alternatively one - // of your structs that implements the `FormatDesc` trait). Here we use the - // same format as the swapchain. + // `format: ` indicates the type of the format of the image. This has to be one + // of the types of the `vulkano::format` module (or alternatively one of your + // structs that implements the `FormatDesc` trait). Here we use the same format as + // the swapchain. format: swapchain.image_format(), // `samples: 1` means that we ask the GPU to use one sample to determine the value - // of each pixel in the color attachment. We could use a larger value (multisampling) - // for antialiasing. An example of this can be found in msaa-renderpass.rs. + // of each pixel in the color attachment. We could use a larger value + // (multisampling) for antialiasing. An example of this can be found in + // msaa-renderpass.rs. samples: 1, } }, @@ -399,7 +397,7 @@ fn main() { .build(device.clone()) .unwrap(); - // Dynamic viewports allow us to recreate just the viewport when the window is resized + // Dynamic viewports allow us to recreate just the viewport when the window is resized. // Otherwise we would have to recreate the whole pipeline. let mut viewport = Viewport { origin: [0.0, 0.0], @@ -429,8 +427,8 @@ fn main() { // // In this situation, acquiring a swapchain image or presenting it will return an error. // Rendering to an image of that swapchain will not produce any error, but may or may not work. - // To continue rendering, we need to recreate the swapchain by creating a new swapchain. - // Here, we remember that we need to do this for the next loop iteration. + // To continue rendering, we need to recreate the swapchain by creating a new swapchain. Here, + // we remember that we need to do this for the next loop iteration. let mut recreate_swapchain = false; // In the loop below we are going to submit commands to the GPU. Submitting a command produces @@ -456,22 +454,23 @@ fn main() { recreate_swapchain = true; } Event::RedrawEventsCleared => { - // Do not draw frame when screen dimensions are zero. - // On Windows, this can occur from minimizing the application. + // Do not draw the frame when the screen dimensions are zero. On Windows, this can + // occur when minimizing the application. let window = surface.object().unwrap().downcast_ref::().unwrap(); let dimensions = window.inner_size(); if dimensions.width == 0 || dimensions.height == 0 { return; } - // It is important to call this function from time to time, otherwise resources will keep - // accumulating and you will eventually reach an out of memory error. - // Calling this function polls various fences in order to determine what the GPU has - // already processed, and frees the resources that are no longer needed. + // It is important to call this function from time to time, otherwise resources + // will keep accumulating and you will eventually reach an out of memory error. + // Calling this function polls various fences in order to determine what the GPU + // has already processed, and frees the resources that are no longer needed. previous_frame_end.as_mut().unwrap().cleanup_finished(); - // Whenever the window resizes we need to recreate everything dependent on the window size. - // In this example that includes the swapchain, the framebuffers and the dynamic state viewport. + // Whenever the window resizes we need to recreate everything dependent on the + // window size. In this example that includes the swapchain, the framebuffers and + // the dynamic state viewport. if recreate_swapchain { // Use the new dimensions of the window. @@ -481,30 +480,33 @@ fn main() { ..swapchain.create_info() }) { Ok(r) => r, - // This error tends to happen when the user is manually resizing the window. - // Simply restarting the loop is the easiest way to fix this issue. + // This error tends to happen when the user is manually resizing the + // window. Simply restarting the loop is the easiest way to fix this + // issue. Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, - Err(e) => panic!("Failed to recreate swapchain: {e:?}"), + Err(e) => panic!("failed to recreate swapchain: {e}"), }; swapchain = new_swapchain; - // Because framebuffers contains an Arc on the old swapchain, we need to + + // Because framebuffers contains a reference to the old swapchain, we need to // recreate framebuffers as well. framebuffers = window_size_dependent_setup( &new_images, render_pass.clone(), &mut viewport, ); + recreate_swapchain = false; } - // Before we can draw on the output, we have to *acquire* an image from the swapchain. If - // no image is available (which happens if you submit draw commands too quickly), then the - // function will block. - // This operation returns the index of the image that we are allowed to draw upon. + // Before we can draw on the output, we have to *acquire* an image from the + // swapchain. If no image is available (which happens if you submit draw commands + // too quickly), then the function will block. This operation returns the index of + // the image that we are allowed to draw upon. // - // This function can block if no image is available. The parameter is an optional timeout - // after which the function call will return an error. + // This function can block if no image is available. The parameter is an optional + // timeout after which the function call will return an error. let (image_index, suboptimal, acquire_future) = match acquire_next_image(swapchain.clone(), None) { Ok(r) => r, @@ -512,25 +514,26 @@ fn main() { recreate_swapchain = true; return; } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("failed to acquire next image: {e}"), }; - // acquire_next_image can be successful, but suboptimal. This means that the swapchain image - // will still work, but it may not display correctly. With some drivers this can be when - // the window resizes, but it may not cause the swapchain to become out of date. + // `acquire_next_image` can be successful, but suboptimal. This means that the + // swapchain image will still work, but it may not display correctly. With some + // drivers this can be when the window resizes, but it may not cause the swapchain + // to become out of date. if suboptimal { recreate_swapchain = true; } - // In order to draw, we have to build a *command buffer*. The command buffer object holds - // the list of commands that are going to be executed. + // In order to draw, we have to build a *command buffer*. The command buffer object + // holds the list of commands that are going to be executed. // // Building a command buffer is an expensive operation (usually a few hundred - // microseconds), but it is known to be a hot path in the driver and is expected to be - // optimized. + // microseconds), but it is known to be a hot path in the driver and is expected to + // be optimized. // - // Note that we have to pass a queue family when we create the command buffer. The command - // buffer will only be executable on that given queue family. + // Note that we have to pass a queue family when we create the command buffer. The + // command buffer will only be executable on that given queue family. let mut builder = AutoCommandBufferBuilder::primary( &command_buffer_allocator, queue.queue_family_index(), @@ -543,12 +546,13 @@ fn main() { .begin_render_pass( RenderPassBeginInfo { // A list of values to clear the attachments with. This list contains - // one item for each attachment in the render pass. In this case, - // there is only one attachment, and we clear it with a blue color. + // one item for each attachment in the render pass. In this case, there + // is only one attachment, and we clear it with a blue color. // // Only attachments that have `LoadOp::Clear` are provided with clear // values, any others should use `ClearValue::None` as the clear value. clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into())], + ..RenderPassBeginInfo::framebuffer( framebuffers[image_index as usize].clone(), ) @@ -559,17 +563,17 @@ fn main() { SubpassContents::Inline, ) .unwrap() - // We are now inside the first subpass of the render pass. We add a draw command. + // We are now inside the first subpass of the render pass. // - // The last two parameters contain the list of resources to pass to the shaders. - // Since we used an `EmptyPipeline` object, the objects have to be `()`. + // TODO: Document state setting and how it affects subsequent draw commands. .set_viewport(0, [viewport.clone()]) .bind_pipeline_graphics(pipeline.clone()) .bind_vertex_buffers(0, vertex_buffer.clone()) + // We add a draw command. .draw(vertex_buffer.len() as u32, 1, 0, 0) .unwrap() - // We leave the render pass. Note that if we had multiple - // subpasses we could have called `next_subpass` to jump to the next subpass. + // We leave the render pass. Note that if we had multiple subpasses we could + // have called `next_subpass` to jump to the next subpass. .end_render_pass() .unwrap(); @@ -582,12 +586,14 @@ fn main() { .join(acquire_future) .then_execute(queue.clone(), command_buffer) .unwrap() - // The color output is now expected to contain our triangle. But in order to show it on - // the screen, we have to *present* the image by calling `present`. + // The color output is now expected to contain our triangle. But in order to + // show it on the screen, we have to *present* the image by calling + // `then_swapchain_present`. // - // This function does not actually present the image immediately. Instead it submits a - // present command at the end of the queue. This means that it will only be presented once - // the GPU has finished executing the command buffer that draws the triangle. + // This function does not actually present the image immediately. Instead it + // submits a present command at the end of the queue. This means that it will + // only be presented once the GPU has finished executing the command buffer + // that draws the triangle. .then_swapchain_present( queue.clone(), SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index), @@ -603,7 +609,7 @@ fn main() { previous_frame_end = Some(sync::now(device.clone()).boxed()); } Err(e) => { - panic!("Failed to flush future: {e:?}"); + panic!("failed to flush future: {e}"); // previous_frame_end = Some(sync::now(device.clone()).boxed()); } } @@ -613,7 +619,7 @@ fn main() { }); } -/// This method is called once during initialization, then again whenever the window is resized +/// This function is called once during initialization, then again whenever the window is resized. fn window_size_dependent_setup( images: &[Arc], render_pass: Arc, diff --git a/examples/src/lib.rs b/examples/src/lib.rs index 74843acf..625f50cc 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -7,11 +7,10 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use bytemuck::{Pod, Zeroable}; -use vulkano::pipeline::graphics::vertex_input::Vertex; +use vulkano::{buffer::BufferContents, pipeline::graphics::vertex_input::Vertex}; +#[derive(BufferContents, Vertex)] #[repr(C)] -#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] pub struct Position { #[format(R32G32B32_SFLOAT)] position: [f32; 3], @@ -1614,8 +1613,8 @@ pub const POSITIONS: [Position; 531] = [ }, ]; +#[derive(BufferContents, Vertex)] #[repr(C)] -#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] pub struct Normal { #[format(R32G32B32_SFLOAT)] normal: [f32; 3], diff --git a/vulkano-shaders/Cargo.toml b/vulkano-shaders/Cargo.toml index 30f19541..0d67c483 100644 --- a/vulkano-shaders/Cargo.toml +++ b/vulkano-shaders/Cargo.toml @@ -2,7 +2,10 @@ name = "vulkano-shaders" version = "0.32.0" edition = "2021" -authors = ["Pierre Krieger ", "The vulkano contributors"] +authors = [ + "Pierre Krieger ", + "The vulkano contributors", +] repository = "https://github.com/vulkano-rs/vulkano" description = "Shaders rust code generation macro" license = "MIT/Apache-2.0" @@ -24,7 +27,5 @@ syn = { version = "1.0", features = ["full", "extra-traits"] } vulkano = { version = "0.32.0", path = "../vulkano" } [features] -cgmath = [] -nalgebra = [] shaderc-build-from-source = ["shaderc/build-from-source"] shaderc-debug = [] diff --git a/vulkano-shaders/src/codegen.rs b/vulkano-shaders/src/codegen.rs index de58654d..c9482b7f 100644 --- a/vulkano-shaders/src/codegen.rs +++ b/vulkano-shaders/src/codegen.rs @@ -7,27 +7,31 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use crate::{entry_point, read_file_to_string, structs, LinAlgType, RegisteredType, TypesMeta}; -use ahash::HashMap; +use crate::{ + entry_point, + structs::{self, TypeRegistry}, + MacroInput, +}; +use heck::ToSnakeCase; use proc_macro2::TokenStream; pub use shaderc::{CompilationArtifact, IncludeType, ResolvedInclude, ShaderKind}; -use shaderc::{CompileOptions, Compiler, EnvVersion, SpirvVersion, TargetEnv}; +use shaderc::{CompileOptions, Compiler, EnvVersion, TargetEnv}; use std::{ - cell::{RefCell, RefMut}, - io::Error as IoError, + cell::RefCell, + fs, iter::Iterator, - path::Path, + path::{Path, PathBuf}, }; -use vulkano::shader::{ - reflect, - spirv::{Spirv, SpirvError}, +use syn::{Error, LitStr}; +use vulkano::{ + shader::{reflect, spirv::Spirv}, + Version, }; -pub(super) fn path_to_str(path: &Path) -> &str { - path.to_str().expect( - "Could not stringify the file to be included. Make sure the path consists of \ - valid unicode characters.", - ) +pub struct Shader { + pub source: LitStr, + pub name: String, + pub spirv: Spirv, } #[allow(clippy::too_many_arguments)] @@ -36,40 +40,39 @@ fn include_callback( directive_type: IncludeType, contained_within_path_raw: &str, recursion_depth: usize, - include_directories: &[impl AsRef], + include_directories: &[PathBuf], root_source_has_path: bool, - base_path: &impl AsRef, - mut includes_tracker: RefMut<'_, Vec>, + base_path: &Path, + includes: &mut Vec, ) -> Result { let file_to_include = match directive_type { IncludeType::Relative => { let requested_source_path = Path::new(requested_source_path_raw); - // Is embedded current shader source embedded within a rust macro? - // If so, abort unless absolute path. + // If the shader source is embedded within the macro, abort unless we get an absolute + // path. if !root_source_has_path && recursion_depth == 1 && !requested_source_path.is_absolute() { let requested_source_name = requested_source_path .file_name() - .expect("Could not get the name of the requested source file.") + .expect("failed to get the name of the requested source file") .to_string_lossy(); let requested_source_directory = requested_source_path .parent() - .expect("Could not get the directory of the requested source file.") + .expect("failed to get the directory of the requested source file") .to_string_lossy(); return Err(format!( - "Usage of relative paths in imports in embedded GLSL is not \ - allowed, try using `#include <{}>` and adding the directory \ - `{}` to the `include` array in your `shader!` macro call \ - instead.", - requested_source_name, requested_source_directory + "usage of relative paths in imports in embedded GLSL is not allowed, try \ + using `#include <{}>` and adding the directory `{}` to the `include` array in \ + your `shader!` macro call instead", + requested_source_name, requested_source_directory, )); } let mut resolved_path = if recursion_depth == 1 { Path::new(contained_within_path_raw) .parent() - .map(|parent| base_path.as_ref().join(parent)) + .map(|parent| base_path.join(parent)) } else { Path::new(contained_within_path_raw) .parent() @@ -77,17 +80,17 @@ fn include_callback( } .unwrap_or_else(|| { panic!( - "The file `{}` does not reside in a directory. This is \ - an implementation error.", - contained_within_path_raw + "the file `{}` does not reside in a directory, this is an implementation \ + error", + contained_within_path_raw, ) }); resolved_path.push(requested_source_path); if !resolved_path.is_file() { return Err(format!( - "Invalid inclusion path `{}`, the path does not point to a file.", - requested_source_path_raw + "invalid inclusion path `{}`, the path does not point to a file", + requested_source_path_raw, )); } @@ -101,79 +104,78 @@ fn include_callback( // in the relative include directive or when using absolute paths in a standard // include directive. return Err(format!( - "No such file found, as specified by the absolute path. \ - Keep in mind, that absolute paths cannot be used with \ - inclusion from standard directories (`#include <...>`), try \ - using `#include \"...\"` instead. Requested path: {}", - requested_source_path_raw + "no such file found as specified by the absolute path; keep in mind that \ + absolute paths cannot be used with inclusion from standard directories \ + (`#include <...>`), try using `#include \"...\"` instead; requested path: {}", + requested_source_path_raw, )); } let found_requested_source_path = include_directories .iter() - .map(|include_directory| include_directory.as_ref().join(requested_source_path)) + .map(|include_directory| include_directory.join(requested_source_path)) .find(|resolved_requested_source_path| resolved_requested_source_path.is_file()); if let Some(found_requested_source_path) = found_requested_source_path { found_requested_source_path } else { return Err(format!( - "Could not include the file `{}` from any include directories.", - requested_source_path_raw + "failed to include the file `{}` from any include directories", + requested_source_path_raw, )); } } }; - let file_to_include_string = path_to_str(file_to_include.as_path()).to_string(); - let content = read_file_to_string(file_to_include.as_path()).map_err(|_| { + let content = fs::read_to_string(file_to_include.as_path()).map_err(|err| { format!( - "Could not read the contents of file `{}` to be included in the \ - shader source.", - &file_to_include_string + "failed to read the contents of file `{file_to_include:?}` to be included in the \ + shader source: {err}", ) })?; + let resolved_name = file_to_include + .into_os_string() + .into_string() + .map_err(|_| { + "failed to stringify the file to be included; make sure the path consists of valid \ + unicode characters" + })?; - includes_tracker.push(file_to_include_string.clone()); + includes.push(resolved_name.clone()); Ok(ResolvedInclude { - resolved_name: file_to_include_string, + resolved_name, content, }) } -#[allow(clippy::too_many_arguments)] -pub fn compile( +pub(super) fn compile( + input: &MacroInput, path: Option, - base_path: &impl AsRef, + base_path: &Path, code: &str, - ty: ShaderKind, - include_directories: &[impl AsRef], - macro_defines: &[(impl AsRef, impl AsRef)], - vulkan_version: Option, - spirv_version: Option, + shader_kind: ShaderKind, ) -> Result<(CompilationArtifact, Vec), String> { - let includes_tracker = RefCell::new(Vec::new()); + let includes = RefCell::new(Vec::new()); let compiler = Compiler::new().ok_or("failed to create GLSL compiler")?; - let mut compile_options = CompileOptions::new().ok_or("failed to initialize compile option")?; + let mut compile_options = + CompileOptions::new().ok_or("failed to initialize compile options")?; compile_options.set_target_env( TargetEnv::Vulkan, - vulkan_version.unwrap_or(EnvVersion::Vulkan1_0) as u32, + input.vulkan_version.unwrap_or(EnvVersion::Vulkan1_0) as u32, ); - if let Some(spirv_version) = spirv_version { + if let Some(spirv_version) = input.spirv_version { compile_options.set_target_spirv(spirv_version); } - let root_source_path = if let &Some(ref path) = &path { - path - } else { - // An arbitrary placeholder file name for embedded shaders - "shader.glsl" - }; + let root_source_path = path.as_deref().unwrap_or( + // An arbitrary placeholder file name for embedded shaders. + "shader.glsl", + ); - // Specify file resolution callback for the `#include` directive + // Specify the file resolution callback for the `#include` directive. compile_options.set_include_callback( |requested_source_path, directive_type, contained_within_path, recursion_depth| { include_callback( @@ -181,52 +183,68 @@ pub fn compile( directive_type, contained_within_path, recursion_depth, - include_directories, + &input.include_directories, path.is_some(), base_path, - includes_tracker.borrow_mut(), + &mut includes.borrow_mut(), ) }, ); - for (macro_name, macro_value) in macro_defines.iter() { - compile_options.add_macro_definition(macro_name.as_ref(), Some(macro_value.as_ref())); + for (macro_name, macro_value) in &input.macro_defines { + compile_options.add_macro_definition(macro_name, Some(macro_value)); } #[cfg(feature = "shaderc-debug")] compile_options.set_generate_debug_info(); let content = compiler - .compile_into_spirv(code, ty, root_source_path, "main", Some(&compile_options)) - .map_err(|e| e.to_string())?; + .compile_into_spirv( + code, + shader_kind, + root_source_path, + "main", + Some(&compile_options), + ) + .map_err(|e| e.to_string().replace("(s): ", "(s):\n"))?; - let includes = includes_tracker.borrow().clone(); + drop(compile_options); - Ok((content, includes)) + Ok((content, includes.into_inner())) } -pub(super) fn reflect<'a, L: LinAlgType>( - prefix: &'a str, +pub(super) fn reflect( + input: &MacroInput, + source: LitStr, + name: String, words: &[u32], - types_meta: &TypesMeta, - input_paths: impl IntoIterator, - shared_constants: bool, - types_registry: &'a mut HashMap, + input_paths: Vec, + type_registry: &mut TypeRegistry, ) -> Result<(TokenStream, TokenStream), Error> { - let spirv = Spirv::new(words)?; + let spirv = Spirv::new(words).map_err(|err| { + Error::new_spanned(&source, format!("failed to parse SPIR-V words: {err}")) + })?; + let shader = Shader { + source, + name, + spirv, + }; let include_bytes = input_paths.into_iter().map(|s| { quote! { - // using include_bytes here ensures that changing the shader will force recompilation. + // Using `include_bytes` here ensures that changing the shader will force recompilation. // The bytes themselves can be optimized out by the compiler as they are unused. ::std::include_bytes!( #s ) } }); let spirv_version = { - let major = spirv.version().major; - let minor = spirv.version().minor; - let patch = spirv.version().patch; + let Version { + major, + minor, + patch, + } = shader.spirv.version(); + quote! { ::vulkano::Version { major: #major, @@ -235,35 +253,34 @@ pub(super) fn reflect<'a, L: LinAlgType>( } } }; - let spirv_capabilities = reflect::spirv_capabilities(&spirv).map(|capability| { + let spirv_capabilities = reflect::spirv_capabilities(&shader.spirv).map(|capability| { let name = format_ident!("{}", format!("{:?}", capability)); quote! { &::vulkano::shader::spirv::Capability::#name } }); - let spirv_extensions = reflect::spirv_extensions(&spirv); - let entry_points = reflect::entry_points(&spirv) + let spirv_extensions = reflect::spirv_extensions(&shader.spirv); + let entry_points = reflect::entry_points(&shader.spirv) .map(|(name, model, info)| entry_point::write_entry_point(&name, model, &info)); - let specialization_constants = structs::write_specialization_constants::( - prefix, - &spirv, - shared_constants, - types_registry, - ); + let specialization_constants = + structs::write_specialization_constants(input, &shader, type_registry)?; - let load_name = if prefix.is_empty() { + let load_name = if shader.name.is_empty() { format_ident!("load") } else { - format_ident!("load_{}", prefix) + format_ident!("load_{}", shader.name.to_snake_case()) }; let shader_code = quote! { - /// Loads the shader in Vulkan as a `ShaderModule`. - #[inline] + /// Loads the shader as a `ShaderModule`. #[allow(unsafe_code)] - pub fn #load_name(device: ::std::sync::Arc<::vulkano::device::Device>) - -> Result<::std::sync::Arc<::vulkano::shader::ShaderModule>, ::vulkano::shader::ShaderCreationError> - { - let _bytes = ( #( #include_bytes),* ); + #[inline] + pub fn #load_name( + device: ::std::sync::Arc<::vulkano::device::Device>, + ) -> ::std::result::Result< + ::std::sync::Arc<::vulkano::shader::ShaderModule>, + ::vulkano::shader::ShaderCreationError, + > { + let _bytes = ( #( #include_bytes ),* ); static WORDS: &[u32] = &[ #( #words ),* ]; @@ -272,9 +289,9 @@ pub(super) fn reflect<'a, L: LinAlgType>( device, WORDS, #spirv_version, - [#(#spirv_capabilities),*], - [#(#spirv_extensions),*], - [#(#entry_points),*], + [ #( #spirv_capabilities ),* ], + [ #( #spirv_extensions ),* ], + [ #( #entry_points ),* ], ) } } @@ -282,51 +299,19 @@ pub(super) fn reflect<'a, L: LinAlgType>( #specialization_constants }; - let structs = structs::write_structs::(prefix, &spirv, types_meta, types_registry); + let structs = structs::write_structs(input, &shader, type_registry)?; Ok((shader_code, structs)) } -#[derive(Debug)] -pub enum Error { - IoError(IoError), - SpirvError(SpirvError), -} - -impl From for Error { - fn from(err: IoError) -> Error { - Error::IoError(err) - } -} - -impl From for Error { - fn from(err: SpirvError) -> Error { - Error::SpirvError(err) - } -} - #[cfg(test)] mod tests { use super::*; - use crate::{codegen::compile, StdArray}; - use shaderc::ShaderKind; - use std::path::{Path, PathBuf}; - use vulkano::shader::{reflect, spirv::Spirv}; - #[cfg(not(target_os = "windows"))] - pub fn path_separator() -> &'static str { - "/" - } - - #[cfg(target_os = "windows")] - pub fn path_separator() -> &'static str { - "\\" - } - - fn convert_paths(root_path: &Path, paths: &[String]) -> Vec { + fn convert_paths(root_path: &Path, paths: &[PathBuf]) -> Vec { paths .iter() - .map(|p| path_to_str(root_path.join(p).as_path()).to_owned()) + .map(|p| root_path.join(p).into_os_string().into_string().unwrap()) .collect() } @@ -344,194 +329,88 @@ mod tests { } #[test] - fn test_bad_alignment() { - // vec3/mat3/mat3x* are problematic in arrays since their rust - // representations don't have the same array stride as the SPIR-V - // ones. E.g. in a vec3[2], the second element starts on the 16th - // byte, but in a rust [[f32;3];2], the second element starts on the - // 12th byte. Since we can't generate code for these types, we should - // create an error instead of generating incorrect code. - let includes: [PathBuf; 0] = []; - let defines: [(String, String); 0] = []; - let (comp, _) = compile( - None, - &Path::new(""), - " - #version 450 - struct MyStruct { - vec3 vs[2]; - }; - layout(binding=0) uniform UBO { - MyStruct s; - }; - void main() {} - ", - ShaderKind::Vertex, - &includes, - &defines, - None, - None, - ) - .unwrap(); - let spirv = Spirv::new(comp.as_binary()).unwrap(); - let res = std::panic::catch_unwind(|| { - structs::write_structs::( - "", - &spirv, - &TypesMeta::default(), - &mut HashMap::default(), - ) - }); - assert!(res.is_err()); - } - #[test] - fn test_trivial_alignment() { - let includes: [PathBuf; 0] = []; - let defines: [(String, String); 0] = []; - let (comp, _) = compile( - None, - &Path::new(""), - " - #version 450 - struct MyStruct { - vec4 vs[2]; - }; - layout(binding=0) uniform UBO { - MyStruct s; - }; - void main() {} - ", - ShaderKind::Vertex, - &includes, - &defines, - None, - None, - ) - .unwrap(); - let spirv = Spirv::new(comp.as_binary()).unwrap(); - structs::write_structs::( - "", - &spirv, - &TypesMeta::default(), - &mut HashMap::default(), - ); - } - #[test] - fn test_wrap_alignment() { - // This is a workaround suggested in the case of test_bad_alignment, - // so we should make sure it works. - let includes: [PathBuf; 0] = []; - let defines: [(String, String); 0] = []; - let (comp, _) = compile( - None, - &Path::new(""), - " - #version 450 - struct Vec3Wrap { - vec3 v; - }; - struct MyStruct { - Vec3Wrap vs[2]; - }; - layout(binding=0) uniform UBO { - MyStruct s; - }; - void main() {} - ", - ShaderKind::Vertex, - &includes, - &defines, - None, - None, - ) - .unwrap(); - let spirv = Spirv::new(comp.as_binary()).unwrap(); - structs::write_structs::( - "", - &spirv, - &TypesMeta::default(), - &mut HashMap::default(), - ); - } - - #[test] - fn test_include_resolution() { + fn include_resolution() { let root_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let empty_includes: [PathBuf; 0] = []; - let defines: [(String, String); 0] = []; + let (_compile_relative, _) = compile( + &MacroInput::empty(), Some(String::from("tests/include_test.glsl")), &root_path, - " - #version 450 - #include \"include_dir_a/target_a.glsl\" - #include \"include_dir_b/target_b.glsl\" - void main() {} - ", + r#" + #version 450 + #include "include_dir_a/target_a.glsl" + #include "include_dir_b/target_b.glsl" + void main() {} + "#, ShaderKind::Vertex, - &empty_includes, - &defines, - None, - None, ) - .expect("Cannot resolve include files"); + .expect("cannot resolve include files"); let (_compile_include_paths, includes) = compile( + &MacroInput { + include_directories: vec![ + root_path.join("tests").join("include_dir_a"), + root_path.join("tests").join("include_dir_b"), + ], + ..MacroInput::empty() + }, Some(String::from("tests/include_test.glsl")), &root_path, - " - #version 450 - #include - #include - void main() {} - ", + r#" + #version 450 + #include + #include + void main() {} + "#, ShaderKind::Vertex, - &[ - root_path.join("tests").join("include_dir_a"), - root_path.join("tests").join("include_dir_b"), - ], - &defines, - None, - None, ) - .expect("Cannot resolve include files"); + .expect("cannot resolve include files"); + assert_eq!( includes, convert_paths( &root_path, &[ - vec!["tests", "include_dir_a", "target_a.glsl"].join(path_separator()), - vec!["tests", "include_dir_b", "target_b.glsl"].join(path_separator()), - ] - ) + ["tests", "include_dir_a", "target_a.glsl"] + .into_iter() + .collect(), + ["tests", "include_dir_b", "target_b.glsl"] + .into_iter() + .collect(), + ], + ), ); let (_compile_include_paths_with_relative, includes_with_relative) = compile( + &MacroInput { + include_directories: vec![root_path.join("tests").join("include_dir_a")], + ..MacroInput::empty() + }, Some(String::from("tests/include_test.glsl")), &root_path, - " - #version 450 - #include - #include <../include_dir_b/target_b.glsl> - void main() {} - ", + r#" + #version 450 + #include + #include <../include_dir_b/target_b.glsl> + void main() {} + "#, ShaderKind::Vertex, - &[root_path.join("tests").join("include_dir_a")], - &defines, - None, - None, ) - .expect("Cannot resolve include files"); + .expect("cannot resolve include files"); + assert_eq!( includes_with_relative, convert_paths( &root_path, &[ - vec!["tests", "include_dir_a", "target_a.glsl"].join(path_separator()), - vec!["tests", "include_dir_a", "../include_dir_b/target_b.glsl"] - .join(path_separator()), - ] - ) + ["tests", "include_dir_a", "target_a.glsl"] + .into_iter() + .collect(), + ["tests", "include_dir_a", "../include_dir_b/target_b.glsl"] + .into_iter() + .collect(), + ], + ), ); let absolute_path = root_path @@ -540,99 +419,99 @@ mod tests { .join("target_a.glsl"); let absolute_path_str = absolute_path .to_str() - .expect("Cannot run tests in a folder with non unicode characters"); + .expect("cannot run tests in a folder with non unicode characters"); let (_compile_absolute_path, includes_absolute_path) = compile( + &MacroInput::empty(), Some(String::from("tests/include_test.glsl")), &root_path, &format!( - " - #version 450 - #include \"{}\" - void main() {{}} - ", - absolute_path_str + r#" + #version 450 + #include "{absolute_path_str}" + void main() {{}} + "#, ), ShaderKind::Vertex, - &empty_includes, - &defines, - None, - None, ) - .expect("Cannot resolve include files"); + .expect("cannot resolve include files"); + assert_eq!( includes_absolute_path, convert_paths( &root_path, - &[vec!["tests", "include_dir_a", "target_a.glsl"].join(path_separator())] - ) + &[["tests", "include_dir_a", "target_a.glsl"] + .into_iter() + .collect()], + ), ); let (_compile_recursive_, includes_recursive) = compile( + &MacroInput { + include_directories: vec![ + root_path.join("tests").join("include_dir_b"), + root_path.join("tests").join("include_dir_c"), + ], + ..MacroInput::empty() + }, Some(String::from("tests/include_test.glsl")), &root_path, - " - #version 450 - #include - void main() {} - ", + r#" + #version 450 + #include + void main() {} + "#, ShaderKind::Vertex, - &[ - root_path.join("tests").join("include_dir_b"), - root_path.join("tests").join("include_dir_c"), - ], - &defines, - None, - None, ) - .expect("Cannot resolve include files"); + .expect("cannot resolve include files"); + assert_eq!( includes_recursive, convert_paths( &root_path, &[ - vec!["tests", "include_dir_c", "target_c.glsl"].join(path_separator()), - vec!["tests", "include_dir_c", "../include_dir_a/target_a.glsl"] - .join(path_separator()), - vec!["tests", "include_dir_b", "target_b.glsl"].join(path_separator()), - ] - ) + ["tests", "include_dir_c", "target_c.glsl"] + .into_iter() + .collect(), + ["tests", "include_dir_c", "../include_dir_a/target_a.glsl"] + .into_iter() + .collect(), + ["tests", "include_dir_b", "target_b.glsl"] + .into_iter() + .collect(), + ], + ), ); } #[test] - fn test_macros() { - let empty_includes: [PathBuf; 0] = []; - let defines = vec![("NAME1", ""), ("NAME2", "58")]; - let no_defines: [(String, String); 0] = []; - let need_defines = " - #version 450 - #if defined(NAME1) && NAME2 > 29 - void main() {} - #endif - "; + fn macros() { + let need_defines = r#" + #version 450 + #if defined(NAME1) && NAME2 > 29 + void main() {} + #endif + "#; + let compile_no_defines = compile( + &MacroInput::empty(), None, - &Path::new(""), + Path::new(""), need_defines, ShaderKind::Vertex, - &empty_includes, - &no_defines, - None, - None, ); assert!(compile_no_defines.is_err()); - let compile_defines = compile( + compile( + &MacroInput { + macro_defines: vec![("NAME1".into(), "".into()), ("NAME2".into(), "58".into())], + ..MacroInput::empty() + }, None, - &Path::new(""), + Path::new(""), need_defines, ShaderKind::Vertex, - &empty_includes, - &defines, - None, - None, - ); - compile_defines.expect("Setting shader macros did not work"); + ) + .expect("setting shader macros did not work"); } /// `entrypoint1.frag.glsl`: @@ -699,7 +578,7 @@ mod tests { /// spirv-link entrypoint1.spv entrypoint2.spv -o multiple_entrypoints.spv /// ``` #[test] - fn test_descriptor_calculation_with_multiple_entrypoints() { + fn descriptor_calculation_with_multiple_entrypoints() { let data = include_bytes!("../tests/multiple_entrypoints.spv"); let instructions: Vec = data .chunks(4) @@ -715,11 +594,12 @@ mod tests { } // Check first entrypoint - let e1_descriptors = descriptors.get(0).expect("Could not find entrypoint1"); + let e1_descriptors = descriptors.get(0).expect("could not find entrypoint1"); let mut e1_bindings = Vec::new(); for loc in e1_descriptors.keys() { e1_bindings.push(*loc); } + assert_eq!(e1_bindings.len(), 5); assert!(e1_bindings.contains(&(0, 0))); assert!(e1_bindings.contains(&(0, 1))); @@ -728,11 +608,12 @@ mod tests { assert!(e1_bindings.contains(&(0, 4))); // Check second entrypoint - let e2_descriptors = descriptors.get(1).expect("Could not find entrypoint2"); + let e2_descriptors = descriptors.get(1).expect("could not find entrypoint2"); let mut e2_bindings = Vec::new(); for loc in e2_descriptors.keys() { e2_bindings.push(*loc); } + assert_eq!(e2_bindings.len(), 3); assert!(e2_bindings.contains(&(0, 0))); assert!(e2_bindings.contains(&(0, 1))); @@ -740,43 +621,38 @@ mod tests { } #[test] - fn test_descriptor_calculation_with_multiple_functions() { - let includes: [PathBuf; 0] = []; - let defines: [(String, String); 0] = []; + fn descriptor_calculation_with_multiple_functions() { let (comp, _) = compile( + &MacroInput::empty(), None, - &Path::new(""), - " - #version 450 + Path::new(""), + r#" + #version 450 - layout(set = 1, binding = 0) buffer Buffer { - vec3 data; - } bo; + layout(set = 1, binding = 0) buffer Buffer { + vec3 data; + } bo; - layout(set = 2, binding = 0) uniform Uniform { - float data; - } ubo; + layout(set = 2, binding = 0) uniform Uniform { + float data; + } ubo; - layout(set = 3, binding = 1) uniform sampler textureSampler; - layout(set = 3, binding = 2) uniform texture2D imageTexture; + layout(set = 3, binding = 1) uniform sampler textureSampler; + layout(set = 3, binding = 2) uniform texture2D imageTexture; - float withMagicSparkles(float data) { - return texture(sampler2D(imageTexture, textureSampler), vec2(data, data)).x; - } + float withMagicSparkles(float data) { + return texture(sampler2D(imageTexture, textureSampler), vec2(data, data)).x; + } - vec3 makeSecretSauce() { - return vec3(withMagicSparkles(ubo.data)); - } + vec3 makeSecretSauce() { + return vec3(withMagicSparkles(ubo.data)); + } - void main() { - bo.data = makeSecretSauce(); - } - ", + void main() { + bo.data = makeSecretSauce(); + } + "#, ShaderKind::Vertex, - &includes, - &defines, - None, - None, ) .unwrap(); let spirv = Spirv::new(comp.as_binary()).unwrap(); @@ -786,6 +662,7 @@ mod tests { for (loc, _reqs) in info.descriptor_binding_requirements { bindings.push(loc); } + assert_eq!(bindings.len(), 4); assert!(bindings.contains(&(1, 0))); assert!(bindings.contains(&(2, 0))); @@ -794,6 +671,6 @@ mod tests { return; } - panic!("Could not find entrypoint"); + panic!("could not find entrypoint"); } } diff --git a/vulkano-shaders/src/entry_point.rs b/vulkano-shaders/src/entry_point.rs index a8e547b1..76866ebd 100644 --- a/vulkano-shaders/src/entry_point.rs +++ b/vulkano-shaders/src/entry_point.rs @@ -8,7 +8,7 @@ // according to those terms. use ahash::HashMap; -use proc_macro2::TokenStream; +use proc_macro2::{Ident, Span, TokenStream}; use vulkano::{ pipeline::layout::PushConstantRange, shader::{ @@ -25,11 +25,7 @@ pub(super) fn write_entry_point( info: &EntryPointInfo, ) -> TokenStream { let execution = write_shader_execution(&info.execution); - let model = syn::parse_str::(&format!( - "::vulkano::shader::spirv::ExecutionModel::{:?}", - model - )) - .unwrap(); + let model = Ident::new(&format!("{:?}", model), Span::call_site()); let descriptor_binding_requirements = write_descriptor_binding_requirements(&info.descriptor_binding_requirements); let push_constant_requirements = @@ -42,7 +38,7 @@ pub(super) fn write_entry_point( quote! { ( #name.to_owned(), - #model, + ::vulkano::shader::spirv::ExecutionModel::#model, ::vulkano::shader::EntryPointInfo { execution: #execution, descriptor_binding_requirements: #descriptor_binding_requirements.into_iter().collect(), @@ -194,7 +190,9 @@ fn write_descriptor_binding_requirements( sampler_compare: #sampler_compare, sampler_no_unnormalized_coordinates: #sampler_no_unnormalized_coordinates, sampler_no_ycbcr_conversion: #sampler_no_ycbcr_conversion, - sampler_with_images: [#(#sampler_with_images_items),*].into_iter().collect(), + sampler_with_images: [ #( #sampler_with_images_items ),* ] + .into_iter() + .collect(), storage_image_atomic: #storage_image_atomic, } ) @@ -205,22 +203,22 @@ fn write_descriptor_binding_requirements( ( (#set_num, #binding_num), ::vulkano::shader::DescriptorBindingRequirements { - descriptor_types: vec![#(#descriptor_types_items),*], + descriptor_types: vec![ #( #descriptor_types_items ),* ], descriptor_count: #descriptor_count, image_format: #image_format, image_multisampled: #image_multisampled, image_scalar_type: #image_scalar_type, image_view_type: #image_view_type, stages: #stages, - descriptors: [#(#descriptor_items),*].into_iter().collect(), + descriptors: [ #( #descriptor_items ),* ].into_iter().collect(), }, - ), + ) } }); quote! { [ - #( #descriptor_binding_requirements )* + #( #descriptor_binding_requirements ),* ] } } @@ -264,13 +262,13 @@ fn write_specialization_constant_requirements( ::vulkano::shader::SpecializationConstantRequirements { size: #size, }, - ), + ) } }); quote! { [ - #( #specialization_constant_requirements )* + #( #specialization_constant_requirements ),* ] } } @@ -301,15 +299,15 @@ fn write_interface(interface: &ShaderInterface) -> TokenStream { num_elements: #num_elements, is_64bit: #is_64bit, }, - name: Some(::std::borrow::Cow::Borrowed(#name)) - }, + name: ::std::option::Option::Some(::std::borrow::Cow::Borrowed(#name)), + } } }, ); quote! { ::vulkano::shader::ShaderInterface::new_unchecked(vec![ - #( #items )* + #( #items ),* ]) } } @@ -363,6 +361,6 @@ fn stages_to_items(stages: ShaderStages) -> TokenStream { .into_iter() .flatten(); - quote! { #(#stages_items)|* } + quote! { #( #stages_items )|* } } } diff --git a/vulkano-shaders/src/lib.rs b/vulkano-shaders/src/lib.rs index e7653ceb..b93515ab 100644 --- a/vulkano-shaders/src/lib.rs +++ b/vulkano-shaders/src/lib.rs @@ -1,14 +1,12 @@ //! The procedural macro for vulkano's shader system. -//! Manages the compile-time compilation of GLSL into SPIR-V and generation of assosciated rust code. +//! Manages the compile-time compilation of GLSL into SPIR-V and generation of associated Rust code. //! //! # Cargo features //! -//! | Feature | Description | -//! |-----------------------------|------------------------------------------------------------------------------| -//! | `shaderc-build-from-source` | Build the `shaderc` library from source when compiling. | -//! | `shaderc-debug` | Compile shaders with debug information included. | -//! | `cgmath` | Generate structures with vectors and matrices from the [`cgmath`] library. | -//! | `nalgebra` | Generate structures with vectors and matrices from the [`nalgebra`] library. | +//! | Feature | Description | +//! |-----------------------------|---------------------------------------------------------| +//! | `shaderc-build-from-source` | Build the `shaderc` library from source when compiling. | +//! | `shaderc-debug` | Compile shaders with debug information included. | //! //! # Basic usage //! @@ -16,14 +14,15 @@ //! mod vs { //! vulkano_shaders::shader!{ //! ty: "vertex", -//! src: " -//! #version 450 +//! src: r" +//! #version 450 //! -//! layout(location = 0) in vec3 position; +//! layout(location = 0) in vec3 position; //! -//! void main() { -//! gl_Position = vec4(position, 1.0); -//! }" +//! void main() { +//! gl_Position = vec4(position, 1.0); +//! } +//! ", //! } //! } //! # fn main() {} @@ -31,37 +30,32 @@ //! //! # Details //! -//! If you want to take a look at what the macro generates, your best options -//! are to either read through the code that handles the generation (the -//! [`reflect`][reflect] function in the `vulkano-shaders` crate) or use a tool -//! such as [cargo-expand][cargo-expand] to view the expansion of the macro in your -//! own code. It is unfortunately not possible to provide a `generated_example` -//! module like some normal macro crates do since derive macros cannot be used from -//! the crate they are declared in. On the other hand, if you are looking for a -//! high-level overview, you can see the below section. +//! If you want to take a look at what the macro generates, your best option is to use +//! [cargo-expand] to view the expansion of the macro in your own code. On the other hand, if you +//! are looking for a high-level overview, you can see the below section. //! //! # Generated code overview //! //! The macro generates the following items of interest: -//! * The `load` constructor. This method takes an `Arc`, calls -//! [`ShaderModule::new`][ShaderModule::new] with the passed-in device and the shader data provided -//! via the macro, and returns `Result, ShaderCreationError>`. -//! Before doing so, it loops through every capability instruction in the shader -//! data, verifying that the passed-in `Device` has the appropriate features -//! enabled. -//! * If the `shaders` option is used, then instead of one `load` constructor, there is one for each -//! shader. They are named based on the provided names, `load_first`, `load_second` etc. -//! * A Rust struct translated from each struct contained in the shader data. -//! By default each structure has a `Clone` and a `Copy` implementations. This -//! behavior could be customized through the `types_meta` macro option(see below -//! for details). -//! * The `SpecializationConstants` struct. This contains a field for every -//! specialization constant found in the shader data. Implementations of -//! `Default` and [`SpecializationConstants`][SpecializationConstants] are also -//! generated for the struct. //! -//! All of these generated items will be accessed through the module when the macro was invoked. -//! If you wanted to store the `Shader` in a struct of your own, you could do something like this: +//! - The `load` constructor. This function takes an `Arc`, calls +//! [`ShaderModule::from_words_with_data`] with the passed-in device and the shader data provided +//! via the macro, and returns `Result, ShaderCreationError>`. Before doing so, +//! it loops through every capability instruction in the shader data, verifying that the +//! passed-in `Device` has the appropriate features enabled. +//! - If the `shaders` option is used, then instead of one `load` constructor, there is one for +//! each shader. They are named based on the provided names, `load_first`, `load_second` etc. +//! - A Rust struct translated from each struct contained in the shader data. By default each +//! structure has a `Clone` and a `Copy` implementation. This behavior could be customized +//! through the `custom_derives` macro option (see below for details). Each struct also has an +//! implementation of [`BufferContents`], so that it can be read from/written to a buffer. +//! - The `SpecializationConstants` struct. This contains a field for every specialization constant +//! found in the shader data. Implementations of [`Default`] and [`SpecializationConstants`] are +//! also generated for the struct. +//! +//! All of these generated items will be accessed through the module where the macro was invoked. +//! If you wanted to store the `ShaderModule` in a struct of your own, you could do something like +//! this: //! //! ``` //! # fn main() {} @@ -72,18 +66,19 @@ //! # mod vs { //! # vulkano_shaders::shader!{ //! # ty: "vertex", -//! # src: " -//! # #version 450 +//! # src: r" +//! # #version 450 //! # -//! # layout(location = 0) in vec3 position; +//! # layout(location = 0) in vec3 position; //! # -//! # void main() { -//! # gl_Position = vec4(position, 1.0); -//! # }" +//! # void main() { +//! # gl_Position = vec4(position, 1.0); +//! # } +//! # ", //! # } //! # } -//! // various use statements -//! // `vertex_shader` module with shader derive +//! // ...various use statements... +//! // ...`vs` module containing a `shader!` call... //! //! pub struct Shaders { //! pub vs: Arc, @@ -100,131 +95,118 @@ //! //! # Options //! -//! The options available are in the form of the following attributes: +//! The options available are in the form of the following fields: //! //! ## `ty: "..."` //! -//! This defines what shader type the given GLSL source will be compiled into. -//! The type can be any of the following: +//! This defines what shader type the given GLSL source will be compiled into. The type can be any +//! of the following: //! -//! * `vertex` -//! * `fragment` -//! * `geometry` -//! * `tess_ctrl` -//! * `tess_eval` -//! * `compute` +//! - `vertex` +//! - `fragment` +//! - `geometry` +//! - `tess_ctrl` +//! - `tess_eval` +//! - `compute` +//! - `raygen` +//! - `anyhit` +//! - `closesthit` +//! - `miss` +//! - `intersection` +//! - `callable` //! //! For details on what these shader types mean, [see Vulkano's documentation][pipeline]. //! //! ## `src: "..."` //! -//! Provides the raw GLSL source to be compiled in the form of a string. Cannot -//! be used in conjunction with the `path` or `bytes` field. +//! Provides the raw GLSL source to be compiled in the form of a string. Cannot be used in +//! conjunction with the `path` or `bytes` field. //! //! ## `path: "..."` //! -//! Provides the path to the GLSL source to be compiled, relative to `Cargo.toml`. -//! Cannot be used in conjunction with the `src` or `bytes` field. +//! Provides the path to the GLSL source to be compiled, relative to your `Cargo.toml`. Cannot be +//! used in conjunction with the `src` or `bytes` field. //! //! ## `bytes: "..."` //! -//! Provides the path to precompiled SPIR-V bytecode, relative to `Cargo.toml`. -//! Cannot be used in conjunction with the `src` or `path` field. -//! This allows using shaders compiled through a separate build system. -//! **Note**: If your shader contains multiple entrypoints with different -//! descriptor sets, you may also need to enable `exact_entrypoint_interface`. +//! Provides the path to precompiled SPIR-V bytecode, relative to your `Cargo.toml`. Cannot be used +//! in conjunction with the `src` or `path` field. This allows using shaders compiled through a +//! separate build system. //! -//! ## `shaders: { First: {src: "...", ty: "..."}, ... }` +//! ## `shaders: { first: { src: "...", ty: "..." }, ... }` //! -//! With these options the user can compile several shaders at a single macro invocation. -//! Each entry key is a suffix that will be put after the name of the generated `load` function and -//! `SpecializationConstants` struct(`FirstSpecializationConstants` in this case). However all other -//! Rust structs translated from the shader source will be shared between shaders. The macro checks -//! that the source structs with the same names between different shaders have the same declaration -//! signature, and throws a compile-time error if they don't. +//! With these options the user can compile several shaders in a single macro invocation. Each +//! entry key will be the suffix of the generated `load` function (`load_first` in this case) and +//! the prefix of the `SpecializationConstants` struct (`FirstSpecializationConstants` in this +//! case). However all other Rust structs translated from the shader source will be shared between +//! shaders. The macro checks that the source structs with the same names between different shaders +//! have the same declaration signature, and throws a compile-time error if they don't. //! -//! Each entry values expecting `src`, `path`, `bytes`, and `ty` pairs same as above. +//! Each entry expects a `src`, `path`, `bytes`, and `ty` pairs same as above. //! -//! Also `SpecializationConstants` can all be shared between shaders by specifying -//! `shared_constants: true,` entry-flag of the `shaders` map. This feature is turned-off by +//! Also, `SpecializationConstants` can be shared between all shaders by specifying the +//! `shared_constants: true,` entry-flag in the `shaders` map. This feature is turned off by //! default. //! -//! ## `include: ["...", "...", ..., "..."]` +//! ## `include: ["...", "...", ...]` //! //! Specifies the standard include directories to be searched through when using the -//! `#include <...>` directive within a shader source. Include directories can be absolute -//! or relative to `Cargo.toml`. -//! If `path` was specified, relative paths can also be used (`#include "..."`), without the need -//! to specify one or more standard include directories. Relative paths are relative to the -//! directory, which contains the source file the `#include "..."` directive is declared in. +//! `#include <...>` directive within a shader source. Include directories can be absolute or +//! relative to your `Cargo.toml`. If `path` was specified, relative paths can also be used +//! (`#include "..."`), without the need to specify one or more standard include directories. +//! Relative paths are relative to the directory which contains the source file the +//! `#include "..."` directive is declared in. //! //! ## `define: [("NAME", "VALUE"), ...]` //! -//! Adds the given macro definitions to the pre-processor. This is equivalent to passing `-DNAME=VALUE` -//! on the command line. +//! Adds the given macro definitions to the pre-processor. This is equivalent to passing the +//! `-DNAME=VALUE` argument on the command line. //! //! ## `vulkan_version: "major.minor"` and `spirv_version: "major.minor"` //! //! Sets the Vulkan and SPIR-V versions to compile into, respectively. These map directly to the -//! [`set_target_env`](shaderc::CompileOptions::set_target_env) and -//! [`set_target_spirv`](shaderc::CompileOptions::set_target_spirv) compile options. -//! If neither option is specified, then SPIR-V 1.0 code targeting Vulkan 1.0 will be generated. +//! [`set_target_env`] and [`set_target_spirv`] compile options. If neither option is specified, +//! then SPIR-V 1.0 code targeting Vulkan 1.0 will be generated. //! //! The generated code must be supported by the device at runtime. If not, then an error will be -//! returned when calling `Shader::load`. +//! returned when calling `load`. //! -//! ## `types_meta: { use a::b; #[derive(Clone, Default, PartialEq ...)] impl Eq }` +//! ## `custom_derives: [Clone, Default, PartialEq, ...]` //! -//! Extends implementations of Rust structs that represent Shader structs. +//! Extends the list of derive macros that are added to the `derive` attribute of Rust structs that +//! represent shader structs. //! -//! By default each generated struct has a `Clone` and a `Copy` implementations -//! only. If the struct has unsized members none of derives or impls applied on -//! this struct. +//! By default each generated struct has a derive for `Clone` and `Copy`. If the struct has unsized +//! members none of the derives are applied on the struct, except [`BufferContents`], which is +//! always derived. //! -//! The block may have as many `use`, `derive` or `impl` statements as needed -//! and in any order. +//! ## `linalg_type: "..."` //! -//! Each `use` declaration will be added to generated `ty` module. And each -//! `derive`'s trait and `impl` statement will be applied to each generated -//! struct inside `ty` module. +//! Specifies the way that linear algebra types should be generated. It can be any of the +//! following: //! -//! For `Default` derive implementation fills a struct data with all zeroes. -//! For `Display` and `Debug` derive implementation prints all fields except `_dummyX`. -//! For `PartialEq` derive implementation all non-`_dummyX` are checking for equality. +//! - `std` +//! - `cgmath` +//! - `nalgebra` //! -//! The macro performs trivial checking for duplicate declarations. To see the -//! final output of generated code the user can also use `dump` macro -//! option(see below). +//! The default is `std`, which uses arrays to represent vectors and matrices. Note that if the +//! chosen crate doesn't have a type that represents a certain linear algebra type (e.g. `mat3`, or +//! a rectangular matrix) then the macro will default back to arrays for that type. //! -//! ## `exact_entrypoint_interface: true` -//! -//! By default, the macro assumes that all resources (Uniforms, Storage Buffers, -//! Images, Samplers, etc) need to be bound into a descriptor set, even if they are -//! not used in the shader code. However, shaders with multiple entrypoints may have -//! conflicting descriptor sets for each entrypoint. Enabling this option will force -//! the macro to only generate descriptor information for resources that are used -//! in each entrypoint. -//! -//! The macro determines which resources are used by looking at each entrypoint's -//! interface and bytecode. See [`src/descriptor_sets.rs`][descriptor_sets] -//! for the exact logic. +//! If you use linear algebra types from a third-party crate, then you have to have the crate in +//! your dependencies with the appropriate feature enabled that adds `bytemuck` support. //! //! ## `dump: true` //! -//! The crate fails to compile but prints the generated rust code to stdout. +//! The crate fails to compile but prints the generated Rust code to stdout. //! -//! [`cgmath`]: https://crates.io/crates/cgmath -//! [`nalgebra`]: https://crates.io/crates/nalgebra -//! [reflect]: https://github.com/vulkano-rs/vulkano/blob/master/vulkano-shaders/src/lib.rs#L67 //! [cargo-expand]: https://github.com/dtolnay/cargo-expand -//! [ShaderModule::new]: https://docs.rs/vulkano/*/vulkano/pipeline/shader/struct.ShaderModule.html#method.new -//! [OomError]: https://docs.rs/vulkano/*/vulkano/enum.OomError.html -//! [pipeline::shader]: https://docs.rs/vulkano/*/vulkano/pipeline/shader/index.html -//! [descriptor]: https://docs.rs/vulkano/*/vulkano/descriptor/index.html -//! [ShaderStages]: https://docs.rs/vulkano/*/vulkano/descriptor/descriptor/struct.ShaderStages.html -//! [SpecializationConstants]: https://docs.rs/vulkano/*/vulkano/pipeline/shader/trait.SpecializationConstants.html -//! [pipeline]: https://docs.rs/vulkano/*/vulkano/pipeline/index.html -//! [descriptor_sets]: https://github.com/vulkano-rs/vulkano/blob/master/vulkano-shaders/src/descriptor_sets.rs#L142 +//! [`ShaderModule::from_words_with_data`]: vulkano::shader::ShaderModule::from_words_with_data +//! [`SpecializationConstants`]: vulkano::shader::SpecializationConstants +//! [pipeline]: vulkano::pipeline +//! [`set_target_env`]: shaderc::CompileOptions::set_target_env +//! [`set_target_spirv`]: shaderc::CompileOptions::set_target_spirv +//! [`BufferContents`]: vulkano::buffer::BufferContents #![doc(html_logo_url = "https://raw.githubusercontent.com/vulkano-rs/vulkano/master/logo.png")] #![recursion_limit = "1024"] @@ -237,313 +219,177 @@ extern crate syn; use crate::codegen::ShaderKind; use ahash::HashMap; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use shaderc::{EnvVersion, SpirvVersion}; use std::{ - borrow::Cow, - env, fs, - fs::File, - io::{Read, Result as IoResult}, - iter::empty, - path::Path, - slice::from_raw_parts, + env, fs, mem, + path::{Path, PathBuf}, + slice, }; +use structs::TypeRegistry; use syn::{ parse::{Parse, ParseStream, Result}, - Ident, ItemUse, LitBool, LitStr, Meta, MetaList, NestedMeta, Path as SynPath, TypeImplTrait, + Error, Ident, LitBool, LitStr, Path as SynPath, }; mod codegen; mod entry_point; mod structs; -/// Generates vectors and matrices using standard Rust arrays. #[proc_macro] pub fn shader(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - shader_inner::(input) -} - -/// Generates vectors and matrices using the [`cgmath`] library where possible, falling back to -/// standard Rust arrays otherwise. -/// -/// [`cgmath`]: https://crates.io/crates/cgmath -#[cfg(feature = "cgmath")] -#[proc_macro] -pub fn shader_cgmath(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - shader_inner::(input) -} - -/// Generates vectors and matrices using the [`nalgebra`] library. -/// -/// [`nalgebra`]: https://crates.io/crates/nalgebra -#[cfg(feature = "nalgebra")] -#[proc_macro] -pub fn shader_nalgebra(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - shader_inner::(input) -} - -fn shader_inner(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as MacroInput); - let is_single = input.shaders.len() == 1; + shader_inner(input) + .unwrap_or_else(Error::into_compile_error) + .into() +} + +fn shader_inner(mut input: MacroInput) -> Result { let root = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()); let root_path = Path::new(&root); + let shaders = mem::take(&mut input.shaders); // yoink let mut shaders_code = Vec::with_capacity(input.shaders.len()); let mut types_code = Vec::with_capacity(input.shaders.len()); - let mut types_registry = HashMap::default(); + let mut type_registry = TypeRegistry::default(); - for (prefix, (shader_kind, shader_source)) in input.shaders { - let (code, types) = if let SourceKind::Bytes(path) = shader_source { - let full_path = root_path.join(&path); + for (name, (shader_kind, source_kind)) in shaders { + let (code, types) = match source_kind { + SourceKind::Src(source) => { + let (artifact, includes) = + codegen::compile(&input, None, root_path, &source.value(), shader_kind) + .map_err(|err| Error::new_spanned(&source, err))?; - let bytes = if full_path.is_file() { - fs::read(full_path) - .unwrap_or_else(|_| panic!("Error reading source from {:?}", path)) - } else { - panic!( - "File {:?} was not found; note that the path must be relative to your Cargo.toml", - path - ); - }; + let words = artifact.as_binary(); - // The SPIR-V specification essentially guarantees that - // a shader will always be an integer number of words - assert_eq!(0, bytes.len() % 4); - codegen::reflect::( - prefix.as_str(), - unsafe { from_raw_parts(bytes.as_slice().as_ptr() as *const u32, bytes.len() / 4) }, - &input.types_meta, - empty(), - input.shared_constants, - &mut types_registry, - ) - .unwrap() - } else { - let (path, full_path, source_code) = match shader_source { - SourceKind::Src(source) => (None, None, source), - SourceKind::Path(path) => { - let full_path = root_path.join(&path); - let source_code = read_file_to_string(&full_path) - .unwrap_or_else(|_| panic!("Error reading source from {:?}", path)); + codegen::reflect(&input, source, name, words, includes, &mut type_registry)? + } + SourceKind::Path(path) => { + let full_path = root_path.join(path.value()); - if full_path.is_file() { - (Some(path.clone()), Some(full_path), source_code) - } else { - panic!("File {:?} was not found; note that the path must be relative to your Cargo.toml", path); - } + if !full_path.is_file() { + bail!( + path, + "file `{full_path:?}` was not found, note that the path must be relative \ + to your Cargo.toml", + ); } - SourceKind::Bytes(_) => unreachable!(), - }; - let include_paths = input - .include_directories - .iter() - .map(|include_directory| { - let include_path = Path::new(include_directory); - let mut full_include_path = root_path.to_owned(); - full_include_path.push(include_path); - full_include_path - }) - .collect::>(); + let source_code = fs::read_to_string(&full_path) + .or_else(|err| bail!(path, "failed to read source `{full_path:?}`: {err}"))?; - let (content, includes) = match codegen::compile( - path, - &root_path, - &source_code, - shader_kind, - &include_paths, - &input.macro_defines, - input.vulkan_version, - input.spirv_version, - ) { - Ok(ok) => ok, - Err(e) => { - if is_single { - panic!("{}", e.replace("(s): ", "(s):\n")) - } else { - panic!("Shader {:?} {}", prefix, e.replace("(s): ", "(s):\n")) - } + let (artifact, mut includes) = codegen::compile( + &input, + Some(path.value()), + root_path, + &source_code, + shader_kind, + ) + .map_err(|err| Error::new_spanned(&path, err))?; + + let words = artifact.as_binary(); + + includes.push(full_path.into_os_string().into_string().unwrap()); + + codegen::reflect(&input, path, name, words, includes, &mut type_registry)? + } + SourceKind::Bytes(path) => { + let full_path = root_path.join(path.value()); + + if !full_path.is_file() { + bail!( + path, + "file `{full_path:?}` was not found, note that the path must be relative \ + to your Cargo.toml", + ); } - }; - let input_paths = includes - .iter() - .map(|s| s.as_ref()) - .chain(full_path.as_deref().map(codegen::path_to_str)); + let bytes = fs::read(&full_path) + .or_else(|err| bail!(path, "failed to read source `{full_path:?}`: {err}"))?; - codegen::reflect::( - prefix.as_str(), - content.as_binary(), - &input.types_meta, - input_paths, - input.shared_constants, - &mut types_registry, - ) - .unwrap() + if bytes.len() % 4 != 0 { + bail!(path, "SPIR-V bytes must be an integer multiple of 4"); + } + + // Here, we are praying that the system allocator of the user aligns allocations to + // at least 4, which *should* be the case on all targets. + assert_eq!(bytes.as_ptr() as usize % 4, 0); + + // SAFETY: We checked that the bytes are aligned correctly for `u32`, and that + // there is an integer number of `u32`s contained. + let words = + unsafe { slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len() / 4) }; + + codegen::reflect(&input, path, name, words, Vec::new(), &mut type_registry)? + } }; shaders_code.push(code); types_code.push(types); } - let uses = &input.types_meta.uses; - let result = quote! { - #( - #shaders_code - )* - - pub mod ty { - #( #uses )* - - #( - #types_code - )* - } + #( #shaders_code )* + #( #types_code )* }; - if input.dump { + if input.dump.value { println!("{}", result); - panic!("`shader!` rust codegen dumped") // TODO: use span from dump + bail!(input.dump, "`shader!` Rust codegen dumped"); } - proc_macro::TokenStream::from(result) + Ok(result) } enum SourceKind { - Src(String), - Path(String), - Bytes(String), -} - -struct TypesMeta { - custom_derives: Vec, - display: bool, - debug: bool, - default: bool, - partial_eq: bool, - uses: Vec, - impls: Vec, -} - -impl Default for TypesMeta { - #[inline] - fn default() -> Self { - Self { - custom_derives: vec![ - parse_quote! { Clone }, - parse_quote! { Copy }, - parse_quote! { bytemuck::Pod }, - parse_quote! { bytemuck::Zeroable }, - ], - partial_eq: false, - debug: false, - display: false, - default: false, - uses: Vec::new(), - impls: Vec::new(), - } - } -} - -impl TypesMeta { - #[inline] - fn empty() -> Self { - Self { - custom_derives: Vec::new(), - partial_eq: false, - debug: false, - display: false, - default: false, - uses: Vec::new(), - impls: Vec::new(), - } - } -} - -struct RegisteredType { - shader: String, - signature: Vec<(String, Cow<'static, str>)>, -} - -impl RegisteredType { - #[inline] - fn assert_signatures(&self, type_name: &str, target_type: &Self) { - if self.signature.len() > target_type.signature.len() { - panic!( - "Shaders {shader_a:} and {shader_b:} declare structs with the \ - same name \"`{type_name:}\", but the struct from {shader_a:} shader \ - contains extra field \"{field:}\"", - shader_a = self.shader, - shader_b = target_type.shader, - type_name = type_name, - field = self.signature[target_type.signature.len()].0 - ); - } - - if self.signature.len() < target_type.signature.len() { - panic!( - "Shaders {shader_a:} and {shader_b:} declare structs with the \ - same name \"{type_name:}\", but the struct from {shader_b:} shader \ - contains extra field \"{field:}\"", - shader_a = self.shader, - shader_b = target_type.shader, - type_name = type_name, - field = target_type.signature[self.signature.len()].0 - ); - } - - let comparison = self - .signature - .iter() - .zip(target_type.signature.iter()) - .enumerate(); - - for (index, ((a_name, a_type), (b_name, b_type))) in comparison { - if a_name != b_name || a_type != b_type { - panic!( - "Shaders {shader_a:} and {shader_b:} declare structs with the \ - same name \"{type_name:}\", but the struct from {shader_a:} shader \ - contains field \"{a_name:}\" of type \"{a_type:}\" in position {index:}, \ - whereas the same struct from {shader_b:} contains field \"{b_name:}\" \ - of type \"{b_type:}\" in the same position", - shader_a = self.shader, - shader_b = target_type.shader, - type_name = type_name, - index = index, - a_name = a_name, - a_type = a_type, - b_name = b_name, - b_type = b_type, - ); - } - } - } + Src(LitStr), + Path(LitStr), + Bytes(LitStr), } struct MacroInput { - dump: bool, - include_directories: Vec, + include_directories: Vec, macro_defines: Vec<(String, String)>, shared_constants: bool, shaders: HashMap, spirv_version: Option, - types_meta: TypesMeta, vulkan_version: Option, + custom_derives: Vec, + linalg_type: LinAlgType, + dump: LitBool, +} + +impl MacroInput { + #[cfg(test)] + fn empty() -> Self { + MacroInput { + include_directories: Vec::new(), + macro_defines: Vec::new(), + shared_constants: false, + shaders: HashMap::default(), + vulkan_version: None, + spirv_version: None, + custom_derives: Vec::new(), + linalg_type: LinAlgType::default(), + dump: LitBool::new(false, Span::call_site()), + } + } } impl Parse for MacroInput { fn parse(input: ParseStream<'_>) -> Result { - let mut dump = None; - let mut exact_entrypoint_interface = None; + let root = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()); + let mut include_directories = Vec::new(); let mut macro_defines = Vec::new(); let mut shared_constants = None; let mut shaders = HashMap::default(); - let mut spirv_version = None; - let mut types_meta = None; let mut vulkan_version = None; + let mut spirv_version = None; + let mut custom_derives = None; + let mut linalg_type = None; + let mut dump = None; fn parse_shader_fields<'k>( output: &mut (Option, Option), @@ -552,12 +398,12 @@ impl Parse for MacroInput { ) -> Result<()> { match name { "ty" => { + let lit = input.parse::()?; if output.0.is_some() { - panic!("Only one `ty` can be defined") + bail!(lit, "field `ty` is already defined"); } - let ty: LitStr = input.parse()?; - let ty = match ty.value().as_ref() { + output.0 = Some(match lit.value().as_str() { "vertex" => ShaderKind::Vertex, "fragment" => ShaderKind::Fragment, "geometry" => ShaderKind::Geometry, @@ -570,89 +416,92 @@ impl Parse for MacroInput { "miss" => ShaderKind::Miss, "intersection" => ShaderKind::Intersection, "callable" => ShaderKind::Callable, - _ => panic!(concat!("Unexpected shader type, valid values: vertex, fragment, geometry, tess_ctrl, ", - "tess_eval, compute, raygen, anyhit, closesthit, miss, intersection, callable")) - }; - - output.0 = Some(ty); + ty => bail!( + lit, + "expected `vertex`, `fragment`, `geometry`, `tess_ctrl`, `tess_eval`, \ + `compute`, `raygen`, `anyhit`, `closesthit`, `miss`, `intersection` or \ + `callable`, found `{ty}`", + ), + }); } - "bytes" => { + let lit = input.parse::()?; if output.1.is_some() { - panic!( - "Only one of `src`, `path`, or `bytes` can be defined per Shader entry" - ) + bail!( + lit, + "only one of `src`, `path`, or `bytes` can be defined per shader entry", + ); } - let path: LitStr = input.parse()?; - output.1 = Some(SourceKind::Bytes(path.value())); + output.1 = Some(SourceKind::Bytes(lit)); } - "path" => { + let lit = input.parse::()?; if output.1.is_some() { - panic!( - "Only one of `src`, `path`, or `bytes` can be defined per Shader entry" - ) + bail!( + lit, + "only one of `src`, `path` or `bytes` can be defined per shader entry", + ); } - let path: LitStr = input.parse()?; - output.1 = Some(SourceKind::Path(path.value())); + output.1 = Some(SourceKind::Path(lit)); } - "src" => { + let lit = input.parse::()?; if output.1.is_some() { - panic!("Only one of `src`, `path`, `bytes` can be defined per Shader entry") + bail!( + lit, + "only one of `src`, `path` or `bytes` can be defined per shader entry", + ); } - let src: LitStr = input.parse()?; - output.1 = Some(SourceKind::Src(src.value())); + output.1 = Some(SourceKind::Src(lit)); } - - other => unreachable!("Unexpected entry key {:?}", other), + _ => unreachable!(), } Ok(()) } while !input.is_empty() { - let name: Ident = input.parse()?; + let field_ident = input.parse::()?; input.parse::()?; - let name = name.to_string(); + let field = field_ident.to_string(); - match name.as_str() { + match field.as_str() { "bytes" | "src" | "path" | "ty" => { if shaders.len() > 1 || (shaders.len() == 1 && !shaders.contains_key("")) { - panic!("Only one of `shaders`, `src`, `path`, or `bytes` can be defined"); + bail!( + field_ident, + "only one of `src`, `path`, `bytes` or `shaders` can be defined", + ); } - parse_shader_fields( - shaders - .entry("".to_string()) - .or_insert_with(Default::default), - name.as_str(), - input, - )?; + parse_shader_fields(shaders.entry(String::new()).or_default(), &field, input)?; } "shaders" => { if !shaders.is_empty() { - panic!("Only one of `shaders`, `src`, `path`, or `bytes` can be defined"); + bail!( + field_ident, + "only one of `src`, `path`, `bytes` or `shaders` can be defined", + ); } let in_braces; braced!(in_braces in input); while !in_braces.is_empty() { - let prefix: Ident = in_braces.parse()?; - let prefix = prefix.to_string(); + let name_ident = in_braces.parse::()?; + let name = name_ident.to_string(); - if prefix.to_string().as_str() == "shared_constants" { + if &name == "shared_constants" { in_braces.parse::()?; + let lit = in_braces.parse::()?; if shared_constants.is_some() { - panic!("Only one `shared_constants` can be defined") + bail!(lit, "field `shared_constants` is already defined"); } - let independent_constants_lit: LitBool = in_braces.parse()?; - shared_constants = Some(independent_constants_lit.value); + shared_constants = Some(lit.value); if !in_braces.is_empty() { in_braces.parse::()?; @@ -661,8 +510,8 @@ impl Parse for MacroInput { continue; } - if shaders.contains_key(&prefix) { - panic!("Shader entry {:?} already defined", prefix); + if shaders.contains_key(&name) { + bail!(name_ident, "shader entry `{name}` is already defined"); } in_braces.parse::()?; @@ -671,22 +520,23 @@ impl Parse for MacroInput { braced!(in_shader_definition in in_braces); while !in_shader_definition.is_empty() { - let name: Ident = in_shader_definition.parse()?; + let field_ident = in_shader_definition.parse::()?; in_shader_definition.parse::()?; - let name = name.to_string(); + let field = field_ident.to_string(); - match name.as_ref() { + match field.as_str() { "bytes" | "src" | "path" | "ty" => { parse_shader_fields( - shaders - .entry(prefix.clone()) - .or_insert_with(Default::default), - name.as_str(), + shaders.entry(name.clone()).or_default(), + &field, &in_shader_definition, )?; } - - name => panic!("Unknown Shader definition field {:?}", name), + field => bail!( + field_ident, + "expected `bytes`, `src`, `path` or `ty` as a field, found \ + `{field}`", + ), } if !in_shader_definition.is_empty() { @@ -698,15 +548,20 @@ impl Parse for MacroInput { in_braces.parse::()?; } - match shaders.get(&prefix).unwrap() { - (None, _) => panic!("Please specify shader's {} type e.g. `ty: \"vertex\"`", prefix), - (_, None) => panic!("Please specify shader's {} source e.g. `path: \"entry_point.glsl\"`", prefix), - _ => () + match shaders.get(&name).unwrap() { + (None, _) => bail!( + "please specify a type for shader `{name}` e.g. `ty: \"vertex\"`", + ), + (_, None) => bail!( + "please specify a source for shader `{name}` e.g. \ + `path: \"entry_point.glsl\"`", + ), + _ => (), } } if shaders.is_empty() { - panic!("At least one Shader entry must be defined"); + bail!("at least one shader entry must be defined"); } } "define" => { @@ -717,9 +572,9 @@ impl Parse for MacroInput { let tuple_input; parenthesized!(tuple_input in array_input); - let name: LitStr = tuple_input.parse()?; + let name = tuple_input.parse::()?; tuple_input.parse::()?; - let value: LitStr = tuple_input.parse()?; + let value = tuple_input.parse::()?; macro_defines.push((name.value(), value.value())); if !array_input.is_empty() { @@ -727,37 +582,40 @@ impl Parse for MacroInput { } } } - "dump" => { - if dump.is_some() { - panic!("Only one `dump` can be defined") - } - let dump_lit: LitBool = input.parse()?; - dump = Some(dump_lit.value); - } - "exact_entrypoint_interface" => { - if exact_entrypoint_interface.is_some() { - panic!("Only one `dump` can be defined") - } - let lit: LitBool = input.parse()?; - exact_entrypoint_interface = Some(lit.value); - } "include" => { let in_brackets; bracketed!(in_brackets in input); while !in_brackets.is_empty() { - let path: LitStr = in_brackets.parse()?; + let path = in_brackets.parse::()?; - include_directories.push(path.value()); + include_directories.push([&root, &path.value()].into_iter().collect()); if !in_brackets.is_empty() { in_brackets.parse::()?; } } } + "vulkan_version" => { + let lit = input.parse::()?; + if vulkan_version.is_some() { + bail!(lit, "field `vulkan_version` is already defined"); + } + + vulkan_version = Some(match lit.value().as_str() { + "1.0" => EnvVersion::Vulkan1_0, + "1.1" => EnvVersion::Vulkan1_1, + "1.2" => EnvVersion::Vulkan1_2, + ver => bail!(lit, "expected `1.0`, `1.1` or `1.2`, found `{ver}`"), + }); + } "spirv_version" => { - let version: LitStr = input.parse()?; - spirv_version = Some(match version.value().as_ref() { + let lit = input.parse::()?; + if spirv_version.is_some() { + bail!(lit, "field `spirv_version` is already defined"); + } + + spirv_version = Some(match lit.value().as_str() { "1.0" => SpirvVersion::V1_0, "1.1" => SpirvVersion::V1_1, "1.2" => SpirvVersion::V1_2, @@ -765,137 +623,69 @@ impl Parse for MacroInput { "1.4" => SpirvVersion::V1_4, "1.5" => SpirvVersion::V1_5, "1.6" => SpirvVersion::V1_6, - _ => panic!("Unknown SPIR-V version: {}", version.value()), + ver => bail!( + lit, + "expected `1.0`, `1.1`, `1.2`, `1.3`, `1.4`, `1.5` or `1.6`, found \ + `{ver}`", + ), }); } + "custom_derives" => { + let in_brackets; + bracketed!(in_brackets in input); + + while !in_brackets.is_empty() { + if custom_derives.is_none() { + custom_derives = Some(Vec::new()); + } + + custom_derives + .as_mut() + .unwrap() + .push(in_brackets.parse::()?); + + if !in_brackets.is_empty() { + in_brackets.parse::()?; + } + } + } "types_meta" => { - let in_braces; - braced!(in_braces in input); - - let mut meta = TypesMeta::empty(); - - while !in_braces.is_empty() { - if in_braces.peek(Token![#]) { - in_braces.parse::()?; - - let in_brackets; - bracketed!(in_brackets in in_braces); - - let derive_list: MetaList = in_brackets.parse()?; - - for derive in derive_list.nested { - match derive { - NestedMeta::Meta(Meta::Path(path)) => { - let custom_derive = if let Some(derive_ident) = - path.get_ident() - { - match derive_ident.to_string().as_str() { - "PartialEq" => { - if meta.partial_eq { - return Err(in_brackets - .error("Duplicate PartialEq derive")); - } - - meta.partial_eq = true; - - false - } - "Debug" => { - if meta.debug { - return Err(in_brackets - .error("Duplicate Debug derive")); - } - - meta.debug = true; - - false - } - "Display" => { - if meta.display { - return Err(in_brackets - .error("Duplicate Display derive")); - } - - meta.display = true; - - false - } - "Default" => { - if meta.default { - return Err(in_brackets - .error("Duplicate Default derive")); - } - - meta.default = true; - - false - } - _ => true, - } - } else { - true - }; - - if custom_derive { - if meta - .custom_derives - .iter() - .any(|candidate| *candidate == path) - { - return Err( - in_braces.error("Duplicate derive declaration") - ); - } - - meta.custom_derives.push(path); - } - } - _ => return Err(in_brackets.error("Unsupported syntax")), - } - } - - continue; - } - - if in_braces.peek(Token![impl]) { - let impl_trait: TypeImplTrait = in_braces.parse()?; - - if meta.impls.iter().any(|candidate| candidate == &impl_trait) { - return Err(in_braces.error("Duplicate \"impl\" declaration")); - } - - meta.impls.push(impl_trait); - - continue; - } - - if in_braces.peek(Token![use]) { - let item_use: ItemUse = in_braces.parse()?; - - if meta.uses.iter().any(|candidate| candidate == &item_use) { - return Err(in_braces.error("Duplicate \"use\" declaration")); - } - - meta.uses.push(item_use); - - continue; - } - - return Err(in_braces.error("Type meta must by \"use a::b::c\", \"#[derive(Type1, Type2, ..)]\" or \"impl Type\"")); + bail!( + field_ident, + "you no longer need to add any derives to use the generated structs in \ + buffers, and you also no longer need bytemuck as a dependency, because \ + `BufferContents` is derived automatically for the generated structs; if \ + you need to add additional derives (e.g. `Debug`, `PartialEq`) then please \ + use the `custom_derives` field of the macro", + ); + } + "linalg_type" => { + let lit = input.parse::()?; + if linalg_type.is_some() { + bail!(lit, "field `linalg_type` is already defined"); } - types_meta = Some(meta); - } - "vulkan_version" => { - let version: LitStr = input.parse()?; - vulkan_version = Some(match version.value().as_ref() { - "1.0" => EnvVersion::Vulkan1_0, - "1.1" => EnvVersion::Vulkan1_1, - "1.2" => EnvVersion::Vulkan1_2, - _ => panic!("Unknown Vulkan version: {}", version.value()), + linalg_type = Some(match lit.value().as_str() { + "std" => LinAlgType::Std, + "cgmath" => LinAlgType::CgMath, + "nalgebra" => LinAlgType::Nalgebra, + ty => bail!(lit, "expected `std`, `cgmath` or `nalgebra`, found `{ty}`"), }); } - name => panic!("Unknown field {:?}", name), + "dump" => { + let lit = input.parse::()?; + if dump.is_some() { + bail!(lit, "field `dump` is already defined"); + } + + dump = Some(lit); + } + field => bail!( + field_ident, + "expected `bytes`, `src`, `path`, `ty`, `shaders`, `define`, `include`, \ + `vulkan_version`, `spirv_version`, `custom_derives`, `linalg_type` or `dump` \ + as a field, found `{field}`", + ), } if !input.is_empty() { @@ -904,19 +694,20 @@ impl Parse for MacroInput { } if shaders.is_empty() { - panic!("Please specify at least one shader e.g. `ty: \"vertex\", src: \"glsl source code\"`"); + bail!(r#"please specify at least one shader e.g. `ty: "vertex", src: ""`"#); } match shaders.get("") { - Some((None, _)) => panic!("Please specify shader's type e.g. `ty: \"vertex\"`"), - Some((_, None)) => { - panic!("Please specify shader's source e.g. `src: \"glsl source code\"`") + Some((None, _)) => { + bail!(r#"please specify the type of the shader e.g. `ty: "vertex"`"#); } - _ => (), + Some((_, None)) => { + bail!(r#"please specify the source of the shader e.g. `src: ""`"#); + } + _ => {} } - Ok(Self { - dump: dump.unwrap_or(false), + Ok(MacroInput { include_directories, macro_defines, shared_constants: shared_constants.unwrap_or(false), @@ -926,70 +717,42 @@ impl Parse for MacroInput { (key, (shader_kind.unwrap(), shader_source.unwrap())) }) .collect(), - spirv_version, - types_meta: types_meta.unwrap_or_default(), vulkan_version, + spirv_version, + custom_derives: custom_derives.unwrap_or_else(|| { + vec![ + parse_quote! { ::std::clone::Clone }, + parse_quote! { ::std::marker::Copy }, + ] + }), + linalg_type: linalg_type.unwrap_or_default(), + dump: dump.unwrap_or_else(|| LitBool::new(false, Span::call_site())), }) } } -pub(self) fn read_file_to_string(full_path: &Path) -> IoResult { - let mut buf = String::new(); - File::open(full_path).and_then(|mut file| file.read_to_string(&mut buf))?; - Ok(buf) +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum LinAlgType { + Std, + CgMath, + Nalgebra, } -trait LinAlgType { - fn vector(component_type: &TokenStream, component_count: usize) -> TokenStream; - fn matrix(component_type: &TokenStream, row_count: usize, column_count: usize) -> TokenStream; -} - -struct StdArray; - -impl LinAlgType for StdArray { - fn vector(component_type: &TokenStream, component_count: usize) -> TokenStream { - quote! { [#component_type; #component_count] } - } - - fn matrix(component_type: &TokenStream, row_count: usize, column_count: usize) -> TokenStream { - quote! { [[#component_type; #row_count]; #column_count] } +impl Default for LinAlgType { + fn default() -> Self { + LinAlgType::Std } } -struct CGMath; - -impl LinAlgType for CGMath { - fn vector(component_type: &TokenStream, component_count: usize) -> TokenStream { - // cgmath only has 1, 2, 3 and 4-component vector types. - // Fall back to arrays for anything else. - if matches!(component_count, 1 | 2 | 3 | 4) { - let ty = format_ident!("{}", format!("Vector{}", component_count)); - quote! { cgmath::#ty<#component_type> } - } else { - StdArray::vector(component_type, component_count) - } - } - - fn matrix(component_type: &TokenStream, row_count: usize, column_count: usize) -> TokenStream { - // cgmath only has square 2x2, 3x3 and 4x4 matrix types. - // Fall back to arrays for anything else. - if row_count == column_count && matches!(column_count, 2 | 3 | 4) { - let ty = format_ident!("{}", format!("Matrix{}", column_count)); - quote! { cgmath::#ty<#component_type> } - } else { - StdArray::matrix(component_type, row_count, column_count) - } - } -} - -struct Nalgebra; - -impl LinAlgType for Nalgebra { - fn vector(component_type: &TokenStream, component_count: usize) -> TokenStream { - quote! { nalgebra::base::SVector<#component_type, #component_count> } - } - - fn matrix(component_type: &TokenStream, row_count: usize, column_count: usize) -> TokenStream { - quote! { nalgebra::base::SMatrix<#component_type, #row_count, #column_count> } - } +macro_rules! bail { + ($msg:literal $(,)?) => { + return Err(syn::Error::new( + proc_macro2::Span::call_site(), + format!($msg), + )) + }; + ($span:expr, $msg:literal $(,)?) => { + return Err(syn::Error::new_spanned(&$span, format!($msg))) + }; } +use bail; diff --git a/vulkano-shaders/src/structs.rs b/vulkano-shaders/src/structs.rs index debd3e9a..e4cb053c 100644 --- a/vulkano-shaders/src/structs.rs +++ b/vulkano-shaders/src/structs.rs @@ -7,327 +7,140 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use crate::{LinAlgType, RegisteredType, TypesMeta}; +use crate::{bail, codegen::Shader, LinAlgType, MacroInput}; use ahash::HashMap; use heck::ToUpperCamelCase; use proc_macro2::{Span, TokenStream}; -use std::{borrow::Cow, mem}; -use syn::{Ident, LitStr}; -use vulkano::shader::spirv::{Decoration, Id, Instruction, Spirv}; +use quote::{ToTokens, TokenStreamExt}; +use std::{cmp::Ordering, num::NonZeroUsize}; +use syn::{Error, Ident, Result}; +use vulkano::shader::spirv::{Decoration, Id, Instruction}; + +#[derive(Default)] +pub struct TypeRegistry { + registered_structs: HashMap, +} + +impl TypeRegistry { + fn register_struct(&mut self, shader: &Shader, ty: &TypeStruct) -> Result { + // Checking with registry if this struct is already registered by another shader, and if + // their signatures match. + if let Some(registered) = self.registered_structs.get(&ty.ident) { + registered.validate_signatures(&shader.name, ty)?; + + // If the struct is already registered and matches this one, skip the duplicate. + Ok(false) + } else { + self.registered_structs.insert( + ty.ident.clone(), + RegisteredType { + shader: shader.name.clone(), + ty: ty.clone(), + }, + ); + + Ok(true) + } + } +} + +struct RegisteredType { + shader: String, + ty: TypeStruct, +} + +impl RegisteredType { + fn validate_signatures(&self, other_shader: &str, other_ty: &TypeStruct) -> Result<()> { + let (shader, struct_ident) = (&self.shader, &self.ty.ident); + + if self.ty.members.len() > other_ty.members.len() { + let member_ident = &self.ty.members[other_ty.members.len()].ident; + bail!( + "shaders `{shader}` and `{other_shader}` declare structs with the same name \ + `{struct_ident}`, but the struct from shader `{shader}` contains an extra field \ + `{member_ident}`", + ); + } + + if self.ty.members.len() < other_ty.members.len() { + let member_ident = &other_ty.members[self.ty.members.len()].ident; + bail!( + "shaders `{shader}` and `{other_shader}` declare structs with the same name \ + `{struct_ident}`, but the struct from shader `{other_shader}` contains an extra \ + field `{member_ident}`", + ); + } + + for (index, (member, other_member)) in self + .ty + .members + .iter() + .zip(other_ty.members.iter()) + .enumerate() + { + if member.ty != other_member.ty { + let (member_ty, other_member_ty) = (&member.ty, &other_member.ty); + bail!( + "shaders `{shader}` and `{other_shader}` declare structs with the same name \ + `{struct_ident}`, but the struct from shader `{shader}` contains a field of \ + type `{member_ty:?}` at index `{index}`, whereas the same struct from shader \ + `{other_shader}` contains a field of type `{other_member_ty:?}` in the same \ + position", + ); + } + } + + Ok(()) + } +} /// Translates all the structs that are contained in the SPIR-V document as Rust structs. -pub(super) fn write_structs<'a, L: LinAlgType>( - shader: &'a str, - spirv: &'a Spirv, - types_meta: &'a TypesMeta, - types_registry: &'a mut HashMap, -) -> TokenStream { - spirv +pub(super) fn write_structs( + input: &MacroInput, + shader: &Shader, + type_registry: &mut TypeRegistry, +) -> Result { + let mut structs = TokenStream::new(); + + for (struct_id, member_type_ids) in shader + .spirv .iter_global() - .filter_map(|instruction| match instruction { - &Instruction::TypeStruct { + .filter_map(|instruction| match *instruction { + Instruction::TypeStruct { result_id, ref member_types, } => Some((result_id, member_types)), _ => None, }) - .filter(|&(struct_id, _member_types)| has_defined_layout(spirv, struct_id)) - .filter_map(|(struct_id, member_types)| { - let (rust_members, is_sized) = - write_struct_members::(spirv, struct_id, member_types); - - let struct_name = spirv - .id(struct_id) - .iter_name() - .find_map(|instruction| match instruction { - Instruction::Name { name, .. } => Some(name.as_str()), - _ => None, - }) - .unwrap_or("__unnamed"); - - // Register the type if needed - if !register_struct(types_registry, shader, &rust_members, struct_name) { - return None; - } - - let struct_ident = format_ident!("{}", struct_name); - let members = rust_members - .iter() - .map(|Member { name, ty, .. }| quote!(pub #name: #ty,)); - - let struct_body = quote! { - #[repr(C)] - #[allow(non_snake_case)] - pub struct #struct_ident { - #( #members )* - } - }; - - Some(if is_sized { - let derives = write_derives(types_meta); - let impls = write_impls(types_meta, struct_name, &rust_members); - quote! { - #derives - #struct_body - #(#impls)* - } - } else { - struct_body - }) - }) - .collect() -} - -// The members of this struct. -struct Member { - name: Ident, - is_dummy: bool, - ty: TokenStream, - signature: Cow<'static, str>, -} - -fn write_struct_members( - spirv: &Spirv, - struct_id: Id, - members: &[Id], -) -> (Vec, bool) { - let mut rust_members = Vec::with_capacity(members.len()); - - // Dummy members will be named `_dummyN` where `N` is determined by this variable. - let mut next_dummy_num = 0; - - // Contains the offset of the next field. - // Equals to `None` if there's a runtime-sized field in there. - let mut current_rust_offset = Some(0); - - for (member_index, (&member, member_info)) in members - .iter() - .zip(spirv.id(struct_id).iter_members()) - .enumerate() + .filter(|&(struct_id, _)| has_defined_layout(shader, struct_id)) { - // Compute infos about the member. - let (ty, signature, rust_size, rust_align) = type_from_id::(spirv, member); - let member_name = member_info - .iter_name() - .find_map(|instruction| match instruction { - Instruction::MemberName { name, .. } => Some(Cow::from(name.as_str())), - _ => None, - }) - .unwrap_or_else(|| Cow::from(format!("__unnamed{}", member_index))); + let struct_ty = TypeStruct::new(shader, struct_id, member_type_ids)?; - // Finding offset of the current member, as requested by the SPIR-V code. - let spirv_offset = member_info - .iter_decoration() - .find_map(|instruction| match instruction { - Instruction::MemberDecorate { - decoration: Decoration::Offset { byte_offset }, - .. - } => Some(*byte_offset as usize), - _ => None, - }) - .unwrap(); - - // We need to add a dummy field if necessary. - { - let current_rust_offset = current_rust_offset - .as_mut() - .expect("Found runtime-sized member in non-final position"); - - // Updating current_rust_offset to take the alignment of the next field into account - *current_rust_offset = if *current_rust_offset == 0 { - 0 - } else { - (1 + (*current_rust_offset - 1) / rust_align) * rust_align - }; - - if spirv_offset != *current_rust_offset { - let diff = spirv_offset.checked_sub(*current_rust_offset).unwrap(); - rust_members.push(Member { - name: format_ident!("_dummy{}", next_dummy_num.to_string()), - is_dummy: true, - ty: quote! { [u8; #diff] }, - signature: Cow::from(format!("[u8; {}]", diff)), - }); - next_dummy_num += 1; - *current_rust_offset += diff; - } + // Register the type if needed. + if !type_registry.register_struct(shader, &struct_ty)? { + continue; } - // Updating `current_rust_offset`. - if let Some(s) = rust_size { - *current_rust_offset.as_mut().unwrap() += s; + let custom_derives = if struct_ty.size().is_some() { + input.custom_derives.as_slice() } else { - current_rust_offset = None; - } + &[] + }; + let struct_ser = Serializer(&struct_ty, input); - rust_members.push(Member { - name: Ident::new(&member_name, Span::call_site()), - is_dummy: false, - ty, - signature, - }); - } - - // Adding the final padding members, if the struct is sized. - if let Some(cur_size) = current_rust_offset { - // Try to determine the total size of the struct. - if let Some(req_size) = struct_size_from_array_stride(spirv, struct_id) { - let diff = req_size.checked_sub(cur_size as u32).unwrap(); - - if diff >= 1 { - rust_members.push(Member { - name: Ident::new(&format!("_dummy{}", next_dummy_num), Span::call_site()), - is_dummy: true, - ty: quote! { [u8; #diff as usize] }, - signature: Cow::from(format!("[u8; {}]", diff)), - }); - } - } - } - - (rust_members, current_rust_offset.is_some()) -} - -fn register_struct( - types_registry: &mut HashMap, - shader: &str, - rust_members: &[Member], - struct_name: &str, -) -> bool { - let target_type = RegisteredType { - shader: shader.to_string(), - signature: rust_members - .iter() - .map(|member| (member.name.to_string(), member.signature.clone())) - .collect(), - }; - - // Checking with Registry if this struct already registered by another shader, and if their - // signatures match. - if let Some(registered) = types_registry.get(struct_name) { - registered.assert_signatures(struct_name, &target_type); - - // If the struct already registered and matches this one, skip duplicate. - false - } else { - assert!(types_registry - .insert(struct_name.to_owned(), target_type) - .is_none()); - true - } -} - -fn write_derives(types_meta: &TypesMeta) -> TokenStream { - let mut derives = vec![]; - - derives.extend( - types_meta - .custom_derives - .iter() - .map(|derive| quote! { #derive }), - ); - - if !derives.is_empty() { - quote! { - #[derive(#(#derives),*)] - } - } else { - quote! {} - } -} - -fn write_impls<'a>( - types_meta: &'a TypesMeta, - struct_name: &'a str, - rust_members: &'a [Member], -) -> impl Iterator + 'a { - let struct_ident = format_ident!("{}", struct_name); - - (types_meta - .partial_eq - .then(|| { - let fields = rust_members - .iter() - .filter(|Member { is_dummy, .. }| !is_dummy) - .map(|Member { name, .. }| { - quote! { - if self.#name != other.#name { - return false - } - } - }); - - quote! { - impl PartialEq for #struct_ident { - fn eq(&self, other: &Self) -> bool { - #( #fields )* - true - } - } - } + structs.extend(quote! { + #[allow(non_camel_case_types, non_snake_case)] + #[derive(::vulkano::buffer::BufferContents #(, #custom_derives )* )] + #[repr(C)] + #struct_ser }) - .into_iter()) - .chain(types_meta.debug.then(|| { - let fields = rust_members - .iter() - .filter(|Member { is_dummy, .. }| !is_dummy) - .map(|Member { name, .. }| { - let name_string = LitStr::new(name.to_string().as_ref(), name.span()); - quote! { .field(#name_string, &self.#name) } - }); + } - quote! { - impl std::fmt::Debug for #struct_ident { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f - .debug_struct(#struct_name) - #( #fields )* - .finish() - } - } - } - })) - .chain(types_meta.display.then(|| { - let fields = rust_members - .iter() - .filter(|Member { is_dummy, .. }| !is_dummy) - .map(|Member { name, .. }| { - let name_string = LitStr::new(name.to_string().as_ref(), name.span()); - quote! { .field(#name_string, &self.#name) } - }); - - quote! { - impl std::fmt::Display for #struct_ident { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f - .debug_struct(#struct_name) - #( #fields )* - .finish() - } - } - } - })) - .chain(types_meta.default.then(|| { - quote! { - impl Default for #struct_ident { - fn default() -> Self { - unsafe { - std::mem::MaybeUninit::::zeroed().assume_init() - } - } - } - } - })) - .chain( - types_meta - .impls - .iter() - .map(move |i| quote! { #i for #struct_ident {} }), - ) + Ok(structs) } -fn has_defined_layout(spirv: &Spirv, struct_id: Id) -> bool { - for member_info in spirv.id(struct_id).iter_members() { +fn has_defined_layout(shader: &Shader, struct_id: Id) -> bool { + for member_info in shader.spirv.id(struct_id).iter_members() { let mut offset_found = false; for instruction in member_info.iter_decoration() { @@ -350,7 +163,7 @@ fn has_defined_layout(spirv: &Spirv, struct_id: Id) -> bool { } } - // Some structs don't have `Offset` decorations, in the case they are used as local + // Some structs don't have `Offset` decorations, in that case they are used as local // variables only. Ignoring these. if !offset_found { return false; @@ -360,397 +173,39 @@ fn has_defined_layout(spirv: &Spirv, struct_id: Id) -> bool { true } -fn struct_size_from_array_stride(spirv: &Spirv, type_id: Id) -> Option { - let mut iter = spirv.iter_global().filter_map(|inst| match inst { - Instruction::TypeArray { - result_id, - element_type, - .. - } - | Instruction::TypeRuntimeArray { - result_id, - element_type, - } if *element_type == type_id => { - spirv - .id(*result_id) - .iter_decoration() - .find_map(|instruction| match instruction { - Instruction::Decorate { - decoration: Decoration::ArrayStride { array_stride }, - .. - } => Some(*array_stride), - _ => None, - }) - } - _ => None, - }); - iter.next().map(|first_stride| { - // Ensure that all strides we find match the first one. - debug_assert!(iter.all(|array_stride| array_stride == first_stride)); - first_stride - }) -} - -/// Returns the type name to put in the Rust struct, and its size and alignment. -/// -/// The size can be `None` if it's only known at runtime. -pub(super) fn type_from_id( - spirv: &Spirv, - type_id: Id, -) -> (TokenStream, Cow<'static, str>, Option, usize) { - let id_info = spirv.id(type_id); - - match id_info.instruction() { - Instruction::TypeBool { .. } => { - panic!("Can't put booleans in structs") - } - Instruction::TypeInt { - width, signedness, .. - } => match (width, signedness) { - (8, 1) => { - #[repr(C)] - struct Foo { - data: i8, - after: u8, - } - ( - quote! {i8}, - Cow::from("i8"), - Some(std::mem::size_of::()), - mem::align_of::(), - ) - } - (8, 0) => { - #[repr(C)] - struct Foo { - data: u8, - after: u8, - } - ( - quote! {u8}, - Cow::from("u8"), - Some(std::mem::size_of::()), - mem::align_of::(), - ) - } - (16, 1) => { - #[repr(C)] - struct Foo { - data: i16, - after: u8, - } - ( - quote! {i16}, - Cow::from("i16"), - Some(std::mem::size_of::()), - mem::align_of::(), - ) - } - (16, 0) => { - #[repr(C)] - struct Foo { - data: u16, - after: u8, - } - ( - quote! {u16}, - Cow::from("u16"), - Some(std::mem::size_of::()), - mem::align_of::(), - ) - } - (32, 1) => { - #[repr(C)] - struct Foo { - data: i32, - after: u8, - } - ( - quote! {i32}, - Cow::from("i32"), - Some(std::mem::size_of::()), - mem::align_of::(), - ) - } - (32, 0) => { - #[repr(C)] - struct Foo { - data: u32, - after: u8, - } - ( - quote! {u32}, - Cow::from("u32"), - Some(std::mem::size_of::()), - mem::align_of::(), - ) - } - (64, 1) => { - #[repr(C)] - struct Foo { - data: i64, - after: u8, - } - ( - quote! {i64}, - Cow::from("i64"), - Some(std::mem::size_of::()), - mem::align_of::(), - ) - } - (64, 0) => { - #[repr(C)] - struct Foo { - data: u64, - after: u8, - } - ( - quote! {u64}, - Cow::from("u64"), - Some(std::mem::size_of::()), - mem::align_of::(), - ) - } - _ => panic!("No Rust equivalent for an integer of width {}", width), - }, - Instruction::TypeFloat { width, .. } => match width { - 32 => { - #[repr(C)] - struct Foo { - data: f32, - after: u8, - } - ( - quote! {f32}, - Cow::from("f32"), - Some(std::mem::size_of::()), - mem::align_of::(), - ) - } - 64 => { - #[repr(C)] - struct Foo { - data: f64, - after: u8, - } - ( - quote! {f64}, - Cow::from("f64"), - Some(std::mem::size_of::()), - mem::align_of::(), - ) - } - _ => panic!("No Rust equivalent for a floating-point of width {}", width), - }, - &Instruction::TypeVector { - component_type, - component_count, - .. - } => { - debug_assert_eq!(mem::align_of::<[u32; 3]>(), mem::align_of::()); - let component_count = component_count as usize; - let (element_ty, element_item, element_size, align) = - type_from_id::(spirv, component_type); - - let ty = L::vector(&element_ty, component_count); - let item = Cow::from(format!("[{}; {}]", element_item, component_count)); - let size = element_size.map(|s| s * component_count); - - (ty, item, size, align) - } - &Instruction::TypeMatrix { - column_type, - column_count, - .. - } => { - debug_assert_eq!(mem::align_of::<[u32; 3]>(), mem::align_of::()); - let column_count = column_count as usize; - - // FIXME: row-major or column-major - let (row_count, element, element_item, element_size, align) = - match spirv.id(column_type).instruction() { - &Instruction::TypeVector { - component_type, - component_count, - .. - } => { - let (element, element_item, element_size, align) = - type_from_id::(spirv, component_type); - ( - component_count as usize, - element, - element_item, - element_size, - align, - ) - } - _ => unreachable!(), - }; - - let ty = L::matrix(&element, row_count, column_count); - let size = element_size.map(|s| s * row_count * column_count); - let item = Cow::from(format!( - "[[{}; {}]; {}]", - element_item, row_count, column_count - )); - - (ty, item, size, align) - } - &Instruction::TypeArray { - element_type, - length, - .. - } => { - debug_assert_eq!(mem::align_of::<[u32; 3]>(), mem::align_of::()); - - let (element_type, element_type_string, element_size, element_align) = - type_from_id::(spirv, element_type); - - let element_size = element_size.expect("array components must be sized"); - let array_length = match spirv.id(length).instruction() { - &Instruction::Constant { ref value, .. } => { - value.iter().rev().fold(0u64, |a, &b| (a << 32) | b as u64) - } - _ => panic!("failed to find array length"), - } as usize; - - let stride = id_info - .iter_decoration() - .find_map(|instruction| match instruction { - Instruction::Decorate { - decoration: Decoration::ArrayStride { array_stride }, - .. - } => Some(*array_stride), - _ => None, - }) - .unwrap(); - if stride as usize > element_size { - panic!("Not possible to generate a rust array with the correct alignment since the SPIR-V \ - ArrayStride is larger than the size of the array element in rust. Try wrapping \ - the array element in a struct or rounding up the size of a vector or matrix \ - (e.g. increase a vec3 to a vec4)") - } - - ( - quote! { [#element_type; #array_length] }, - Cow::from(format!("[{}; {}]", element_type_string, array_length)), - Some(element_size * array_length), - element_align, - ) - } - &Instruction::TypeRuntimeArray { element_type, .. } => { - debug_assert_eq!(mem::align_of::<[u32; 3]>(), mem::align_of::()); - - let (element_type, element_type_string, _, element_align) = - type_from_id::(spirv, element_type); - - ( - quote! { [#element_type] }, - Cow::from(format!("[{}]", element_type_string)), - None, - element_align, - ) - } - Instruction::TypeStruct { member_types, .. } => { - // TODO: take the Offset member decorate into account? - let size = if !has_defined_layout(spirv, type_id) { - None - } else { - // If the struct appears in an array, then first try to get the size from the - // array stride. - struct_size_from_array_stride(spirv, type_id) - .map(|size| size as usize) - .or_else(|| { - // We haven't found any strides, so we have to calculate the size based - // on the offset and size of the last member. - member_types - .iter() - .zip(spirv.id(type_id).iter_members()) - .last() - .map_or(Some(0), |(&member, member_info)| { - let spirv_offset = member_info - .iter_decoration() - .find_map(|instruction| match instruction { - Instruction::MemberDecorate { - decoration: Decoration::Offset { byte_offset }, - .. - } => Some(*byte_offset as usize), - _ => None, - }) - .unwrap(); - let (_, _, rust_size, _) = type_from_id::(spirv, member); - rust_size.map(|rust_size| spirv_offset + rust_size) - }) - }) - }; - - let align = member_types - .iter() - .map(|&t| type_from_id::(spirv, t).3) - .max() - .unwrap_or(1); - - let name_string = id_info - .iter_name() - .find_map(|instruction| match instruction { - Instruction::Name { name, .. } => Some(Cow::from(name.clone())), - _ => None, - }) - .unwrap_or_else(|| Cow::from("__unnamed")); - let name = { - let name = format_ident!("{}", name_string); - quote! { #name } - }; - - (name, name_string, size, align) - } - _ => panic!("Type #{} not found", type_id), - } -} - /// Writes the `SpecializationConstants` struct that contains the specialization constants and /// implements the `Default` and the `vulkano::shader::SpecializationConstants` traits. -pub(super) fn write_specialization_constants<'a, L: LinAlgType>( - shader: &'a str, - spirv: &Spirv, - shared_constants: bool, - types_registry: &'a mut HashMap, -) -> TokenStream { - struct SpecConst { - name: String, - constant_id: u32, - rust_ty: TokenStream, - rust_signature: Cow<'static, str>, - rust_size: usize, - rust_alignment: u32, - default_value: TokenStream, - } +pub(super) fn write_specialization_constants( + input: &MacroInput, + shader: &Shader, + type_registry: &mut TypeRegistry, +) -> Result { + let mut members = Vec::new(); + let mut member_defaults = Vec::new(); + let mut map_entries = Vec::new(); + let mut current_offset = 0; - let mut spec_consts = Vec::new(); - - for instruction in spirv.iter_global() { + for instruction in shader.spirv.iter_global() { let (result_type_id, result_id, default_value) = match *instruction { Instruction::SpecConstantTrue { result_type_id, result_id, - } => (result_type_id, result_id, quote! {1u32}), - + } => (result_type_id, result_id, quote! { 1u32 }), Instruction::SpecConstantFalse { result_type_id, result_id, - } => (result_type_id, result_id, quote! {0u32}), - + } => (result_type_id, result_id, quote! { 0u32 }), Instruction::SpecConstant { result_type_id, result_id, ref value, } => { let def_val = quote! { - unsafe {{ ::std::mem::transmute([ #( #value ),* ]) }} + unsafe { ::std::mem::transmute([ #( #value ),* ]) } }; + (result_type_id, result_id, def_val) } - Instruction::SpecConstantComposite { result_type_id, result_id, @@ -758,152 +213,978 @@ pub(super) fn write_specialization_constants<'a, L: LinAlgType>( } => { let constituents = constituents.iter().map(|&id| u32::from(id)); let def_val = quote! { - unsafe {{ ::std::mem::transmute([ #( #constituents ),* ]) }} + unsafe { ::std::mem::transmute([ #( #constituents ),* ]) } }; + (result_type_id, result_id, def_val) } - _ => continue, }; - // Translate bool to u32 - let (rust_ty, rust_signature, rust_size, rust_alignment) = - match spirv.id(result_type_id).instruction() { - Instruction::TypeBool { .. } => ( - quote! {u32}, - Cow::from("u32"), - Some(mem::size_of::()), - mem::align_of::(), - ), - _ => type_from_id::(spirv, result_type_id), - }; - let rust_size = rust_size.expect("Found runtime-sized specialization constant"); - - let id_info = spirv.id(result_id); + let id_info = shader.spirv.id(result_id); let constant_id = id_info .iter_decoration() - .find_map(|instruction| match instruction { + .find_map(|instruction| match *instruction { Instruction::Decorate { decoration: Decoration::SpecId { specialization_constant_id, }, .. - } => Some(*specialization_constant_id), + } => Some(specialization_constant_id), _ => None, }); if let Some(constant_id) = constant_id { - let name = match id_info + let ident = id_info .iter_name() .find_map(|instruction| match instruction { - Instruction::Name { name, .. } => Some(name.as_str()), + Instruction::Name { name, .. } => { + Some(Ident::new(name.as_str(), Span::call_site())) + } _ => None, - }) { - Some(name) => name.to_owned(), - None => format!("constant_{}", constant_id), + }) + .unwrap_or_else(|| format_ident!("constant_{}", constant_id)); + + let ty = match *shader.spirv.id(result_type_id).instruction() { + // Translate bool to u32. + Instruction::TypeBool { .. } => Type::Scalar(TypeScalar::Int(TypeInt { + width: IntWidth::W32, + signed: false, + })), + _ => Type::new(shader, result_type_id)?, }; - spec_consts.push(SpecConst { - name, - constant_id, - rust_ty, - rust_signature, - rust_size, - rust_alignment: rust_alignment as u32, - default_value, - }); - } - } + let offset = current_offset; + let size = ty.size().ok_or_else(|| { + Error::new_spanned( + &shader.source, + "found runtime-sized specialization constant", + ) + })?; + current_offset = align_up(current_offset + size, ty.scalar_alignment()); - let struct_name = if shared_constants { - format_ident!("SpecializationConstants") - } else { - format_ident!("{}SpecializationConstants", shader.to_upper_camel_case()) - }; - - // For multi-constants mode registration mechanism skipped - if shared_constants { - let target_type = RegisteredType { - shader: shader.to_string(), - signature: spec_consts - .iter() - .map(|member| (member.name.to_string(), member.rust_signature.clone())) - .collect(), - }; - - let name = struct_name.to_string(); - - // Checking with Registry if this struct already registered by another shader, and if their - // signatures match. - if let Some(registered) = types_registry.get(name.as_str()) { - registered.assert_signatures(name.as_str(), &target_type); - - // If the struct already registered and matches this one, skip duplicate. - return quote! {}; - } - - assert!(types_registry.insert(name, target_type).is_none()); - } - - let map_entries = { - let mut map_entries = Vec::new(); - let mut curr_offset = 0; - for spec_const in &spec_consts { - let constant_id = spec_const.constant_id; - let rust_size = spec_const.rust_size; + member_defaults.push(quote! { #ident: #default_value }); + members.push(Member { ident, ty, offset }); map_entries.push(quote! { ::vulkano::shader::SpecializationMapEntry { constant_id: #constant_id, - offset: #curr_offset, - size: #rust_size, + offset: #offset as u32, + size: #size, } }); - - assert_ne!(spec_const.rust_size, 0); - curr_offset += spec_const.rust_size as u32; - curr_offset = - spec_const.rust_alignment * (1 + (curr_offset - 1) / spec_const.rust_alignment); } - map_entries - }; - - let num_map_entries = map_entries.len(); - - let mut struct_members = vec![]; - let mut struct_member_defaults = vec![]; - for spec_const in spec_consts { - let name = Ident::new(&spec_const.name, Span::call_site()); - let rust_ty = spec_const.rust_ty; - let default_value = spec_const.default_value; - struct_members.push(quote! { pub #name: #rust_ty }); - struct_member_defaults.push(quote! { #name: #default_value }); } - quote! { - #[derive(Debug, Copy, Clone)] - #[allow(non_snake_case)] - #[repr(C)] - pub struct #struct_name { - #( #struct_members ),* - } + let struct_ty = TypeStruct { + ident: if input.shared_constants { + format_ident!("SpecializationConstants") + } else { + format_ident!( + "{}SpecializationConstants", + shader.name.to_upper_camel_case(), + ) + }, + members, + }; - impl Default for #struct_name { - fn default() -> #struct_name { - #struct_name { - #( #struct_member_defaults ),* + // For multi-constants mode, the registration mechanism is skipped. + if input.shared_constants && !type_registry.register_struct(shader, &struct_ty)? { + return Ok(TokenStream::new()); + } + + let struct_ser = Serializer(&struct_ty, input); + let struct_ident = &struct_ty.ident; + let num_map_entries = map_entries.len(); + + Ok(quote! { + #[allow(non_snake_case)] + #[derive(::std::clone::Clone, ::std::marker::Copy, ::std::fmt::Debug)] + #[repr(C)] + #struct_ser + + impl ::std::default::Default for #struct_ident { + #[inline] + fn default() -> #struct_ident { + #struct_ident { + #( #member_defaults ),* } } } - unsafe impl ::vulkano::shader::SpecializationConstants for #struct_name { + #[allow(unsafe_code)] + unsafe impl ::vulkano::shader::SpecializationConstants for #struct_ident { + #[inline(always)] fn descriptors() -> &'static [::vulkano::shader::SpecializationMapEntry] { - static DESCRIPTORS: [::vulkano::shader::SpecializationMapEntry; #num_map_entries] = [ - #( #map_entries ),* - ]; + static DESCRIPTORS: [::vulkano::shader::SpecializationMapEntry; #num_map_entries] = + [ #( #map_entries ),* ]; + &DESCRIPTORS } } + }) +} + +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +enum Alignment { + A1 = 1, + A2 = 2, + A4 = 4, + A8 = 8, + A16 = 16, + A32 = 32, +} + +impl Alignment { + fn new(alignment: usize) -> Self { + match alignment { + 1 => Alignment::A1, + 2 => Alignment::A2, + 4 => Alignment::A4, + 8 => Alignment::A8, + 16 => Alignment::A16, + 32 => Alignment::A32, + _ => unreachable!(), + } + } +} + +impl PartialOrd for Alignment { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Alignment { + fn cmp(&self, other: &Self) -> Ordering { + (*self as usize).cmp(&(*other as usize)) + } +} + +fn align_up(offset: usize, alignment: Alignment) -> usize { + (offset + alignment as usize - 1) & !(alignment as usize - 1) +} + +fn is_aligned(offset: usize, alignment: Alignment) -> bool { + offset & (alignment as usize - 1) == 0 +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum Type { + Scalar(TypeScalar), + Vector(TypeVector), + Matrix(TypeMatrix), + Array(TypeArray), + Struct(TypeStruct), +} + +impl Type { + fn new(shader: &Shader, type_id: Id) -> Result { + let id_info = shader.spirv.id(type_id); + + let ty = match *id_info.instruction() { + Instruction::TypeBool { .. } => bail!(shader.source, "can't put booleans in structs"), + Instruction::TypeInt { + width, signedness, .. + } => Type::Scalar(TypeScalar::Int(TypeInt::new(shader, width, signedness)?)), + Instruction::TypeFloat { width, .. } => { + Type::Scalar(TypeScalar::Float(TypeFloat::new(shader, width)?)) + } + Instruction::TypeVector { + component_type, + component_count, + .. + } => Type::Vector(TypeVector::new(shader, component_type, component_count)?), + Instruction::TypeMatrix { + column_type, + column_count, + .. + } => Type::Matrix(TypeMatrix::new(shader, column_type, column_count)?), + Instruction::TypeArray { + element_type, + length, + .. + } => Type::Array(TypeArray::new(shader, type_id, element_type, Some(length))?), + Instruction::TypeRuntimeArray { element_type, .. } => { + Type::Array(TypeArray::new(shader, type_id, element_type, None)?) + } + Instruction::TypeStruct { + ref member_types, .. + } => Type::Struct(TypeStruct::new(shader, type_id, member_types)?), + _ => bail!(shader.source, "type {type_id} was not found"), + }; + + Ok(ty) + } + + fn size(&self) -> Option { + match self { + Self::Scalar(ty) => Some(ty.size()), + Self::Vector(ty) => Some(ty.size()), + Self::Matrix(ty) => Some(ty.size()), + Self::Array(ty) => ty.size(), + Self::Struct(ty) => ty.size(), + } + } + + fn scalar_alignment(&self) -> Alignment { + match self { + Self::Scalar(ty) => ty.alignment(), + Self::Vector(ty) => ty.component_type.alignment(), + Self::Matrix(ty) => ty.component_type.alignment(), + Self::Array(ty) => ty.scalar_alignment(), + Self::Struct(ty) => ty.scalar_alignment(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum TypeScalar { + Int(TypeInt), + Float(TypeFloat), +} + +impl TypeScalar { + fn size(&self) -> usize { + match self { + Self::Int(ty) => ty.size(), + Self::Float(ty) => ty.size(), + } + } + + fn alignment(&self) -> Alignment { + match self { + Self::Int(ty) => ty.alignment(), + Self::Float(ty) => ty.alignment(), + } + } +} + +impl ToTokens for TypeScalar { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Int(ty) => ty.to_tokens(tokens), + Self::Float(ty) => ty.to_tokens(tokens), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct TypeInt { + width: IntWidth, + signed: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +enum IntWidth { + W8 = 8, + W16 = 16, + W32 = 32, + W64 = 64, +} + +impl TypeInt { + fn new(shader: &Shader, width: u32, signedness: u32) -> Result { + let width = match width { + 8 => IntWidth::W8, + 16 => IntWidth::W16, + 32 => IntWidth::W32, + 64 => IntWidth::W64, + _ => bail!(shader.source, "integers must be 8, 16, 32, or 64-bit wide"), + }; + + let signed = match signedness { + 0 => false, + 1 => true, + _ => bail!(shader.source, "signedness must be 0 or 1"), + }; + + Ok(TypeInt { width, signed }) + } + + fn size(&self) -> usize { + self.width as usize >> 3 + } + + fn alignment(&self) -> Alignment { + Alignment::new(self.size()) + } + + #[rustfmt::skip] + fn as_str(&self) -> &'static str { + match (self.width, self.signed) { + (IntWidth::W8, false) => "u8", + (IntWidth::W16, false) => "u16", + (IntWidth::W32, false) => "u32", + (IntWidth::W64, false) => "u64", + (IntWidth::W8, true) => "i8", + (IntWidth::W16, true) => "i16", + (IntWidth::W32, true) => "i32", + (IntWidth::W64, true) => "i64", + } + } + + fn to_ident(&self) -> Ident { + Ident::new(self.as_str(), Span::call_site()) + } +} + +impl ToTokens for TypeInt { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append(self.to_ident()); + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct TypeFloat { + width: FloatWidth, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +enum FloatWidth { + W16 = 16, + W32 = 32, + W64 = 64, +} + +impl TypeFloat { + fn new(shader: &Shader, width: u32) -> Result { + let width = match width { + 16 => FloatWidth::W16, + 32 => FloatWidth::W32, + 64 => FloatWidth::W64, + _ => bail!(shader.source, "floats must be 16, 32, or 64-bit wide"), + }; + + Ok(TypeFloat { width }) + } + + fn size(&self) -> usize { + self.width as usize >> 3 + } + + fn alignment(&self) -> Alignment { + Alignment::new(self.size()) + } + + fn as_str(&self) -> &'static str { + match self.width { + FloatWidth::W16 => "f16", + FloatWidth::W32 => "f32", + FloatWidth::W64 => "f64", + } + } + + fn to_ident(&self) -> Ident { + Ident::new(self.as_str(), Span::call_site()) + } +} + +impl ToTokens for TypeFloat { + fn to_tokens(&self, tokens: &mut TokenStream) { + if self.width == FloatWidth::W16 { + tokens.extend(quote! { ::vulkano::half:: }); + } + tokens.append(self.to_ident()); + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct TypeVector { + component_type: TypeScalar, + component_count: ComponentCount, +} + +impl TypeVector { + fn new(shader: &Shader, component_type_id: Id, component_count: u32) -> Result { + let component_count = ComponentCount::new(shader, component_count)?; + + let component_type = match *shader.spirv.id(component_type_id).instruction() { + Instruction::TypeBool { .. } => bail!(shader.source, "can't put booleans in structs"), + Instruction::TypeInt { + width, signedness, .. + } => TypeScalar::Int(TypeInt::new(shader, width, signedness)?), + Instruction::TypeFloat { width, .. } => { + TypeScalar::Float(TypeFloat::new(shader, width)?) + } + _ => bail!(shader.source, "vector components must be scalars"), + }; + + Ok(TypeVector { + component_type, + component_count, + }) + } + + fn size(&self) -> usize { + self.component_type.size() * self.component_count as usize + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct TypeMatrix { + component_type: TypeFloat, + column_count: ComponentCount, + row_count: ComponentCount, + stride: usize, + majorness: MatrixMajorness, +} + +impl TypeMatrix { + fn new(shader: &Shader, column_type_id: Id, column_count: u32) -> Result { + let column_count = ComponentCount::new(shader, column_count)?; + + let (component_type, row_count) = match *shader.spirv.id(column_type_id).instruction() { + Instruction::TypeVector { + component_type, + component_count, + .. + } => match *shader.spirv.id(component_type).instruction() { + Instruction::TypeFloat { width, .. } => ( + TypeFloat::new(shader, width)?, + ComponentCount::new(shader, component_count)?, + ), + _ => bail!(shader.source, "matrix components must be floats"), + }, + _ => bail!(shader.source, "matrix columns must be vectors"), + }; + + // We can't know these until we get to the members and their decorations, so just use + // defaults for now. + let stride = component_type.size() * row_count as usize; + let majorness = MatrixMajorness::ColumnMajor; + + Ok(TypeMatrix { + component_type, + column_count, + row_count, + stride, + majorness, + }) + } + + fn size(&self) -> usize { + self.stride * self.vector_count() as usize + } + + fn vector_size(&self) -> usize { + self.component_type.size() * self.component_count() as usize + } + + fn vector_count(&self) -> ComponentCount { + match self.majorness { + MatrixMajorness::ColumnMajor => self.column_count, + MatrixMajorness::RowMajor => self.row_count, + } + } + + fn component_count(&self) -> ComponentCount { + match self.majorness { + MatrixMajorness::ColumnMajor => self.row_count, + MatrixMajorness::RowMajor => self.column_count, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum MatrixMajorness { + ColumnMajor, + RowMajor, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +enum ComponentCount { + C2 = 2, + C3 = 3, + C4 = 4, +} + +impl ComponentCount { + fn new(shader: &Shader, count: u32) -> Result { + let count = match count { + 2 => ComponentCount::C2, + 3 => ComponentCount::C3, + 4 => ComponentCount::C4, + _ => bail!(shader.source, "component counts must be 2, 3 or 4"), + }; + + Ok(count) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct TypeArray { + element_type: Box, + length: Option, + stride: usize, +} + +impl TypeArray { + fn new( + shader: &Shader, + array_id: Id, + element_type_id: Id, + length_id: Option, + ) -> Result { + let element_type = Box::new(Type::new(shader, element_type_id)?); + + let length = length_id + .map(|id| match shader.spirv.id(id).instruction() { + Instruction::Constant { value, .. } => { + assert!(matches!(value.len(), 1 | 2)); + let len = value.iter().rev().fold(0u64, |a, &b| (a << 32) | b as u64); + + NonZeroUsize::new(len.try_into().unwrap()).ok_or_else(|| { + Error::new_spanned(&shader.source, "arrays must have a non-zero length") + }) + } + _ => bail!(shader.source, "failed to find array length"), + }) + .transpose()?; + + let stride = { + let mut strides = + shader + .spirv + .id(array_id) + .iter_decoration() + .filter_map(|instruction| match *instruction { + Instruction::Decorate { + decoration: Decoration::ArrayStride { array_stride }, + .. + } => Some(array_stride as usize), + _ => None, + }); + let stride = strides.next().ok_or_else(|| { + Error::new_spanned( + &shader.source, + "arrays inside structs must have an `ArrayStride` decoration", + ) + })?; + + if !strides.all(|s| s == stride) { + bail!(shader.source, "found conflicting `ArrayStride` decorations"); + } + + if !is_aligned(stride, element_type.scalar_alignment()) { + bail!( + shader.source, + "array strides must be aligned for the element type", + ); + } + + let element_size = element_type.size().ok_or_else(|| { + Error::new_spanned(&shader.source, "array elements must be sized") + })?; + + if stride < element_size { + bail!(shader.source, "array elements must not overlap"); + } + + stride + }; + + Ok(TypeArray { + element_type, + length, + stride, + }) + } + + fn size(&self) -> Option { + self.length.map(|length| self.stride * length.get()) + } + + fn scalar_alignment(&self) -> Alignment { + self.element_type.scalar_alignment() + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct TypeStruct { + ident: Ident, + members: Vec, +} + +impl TypeStruct { + fn new(shader: &Shader, struct_id: Id, member_type_ids: &[Id]) -> Result { + let id_info = shader.spirv.id(struct_id); + + let ident = id_info + .iter_name() + .find_map(|instruction| match instruction { + Instruction::Name { name, .. } => Some(Ident::new(name, Span::call_site())), + _ => None, + }) + .ok_or_else(|| { + Error::new_spanned( + &shader.source, + "expected struct in shader interface to have an associated `Name` instruction", + ) + })?; + + let mut members = Vec::::with_capacity(member_type_ids.len()); + + for (member_index, (&member_id, member_info)) in member_type_ids + .iter() + .zip(id_info.iter_members()) + .enumerate() + { + let ident = member_info + .iter_name() + .find_map(|instruction| match instruction { + Instruction::MemberName { name, .. } => { + Some(Ident::new(name, Span::call_site())) + } + _ => None, + }) + .unwrap_or_else(|| format_ident!("unnamed{member_index}")); + + let mut ty = Type::new(shader, member_id)?; + + { + // If the member is an array, then matrix-decorations can be applied to it if the + // innermost type of the array is a matrix. Else this will stay being the type of + // the member. + let mut ty = &mut ty; + while let Type::Array(TypeArray { element_type, .. }) = ty { + ty = element_type; + } + + if let Type::Matrix(matrix) = ty { + let mut strides = + member_info.iter_decoration().filter_map( + |instruction| match *instruction { + Instruction::MemberDecorate { + decoration: Decoration::MatrixStride { matrix_stride }, + .. + } => Some(matrix_stride as usize), + _ => None, + }, + ); + matrix.stride = strides.next().ok_or_else(|| { + Error::new_spanned( + &shader.source, + "matrices inside structs must have a `MatrixStride` decoration", + ) + })?; + + if !strides.all(|s| s == matrix.stride) { + bail!( + shader.source, + "found conflicting `MatrixStride` decorations", + ); + } + + if !is_aligned(matrix.stride, matrix.component_type.alignment()) { + bail!( + shader.source, + "matrix strides must be an integer multiple of the size of the \ + component", + ); + } + + let mut majornessess = member_info.iter_decoration().filter_map( + |instruction| match *instruction { + Instruction::MemberDecorate { + decoration: Decoration::ColMajor, + .. + } => Some(MatrixMajorness::ColumnMajor), + Instruction::MemberDecorate { + decoration: Decoration::RowMajor, + .. + } => Some(MatrixMajorness::RowMajor), + _ => None, + }, + ); + matrix.majorness = majornessess.next().ok_or_else(|| { + Error::new_spanned( + &shader.source, + "matrices inside structs must have a `ColMajor` or `RowMajor` \ + decoration", + ) + })?; + + if !majornessess.all(|m| m == matrix.majorness) { + bail!( + shader.source, + "found conflicting matrix majorness decorations", + ); + } + + // NOTE(Marc): It is crucial that we do this check after setting the majorness, + // because `TypeMatrix::vector_size` depends on it. + if matrix.stride < matrix.vector_size() { + bail!(shader.source, "matrix columns/rows must not overlap"); + } + } + } + + let offset = member_info + .iter_decoration() + .find_map(|instruction| match *instruction { + Instruction::MemberDecorate { + decoration: Decoration::Offset { byte_offset }, + .. + } => Some(byte_offset as usize), + _ => None, + }) + .ok_or_else(|| { + Error::new_spanned( + &shader.source, + "struct members must have an `Offset` decoration", + ) + })?; + + if !is_aligned(offset, ty.scalar_alignment()) { + bail!( + shader.source, + "struct member offsets must be aligned for the member type", + ); + } + + if let Some(last) = members.last() { + if !is_aligned(offset, last.ty.scalar_alignment()) { + bail!( + shader.source, + "expected struct member offset to be aligned for the preceding member type", + ); + } + + let last_size = last.ty.size().ok_or_else(|| { + Error::new_spanned( + &shader.source, + "all members except the last member of a struct must be sized", + ) + })?; + + if last.offset + last_size > offset { + bail!(shader.source, "struct members must not overlap"); + } + } else if offset != 0 { + bail!( + shader.source, + "expected struct member at index 0 to have an `Offset` decoration of 0", + ); + } + + members.push(Member { ident, ty, offset }); + } + + Ok(TypeStruct { ident, members }) + } + + fn size(&self) -> Option { + self.members + .last() + .map(|member| { + member + .ty + .size() + .map(|size| align_up(member.offset + size, self.scalar_alignment())) + }) + .unwrap_or(Some(0)) + } + + fn scalar_alignment(&self) -> Alignment { + self.members + .iter() + .map(|member| member.ty.scalar_alignment()) + .max() + .unwrap_or(Alignment::A1) + } +} + +#[derive(Clone, Debug)] +struct Member { + ident: Ident, + ty: Type, + offset: usize, +} + +impl PartialEq for Member { + fn eq(&self, other: &Self) -> bool { + self.ty == other.ty && self.offset == other.offset + } +} + +impl Eq for Member {} + +/// Helper for serializing a type to tokens with respect to macro input. +struct Serializer<'a, T>(&'a T, &'a MacroInput); + +impl ToTokens for Serializer<'_, Type> { + fn to_tokens(&self, tokens: &mut TokenStream) { + match &self.0 { + Type::Scalar(ty) => ty.to_tokens(tokens), + Type::Vector(ty) => Serializer(ty, self.1).to_tokens(tokens), + Type::Matrix(ty) => Serializer(ty, self.1).to_tokens(tokens), + Type::Array(ty) => Serializer(ty, self.1).to_tokens(tokens), + Type::Struct(ty) => tokens.append(ty.ident.clone()), + } + } +} + +impl ToTokens for Serializer<'_, TypeVector> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let component_type = &self.0.component_type; + let component_count = self.0.component_count as usize; + + match self.1.linalg_type { + LinAlgType::Std => { + tokens.extend(quote! { [#component_type; #component_count] }); + } + LinAlgType::CgMath => { + let vector = format_ident!("Vector{}", component_count); + tokens.extend(quote! { ::cgmath::#vector<#component_type> }); + } + LinAlgType::Nalgebra => { + tokens.extend(quote! { + ::nalgebra::base::SVector<#component_type, #component_count> + }); + } + } + } +} + +impl ToTokens for Serializer<'_, TypeMatrix> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let component_type = &self.0.component_type; + let vector_count = self.0.vector_count() as usize; + let component_count = self.0.component_count() as usize; + let majorness = self.0.majorness; + + // This can't overflow because the stride must be at least the vector size. + let padding = self.0.stride - self.0.vector_size(); + + match self.1.linalg_type { + // cgmath only has column-major matrices. It also only has square matrices, and its 3x3 + // matrix is not padded right. Fall back to std for anything else. + LinAlgType::CgMath + if majorness == MatrixMajorness::ColumnMajor + && padding == 0 + && vector_count == component_count => + { + let matrix = format_ident!("Matrix{}", component_count); + tokens.extend(quote! { ::cgmath::#matrix<#component_type> }); + } + // nalgebra only has column-major matrices, and its 3xN matrices are not padded right. + // Fall back to std for anything else. + LinAlgType::Nalgebra if majorness == MatrixMajorness::ColumnMajor && padding == 0 => { + tokens.extend(quote! { + ::nalgebra::base::SMatrix<#component_type, #component_count, #vector_count> + }); + } + _ => { + let vector = Padded(quote! { [#component_type; #component_count] }, padding); + tokens.extend(quote! { [#vector; #vector_count] }); + } + } + } +} + +impl ToTokens for Serializer<'_, TypeArray> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let element_type = &*self.0.element_type; + // This can't panic because array elements must be sized. + let element_size = element_type.size().unwrap(); + // This can't overflow because the stride must be at least the element size. + let padding = self.0.stride - element_size; + + let element_type = Padded(Serializer(element_type, self.1), padding); + + if let Some(length) = self.0.length.map(NonZeroUsize::get) { + tokens.extend(quote! { [#element_type; #length] }); + } else { + tokens.extend(quote! { [#element_type] }); + } + } +} + +impl ToTokens for Serializer<'_, TypeStruct> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let struct_ident = &self.0.ident; + let member_idents = self.0.members.iter().map(|member| &member.ident); + let mut member_types = Vec::new(); + + // TODO: Replace with the `ArrayWindows` iterator once it is stabilized. + for member_pair in self.0.members.windows(2) { + let member = &member_pair[0]; + let next_member = &member_pair[1]; + + let offset = member.offset; + // This can't panic, because only the last member can be unsized. + let size = member.ty.size().unwrap(); + let next_offset = next_member.offset; + let next_alignment = next_member.ty.scalar_alignment(); + + let ser = Serializer(&member.ty, self.1); + + // Wrap the member in `Padded` in order to pad the next member, if required. Note that + // the next offset can't be larger than the aligned offset, and all offsets must be + // aligned to the scalar alignment of its member's type. + // + // NOTE(Marc): The next offset must also be aligned to the scalar alignment of the + // previous member's type. What this prevents is a situation like this: + // + // | 0x0 | | | | | 0x5 | + // |-----------------------|-----|-----| + // | u32 | | u8 | + // + // We can't pad the `u8` using a `Padded`, because its size will always be + // rounded up to the nearest multiple of its alignment which is 4 in this case. We + // could pad the `u8` field if `Padded` also had a const parameter for the number of + // leading padding bytes, but that would make the logic here more complicated than I + // feel is necessary for now. We can always add this functionality later if someone's + // life depends on it. + // + // The reason it makes more sense to have trailing padding than leading padding is: + // + // 1. The above can only be achieved by setting the offset explicitly, and makes little + // sense from a practical standpoint. The compiler by default only adds padding if + // the following field has a *higher alignment*. + // 2. Arrays and by extension matrices need trailing padding to satisfy their strides. + // + // For now, the first member must also start at offset 0, for simplicity. This can be + // adjusted in the future also by adding a parameter for leading padding. + if align_up(offset + size, next_alignment) < next_offset { + member_types.push(Padded(ser, next_offset - offset - size).into_token_stream()); + } else { + member_types.push(ser.into_token_stream()); + } + } + + // Add the last field, which is excluded in the above loop (both if the number of members + // is 1 and >= 2). + if let Some(last) = self.0.members.last() { + member_types.push(Serializer(&last.ty, self.1).into_token_stream()); + } + + tokens.extend(quote! { + pub struct #struct_ident { + #( pub #member_idents: #member_types, )* + } + }) + } +} + +/// Helper for wrapping tokens in `Padded`. Doesn't wrap if the padding is `0`. +struct Padded(T, usize); + +impl ToTokens for Padded { + fn to_tokens(&self, tokens: &mut TokenStream) { + let ty = &self.0; + let padding = self.1; + + if padding == 0 { + ty.to_tokens(tokens); + } else { + tokens.extend(quote! { ::vulkano::padded::Padded<#ty, #padding> }); + } } } diff --git a/vulkano/Cargo.toml b/vulkano/Cargo.toml index d805fca5..086a56f6 100644 --- a/vulkano/Cargo.toml +++ b/vulkano/Cargo.toml @@ -21,18 +21,15 @@ ahash = "0.8" # When updating Ash, also update vk.xml to the same Vulkan patch version that Ash uses. # All versions of vk.xml can be found at https://github.com/KhronosGroup/Vulkan-Headers/commits/main/registry/vk.xml. ash = "^0.37.1" -bytemuck = { version = "1.7", features = [ - "derive", - "extern_crate_std", - "min_const_generics", -] } +bytemuck = "1.7" cgmath = { version = "0.18.0", optional = true } crossbeam-queue = "0.3" -half = "2" +half = { version = "2", features = ["bytemuck"] } libloading = "0.7" nalgebra = { version = "0.31.0", optional = true } once_cell = "1.16" parking_lot = { version = "0.12", features = ["send_guard"] } +serde = { version = "1.0", optional = true } smallvec = "1.8" thread_local = "1.1" vulkano-macros = { path = "macros", version = "0.32.0" } diff --git a/vulkano/macros/Cargo.toml b/vulkano/macros/Cargo.toml index 663a081f..9125ddc4 100644 --- a/vulkano/macros/Cargo.toml +++ b/vulkano/macros/Cargo.toml @@ -21,3 +21,6 @@ syn = "1.0" quote = "1.0" proc-macro2 = "1.0" proc-macro-crate = "1.2" + +[dev-dependencies] +vulkano = { path = ".." } diff --git a/vulkano/macros/src/derive_buffer_contents.rs b/vulkano/macros/src/derive_buffer_contents.rs new file mode 100644 index 00000000..2910e27e --- /dev/null +++ b/vulkano/macros/src/derive_buffer_contents.rs @@ -0,0 +1,329 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use crate::bail; +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::{ + parse_quote, spanned::Spanned, Data, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, Ident, + Meta, MetaList, NestedMeta, Result, Type, TypeArray, TypeSlice, WherePredicate, +}; + +pub fn derive_buffer_contents(mut ast: DeriveInput) -> Result { + let crate_ident = crate::crate_ident(); + + let struct_ident = &ast.ident; + + if !ast + .attrs + .iter() + .filter_map(|attr| { + attr.path + .is_ident("repr") + .then(|| attr.parse_meta().unwrap()) + }) + .any(|meta| match meta { + Meta::List(MetaList { nested, .. }) => { + nested.iter().any(|nested_meta| match nested_meta { + NestedMeta::Meta(Meta::Path(path)) => { + path.is_ident("C") || path.is_ident("transparent") + } + _ => false, + }) + } + _ => false, + }) + { + bail!( + "deriving `BufferContents` is only supported for types that are marked `#[repr(C)]` \ + or `#[repr(transparent)]`", + ); + } + + let (impl_generics, type_generics, where_clause) = { + let predicates = ast + .generics + .type_params() + .map(|ty| { + parse_quote! { #ty: ::#crate_ident::buffer::BufferContents } + }) + .collect::>(); + + ast.generics + .make_where_clause() + .predicates + .extend(predicates); + + ast.generics.split_for_impl() + }; + + let layout = write_layout(&crate_ident, &ast)?; + + Ok(quote! { + #[allow(unsafe_code)] + unsafe impl #impl_generics ::#crate_ident::buffer::BufferContents + for #struct_ident #type_generics #where_clause + { + const LAYOUT: ::#crate_ident::buffer::BufferContentsLayout = #layout; + + #[inline(always)] + unsafe fn from_ffi(data: *mut ::std::ffi::c_void, range: usize) -> *mut Self { + #[repr(C)] + union PtrRepr { + components: PtrComponents, + ptr: *mut T, + } + + #[derive(Clone, Copy)] + #[repr(C)] + struct PtrComponents { + data: *mut ::std::ffi::c_void, + len: usize, + } + + let alignment = ::LAYOUT + .alignment() + .as_devicesize() as usize; + ::std::debug_assert!(data as usize % alignment == 0); + + let head_size = ::LAYOUT + .head_size() as usize; + let element_size = ::LAYOUT + .element_size() + .unwrap_or(1) as usize; + + ::std::debug_assert!(range >= head_size); + let tail_size = range - head_size; + ::std::debug_assert!(tail_size % element_size == 0); + let len = tail_size / element_size; + + let components = PtrComponents { data, len }; + + // SAFETY: All fields must implement `BufferContents`. The last field, if it is + // unsized, must therefore be a slice or a DST derived from a slice. It cannot be + // any other kind of DST, unless unsafe code was used to achieve that. + // + // That means we can safely rely on knowing what kind of DST the implementing type + // is, but it doesn't tell us what the correct representation for the pointer of + // this kind of DST is. For that we have to rely on what the docs tell us, namely + // that for structs where the last field is a DST, the metadata is the same as the + // last field's. We also know that the metadata of a slice is its length measured + // in the number of elements. This tells us that the components of a pointer to the + // implementing type are the address to the start of the data, and a length. It + // still does not tell us what the representation of the pointer is though. + // + // In fact, there is no way to be certain that this representation is correct. + // *Theoretically* rustc could decide tomorrow that the metadata comes first and + // the address comes last, but the chance of that ever happening is zero. + // + // But what if the implementing type is actually sized? In that case this + // conversion will simply discard the length field, and only leave the pointer. + PtrRepr { components }.ptr + } + } + }) +} + +fn write_layout(crate_ident: &Ident, ast: &DeriveInput) -> Result { + let data = match &ast.data { + Data::Struct(data) => data, + Data::Enum(_) => bail!("deriving `BufferContents` for enums is not supported"), + Data::Union(_) => bail!("deriving `BufferContents` for unions is not supported"), + }; + + let fields = match &data.fields { + Fields::Named(FieldsNamed { named, .. }) => named, + Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => unnamed, + Fields::Unit => bail!("zero-sized types are not valid buffer contents"), + }; + + let mut field_types = fields.iter().map(|field| &field.ty); + let last_field_type = field_types.next_back().unwrap(); + let mut layout = quote! { ::std::alloc::Layout::new::<()>() }; + + let mut bound_types = Vec::new(); + + // Construct the layout of the head and accumulate the types that have to implement + // `BufferContents` in order for the struct to implement the trait as well. + for field_type in field_types { + bound_types.push(find_innermost_element_type(field_type)); + + layout = quote! { + extend_layout(#layout, ::std::alloc::Layout::new::<#field_type>()) + }; + } + + // The last field needs special treatment. + match last_field_type { + // An array might not implement `BufferContents` depending on the element, and therefore we + // can't use `BufferContents::extend_from_layout` on it. + Type::Array(TypeArray { elem, .. }) => { + bound_types.push(find_innermost_element_type(elem)); + layout = quote! { + ::#crate_ident::buffer::BufferContentsLayout::from_sized( + ::std::alloc::Layout::new::() + ) + }; + } + // A slice might contain an array same as above, and therefore we can't use + // `BufferContents::extend_from_layout` on it either. + Type::Slice(TypeSlice { elem, .. }) => { + bound_types.push(find_innermost_element_type(elem)); + layout = quote! { + ::#crate_ident::buffer::BufferContentsLayout::from_head_element_layout( + #layout, + ::std::alloc::Layout::new::<#elem>(), + ) + }; + } + ty => { + bound_types.push(ty); + layout = quote! { + <#last_field_type as ::#crate_ident::buffer::BufferContents>::LAYOUT + .extend_from_layout(&#layout) + }; + } + } + + let (impl_generics, _, where_clause) = ast.generics.split_for_impl(); + + let bounds = bound_types.into_iter().map(|ty| { + quote_spanned! { ty.span() => + { + // HACK: This works around Rust issue #48214, which makes it impossible to put + // these bounds in the where clause of the trait implementation where they actually + // belong until that is resolved. + #[allow(unused)] + fn bound #impl_generics () #where_clause { + fn assert_impl() {} + assert_impl::<#ty>(); + } + } + } + }); + + let layout = quote! { + { + #( #bounds )* + + // HACK: Very depressingly, `Layout::extend` is not const. + const fn extend_layout( + layout: ::std::alloc::Layout, + next: ::std::alloc::Layout, + ) -> ::std::alloc::Layout { + let padded_size = if let Some(val) = + layout.size().checked_add(next.align() - 1) + { + val & !(next.align() - 1) + } else { + ::std::unreachable!() + }; + + // TODO: Replace with `Ord::max` once its constness is stabilized. + let align = if layout.align() >= next.align() { + layout.align() + } else { + next.align() + }; + + if let Some(size) = padded_size.checked_add(next.size()) { + if let Ok(layout) = ::std::alloc::Layout::from_size_align(size, align) { + layout + } else { + ::std::unreachable!() + } + } else { + ::std::unreachable!() + } + } + + if let Some(layout) = #layout { + if let Some(layout) = layout.pad_to_alignment() { + layout + } else { + ::std::unreachable!() + } + } else { + ::std::panic!("zero-sized types are not valid buffer contents") + } + } + }; + + Ok(layout) +} + +// HACK: This works around an inherent limitation of bytemuck, namely that an array where the +// element is `AnyBitPattern` is itself not `AnyBitPattern`, by only requiring that the innermost +// type in the array implements `BufferContents`. +fn find_innermost_element_type(mut field_type: &Type) -> &Type { + while let Type::Array(TypeArray { elem, .. }) = field_type { + field_type = elem; + } + + field_type +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn repr() { + let default_repr = parse_quote! { + struct Test(u8, [u8]); + }; + assert!(derive_buffer_contents(default_repr).is_err()); + + let irellevant_reprs = parse_quote! { + #[repr(packed(2), align(16))] + struct Test(u8, [u8]); + }; + assert!(derive_buffer_contents(irellevant_reprs).is_err()); + + let transparent_repr = parse_quote! { + #[repr(transparent)] + struct Test([u8]); + }; + assert!(derive_buffer_contents(transparent_repr).is_ok()); + + let multiple_reprs = parse_quote! { + #[repr(align(16))] + #[repr(C)] + #[repr(packed)] + struct Test(u8, [u8]); + }; + assert!(derive_buffer_contents(multiple_reprs).is_ok()); + } + + #[test] + fn zero_sized() { + let unit = parse_quote! { + struct Test; + }; + assert!(derive_buffer_contents(unit).is_err()); + } + + #[test] + fn unsupported_datatype() { + let enum_ = parse_quote! { + #[repr(C)] + enum Test { A, B, C } + }; + assert!(derive_buffer_contents(enum_).is_err()); + + let union = parse_quote! { + #[repr(C)] + union Test { + a: u32, + b: f32, + } + }; + assert!(derive_buffer_contents(union).is_err()); + } +} diff --git a/vulkano/macros/src/derive_vertex.rs b/vulkano/macros/src/derive_vertex.rs index da9d7c61..c538df41 100644 --- a/vulkano/macros/src/derive_vertex.rs +++ b/vulkano/macros/src/derive_vertex.rs @@ -7,14 +7,13 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use proc_macro::TokenStream; -use proc_macro2::Span; -use proc_macro_crate::{crate_name, FoundCrate}; +use crate::bail; +use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, - Data, DataStruct, Error, Fields, Ident, LitStr, Result, Token, + Data, DataStruct, Fields, Ident, LitStr, Result, Token, }; pub fn derive_vertex(ast: syn::DeriveInput) -> Result { @@ -25,25 +24,14 @@ pub fn derive_vertex(ast: syn::DeriveInput) -> Result { fields: Fields::Named(fields), .. }) => &fields.named, - _ => { - return Err(Error::new_spanned( - ast, - "Expected a struct with named fields", - )); - } + _ => bail!("expected a struct with named fields"), }; - let found_crate = crate_name("vulkano").expect("vulkano is present in `Cargo.toml`"); - - let crate_ident = match found_crate { - // We use `vulkano` by default as we are exporting crate as vulkano in vulkano/lib.rs. - FoundCrate::Itself => Ident::new("vulkano", Span::call_site()), - FoundCrate::Name(name) => Ident::new(&name, Span::call_site()), - }; + let crate_ident = crate::crate_ident(); let mut members = quote! { let mut offset = 0; - let mut members = HashMap::default(); + let mut members = ::std::collections::HashMap::default(); }; for field in fields.iter() { @@ -64,30 +52,30 @@ pub fn derive_vertex(ast: syn::DeriveInput) -> Result { } else if attr_ident == "format" { let format_ident = attr.parse_args_with(Ident::parse)?; format = quote! { - let format = Format::#format_ident; + let format = ::#crate_ident::format::Format::#format_ident; }; } } if format.is_empty() { - return Err(Error::new( - field_name.span(), - "Expected `#[format(...)]`-attribute with valid `vulkano::format::Format`", - )); + bail!( + field_name, + "expected `#[format(...)]`-attribute with valid `vulkano::format::Format`", + ); } for name in &names { members = quote! { #members - let field_size = std::mem::size_of::<#field_ty>() as u32; + let field_size = ::std::mem::size_of::<#field_ty>() as u32; { #format let format_size = format.block_size().expect("no block size for format") as u32; let num_elements = field_size / format_size; let remainder = field_size % format_size; - assert!(remainder == 0, "struct field `{}` size does not fit multiple of format size", #field_name_lit); + ::std::assert!(remainder == 0, "struct field `{}` size does not fit multiple of format size", #field_name_lit); members.insert( #name.to_string(), - VertexMemberInfo { + ::#crate_ident::pipeline::graphics::vertex_input::VertexMemberInfo { offset, format, num_elements, @@ -100,37 +88,34 @@ pub fn derive_vertex(ast: syn::DeriveInput) -> Result { } let function_body = quote! { - #[allow(unused_imports)] - use std::collections::HashMap; - use #crate_ident::format::Format; - use #crate_ident::pipeline::graphics::vertex_input::{VertexInputRate, VertexMemberInfo}; - #members - #crate_ident::pipeline::graphics::vertex_input::VertexBufferDescription { + ::#crate_ident::pipeline::graphics::vertex_input::VertexBufferDescription { members, - stride: std::mem::size_of::<#struct_name>() as u32, - input_rate: VertexInputRate::Vertex, + stride: ::std::mem::size_of::<#struct_name>() as u32, + input_rate: ::#crate_ident::pipeline::graphics::vertex_input::VertexInputRate::Vertex, } }; - Ok(TokenStream::from(quote! { - #[allow(unsafe_code)] - unsafe impl #crate_ident::pipeline::graphics::vertex_input::Vertex for #struct_name { - #[inline(always)] - fn per_vertex() -> #crate_ident::pipeline::graphics::vertex_input::VertexBufferDescription { - #function_body - } - #[inline(always)] - fn per_instance() -> #crate_ident::pipeline::graphics::vertex_input::VertexBufferDescription { - #function_body.per_instance() - } - #[inline(always)] - fn per_instance_with_divisor(divisor: u32) -> #crate_ident::pipeline::graphics::vertex_input::VertexBufferDescription { - #function_body.per_instance_with_divisor(divisor) - } - } - })) + Ok(quote! { + #[allow(unsafe_code)] + unsafe impl ::#crate_ident::pipeline::graphics::vertex_input::Vertex for #struct_name { + #[inline(always)] + fn per_vertex() -> ::#crate_ident::pipeline::graphics::vertex_input::VertexBufferDescription { + #function_body + } + + #[inline(always)] + fn per_instance() -> ::#crate_ident::pipeline::graphics::vertex_input::VertexBufferDescription { + Self::per_vertex().per_instance() + } + + #[inline(always)] + fn per_instance_with_divisor(divisor: u32) -> ::#crate_ident::pipeline::graphics::vertex_input::VertexBufferDescription { + Self::per_vertex().per_instance_with_divisor(divisor) + } + } + }) } struct NameMeta { diff --git a/vulkano/macros/src/lib.rs b/vulkano/macros/src/lib.rs index 7914e00c..4189e0a7 100644 --- a/vulkano/macros/src/lib.rs +++ b/vulkano/macros/src/lib.rs @@ -8,12 +8,47 @@ // according to those terms. use proc_macro::TokenStream; -use syn::{parse_macro_input, DeriveInput}; +use proc_macro_crate::{crate_name, FoundCrate}; +use syn::{parse_macro_input, DeriveInput, Error}; +mod derive_buffer_contents; mod derive_vertex; #[proc_macro_derive(Vertex, attributes(name, format))] -pub fn proc_derive_vertex(input: TokenStream) -> TokenStream { +pub fn derive_vertex(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - derive_vertex::derive_vertex(ast).unwrap_or_else(|err| err.to_compile_error().into()) + + derive_vertex::derive_vertex(ast) + .unwrap_or_else(Error::into_compile_error) + .into() } + +#[proc_macro_derive(BufferContents)] +pub fn derive_buffer_contents(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + + derive_buffer_contents::derive_buffer_contents(ast) + .unwrap_or_else(Error::into_compile_error) + .into() +} + +fn crate_ident() -> syn::Ident { + let found_crate = crate_name("vulkano").unwrap(); + let name = match &found_crate { + // We use `vulkano` by default as we are exporting crate as vulkano in vulkano/lib.rs. + FoundCrate::Itself => "vulkano", + FoundCrate::Name(name) => name, + }; + + syn::Ident::new(name, proc_macro2::Span::call_site()) +} + +macro_rules! bail { + ($msg:expr $(,)?) => { + return Err(syn::Error::new(proc_macro2::Span::call_site(), $msg)) + }; + ($span:expr, $msg:expr $(,)?) => { + return Err(syn::Error::new_spanned($span, $msg)) + }; +} +use bail; diff --git a/vulkano/src/buffer/allocator.rs b/vulkano/src/buffer/allocator.rs index fd68a020..0645093c 100644 --- a/vulkano/src/buffer/allocator.rs +++ b/vulkano/src/buffer/allocator.rs @@ -9,7 +9,7 @@ //! Efficiently suballocates buffers into smaller subbuffers. -use super::{Buffer, BufferError, BufferMemory, BufferUsage, Subbuffer}; +use super::{Buffer, BufferContents, BufferError, BufferMemory, BufferUsage, Subbuffer}; use crate::{ buffer::BufferAllocateInfo, device::{Device, DeviceOwned}, @@ -17,11 +17,10 @@ use crate::{ align_up, AllocationCreationError, DeviceAlignment, DeviceLayout, MemoryAllocator, MemoryUsage, StandardMemoryAllocator, }, - DeviceSize, + DeviceSize, NonZeroDeviceSize, }; use crossbeam_queue::ArrayQueue; use std::{ - alloc::Layout, cell::UnsafeCell, cmp, hash::{Hash, Hasher}, @@ -196,36 +195,50 @@ where } /// Allocates a subbuffer for sized data. - /// - /// # Panics - /// - /// - Panics if `T` has zero size. - /// - Panics if `T` has an alignment greater than `64`. - pub fn allocate_sized(&self) -> Result, AllocationCreationError> { - let layout = DeviceLayout::from_layout(Layout::new::()) - .expect("can't allocate memory for zero-sized types"); + pub fn allocate_sized(&self) -> Result, AllocationCreationError> + where + T: BufferContents, + { + let layout = T::LAYOUT.unwrap_sized(); - self.allocate(layout) - .map(|subbuffer| unsafe { subbuffer.reinterpret() }) + unsafe { &mut *self.state.get() } + .allocate(layout) + .map(|subbuffer| unsafe { subbuffer.reinterpret_unchecked() }) } /// Allocates a subbuffer for a slice. /// /// # Panics /// - /// - Panics if `T` has zero size. - /// - Panics if `T` has an alignment greater than `64`. /// - Panics if `len` is zero. pub fn allocate_slice( &self, len: DeviceSize, - ) -> Result, AllocationCreationError> { - let layout = - DeviceLayout::from_layout(Layout::array::(len.try_into().unwrap()).unwrap()) - .expect("can't allocate memory for zero-sized types"); + ) -> Result, AllocationCreationError> + where + T: BufferContents, + { + self.allocate_unsized(len) + } - self.allocate(layout) - .map(|subbuffer| unsafe { subbuffer.reinterpret() }) + /// Allocates a subbuffer for unsized data. + /// + /// # Panics + /// + /// - Panics if `len` is zero. + pub fn allocate_unsized( + &self, + len: DeviceSize, + ) -> Result, AllocationCreationError> + where + T: BufferContents + ?Sized, + { + let len = NonZeroDeviceSize::new(len).expect("empty slices are not valid buffer contents"); + let layout = T::LAYOUT.layout_for_len(len).unwrap(); + + unsafe { &mut *self.state.get() } + .allocate(layout) + .map(|subbuffer| unsafe { subbuffer.reinterpret_unchecked() }) } /// Allocates a subbuffer with the given `layout`. @@ -239,11 +252,7 @@ where ) -> Result, AllocationCreationError> { assert!(layout.alignment().as_devicesize() <= 64); - let state = unsafe { &mut *self.state.get() }; - let offset = state.allocate(layout)?; - let arena = state.arena.as_ref().unwrap().clone(); - - Ok(Subbuffer::from_arena(arena, offset, layout.size())) + unsafe { &mut *self.state.get() }.allocate(layout) } } @@ -277,7 +286,10 @@ impl SubbufferAllocatorState where A: MemoryAllocator, { - fn allocate(&mut self, layout: DeviceLayout) -> Result { + fn allocate( + &mut self, + layout: DeviceLayout, + ) -> Result, AllocationCreationError> { let size = layout.size(); let alignment = cmp::max(layout.alignment(), self.buffer_alignment); @@ -310,7 +322,7 @@ where let offset = offset - arena_offset; self.free_start = offset + size; - return Ok(offset); + return Ok(Subbuffer::from_arena(arena.clone(), offset, layout.size())); } // We reached the end of the arena, grab the next one. diff --git a/vulkano/src/buffer/mod.rs b/vulkano/src/buffer/mod.rs index 54d3a0e5..4f4b9f6b 100644 --- a/vulkano/src/buffer/mod.rs +++ b/vulkano/src/buffer/mod.rs @@ -27,8 +27,8 @@ //! `VkBuffer`, and as such doesn't hold onto any memory. //! - [`Buffer`] is a `RawBuffer` with memory bound to it, and with state tracking. //! - [`Subbuffer`] is what you will use most of the time, as it is what all the APIs expect. It is -//! reference to a portion of a `Buffer`. `Subbuffer` also has a type parameter, which is a hint -//! for how the data in the portion of the buffer is going to be interpreted. +//! a reference to a portion of a `Buffer`. `Subbuffer` also has a type parameter, which is a +//! hint for how the data in the portion of the buffer is going to be interpreted. //! //! # `Subbuffer` allocation //! @@ -98,7 +98,10 @@ //! [the `view` module]: self::view //! [the `shader` module documentation]: crate::shader -pub use self::{subbuffer::Subbuffer, usage::BufferUsage}; +pub use self::{ + subbuffer::{BufferContents, BufferContentsLayout, Subbuffer}, + usage::BufferUsage, +}; use self::{ subbuffer::{ReadLockError, WriteLockError}, sys::{BufferCreateInfo, RawBuffer}, @@ -119,15 +122,13 @@ use crate::{ DeviceSize, NonZeroDeviceSize, RequirementNotMet, RequiresOneOf, Version, VulkanError, VulkanObject, }; -use bytemuck::{Pod, PodCastError}; use parking_lot::{Mutex, MutexGuard}; use smallvec::SmallVec; use std::{ - alloc::Layout, error::Error, fmt::{Display, Error as FmtError, Formatter}, hash::{Hash, Hasher}, - mem::{size_of, size_of_val}, + mem::size_of_val, ops::Range, ptr, sync::Arc, @@ -282,8 +283,6 @@ impl Buffer { /// /// # Panics /// - /// - Panics if `T` has zero size. - /// - Panics if `T` has an alignment greater than `64`. /// - Panics if `iter` is empty. pub fn from_iter( allocator: &(impl MemoryAllocator + ?Sized), @@ -291,7 +290,7 @@ impl Buffer { iter: I, ) -> Result, BufferError> where - [T]: BufferContents, + T: BufferContents, I: IntoIterator, I::IntoIter: ExactSizeIterator, { @@ -307,20 +306,17 @@ impl Buffer { /// Creates a new uninitialized `Buffer` for sized data. Returns a [`Subbuffer`] spanning the /// whole buffer. - /// - /// # Panics - /// - /// - Panics if `T` has zero size. - /// - Panics if `T` has an alignment greater than `64`. pub fn new_sized( allocator: &(impl MemoryAllocator + ?Sized), allocate_info: BufferAllocateInfo, - ) -> Result, BufferError> { - let layout = Layout::new::() - .try_into() - .expect("can't allocate memory for zero-sized types"); + ) -> Result, BufferError> + where + T: BufferContents, + { + let layout = T::LAYOUT.unwrap_sized(); + let buffer = Subbuffer::new(Buffer::new(allocator, allocate_info, layout)?); - Buffer::new(allocator, allocate_info, layout).map(Subbuffer::from_buffer) + Ok(unsafe { buffer.reinterpret_unchecked() }) } /// Creates a new uninitialized `Buffer` for a slice. Returns a [`Subbuffer`] spanning the @@ -328,20 +324,37 @@ impl Buffer { /// /// # Panics /// - /// - Panics if `T` has zero size. - /// - Panics if `T` has an alignment greater than `64`. /// - Panics if `len` is zero. pub fn new_slice( allocator: &(impl MemoryAllocator + ?Sized), allocate_info: BufferAllocateInfo, len: DeviceSize, - ) -> Result, BufferError> { - let layout = Layout::array::(len.try_into().unwrap()) - .unwrap() - .try_into() - .expect("can't allocate memory for zero-sized types"); + ) -> Result, BufferError> + where + T: BufferContents, + { + Buffer::new_unsized(allocator, allocate_info, len) + } - Buffer::new(allocator, allocate_info, layout).map(Subbuffer::from_buffer) + /// Creates a new uninitialized `Buffer` for unsized data. Returns a [`Subbuffer`] spanning the + /// whole buffer. + /// + /// # Panics + /// + /// - Panics if `len` is zero. + pub fn new_unsized( + allocator: &(impl MemoryAllocator + ?Sized), + allocate_info: BufferAllocateInfo, + len: DeviceSize, + ) -> Result, BufferError> + where + T: BufferContents + ?Sized, + { + let len = NonZeroDeviceSize::new(len).expect("empty slices are not valid buffer contents"); + let layout = T::LAYOUT.layout_for_len(len).unwrap(); + let buffer = Subbuffer::new(Buffer::new(allocator, allocate_info, layout)?); + + Ok(unsafe { buffer.reinterpret_unchecked() }) } /// Creates a new uninitialized `Buffer` with the given `layout`. @@ -1096,75 +1109,6 @@ vulkan_bitflags! { },*/ } -/// Trait for types of data that can be put in a buffer. These can be safely transmuted to and from -/// a slice of bytes. -pub unsafe trait BufferContents: Send + Sync + 'static { - /// Converts an immutable reference to `Self` to an immutable byte slice. - fn as_bytes(&self) -> &[u8]; - - /// Converts a mutable reference to `Self` to a mutable byte slice. - fn as_bytes_mut(&mut self) -> &mut [u8]; - - /// Converts an immutable byte slice into an immutable reference to `Self`. - fn from_bytes(bytes: &[u8]) -> Result<&Self, PodCastError>; - - /// Converts a mutable byte slice into a mutable reference to `Self`. - fn from_bytes_mut(bytes: &mut [u8]) -> Result<&mut Self, PodCastError>; - - /// Returns the size of an element of the type. - fn size_of_element() -> DeviceSize; -} - -unsafe impl BufferContents for T -where - T: Pod + Send + Sync, -{ - fn as_bytes(&self) -> &[u8] { - bytemuck::bytes_of(self) - } - - fn as_bytes_mut(&mut self) -> &mut [u8] { - bytemuck::bytes_of_mut(self) - } - - fn from_bytes(bytes: &[u8]) -> Result<&T, PodCastError> { - bytemuck::try_from_bytes(bytes) - } - - fn from_bytes_mut(bytes: &mut [u8]) -> Result<&mut T, PodCastError> { - bytemuck::try_from_bytes_mut(bytes) - } - - fn size_of_element() -> DeviceSize { - 1 - } -} - -unsafe impl BufferContents for [T] -where - T: Pod + Send + Sync, -{ - fn as_bytes(&self) -> &[u8] { - bytemuck::cast_slice(self) - } - - fn as_bytes_mut(&mut self) -> &mut [u8] { - bytemuck::cast_slice_mut(self) - } - - fn from_bytes(bytes: &[u8]) -> Result<&[T], PodCastError> { - bytemuck::try_cast_slice(bytes) - } - - fn from_bytes_mut(bytes: &mut [u8]) -> Result<&mut [T], PodCastError> { - bytemuck::try_cast_slice_mut(bytes) - } - - fn size_of_element() -> DeviceSize { - size_of::() as DeviceSize - } -} - /// The buffer configuration to query in /// [`PhysicalDevice::external_buffer_properties`](crate::device::physical::PhysicalDevice::external_buffer_properties). #[derive(Clone, Debug, PartialEq, Eq, Hash)] diff --git a/vulkano/src/buffer/subbuffer.rs b/vulkano/src/buffer/subbuffer.rs index 4996076b..49706722 100644 --- a/vulkano/src/buffer/subbuffer.rs +++ b/vulkano/src/buffer/subbuffer.rs @@ -7,9 +7,12 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use super::{allocator::Arena, Buffer, BufferContents, BufferError, BufferMemory}; +//! A subpart of a buffer. + +use super::{allocator::Arena, Buffer, BufferError, BufferMemory}; use crate::{ device::{Device, DeviceOwned}, + macros::try_opt, memory::{ self, allocator::{align_down, align_up, DeviceAlignment, DeviceLayout}, @@ -17,19 +20,23 @@ use crate::{ }, DeviceSize, NonZeroDeviceSize, }; -use bytemuck::PodCastError; +use bytemuck::{AnyBitPattern, PodCastError}; use std::{ alloc::Layout, cmp, error::Error, + ffi::c_void, fmt::{Display, Error as FmtError, Formatter}, hash::{Hash, Hasher}, marker::PhantomData, mem::{self, align_of, size_of}, ops::{Deref, DerefMut, Range, RangeBounds}, + ptr::{self, NonNull}, sync::Arc, }; +pub use vulkano_macros::BufferContents; + /// A subpart of a buffer. /// /// This type doesn't correspond to any Vulkan object, it exists for API convenience. Most Vulkan @@ -69,15 +76,6 @@ enum SubbufferParent { } impl Subbuffer { - pub(super) fn from_buffer(buffer: Arc) -> Self { - Subbuffer { - offset: 0, - size: buffer.size(), - parent: SubbufferParent::Buffer(buffer), - marker: PhantomData, - } - } - pub(super) fn from_arena(arena: Arc, offset: DeviceSize, size: DeviceSize) -> Self { Subbuffer { offset, @@ -120,6 +118,20 @@ impl Subbuffer { } } + /// Returns the mapped pointer to the start of the subbuffer if the memory is host-visible, + /// otherwise returns [`None`]. + pub fn mapped_ptr(&self) -> Option> { + match self.buffer().memory() { + BufferMemory::Normal(a) => a.mapped_ptr().map(|ptr| { + // SAFETY: The original address came from the Vulkan implementation, and allocation + // sizes are guaranteed to not exceed `isize::MAX` when there's a mapped pointer, + // so the offset better be in range. + unsafe { NonNull::new_unchecked(ptr.as_ptr().add(self.offset as usize)) } + }), + BufferMemory::Sparse => unreachable!(), + } + } + /// Returns the device address for this subbuffer. pub fn device_address(&self) -> Result { self.buffer().device_address().map(|ptr| { @@ -130,46 +142,31 @@ impl Subbuffer { }) } - /// Changes the `T` generic parameter of the subbffer to the desired type. - /// - /// You should **always** prefer the safe functions [`try_from_bytes`], [`into_bytes`], - /// [`try_cast`], [`try_cast_slice`] or [`into_slice`]. - /// - /// # Safety - /// - /// - Correct offset and size must be ensured before using this `Subbuffer` on the device. - /// - /// [`try_from_bytes`]: Self::try_from_bytes - /// [`into_bytes`]: Self::into_bytes - /// [`try_cast`]: Self::try_cast - /// [`try_cast_slice`]: Self::try_cast_slice - /// [`into_slice`]: Self::into_slice - pub unsafe fn reinterpret(self) -> Subbuffer { - // SAFETY: All `Subbuffer`s share the same layout. - mem::transmute::, Subbuffer>(self) - } - - /// Same as [`reinterpret`], except it works with a reference to the subbuffer. - /// - /// [`reinterpret`]: Self::reinterpret - pub unsafe fn reinterpret_ref(&self) -> &Subbuffer { - assert!(size_of::>() == size_of::>()); - assert!(align_of::>() == align_of::>()); - - // SAFETY: All `Subbuffer`s share the same layout. - mem::transmute::<&Subbuffer, &Subbuffer>(self) - } - /// Casts the subbuffer to a slice of raw bytes. pub fn into_bytes(self) -> Subbuffer<[u8]> { - unsafe { self.reinterpret() } + unsafe { self.reinterpret_unchecked_inner() } } /// Same as [`into_bytes`], except it works with a reference to the subbuffer. /// /// [`into_bytes`]: Self::into_bytes pub fn as_bytes(&self) -> &Subbuffer<[u8]> { - unsafe { self.reinterpret_ref() } + unsafe { self.reinterpret_ref_unchecked_inner() } + } + + #[inline(always)] + unsafe fn reinterpret_unchecked_inner(self) -> Subbuffer { + // SAFETY: All `Subbuffer`s share the same layout. + mem::transmute::, Subbuffer>(self) + } + + #[inline(always)] + unsafe fn reinterpret_ref_unchecked_inner(&self) -> &Subbuffer { + assert!(size_of::>() == size_of::>()); + assert!(align_of::>() == align_of::>()); + + // SAFETY: All `Subbuffer`s share the same layout. + mem::transmute::<&Subbuffer, &Subbuffer>(self) } } @@ -177,6 +174,53 @@ impl Subbuffer where T: BufferContents + ?Sized, { + /// Changes the `T` generic parameter of the subbffer to the desired type without checking if + /// the contents are correctly aligned and sized. + /// + /// **NEVER use this function** unless you absolutely have to, and even then, open an issue on + /// GitHub instead. **An unaligned / incorrectly sized subbuffer is undefined behavior _both on + /// the Rust and the Vulkan side!_** + /// + /// # Safety + /// + /// - `self.memory_offset()` must be properly aligned for `U`. + /// - `self.size()` must be valid for `U`, which means: + /// - If `U` is sized, the size must match exactly. + /// - If `U` is unsized, then the subbuffer size minus the size of the head (sized part) of + /// the DST must be evenly divisible by the size of the element type. + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn reinterpret_unchecked(self) -> Subbuffer + where + U: BufferContents + ?Sized, + { + let element_size = U::LAYOUT.element_size().unwrap_or(1); + debug_assert!(is_aligned(self.memory_offset(), U::LAYOUT.alignment())); + debug_assert!(self.size >= U::LAYOUT.head_size()); + debug_assert!((self.size - U::LAYOUT.head_size()) % element_size == 0); + + self.reinterpret_unchecked_inner() + } + + /// Same as [`reinterpret_unchecked`], except it works with a reference to the subbuffer. + /// + /// # Safety + /// + /// Please read the safety docs on [`reinterpret_unchecked`] carefully. + /// + /// [`reinterpret_unchecked`]: Self::reinterpret_unchecked + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn reinterpret_ref_unchecked(&self) -> &Subbuffer + where + U: BufferContents + ?Sized, + { + let element_size = U::LAYOUT.element_size().unwrap_or(1); + debug_assert!(is_aligned(self.memory_offset(), U::LAYOUT.alignment())); + debug_assert!(self.size >= U::LAYOUT.head_size()); + debug_assert!((self.size - U::LAYOUT.head_size()) % element_size == 0); + + self.reinterpret_ref_unchecked_inner() + } + /// Locks the subbuffer in order to read its content from the host. /// /// If the subbuffer is currently used in exclusive mode by the device, this function will @@ -209,9 +253,7 @@ where BufferMemory::Sparse => todo!("`Subbuffer::read` doesn't support sparse binding yet"), }; - let range = self.range(); - - let aligned_range = if let Some(atom_size) = allocation.atom_size() { + let range = if let Some(atom_size) = allocation.atom_size() { // This works because the suballocators align allocations to the non-coherent atom size // when the memory is host-visible but not host-coherent. let start = align_down(self.offset, atom_size); @@ -222,12 +264,12 @@ where Range { start, end } } else { - range.clone() + self.range() }; let mut state = self.buffer().state(); - state.check_cpu_read(aligned_range.clone())?; - unsafe { state.cpu_read_lock(aligned_range.clone()) }; + state.check_cpu_read(range.clone())?; + unsafe { state.cpu_read_lock(range.clone()) }; if allocation.atom_size().is_some() { // If there are other read locks being held at this point, they also called @@ -235,16 +277,17 @@ where // lock, so there will be no new data and this call will do nothing. // TODO: probably still more efficient to call it only if we're the first to acquire a // read lock, but the number of CPU locks isn't currently tracked anywhere. - unsafe { allocation.invalidate_range(aligned_range.clone()) }?; + unsafe { allocation.invalidate_range(range.clone()) }?; } - let bytes = unsafe { allocation.read(range) }.ok_or(BufferError::MemoryNotHostVisible)?; - let data = T::from_bytes(bytes).unwrap(); + let mapped_ptr = self.mapped_ptr().ok_or(BufferError::MemoryNotHostVisible)?; + // SAFETY: `Subbuffer` guarantees that its contents are laid out correctly for `T`. + let data = unsafe { &*T::from_ffi(mapped_ptr.as_ptr(), self.size as usize) }; Ok(BufferReadGuard { subbuffer: self, data, - range: aligned_range, + range, }) } @@ -279,9 +322,7 @@ where BufferMemory::Sparse => todo!("`Subbuffer::write` doesn't support sparse binding yet"), }; - let range = self.range(); - - let aligned_range = if let Some(atom_size) = allocation.atom_size() { + let range = if let Some(atom_size) = allocation.atom_size() { // This works because the suballocators align allocations to the non-coherent atom size // when the memory is host-visible but not host-coherent. let start = align_down(self.offset, atom_size); @@ -292,24 +333,25 @@ where Range { start, end } } else { - range.clone() + self.range() }; let mut state = self.buffer().state(); - state.check_cpu_write(aligned_range.clone())?; - unsafe { state.cpu_write_lock(aligned_range.clone()) }; + state.check_cpu_write(range.clone())?; + unsafe { state.cpu_write_lock(range.clone()) }; if allocation.atom_size().is_some() { - unsafe { allocation.invalidate_range(aligned_range.clone()) }?; + unsafe { allocation.invalidate_range(range.clone()) }?; } - let bytes = unsafe { allocation.write(range) }.ok_or(BufferError::MemoryNotHostVisible)?; - let data = T::from_bytes_mut(bytes).unwrap(); + let mapped_ptr = self.mapped_ptr().ok_or(BufferError::MemoryNotHostVisible)?; + // SAFETY: `Subbuffer` guarantees that its contents are laid out correctly for `T`. + let data = unsafe { &mut *T::from_ffi(mapped_ptr.as_ptr(), self.size as usize) }; Ok(BufferWriteGuard { subbuffer: self, data, - range: aligned_range, + range, }) } } @@ -317,7 +359,14 @@ where impl Subbuffer { /// Converts the subbuffer to a slice of one element. pub fn into_slice(self) -> Subbuffer<[T]> { - unsafe { self.reinterpret() } + unsafe { self.reinterpret_unchecked_inner() } + } + + /// Same as [`into_slice`], except it works with a reference to the subbuffer. + /// + /// [`into_slice`]: Self::into_slice + pub fn as_slice(&self) -> &Subbuffer<[T]> { + unsafe { self.reinterpret_ref_unchecked_inner() } } } @@ -326,35 +375,21 @@ where T: BufferContents, { /// Tries to cast a subbuffer of raw bytes to a `Subbuffer`. - /// - /// # Panics - /// - /// - Panics if `T` has zero size. - /// - Panics if `T` has an alignment greater than `64`. pub fn try_from_bytes(subbuffer: Subbuffer<[u8]>) -> Result { - assert_valid_type_param::(); - if subbuffer.size() != size_of::() as DeviceSize { Err(PodCastError::SizeMismatch) } else if !is_aligned(subbuffer.memory_offset(), DeviceAlignment::of::()) { Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) } else { - Ok(unsafe { subbuffer.reinterpret() }) + Ok(unsafe { subbuffer.reinterpret_unchecked() }) } } /// Tries to cast the subbuffer to a different type. - /// - /// # Panics - /// - /// - Panics if `U` has zero size. - /// - Panics if `U` has an alignment greater than `64`. pub fn try_cast(self) -> Result, PodCastError> where U: BufferContents, { - assert_valid_type_param::(); - if size_of::() != size_of::() { Err(PodCastError::SizeMismatch) } else if align_of::() > align_of::() @@ -362,7 +397,7 @@ where { Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) } else { - Ok(unsafe { self.reinterpret() }) + Ok(unsafe { self.reinterpret_unchecked() }) } } } @@ -370,9 +405,7 @@ where impl Subbuffer<[T]> { /// Returns the number of elements in the slice. pub fn len(&self) -> DeviceSize { - assert_valid_type_param::(); - - debug_assert!(self.size() % size_of::() as DeviceSize == 0); + debug_assert!(self.size % size_of::() as DeviceSize == 0); self.size / size_of::() as DeviceSize } @@ -381,7 +414,7 @@ impl Subbuffer<[T]> { /// /// # Panics /// - /// - Panics if `index` is out of range. + /// - Panics if `index` is out of bounds. pub fn index(self, index: DeviceSize) -> Subbuffer { assert!(index <= self.len()); @@ -403,11 +436,13 @@ impl Subbuffer<[T]> { /// # Panics /// /// - Panics if `range` is out of bounds. + /// - Panics if `range` is empty. pub fn slice(mut self, range: impl RangeBounds) -> Subbuffer<[T]> { let Range { start, end } = memory::range(range, ..self.len()).unwrap(); self.offset += start * size_of::() as DeviceSize; self.size = (end - start) * size_of::() as DeviceSize; + assert!(self.size != 0); self } @@ -418,6 +453,7 @@ impl Subbuffer<[T]> { self.offset += start * size_of::() as DeviceSize; self.size = (end - start) * size_of::() as DeviceSize; + debug_assert!(self.size != 0); self } @@ -426,9 +462,10 @@ impl Subbuffer<[T]> { /// /// # Panics /// - /// - Panics if `mid` is out of bounds. + /// - Panics if `mid` is not greater than `0`. + /// - Panics if `mid` is not less than `self.len()`. pub fn split_at(self, mid: DeviceSize) -> (Subbuffer<[T]>, Subbuffer<[T]>) { - assert!(mid <= self.len()); + assert!(0 < mid && mid < self.len()); unsafe { self.split_at_unchecked(mid) } } @@ -443,6 +480,17 @@ impl Subbuffer<[T]> { } impl Subbuffer<[u8]> { + /// Creates a new `Subbuffer<[u8]>` spanning the whole buffer. + #[inline] + pub fn new(buffer: Arc) -> Self { + Subbuffer { + offset: 0, + size: buffer.size(), + parent: SubbufferParent::Buffer(buffer), + marker: PhantomData, + } + } + /// Casts the slice to a different element type while ensuring correct alignment for the type. /// /// The offset of the subbuffer is rounded up to the alignment of `T` and the size abjusted for @@ -451,13 +499,14 @@ impl Subbuffer<[u8]> { /// # Panics /// /// - Panics if the aligned offset would be out of bounds. - /// - Panics if `T` has zero size. - /// - Panics if `T` has an alignment greater than `64`. - pub fn cast_aligned(self) -> Subbuffer<[T]> { + pub fn cast_aligned(self) -> Subbuffer<[T]> + where + T: BufferContents, + { let layout = DeviceLayout::from_layout(Layout::new::()).unwrap(); let aligned = self.align_to(layout); - unsafe { aligned.reinterpret() } + unsafe { aligned.reinterpret_unchecked() } } /// Aligns the subbuffer to the given `layout` by rounding the offset up to @@ -468,6 +517,7 @@ impl Subbuffer<[u8]> { /// /// - Panics if the aligned offset would be out of bounds. /// - Panics if `layout.alignment()` exceeds `64`. + #[inline] pub fn align_to(mut self, layout: DeviceLayout) -> Subbuffer<[u8]> { assert!(layout.alignment().as_devicesize() <= 64); @@ -484,20 +534,13 @@ impl Subbuffer<[u8]> { impl Subbuffer<[T]> where - [T]: BufferContents, + T: BufferContents, { /// Tries to cast the slice to a different element type. - /// - /// # Panics - /// - /// - Panics if `U` has zero size. - /// - Panics if `U` has an alignment greater than `64`. pub fn try_cast_slice(self) -> Result, PodCastError> where - [U]: BufferContents, + U: BufferContents, { - assert_valid_type_param::(); - if size_of::() != size_of::() && self.size() % size_of::() as DeviceSize != 0 { Err(PodCastError::OutputSliceWouldHaveSlop) } else if align_of::() > align_of::() @@ -505,21 +548,15 @@ where { Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) } else { - Ok(unsafe { self.reinterpret() }) + Ok(unsafe { self.reinterpret_unchecked() }) } } } -#[inline(always)] -fn assert_valid_type_param() { - assert!(size_of::() != 0); - assert!(align_of::() <= 64); -} - impl From> for Subbuffer<[u8]> { #[inline] fn from(buffer: Arc) -> Self { - Self::from_buffer(buffer) + Self::new(buffer) } } @@ -675,6 +712,425 @@ impl Display for WriteLockError { } } +/// Trait for types of data that can be put in a buffer. +/// +/// This trait is not intended to be implemented manually (ever) and attempting so will make you +/// one sad individual very quickly. Rather you should use [the derive macro]. Note also that there +/// are blanket implementations of this trait: you don't need to implement it if the type in +/// question already implements bytemuck's [`AnyBitPattern`]. Most if not all linear algebra crates +/// have a feature flag that you can enable for bytemuck support. The trait is also already +/// implemented for all slices where the element type implements `BufferContents`. +/// +/// # Examples +/// +/// Deriving the trait for sized types: +/// +/// ``` +/// # use vulkano::buffer::BufferContents; +/// #[derive(BufferContents)] +/// #[repr(C)] +/// struct MyData { +/// x: f32, +/// y: f32, +/// array: [i32; 12], +/// } +/// ``` +/// +/// Deriving the trait for unsized types works the same: +/// +/// ``` +/// # use vulkano::buffer::BufferContents; +/// #[derive(BufferContents)] +/// #[repr(C)] +/// struct MyData { +/// x: f32, +/// y: f32, +/// slice: [i32], +/// } +/// ``` +/// +/// This even works if the last field is a user-defined DST too: +/// +/// ``` +/// # use vulkano::buffer::BufferContents; +/// #[derive(BufferContents)] +/// #[repr(C)] +/// struct MyData { +/// x: f32, +/// y: f32, +/// other: OtherData, +/// } +/// +/// #[derive(BufferContents)] +/// #[repr(C)] +/// struct OtherData { +/// slice: [i32], +/// } +/// ``` +/// +/// You can also use generics if you please: +/// +/// ``` +/// # use vulkano::buffer::BufferContents; +/// #[derive(BufferContents)] +/// #[repr(C)] +/// struct MyData { +/// x: T, +/// y: T, +/// slice: [U], +/// } +/// ``` +/// +/// This even works with dependently-sized types: +/// +/// ``` +/// # use vulkano::buffer::BufferContents; +/// #[derive(BufferContents)] +/// #[repr(C)] +/// struct MyData +/// where +/// T: ?Sized, +/// { +/// x: f32, +/// y: f32, +/// z: T, +/// } +/// ``` +/// +/// [the derive macro]: vulkano_macros::BufferContents +// +// If you absolutely *must* implement this trait by hand, here are the safety requirements (but +// please open an issue on GitHub instead): +// +// - The type must be a struct and all fields must implement `BufferContents`. +// - `LAYOUT` must be the correct layout for the type, which also means the type must either be +// sized or if it's unsized then its metadata must be the same as that of a slice. Implementing +// `BufferContents` for any other kind of DST is instantaneous horrifically undefined behavior. +// - `from_ffi` must create a pointer with the same address as the `data` parameter that is passed +// in. The pointer is expected to be aligned properly already. +// - `from_ffi` must create a pointer that is expected to be valid for reads (and potentially +// writes) for exactly `range` bytes. The `data` and `range` are expected to be valid for the +// `LAYOUT`. +pub unsafe trait BufferContents: Send + Sync + 'static { + /// The layout of the contents. + const LAYOUT: BufferContentsLayout; + + /// Creates a pointer to `Self` from a pointer to the start of the data and a range in bytes. + /// + /// # Safety + /// + /// - If `Self` is sized, then `range` must match the size exactly. + /// - If `Self` is unsized, then the `range` minus the size of the head (sized part) of the DST + /// must be evenly divisible by the size of the element type. + #[doc(hidden)] + unsafe fn from_ffi(data: *mut c_void, range: usize) -> *mut Self; +} + +unsafe impl BufferContents for T +where + T: AnyBitPattern + Send + Sync, +{ + const LAYOUT: BufferContentsLayout = + if let Some(layout) = BufferContentsLayout::from_sized(Layout::new::()) { + layout + } else { + panic!("zero-sized types are not valid buffer contents"); + }; + + #[inline(always)] + unsafe fn from_ffi(data: *mut c_void, range: usize) -> *mut Self { + debug_assert!(range == size_of::()); + debug_assert!(data as usize % align_of::() == 0); + + data.cast() + } +} + +unsafe impl BufferContents for [T] +where + T: BufferContents, +{ + const LAYOUT: BufferContentsLayout = BufferContentsLayout(BufferContentsLayoutInner::Unsized { + head_layout: None, + element_layout: T::LAYOUT.unwrap_sized(), + }); + + #[inline(always)] + unsafe fn from_ffi(data: *mut c_void, range: usize) -> *mut Self { + debug_assert!(range % size_of::() == 0); + debug_assert!(data as usize % align_of::() == 0); + let len = range / size_of::(); + + ptr::slice_from_raw_parts_mut(data.cast(), len) + } +} + +/// Describes the layout required for a type so that it can be read from/written to a buffer. This +/// is used to allocate (sub)buffers generically. +/// +/// This is similar to [`DeviceLayout`] except that this exists for the sole purpose of describing +/// the layout of buffer contents specifically. Which means for example that the sizedness of the +/// type is captured, as well as the layout of the head and tail if the layout is for unsized data, +/// in order to be able to represent everything that Vulkan can stuff in a buffer. +/// +/// `BufferContentsLayout` also has an additional invariant compared to `DeviceLayout`: the +/// alignment of the data must not exceed `64`. This is because that's the guaranteed alignment +/// that all `DeviceMemory` blocks must be aligned to at minimum, and hence any greater alignment +/// can't be guaranteed. Other than that, the invariant that sizes must be non-zero applies here as +/// well, for both sized data and the element type of unsized data. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct BufferContentsLayout(BufferContentsLayoutInner); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +enum BufferContentsLayoutInner { + Sized(DeviceLayout), + Unsized { + head_layout: Option, + element_layout: DeviceLayout, + }, +} + +impl BufferContentsLayout { + /// Returns the size of the head (sized part). If the data has no sized part, then this will + /// return 0. + #[inline] + pub const fn head_size(&self) -> DeviceSize { + match &self.0 { + BufferContentsLayoutInner::Sized(sized) => sized.size(), + BufferContentsLayoutInner::Unsized { + head_layout: None, .. + } => 0, + BufferContentsLayoutInner::Unsized { + head_layout: Some(head_layout), + .. + } => head_layout.size(), + } + } + + /// Returns the size of the element type if the data is unsized, or returns [`None`]. + /// Guaranteed to be non-zero. + #[inline] + pub const fn element_size(&self) -> Option { + match &self.0 { + BufferContentsLayoutInner::Sized(_) => None, + BufferContentsLayoutInner::Unsized { element_layout, .. } => { + Some(element_layout.size()) + } + } + } + + /// Returns the alignment required for the data. Guaranteed to not exceed `64`. + #[inline] + pub const fn alignment(&self) -> DeviceAlignment { + match &self.0 { + BufferContentsLayoutInner::Sized(sized) => sized.alignment(), + BufferContentsLayoutInner::Unsized { + head_layout: None, + element_layout, + } => element_layout.alignment(), + BufferContentsLayoutInner::Unsized { + head_layout: Some(head_layout), + .. + } => head_layout.alignment(), + } + } + + /// Returns the [`DeviceLayout`] for the data for the given `len`, or returns [`None`] on + /// arithmetic overflow or if the total size would exceed [`DeviceLayout::MAX_SIZE`]. + #[inline] + pub const fn layout_for_len(&self, len: NonZeroDeviceSize) -> Option { + match &self.0 { + BufferContentsLayoutInner::Sized(sized) => Some(*sized), + BufferContentsLayoutInner::Unsized { + head_layout, + element_layout, + } => { + let (tail_layout, _) = try_opt!(element_layout.repeat(len)); + + if let Some(head_layout) = head_layout { + let (layout, _) = try_opt!(head_layout.extend(tail_layout)); + + Some(layout.pad_to_alignment()) + } else { + Some(tail_layout) + } + } + } + } + + /// Creates a new `BufferContentsLayout` from a sized layout. This is inteded for use by the + /// derive macro only. + #[doc(hidden)] + #[inline] + pub const fn from_sized(sized: Layout) -> Option { + assert!( + sized.align() <= 64, + "types with alignments above 64 are not valid buffer contents", + ); + + if let Ok(sized) = DeviceLayout::from_layout(sized) { + Some(Self(BufferContentsLayoutInner::Sized(sized))) + } else { + None + } + } + + /// Creates a new `BufferContentsLayout` from a head and element layout. This is inteded for + /// use by the derive macro only. + #[doc(hidden)] + #[inline] + pub const fn from_head_element_layout( + head_layout: Layout, + element_layout: Layout, + ) -> Option { + if head_layout.align() > 64 || element_layout.align() > 64 { + panic!("types with alignments above 64 are not valid buffer contents"); + } + + // The head of a `BufferContentsLayout` can be zero-sized. + // TODO: Replace with `Result::ok` once its constness is stabilized. + let head_layout = if let Ok(head_layout) = DeviceLayout::from_layout(head_layout) { + Some(head_layout) + } else { + None + }; + + if let Ok(element_layout) = DeviceLayout::from_layout(element_layout) { + Some(Self(BufferContentsLayoutInner::Unsized { + head_layout, + element_layout, + })) + } else { + None + } + } + + /// Extends the given `previous` [`Layout`] by `self`. This is intended for use by the derive + /// macro only. + #[doc(hidden)] + #[inline] + pub const fn extend_from_layout(self, previous: &Layout) -> Option { + assert!( + previous.align() <= 64, + "types with alignments above 64 are not valid buffer contents", + ); + + match self.0 { + BufferContentsLayoutInner::Sized(sized) => { + let (sized, _) = try_opt!(sized.extend_from_layout(previous)); + + Some(Self(BufferContentsLayoutInner::Sized(sized))) + } + BufferContentsLayoutInner::Unsized { + head_layout: None, + element_layout, + } => { + // The head of a `BufferContentsLayout` can be zero-sized. + // TODO: Replace with `Result::ok` once its constness is stabilized. + let head_layout = if let Ok(head_layout) = DeviceLayout::from_layout(*previous) { + Some(head_layout) + } else { + None + }; + + Some(Self(BufferContentsLayoutInner::Unsized { + head_layout, + element_layout, + })) + } + BufferContentsLayoutInner::Unsized { + head_layout: Some(head_layout), + element_layout, + } => { + let (head_layout, _) = try_opt!(head_layout.extend_from_layout(previous)); + + Some(Self(BufferContentsLayoutInner::Unsized { + head_layout: Some(head_layout), + element_layout, + })) + } + } + } + + /// Creates a new `BufferContentsLayout` by rounding up the size of the head to the nearest + /// multiple of its alignment if the layout is sized, or by rounding up the size of the head to + /// the nearest multiple of the alignment of the element type and aligning the head to the + /// alignment of the element type if there is a sized part. Doesn't do anything if there is no + /// sized part. Returns [`None`] if the new head size would exceed [`DeviceLayout::MAX_SIZE`]. + /// This is inteded for use by the derive macro only. + #[doc(hidden)] + #[inline] + pub const fn pad_to_alignment(&self) -> Option { + match &self.0 { + BufferContentsLayoutInner::Sized(sized) => Some(Self( + BufferContentsLayoutInner::Sized(sized.pad_to_alignment()), + )), + BufferContentsLayoutInner::Unsized { + head_layout: None, + element_layout, + } => Some(Self(BufferContentsLayoutInner::Unsized { + head_layout: None, + element_layout: *element_layout, + })), + BufferContentsLayoutInner::Unsized { + head_layout: Some(head_layout), + element_layout, + } => { + // We must pad the head to the alignment of the element type, *not* the alignment + // of the head. + // + // Consider a head layout of `(u8, u8, u8)` and an element layout of `u32`. If we + // padded the head to its own alignment, like is the case for sized layouts, it + // wouldn't change the size. Yet there is padding between the head and the first + // element of the slice. + // + // The reverse is true: consider a head layout of `(u16, u8)` and an element layout + // of `u8`. If we padded the head to its own alignment, it would be too large. + let padded_head_size = + head_layout.size() + head_layout.padding_needed_for(element_layout.alignment()); + + // SAFETY: `BufferContentsLayout`'s invariant guarantees that the alignment of the + // element type doesn't exceed 64, which together with the overflow invariant of + // `DeviceLayout` means that this can't overflow. + let padded_head_size = + unsafe { NonZeroDeviceSize::new_unchecked(padded_head_size) }; + + // We have to align the head to the alignment of the element type, so that the + // struct as a whole is aligned correctly when a different struct is extended with + // this one. + // + // Note that this is *not* the same as aligning the head to the alignment of the + // element type and then padding the layout to its alignment. Consider the same + // layout from above, with a head layout of `(u16, u8)` and an element layout of + // `u8`. If we aligned the head to the element type and then padded it to its own + // alignment, we would get the same wrong result as above. This instead ensures the + // head is padded to the element and aligned to it, without the alignment of the + // head interfering. + let alignment = + DeviceAlignment::max(head_layout.alignment(), element_layout.alignment()); + + if let Some(head_layout) = DeviceLayout::new(padded_head_size, alignment) { + Some(Self(BufferContentsLayoutInner::Unsized { + head_layout: Some(head_layout), + element_layout: *element_layout, + })) + } else { + None + } + } + } + } + + pub(super) const fn unwrap_sized(self) -> DeviceLayout { + match self.0 { + BufferContentsLayoutInner::Sized(sized) => sized, + BufferContentsLayoutInner::Unsized { .. } => { + panic!("called `BufferContentsLayout::unwrap_sized` on an unsized layout"); + } + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -692,6 +1148,68 @@ mod tests { }, }; + #[test] + fn derive_buffer_contents() { + #[derive(BufferContents)] + #[repr(C)] + struct Test1(u32, u64, u8); + + assert_eq!(Test1::LAYOUT.head_size() as usize, size_of::()); + assert_eq!(Test1::LAYOUT.element_size(), None); + assert_eq!( + Test1::LAYOUT.alignment().as_devicesize() as usize, + align_of::(), + ); + + #[derive(BufferContents)] + #[repr(C)] + struct Composite1(Test1, [f32; 10], Test1); + + assert_eq!( + Composite1::LAYOUT.head_size() as usize, + size_of::(), + ); + assert_eq!(Composite1::LAYOUT.element_size(), None); + assert_eq!( + Composite1::LAYOUT.alignment().as_devicesize() as usize, + align_of::(), + ); + + #[derive(BufferContents)] + #[repr(C)] + struct Test2(u64, u8, [u32]); + + assert_eq!( + Test2::LAYOUT.head_size() as usize, + size_of::() + size_of::(), + ); + assert_eq!( + Test2::LAYOUT.element_size().unwrap() as usize, + size_of::(), + ); + assert_eq!( + Test2::LAYOUT.alignment().as_devicesize() as usize, + align_of::(), + ); + + #[derive(BufferContents)] + #[repr(C)] + struct Composite2(Test1, [f32; 10], Test2); + + assert_eq!( + Composite2::LAYOUT.head_size() as usize, + size_of::() + size_of::<[f32; 10]>() + size_of::() + size_of::(), + ); + assert_eq!( + Composite2::LAYOUT.element_size().unwrap() as usize, + size_of::(), + ); + assert_eq!( + Composite2::LAYOUT.alignment().as_devicesize() as usize, + align_of::(), + ); + } + #[test] fn split_at() { let (device, _) = gfx_dev_and_queue!(); @@ -714,13 +1232,17 @@ mod tests { } { - let (left, right) = buffer.clone().split_at(6); - assert!(left.len() == 6); - assert!(right.len() == 0); + let (left, right) = buffer.clone().split_at(5); + assert!(left.len() == 5); + assert!(right.len() == 1); } { - assert_should_panic!({ buffer.split_at(7) }); + assert_should_panic!({ buffer.clone().split_at(0) }); + } + + { + assert_should_panic!({ buffer.split_at(6) }); } } diff --git a/vulkano/src/command_buffer/commands/bind_push.rs b/vulkano/src/command_buffer/commands/bind_push.rs index d94fc2af..d238cc9c 100644 --- a/vulkano/src/command_buffer/commands/bind_push.rs +++ b/vulkano/src/command_buffer/commands/bind_push.rs @@ -1197,7 +1197,7 @@ impl UnsafeCommandBufferBuilder { stages.into(), offset, size, - data.as_bytes().as_ptr() as *const _, + data as *const _ as *const _, ); } diff --git a/vulkano/src/command_buffer/commands/clear.rs b/vulkano/src/command_buffer/commands/clear.rs index 3eec6732..9e686111 100644 --- a/vulkano/src/command_buffer/commands/clear.rs +++ b/vulkano/src/command_buffer/commands/clear.rs @@ -400,7 +400,7 @@ where assert_eq!(device, dst_buffer.device()); // VUID-vkCmdFillBuffer-size-00026 - assert!(dst_buffer.size() != 0); + // Guaranteed by `Subbuffer` // VUID-vkCmdFillBuffer-dstBuffer-00029 if !dst_buffer @@ -438,7 +438,10 @@ where D: BufferContents + ?Sized, Dd: SafeDeref + Send + Sync + 'static, { - self.validate_update_buffer(dst_buffer.as_bytes(), data.deref().as_bytes())?; + self.validate_update_buffer( + dst_buffer.as_bytes(), + size_of_val(data.deref()) as DeviceSize, + )?; unsafe { self.inner.update_buffer(dst_buffer, data)?; @@ -450,7 +453,7 @@ where fn validate_update_buffer( &self, dst_buffer: &Subbuffer<[u8]>, - data: &[u8], + data_size: DeviceSize, ) -> Result<(), ClearError> { let device = self.device(); @@ -473,7 +476,7 @@ where assert_eq!(device, dst_buffer.device()); // VUID-vkCmdUpdateBuffer-dataSize-arraylength - assert!(size_of_val(data) != 0); + assert!(data_size != 0); // VUID-vkCmdUpdateBuffer-dstBuffer-00034 if !dst_buffer @@ -488,10 +491,10 @@ where // VUID-vkCmdUpdateBuffer-dstOffset-00032 // VUID-vkCmdUpdateBuffer-dataSize-00033 - if size_of_val(data) as DeviceSize > dst_buffer.size() { + if data_size > dst_buffer.size() { return Err(ClearError::RegionOutOfBufferBounds { region_index: 0, - offset_range_end: size_of_val(data) as DeviceSize, + offset_range_end: data_size, buffer_size: dst_buffer.size(), }); } @@ -506,18 +509,18 @@ where } // VUID-vkCmdUpdateBuffer-dataSize-00037 - if size_of_val(data) > 65536 { + if data_size > 65536 { return Err(ClearError::DataTooLarge { - size: size_of_val(data) as DeviceSize, + size: data_size, max: 65536, }); } // VUID-vkCmdUpdateBuffer-dataSize-00038 - if size_of_val(data) % 4 != 0 { + if data_size % 4 != 0 { return Err(ClearError::SizeNotAlignedForBuffer { region_index: 0, - size: size_of_val(data) as DeviceSize, + size: data_size, required_alignment: 4, }); } @@ -736,12 +739,12 @@ impl SyncCommandBufferBuilder { D: BufferContents + ?Sized, Dd: SafeDeref + Send + Sync + 'static, { - struct Cmd
{ - dst_buffer: Subbuffer<[u8]>, + struct Cmd { + dst_buffer: Subbuffer, data: Dd, } - impl Command for Cmd
+ impl Command for Cmd where D: BufferContents + ?Sized, Dd: SafeDeref + Send + Sync + 'static, @@ -751,11 +754,10 @@ impl SyncCommandBufferBuilder { } unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { - out.update_buffer(&self.dst_buffer, self.data.deref().as_bytes()); + out.update_buffer(&self.dst_buffer, self.data.deref()); } } - let dst_buffer = dst_buffer.into_bytes(); let command_index = self.commands.len(); let command_name = "update_buffer"; let resources = [( @@ -766,7 +768,7 @@ impl SyncCommandBufferBuilder { secondary_use_ref: None, }, Resource::Buffer { - buffer: dst_buffer.clone(), + buffer: dst_buffer.as_bytes().clone(), range: 0..size_of_val(data.deref()) as DeviceSize, memory: PipelineMemoryAccess { stages: PipelineStages::ALL_TRANSFER, @@ -887,7 +889,7 @@ impl UnsafeCommandBufferBuilder { dst_buffer.buffer().handle(), dst_buffer.offset(), size_of_val(data) as DeviceSize, - data.as_bytes().as_ptr() as *const _, + data as *const _ as *const _, ); } } diff --git a/vulkano/src/command_buffer/mod.rs b/vulkano/src/command_buffer/mod.rs index bfcd2955..cb2dc936 100644 --- a/vulkano/src/command_buffer/mod.rs +++ b/vulkano/src/command_buffer/mod.rs @@ -71,11 +71,10 @@ //! use vulkano::command_buffer::PrimaryCommandBufferAbstract; //! use vulkano::command_buffer::SubpassContents; //! -//! # use vulkano::pipeline::graphics::vertex_input::Vertex; -//! # use bytemuck::{Pod, Zeroable}; +//! # use vulkano::{buffer::BufferContents, pipeline::graphics::vertex_input::Vertex}; //! +//! # #[derive(BufferContents, Vertex)] //! # #[repr(C)] -//! # #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] //! # struct PosVertex { //! # #[format(R32G32B32_SFLOAT)] //! # position: [f32; 3] diff --git a/vulkano/src/command_buffer/standard/builder/bind_push.rs b/vulkano/src/command_buffer/standard/builder/bind_push.rs index 9aac8df7..afafb3d8 100644 --- a/vulkano/src/command_buffer/standard/builder/bind_push.rs +++ b/vulkano/src/command_buffer/standard/builder/bind_push.rs @@ -28,7 +28,7 @@ use crate::{ DeviceSize, RequiresOneOf, VulkanObject, }; use smallvec::SmallVec; -use std::{cmp::min, sync::Arc}; +use std::{cmp::min, mem::size_of_val, os::raw::c_void, sync::Arc}; impl CommandBufferBuilder where @@ -627,15 +627,9 @@ where &mut self, pipeline_layout: Arc, offset: u32, - push_constants: &impl BufferContents, + push_constants: &(impl BufferContents + ?Sized), ) -> &mut Self { - let push_constants = push_constants.as_bytes(); - - if push_constants.is_empty() { - return self; - } - - self.validate_push_constants(&pipeline_layout, offset, push_constants) + self.validate_push_constants(&pipeline_layout, offset, size_of_val(push_constants) as u32) .unwrap(); unsafe { self.push_constants_unchecked(pipeline_layout, offset, push_constants) } @@ -645,18 +639,18 @@ where &self, pipeline_layout: &PipelineLayout, offset: u32, - push_constants: &[u8], + data_size: u32, ) -> Result<(), BindPushError> { if offset % 4 != 0 { return Err(BindPushError::PushConstantsOffsetNotAligned); } - if push_constants.len() % 4 != 0 { + if data_size % 4 != 0 { return Err(BindPushError::PushConstantsSizeNotAligned); } let mut current_offset = offset; - let mut remaining_size = push_constants.len() as u32; + let mut remaining_size = data_size; for range in pipeline_layout .push_constant_ranges_disjoint() @@ -695,9 +689,8 @@ where offset: u32, push_constants: &(impl BufferContents + ?Sized), ) -> &mut Self { - let push_constants = push_constants.as_bytes(); let mut current_offset = offset; - let mut remaining_size = push_constants.len() as u32; + let mut remaining_size = size_of_val(push_constants) as u32; let fns = self.device().fns(); @@ -715,15 +708,14 @@ where // push the minimum of the whole remaining data, and the part until the end of this range let push_size = min(remaining_size, range.offset + range.size - current_offset); let data_offset = (current_offset - offset) as usize; - let values = &push_constants[data_offset..(data_offset + push_size as usize)]; (fns.v1_0.cmd_push_constants)( self.handle(), pipeline_layout.handle(), range.stages.into(), current_offset, - values.len() as u32, - values.as_ptr() as *const _, + push_size, + (push_constants as *const _ as *const c_void).add(data_offset), ); current_offset += push_size; @@ -743,7 +735,7 @@ where // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/2711 self.builder_state .push_constants - .insert(offset..offset + push_constants.len() as u32); + .insert(offset..offset + size_of_val(push_constants) as u32); self.builder_state.push_constants_pipeline_layout = Some(pipeline_layout.clone()); self.resources.push(Box::new(pipeline_layout)); diff --git a/vulkano/src/command_buffer/standard/builder/clear.rs b/vulkano/src/command_buffer/standard/builder/clear.rs index 15b3de3a..40ba9413 100644 --- a/vulkano/src/command_buffer/standard/builder/clear.rs +++ b/vulkano/src/command_buffer/standard/builder/clear.rs @@ -532,7 +532,7 @@ where assert_eq!(device, dst_buffer.device()); // VUID-vkCmdFillBuffer-size-00026 - assert!(dst_buffer.size() != 0); + // Guaranteed by `Subbuffer` // VUID-vkCmdFillBuffer-dstBuffer-00029 if !dst_buffer @@ -614,15 +614,15 @@ where where D: BufferContents + ?Sized, { - self.validate_update_buffer(dst_buffer.as_bytes(), data.as_bytes())?; + self.validate_update_buffer(dst_buffer.as_bytes(), size_of_val(data) as DeviceSize)?; - unsafe { Ok(self.update_buffer_unchecked(dst_buffer.into_bytes(), data.as_bytes())) } + unsafe { Ok(self.update_buffer_unchecked(dst_buffer, data)) } } fn validate_update_buffer( &self, dst_buffer: &Subbuffer<[u8]>, - data: &[u8], + data_size: DeviceSize, ) -> Result<(), ClearError> { let device = self.device(); @@ -645,7 +645,7 @@ where assert_eq!(device, dst_buffer.device()); // VUID-vkCmdUpdateBuffer-dataSize-arraylength - assert!(size_of_val(data) != 0); + assert!(data_size != 0); // VUID-vkCmdUpdateBuffer-dstBuffer-00034 if !dst_buffer @@ -660,10 +660,10 @@ where // VUID-vkCmdUpdateBuffer-dstOffset-00032 // VUID-vkCmdUpdateBuffer-dataSize-00033 - if size_of_val(data) as DeviceSize > dst_buffer.size() { + if data_size > dst_buffer.size() { return Err(ClearError::RegionOutOfBufferBounds { region_index: 0, - offset_range_end: size_of_val(data) as DeviceSize, + offset_range_end: data_size, buffer_size: dst_buffer.size(), }); } @@ -678,18 +678,18 @@ where } // VUID-vkCmdUpdateBuffer-dataSize-00037 - if size_of_val(data) > 65536 { + if data_size > 65536 { return Err(ClearError::DataTooLarge { - size: size_of_val(data) as DeviceSize, + size: data_size, max: 65536, }); } // VUID-vkCmdUpdateBuffer-dataSize-00038 - if size_of_val(data) % 4 != 0 { + if data_size % 4 != 0 { return Err(ClearError::SizeNotAlignedForBuffer { region_index: 0, - size: size_of_val(data) as DeviceSize, + size: data_size, required_alignment: 4, }); } @@ -714,7 +714,7 @@ where dst_buffer.buffer().handle(), dst_buffer.offset(), size_of_val(data) as DeviceSize, - data.as_bytes().as_ptr() as *const _, + data as *const _ as *const _, ); let command_index = self.next_command_index; diff --git a/vulkano/src/image/immutable.rs b/vulkano/src/image/immutable.rs index eb72f82d..258a4820 100644 --- a/vulkano/src/image/immutable.rs +++ b/vulkano/src/image/immutable.rs @@ -184,7 +184,7 @@ impl ImmutableImage { command_buffer_builder: &mut AutoCommandBufferBuilder, ) -> Result, ImmutableImageCreationError> where - [Px]: BufferContents, + Px: BufferContents, I: IntoIterator, I::IntoIter: ExactSizeIterator, A: CommandBufferAllocator, diff --git a/vulkano/src/lib.rs b/vulkano/src/lib.rs index 5cd7e79b..a35e8d77 100644 --- a/vulkano/src/lib.rs +++ b/vulkano/src/lib.rs @@ -17,6 +17,7 @@ //! | `document_unchecked` | Include `_unchecked` functions in the generated documentation. | //! | `cgmath` | Generate additional definitions and functions using the [`cgmath`] library. | //! | `nalgebra` | Generate additional definitions and functions using the [`nalgebra`] library. | +//! | `serde` | Enables (de)serialization of certain types using [`serde`]. | //! //! # Starting off with Vulkano //! @@ -104,6 +105,7 @@ //! //! [`cgmath`]: https://crates.io/crates/cgmath //! [`nalgebra`]: https://crates.io/crates/nalgebra +//! [`serde`]: https://crates.io/crates/serde //! [`VulkanLibrary`]: crate::VulkanLibrary //! [`Instance`]: crate::instance::Instance //! [`Surface`]: crate::swapchain::Surface @@ -182,6 +184,7 @@ pub mod instance; pub mod library; mod macros; pub mod memory; +pub mod padded; pub mod pipeline; pub mod query; mod range_map; diff --git a/vulkano/src/macros.rs b/vulkano/src/macros.rs index eaebaf67..1c158cad 100644 --- a/vulkano/src/macros.rs +++ b/vulkano/src/macros.rs @@ -888,4 +888,15 @@ macro_rules! vulkan_bitflags_enum { } } -pub(crate) use {vulkan_bitflags, vulkan_bitflags_enum, vulkan_enum}; +// TODO: Replace with the `?` operator once its constness is stabilized. +macro_rules! try_opt { + ($e:expr) => { + if let Some(val) = $e { + val + } else { + return None; + } + }; +} + +pub(crate) use {try_opt, vulkan_bitflags, vulkan_bitflags_enum, vulkan_enum}; diff --git a/vulkano/src/memory/allocator/layout.rs b/vulkano/src/memory/allocator/layout.rs index 1bb73e05..e5b7b488 100644 --- a/vulkano/src/memory/allocator/layout.rs +++ b/vulkano/src/memory/allocator/layout.rs @@ -8,10 +8,10 @@ // according to those terms. use super::align_up; -use crate::{DeviceSize, NonZeroDeviceSize}; +use crate::{macros::try_opt, DeviceSize, NonZeroDeviceSize}; use std::{ alloc::Layout, - cmp::{self, Ordering}, + cmp::Ordering, error::Error, fmt::{Debug, Display, Formatter, Result as FmtResult}, hash::{Hash, Hasher}, @@ -40,7 +40,7 @@ impl DeviceLayout { /// zero size. #[inline] pub const fn from_layout(layout: Layout) -> Result { - let (size, alignment) = (layout.size(), layout.align()); + let (size, alignment) = Self::size_alignment_from_layout(&layout); #[cfg(any( target_pointer_width = "64", @@ -51,10 +51,7 @@ impl DeviceLayout { const _: () = assert!(size_of::() >= size_of::()); const _: () = assert!(DeviceLayout::MAX_SIZE >= isize::MAX as DeviceSize); - if let Some(size) = NonZeroDeviceSize::new(size as DeviceSize) { - // SAFETY: `Layout`'s alignment-invariant guarantees that it is a power of two. - let alignment = unsafe { DeviceAlignment::new_unchecked(alignment as DeviceSize) }; - + if let Some(size) = NonZeroDeviceSize::new(size) { // SAFETY: Under the precondition that `usize` can't overflow `DeviceSize`, which // we checked above, `Layout`'s overflow-invariant is the same if not stricter than // that of `DeviceLayout`. @@ -102,14 +99,10 @@ impl DeviceLayout { /// exceed [`DeviceLayout::MAX_SIZE`] when rounded up to the nearest multiple of `alignment`. #[inline] pub const fn from_size_alignment(size: DeviceSize, alignment: DeviceSize) -> Option { - if let (Some(size), Some(alignment)) = ( - NonZeroDeviceSize::new(size), - DeviceAlignment::new(alignment), - ) { - DeviceLayout::new(size, alignment) - } else { - None - } + let size = try_opt!(NonZeroDeviceSize::new(size)); + let alignment = try_opt!(DeviceAlignment::new(alignment)); + + DeviceLayout::new(size, alignment) } /// Creates a new `DeviceLayout` from the given `size` and `alignment` without doing any @@ -121,6 +114,7 @@ impl DeviceLayout { /// - `alignment` must be a power of two, which also means it must be non-zero. /// - `size`, when rounded up to the nearest multiple of `alignment`, must not exceed /// [`DeviceLayout::MAX_SIZE`]. + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] #[inline] pub const unsafe fn from_size_alignment_unchecked( size: DeviceSize, @@ -159,6 +153,7 @@ impl DeviceLayout { /// /// - `size`, when rounded up to the nearest multiple of `alignment`, must not exceed /// [`DeviceLayout::MAX_SIZE`]. + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] #[inline] pub const unsafe fn new_unchecked(size: NonZeroDeviceSize, alignment: DeviceAlignment) -> Self { debug_assert!(size.get() <= Self::max_size_for_alignment(alignment)); @@ -183,8 +178,8 @@ impl DeviceLayout { /// Returns [`None`] if `self.size()` would overflow [`DeviceLayout::MAX_SIZE`] when rounded up /// to the nearest multiple of `alignment`. #[inline] - pub fn align_to(&self, alignment: DeviceAlignment) -> Option { - DeviceLayout::new(self.size, cmp::max(self.alignment, alignment)) + pub const fn align_to(&self, alignment: DeviceAlignment) -> Option { + DeviceLayout::new(self.size, DeviceAlignment::max(self.alignment, alignment)) } /// Returns the amount of padding that needs to be added to `self.size()` such that the result @@ -220,11 +215,12 @@ impl DeviceLayout { /// returns [`None`] on arithmetic overflow or when the total size would exceed /// [`DeviceLayout::MAX_SIZE`]. #[inline] - pub fn repeat(&self, n: NonZeroDeviceSize) -> Option<(Self, DeviceSize)> { + pub const fn repeat(&self, n: NonZeroDeviceSize) -> Option<(Self, DeviceSize)> { let stride = self.padded_size(); - let size = stride.checked_mul(n)?; + let size = try_opt!(stride.checked_mul(n)); + let layout = try_opt!(DeviceLayout::new(size, self.alignment)); - DeviceLayout::new(size, self.alignment).map(|layout| (layout, stride.get())) + Some((layout, stride.get())) } /// Creates a new `DeviceLayout` describing the record for `self` followed by `next`, including @@ -242,13 +238,70 @@ impl DeviceLayout { /// /// [`pad_to_alignment`]: Self::pad_to_alignment #[inline] - pub fn extend(&self, next: Self) -> Option<(Self, DeviceSize)> { - let padding = self.padding_needed_for(next.alignment); - let offset = self.size.checked_add(padding)?; - let size = offset.checked_add(next.size())?; - let alignment = cmp::max(self.alignment, next.alignment); + pub const fn extend(&self, next: Self) -> Option<(Self, DeviceSize)> { + self.extend_inner(next.size(), next.alignment) + } - DeviceLayout::new(size, alignment).map(|layout| (layout, offset.get())) + /// Same as [`extend`], except it extends with a [`Layout`]. + /// + /// [`extend`]: Self::extend + #[inline] + pub const fn extend_with_layout(&self, next: Layout) -> Option<(Self, DeviceSize)> { + let (next_size, next_alignment) = Self::size_alignment_from_layout(&next); + + self.extend_inner(next_size, next_alignment) + } + + const fn extend_inner( + &self, + next_size: DeviceSize, + next_alignment: DeviceAlignment, + ) -> Option<(Self, DeviceSize)> { + let padding = self.padding_needed_for(next_alignment); + let offset = try_opt!(self.size.checked_add(padding)); + let size = try_opt!(offset.checked_add(next_size)); + let alignment = DeviceAlignment::max(self.alignment, next_alignment); + let layout = try_opt!(DeviceLayout::new(size, alignment)); + + Some((layout, offset.get())) + } + + /// Creates a new `DeviceLayout` describing the record for the `previous` [`Layout`] followed + /// by `self`. This function is otherwise the same as [`extend`]. + /// + /// [`extend`]: Self::extend + #[inline] + pub const fn extend_from_layout(self, previous: &Layout) -> Option<(Self, DeviceSize)> { + let (size, alignment) = Self::size_alignment_from_layout(previous); + + let padding = align_up(size, self.alignment).wrapping_sub(size); + let offset = try_opt!(size.checked_add(padding)); + let size = try_opt!(self.size.checked_add(offset)); + let alignment = DeviceAlignment::max(alignment, self.alignment); + let layout = try_opt!(DeviceLayout::new(size, alignment)); + + Some((layout, offset)) + } + + #[inline(always)] + const fn size_alignment_from_layout(layout: &Layout) -> (DeviceSize, DeviceAlignment) { + #[cfg(any( + target_pointer_width = "64", + target_pointer_width = "32", + target_pointer_width = "16", + ))] + { + const _: () = assert!(size_of::() >= size_of::()); + const _: () = assert!(DeviceLayout::MAX_SIZE >= isize::MAX as DeviceSize); + + // We checked that `usize` can't overflow `DeviceSize`, so this can't truncate. + let (size, alignment) = (layout.size() as DeviceSize, layout.align() as DeviceSize); + + // SAFETY: `Layout`'s alignment-invariant guarantees that it is a power of two. + let alignment = unsafe { DeviceAlignment::new_unchecked(alignment) }; + + (size, alignment) + } } } @@ -374,6 +427,16 @@ impl DeviceAlignment { pub const fn log2(self) -> u32 { self.as_nonzero().trailing_zeros() } + + // TODO: Replace with `Ord::max` once its constness is stabilized. + #[inline(always)] + pub(crate) const fn max(self, other: Self) -> Self { + if self.as_devicesize() >= other.as_devicesize() { + self + } else { + other + } + } } impl Debug for DeviceAlignment { diff --git a/vulkano/src/memory/allocator/suballocator.rs b/vulkano/src/memory/allocator/suballocator.rs index 414a49f6..16e9f41a 100644 --- a/vulkano/src/memory/allocator/suballocator.rs +++ b/vulkano/src/memory/allocator/suballocator.rs @@ -204,28 +204,6 @@ impl MemoryAlloc { .map(|ptr| slice::from_raw_parts_mut(ptr.as_ptr().cast(), self.size as usize)) } - pub(crate) unsafe fn read(&self, range: Range) -> Option<&[u8]> { - debug_assert!(!range.is_empty() && range.end <= self.size); - - self.mapped_ptr.map(|ptr| { - slice::from_raw_parts( - ptr.as_ptr().add(range.start as usize).cast(), - (range.end - range.start) as usize, - ) - }) - } - - pub(crate) unsafe fn write(&self, range: Range) -> Option<&mut [u8]> { - debug_assert!(!range.is_empty() && range.end <= self.size); - - self.mapped_ptr.map(|ptr| { - slice::from_raw_parts_mut( - ptr.as_ptr().add(range.start as usize).cast(), - (range.end - range.start) as usize, - ) - }) - } - pub(crate) fn atom_size(&self) -> Option { self.atom_size } diff --git a/vulkano/src/padded.rs b/vulkano/src/padded.rs new file mode 100644 index 00000000..dbf18f1b --- /dev/null +++ b/vulkano/src/padded.rs @@ -0,0 +1,338 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +//! A newtype wrapper for enforcing correct alignment for external types. + +use crate::buffer::{BufferContents, BufferContentsLayout}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{ + alloc::Layout, + cmp::Ordering, + ffi::c_void, + fmt::{Debug, Display, Formatter, Result as FmtResult}, + hash::{Hash, Hasher}, + mem::{align_of, size_of, MaybeUninit}, + ops::{Deref, DerefMut}, +}; + +/// A newtype wrapper around `T`, with `N` bytes of trailing padding. +/// +/// In Vulkan, the layout of buffer contents is not necessarily as one would expect from the type +/// signature in the shader code. For example, the *extended layout* or *std140 layout* in GLSL, +/// which is used for uniform buffers by default, requires that array elements are aligned to 16 +/// bytes at minimum. That means that even if the array contains a scalar type like `u32` for +/// example, it must be aligned to 16 bytes. We can not enforce that with primitive Rust types +/// alone. In such cases, we can use `Padded` to enforce correct alignment on the Rust side. +/// +/// See also [the `shader` module documentation] for more information about layout in shaders. +/// +/// # Examples +/// +/// ## Aligning struct members +/// +/// Consider this GLSL code: +/// +/// ```glsl +/// layout(binding = 0) uniform MyData { +/// int x; +/// vec3 y; +/// vec4 z; +/// }; +/// ``` +/// +/// By default, the alignment rules require that `y` and `z` are placed at an offset that is an +/// integer multiple of 16. However, `x` is only 4 bytes, which means that there must be 12 bytes +/// of padding between `x` and `y`. Furthermore, `y` is only 12 bytes, which means that there must +/// be 4 bytes of padding between `y` and `z`. +/// +/// We can model this in Rust using `Padded`: +/// +/// ``` +/// # use vulkano::{buffer::BufferContents, padded::Padded}; +/// #[derive(BufferContents)] +/// #[repr(C)] +/// struct MyData { +/// x: Padded, +/// y: Padded<[f32; 3], 4>, +/// z: [f32; 4], +/// } +/// +/// let data = MyData { +/// x: Padded(42), +/// y: Padded([1.0, 2.0, 3.0]), +/// z: [10.0; 4], +/// }; +/// ``` +/// +/// **But note that this layout is extremely suboptimal.** What you should do instead is reorder +/// your fields such that you don't need any padding: +/// +/// ```glsl +/// layout(binding = 0) uniform MyData { +/// vec3 y; +/// int x; +/// vec4 z; +/// }; +/// ``` +/// +/// ``` +/// # use vulkano::buffer::BufferContents; +/// #[derive(BufferContents)] +/// #[repr(C)] +/// struct MyData { +/// y: [f32; 3], +/// x: i32, +/// z: [f32; 4], +/// } +/// ``` +/// +/// This way, the fields are aligned naturally. But reordering fields is not always an option: the +/// notable case being when your structure only contains `vec3`s and `vec4`s, or `vec3`s and +/// `vec2`s, so that there are no scalar fields to fill the gaps with. +/// +/// ## Aligning array elements +/// +/// If you need an array of `vec3`s, then that necessitates that each array element has 4 bytes of +/// trailing padding. The same goes for a matrix with 3 rows, each column will have to have 4 bytes +/// of trailing padding (assuming its column-major). +/// +/// We can model those using `Padded` too: +/// +/// ```glsl +/// layout(binding = 0) uniform MyData { +/// vec3 x[10]; +/// mat3 y; +/// }; +/// ``` +/// +/// ``` +/// # use vulkano::{buffer::BufferContents, padded::Padded}; +/// #[derive(BufferContents)] +/// #[repr(C)] +/// struct MyData { +/// x: [Padded<[f32; 3], 4>; 10], +/// y: [Padded<[f32; 3], 4>; 3], +/// } +/// ``` +/// +/// Another example would be if you have an array of scalars or `vec2`s inside a uniform block: +/// +/// ```glsl +/// layout(binding = 0) uniform MyData { +/// int x[10]; +/// vec2 y[10]; +/// }; +/// ``` +/// +/// By default, arrays inside uniform blocks must have their elements aligned to 16 bytes at +/// minimum, which would look like this in Rust: +/// +/// ``` +/// # use vulkano::{buffer::BufferContents, padded::Padded}; +/// #[derive(BufferContents)] +/// #[repr(C)] +/// struct MyData { +/// x: [Padded; 10], +/// y: [Padded<[f32; 2], 8>; 10], +/// } +/// ``` +/// +/// **But note again, that this layout is suboptimal.** You can instead use a buffer block instead +/// of the uniform block, if memory usage could become an issue: +/// +/// ```glsl +/// layout(binding = 0) buffer MyData { +/// int x[10]; +/// vec2 y[10]; +/// }; +/// ``` +/// +/// ``` +/// # use vulkano::buffer::BufferContents; +/// #[derive(BufferContents)] +/// #[repr(C)] +/// struct MyData { +/// x: [i32; 10], +/// y: [[f32; 2]; 10], +/// } +/// ``` +/// +/// You may also want to consider using [the `uniform_buffer_standard_layout` feature]. +/// +/// [the `shader` module documentation]: crate::shader +/// [the `uniform_buffer_standard_layout` feature]: crate::device::Features::uniform_buffer_standard_layout +#[repr(C)] +pub struct Padded { + value: T, + _padding: [MaybeUninit; N], +} + +#[allow(non_snake_case)] +#[doc(hidden)] +#[inline(always)] +pub const fn Padded(value: T) -> Padded { + Padded { + value, + _padding: [MaybeUninit::uninit(); N], + } +} + +impl AsRef for Padded { + fn as_ref(&self) -> &T { + &self.value + } +} + +impl AsMut for Padded { + fn as_mut(&mut self) -> &mut T { + &mut self.value + } +} + +impl Clone for Padded +where + T: Clone, +{ + fn clone(&self) -> Self { + Padded(self.value.clone()) + } +} + +impl Copy for Padded where T: Copy {} + +impl Debug for Padded +where + T: Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + self.value.fmt(f) + } +} + +impl Default for Padded +where + T: Default, +{ + fn default() -> Self { + Padded(T::default()) + } +} + +impl Deref for Padded { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl DerefMut for Padded { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.value + } +} + +impl Display for Padded +where + T: Display, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + self.value.fmt(f) + } +} + +impl From for Padded { + fn from(value: T) -> Self { + Padded(value) + } +} + +impl PartialEq for Padded +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl Eq for Padded where T: Eq {} + +impl Hash for Padded +where + T: Hash, +{ + fn hash(&self, state: &mut H) { + self.value.hash(state); + } +} + +impl PartialOrd for Padded +where + T: PartialOrd, +{ + fn partial_cmp(&self, other: &Self) -> Option { + self.value.partial_cmp(&other.value) + } +} + +impl Ord for Padded +where + T: Ord, +{ + fn cmp(&self, other: &Self) -> Ordering { + self.value.cmp(&other.value) + } +} + +unsafe impl BufferContents for Padded +where + T: BufferContents, +{ + const LAYOUT: BufferContentsLayout = + if let Some(layout) = BufferContentsLayout::from_sized(Layout::new::()) { + layout + } else { + panic!("zero-sized types are not valid buffer contents"); + }; + + unsafe fn from_ffi(data: *mut c_void, range: usize) -> *mut Self { + debug_assert!(range == size_of::()); + debug_assert!(data as usize % align_of::() == 0); + + data.cast() + } +} + +#[cfg(feature = "serde")] +impl Serialize for Padded +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.value.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, T, const N: usize> Deserialize<'de> for Padded +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + T::deserialize(deserializer).map(Padded) + } +} diff --git a/vulkano/src/pipeline/graphics/input_assembly.rs b/vulkano/src/pipeline/graphics/input_assembly.rs index 5816b053..0e4feda9 100644 --- a/vulkano/src/pipeline/graphics/input_assembly.rs +++ b/vulkano/src/pipeline/graphics/input_assembly.rs @@ -10,11 +10,11 @@ //! Configures how input vertices are assembled into primitives. use crate::{ + buffer::BufferContents, macros::vulkan_enum, pipeline::{PartialStateMode, StateMode}, DeviceSize, }; -use bytemuck::Pod; /// The state in a graphics pipeline describing how the input assembly stage should behave. #[derive(Clone, Copy, Debug)] @@ -199,7 +199,7 @@ impl PrimitiveTopologyClass { } /// Trait for types that can be used as indices by the GPU. -pub unsafe trait Index: Pod + Sync + Send { +pub unsafe trait Index: BufferContents + Sized { /// Returns the type of data. fn ty() -> IndexType; } diff --git a/vulkano/src/pipeline/graphics/vertex_input/collection.rs b/vulkano/src/pipeline/graphics/vertex_input/collection.rs index ac888d6c..cb209f44 100644 --- a/vulkano/src/pipeline/graphics/vertex_input/collection.rs +++ b/vulkano/src/pipeline/graphics/vertex_input/collection.rs @@ -40,7 +40,7 @@ impl VertexBuffersCollection for Vec> { } } -impl VertexBuffersCollection for [Subbuffer; N] { +impl VertexBuffersCollection for [Subbuffer; N] { fn into_vec(self) -> Vec> { self.into_iter().map(Subbuffer::into_bytes).collect() } diff --git a/vulkano/src/pipeline/graphics/vertex_input/impl_vertex.rs b/vulkano/src/pipeline/graphics/vertex_input/impl_vertex.rs index a38f14c6..ddecf953 100644 --- a/vulkano/src/pipeline/graphics/vertex_input/impl_vertex.rs +++ b/vulkano/src/pipeline/graphics/vertex_input/impl_vertex.rs @@ -14,10 +14,10 @@ use crate::format::Format; /// # Examples /// /// ``` -/// # use bytemuck::{Zeroable, Pod}; +/// # use vulkano::buffer::BufferContents; +/// #[derive(BufferContents, Default)] /// #[repr(C)] -/// #[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] -/// struct Vertex{ +/// struct Vertex { /// position: [f32; 3], /// color: [f32; 4], /// } @@ -26,7 +26,7 @@ use crate::format::Format; /// ``` #[deprecated( since = "0.33.0", - note = "Derive `Vertex` instead and use field-level attributes to specify format" + note = "derive `Vertex` instead and use field-level attributes to specify format" )] #[macro_export] macro_rules! impl_vertex { diff --git a/vulkano/src/pipeline/graphics/vertex_input/vertex.rs b/vulkano/src/pipeline/graphics/vertex_input/vertex.rs index d72ffee1..4464063e 100644 --- a/vulkano/src/pipeline/graphics/vertex_input/vertex.rs +++ b/vulkano/src/pipeline/graphics/vertex_input/vertex.rs @@ -8,8 +8,7 @@ // according to those terms. use super::VertexInputRate; -use crate::format::Format; -use bytemuck::Pod; +use crate::{buffer::BufferContents, format::Format}; use std::collections::HashMap; pub use vulkano_macros::Vertex; @@ -21,12 +20,12 @@ pub use vulkano_macros::Vertex; /// /// The vertex trait can be derived and the format has to be specified using the `format` /// field-level attribute: -/// ``` -/// use bytemuck::{Pod, Zeroable}; -/// use vulkano::pipeline::graphics::vertex_input::Vertex; /// +/// ``` +/// use vulkano::{buffer::BufferContents, pipeline::graphics::vertex_input::Vertex}; +/// +/// #[derive(BufferContents, Vertex)] /// #[repr(C)] -/// #[derive(Clone, Copy, Debug, Default, Pod, Zeroable, Vertex)] /// struct MyVertex { /// // Every field needs to explicitly state the desired shader input format /// #[format(R32G32B32_SFLOAT)] @@ -38,10 +37,12 @@ pub use vulkano_macros::Vertex; /// proj: [f32; 16], /// } /// ``` -pub unsafe trait Vertex: Pod + Send + Sync + 'static { +pub unsafe trait Vertex: BufferContents + Sized { /// Returns the information about this Vertex type. fn per_vertex() -> VertexBufferDescription; + fn per_instance() -> VertexBufferDescription; + fn per_instance_with_divisor(divisor: u32) -> VertexBufferDescription; } @@ -100,6 +101,7 @@ pub struct VertexMemberInfo { } impl VertexMemberInfo { + #[inline] pub fn num_components(&self) -> u32 { self.format .components() diff --git a/vulkano/src/query.rs b/vulkano/src/query.rs index 86b49a0a..7cb487c5 100644 --- a/vulkano/src/query.rs +++ b/vulkano/src/query.rs @@ -14,11 +14,11 @@ //! pool and the slot id within that query pool. use crate::{ + buffer::BufferContents, device::{Device, DeviceOwned}, macros::vulkan_bitflags, DeviceSize, OomError, RequirementNotMet, RequiresOneOf, VulkanError, VulkanObject, }; -use bytemuck::Pod; use std::{ error::Error, ffi::c_void, @@ -513,7 +513,7 @@ impl From for GetResultsError { /// # Safety /// This is implemented for `u32` and `u64`. Unless you really know what you're doing, you should /// not implement this trait for any other type. -pub unsafe trait QueryResultElement: Pod + Sync + Send { +pub unsafe trait QueryResultElement: BufferContents + Sized { const FLAG: ash::vk::QueryResultFlags; }