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
This commit is contained in:
marc0246 2023-03-05 19:56:35 +01:00 committed by GitHub
parent 4f03fe55ac
commit baf863ce33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 5187 additions and 4033 deletions

View File

@ -7,7 +7,7 @@ publish = false
[dependencies] [dependencies]
# The `vulkano` crate is the main crate that you must use to use Vulkan. # 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. # Provides the `shader!` macro that is used to generate code for using shaders.
vulkano-shaders = { path = "../vulkano-shaders" } vulkano-shaders = { path = "../vulkano-shaders" }
# The Vulkan library doesn't provide any functionality to create and handle windows, as # 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-win = { path = "../vulkano-win" }
vulkano-util = { path = "../vulkano-util" } vulkano-util = { path = "../vulkano-util" }
bytemuck = { version = "1.7", features = ["derive", "extern_crate_std", "min_const_generics"] }
cgmath = "0.18" cgmath = "0.18"
png = "0.17"
serde = { version = "1.0", features = ["derive"] }
ron = "0.8"
rand = "0.8.4"
glium = "0.32.1" glium = "0.32.1"
png = "0.17"
rand = "0.8.4"
ron = "0.8"
serde = { version = "1.0", features = ["derive"] }

View File

@ -38,7 +38,6 @@ fn main() {
let instance = Instance::new( let instance = Instance::new(
library, library,
InstanceCreateInfo { InstanceCreateInfo {
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -55,8 +54,8 @@ fn main() {
.unwrap() .unwrap()
.filter(|p| p.supported_extensions().contains(&device_extensions)) .filter(|p| p.supported_extensions().contains(&device_extensions))
.filter_map(|p| { .filter_map(|p| {
// The Vulkan specs guarantee that a compliant implementation must provide at least one queue // The Vulkan specs guarantee that a compliant implementation must provide at least one
// that supports compute operations. // queue that supports compute operations.
p.queue_family_properties() p.queue_family_properties()
.iter() .iter()
.position(|q| q.queue_flags.intersects(QueueFlags::COMPUTE)) .position(|q| q.queue_flags.intersects(QueueFlags::COMPUTE))
@ -75,7 +74,7 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
// Now initializing the device. // Now initializing the device.
@ -99,17 +98,17 @@ fn main() {
// Now let's get to the actual example. // 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 // What we are going to do is very basic: we are going to fill a buffer with 64k integers and
// and ask the GPU to multiply each of them by 12. // 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 // 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 // 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. // 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 // Note however that in a real-life situation for such a simple operation the cost of accessing
// accessing memory usually outweighs the benefits of a faster calculation. Since both the CPU // memory usually outweighs the benefits of a faster calculation. Since both the CPU and the
// and the GPU will need to access data, there is no other choice but to transfer the data // GPU will need to access data, there is no other choice but to transfer the data through the
// through the slow PCI express bus. // slow PCI express bus.
// We need to create the compute pipeline that describes our operation. // We need to create the compute pipeline that describes our operation.
// //
@ -119,23 +118,24 @@ fn main() {
mod cs { mod cs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #version 450
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout(set = 0, binding = 0) buffer Data { layout(set = 0, binding = 0) buffer Data {
uint data[]; uint data[];
} data; };
void main() { void main() {
uint idx = gl_GlobalInvocationID.x; uint idx = gl_GlobalInvocationID.x;
data.data[idx] *= 12; data[idx] *= 12;
} }
" ",
} }
} }
let shader = cs::load(device.clone()).unwrap(); let shader = cs::load(device.clone()).unwrap();
ComputePipeline::new( ComputePipeline::new(
device.clone(), device.clone(),
shader.entry_point("main").unwrap(), shader.entry_point("main").unwrap(),
@ -152,20 +152,16 @@ fn main() {
StandardCommandBufferAllocator::new(device.clone(), Default::default()); StandardCommandBufferAllocator::new(device.clone(), Default::default());
// We start by creating the buffer that will store the data. // 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. // Iterator that produces the data.
let data_iter = 0..65536u32; 0..65536u32,
// Builds the buffer and fills it with this iterator. )
Buffer::from_iter( .unwrap();
&memory_allocator,
BufferAllocateInfo {
buffer_usage: BufferUsage::STORAGE_BUFFER,
..Default::default()
},
data_iter,
)
.unwrap()
};
// In order to let the shader access the buffer, we need to build a *descriptor set* that // In order to let the shader access the buffer, we need to build a *descriptor set* that
// contains the buffer. // contains the buffer.
@ -191,13 +187,13 @@ fn main() {
) )
.unwrap(); .unwrap();
builder builder
// The command buffer only does one thing: execute the compute pipeline. // The command buffer only does one thing: execute the compute pipeline. This is called a
// This is called a *dispatch* operation. // *dispatch* operation.
// //
// Note that we clone the pipeline and the set. Since they are both wrapped around an // Note that we clone the pipeline and the set. Since they are both wrapped in an `Arc`,
// `Arc`, this only clones the `Arc` and not the whole pipeline or set (which aren't // this only clones the `Arc` and not the whole pipeline or set (which aren't cloneable
// cloneable anyway). In this example we would avoid cloning them since this is the last // anyway). In this example we would avoid cloning them since this is the last time we use
// time we use them, but in a real code you would probably need to clone them. // them, but in real code you would probably need to clone them.
.bind_pipeline_compute(pipeline.clone()) .bind_pipeline_compute(pipeline.clone())
.bind_descriptor_sets( .bind_descriptor_sets(
PipelineBindPoint::Compute, PipelineBindPoint::Compute,
@ -207,38 +203,37 @@ fn main() {
) )
.dispatch([1024, 1, 1]) .dispatch([1024, 1, 1])
.unwrap(); .unwrap();
// Finish building the command buffer by calling `build`. // Finish building the command buffer by calling `build`.
let command_buffer = builder.build().unwrap(); let command_buffer = builder.build().unwrap();
// Let's execute this command buffer now. // 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) let future = sync::now(device)
.then_execute(queue, command_buffer) .then_execute(queue, command_buffer)
.unwrap() .unwrap()
// This line instructs the GPU to signal a *fence* once the command buffer has finished // 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 // execution. A fence is a Vulkan object that allows the CPU to know when the GPU has
// reached a certain point. // reached a certain point. We need to signal a fence here because below we want to block
// We need to signal a fence here because below we want to block the CPU until the GPU has // the CPU until the GPU has reached that point in the execution.
// reached that point in the execution.
.then_signal_fence_and_flush() .then_signal_fence_and_flush()
.unwrap(); .unwrap();
// Blocks execution until the GPU has finished the operation. This method only exists on the // 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 // 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. // available if we didn't call `.then_signal_fence_and_flush()` earlier. The `None` parameter
// The `None` parameter is an optional timeout. // is an optional timeout.
// //
// Note however that dropping the `future` variable (with `drop(future)` for example) would // 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 // block execution as well, and this would be the case even if we didn't call
// `.then_signal_fence_and_flush()`. // `.then_signal_fence_and_flush()`. Therefore the actual point of calling
// Therefore the actual point of calling `.then_signal_fence_and_flush()` and `.wait()` is to // `.then_signal_fence_and_flush()` and `.wait()` is to make things more explicit. In the
// make things more explicit. In the future, if the Rust language gets linear types vulkano may // future, if the Rust language gets linear types vulkano may get modified so that only
// get modified so that only fence-signalled futures can get destroyed like this. // fence-signalled futures can get destroyed like this.
future.wait(None).unwrap(); future.wait(None).unwrap();
// Now that the GPU is done, the content of the buffer should have been modified. Let's // Now that the GPU is done, the content of the buffer should have been modified. Let's check
// check it out. // it out. The call to `read()` would return an error if the buffer was still in use by the
// The call to `read()` would return an error if the buffer was still in use by the GPU. // GPU.
let data_buffer_content = data_buffer.read().unwrap(); let data_buffer_content = data_buffer.read().unwrap();
for n in 0..65536u32 { for n in 0..65536u32 {
assert_eq!(data_buffer_content[n as usize], n * 12); assert_eq!(data_buffer_content[n as usize], n * 12);

View File

@ -9,7 +9,6 @@
// Modified triangle example to show `SubbufferAllocator`. // Modified triangle example to show `SubbufferAllocator`.
use bytemuck::{Pod, Zeroable};
use std::{ use std::{
sync::Arc, sync::Arc,
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
@ -17,7 +16,7 @@ use std::{
use vulkano::{ use vulkano::{
buffer::{ buffer::{
allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
BufferUsage, BufferContents, BufferUsage,
}, },
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
@ -60,7 +59,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -103,7 +101,7 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
@ -156,8 +154,8 @@ fn main() {
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone()));
#[derive(Clone, Copy, BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -177,30 +175,30 @@ fn main() {
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
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_Position = vec4(position, 0.0, 1.0);
} }
" ",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
void main() { void main() {
f_color = vec4(1.0, 0.0, 0.0, 1.0); f_color = vec4(1.0, 0.0, 0.0, 1.0);
} }
" ",
} }
} }
@ -277,7 +275,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -296,7 +294,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -387,7 +385,7 @@ fn main() {
previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box<_>); previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box<_>);
} }
Err(e) => { 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<_>); 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,

View File

@ -35,8 +35,8 @@ use winit::{
}; };
fn main() { fn main() {
// The start of this example is exactly the same as `triangle`. You should read the // The start of this example is exactly the same as `triangle`. You should read the `triangle`
// `triangle` example if you haven't done so yet. // example if you haven't done so yet.
let library = VulkanLibrary::new().unwrap(); let library = VulkanLibrary::new().unwrap();
let required_extensions = vulkano_win::required_extensions(&library); let required_extensions = vulkano_win::required_extensions(&library);
@ -44,7 +44,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -192,7 +191,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -209,7 +208,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -233,29 +232,28 @@ fn main() {
SubpassContents::Inline, SubpassContents::Inline,
) )
.unwrap() .unwrap()
// Clear attachments with clear values and rects information, all the rects will be cleared by the same value // Clear attachments with clear values and rects information. All the rects will be
// Note that the ClearRect offsets and extents are not affected by the viewport, // cleared by the same value. Note that the ClearRect offsets and extents are not
// they are directly applied to the rendering image // affected by the viewport, they are directly applied to the rendering image.
.clear_attachments( .clear_attachments(
[ClearAttachment::Color { [ClearAttachment::Color {
color_attachment: 0, color_attachment: 0,
clear_value: [1.0, 0.0, 0.0, 1.0].into(), clear_value: [1.0, 0.0, 0.0, 1.0].into(),
}], }],
[ [
// Fixed offset and extent // Fixed offset and extent.
ClearRect { ClearRect {
offset: [0, 0], offset: [0, 0],
extent: [100, 100], extent: [100, 100],
array_layers: 0..1, array_layers: 0..1,
}, },
// Fixed offset // Fixed offset, relative extent.
// Relative extent
ClearRect { ClearRect {
offset: [100, 150], offset: [100, 150],
extent: [width / 4, height / 4], extent: [width / 4, height / 4],
array_layers: 0..1, array_layers: 0..1,
}, },
// Relative offset and extent // Relative offset and extent.
ClearRect { ClearRect {
offset: [width / 2, height / 2], offset: [width / 2, height / 2],
extent: [width / 3, height / 5], extent: [width / 3, height / 5],
@ -289,7 +287,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,

View File

@ -37,7 +37,7 @@ fn main() {
// https://vulkan.lunarg.com/doc/view/1.0.13.0/windows/layers.html // 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 // .. 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 { let extensions = InstanceExtensions {
ext_debug_utils: true, ext_debug_utils: true,
..InstanceExtensions::empty() ..InstanceExtensions::empty()
@ -45,12 +45,12 @@ fn main() {
let library = VulkanLibrary::new().unwrap(); let library = VulkanLibrary::new().unwrap();
// You also need to specify (unless you've used the methods linked above) which debugging layers // You also need to specify (unless you've used the methods linked above) which debugging
// your code should use. Each layer is a bunch of checks or messages that provide information of // layers your code should use. Each layer is a bunch of checks or messages that provide
// some sort. // information of some sort.
// //
// The main layer you might want is: VK_LAYER_LUNARG_standard_validation // The main layer you might want is `VK_LAYER_LUNARG_standard_validation`. This includes a
// This includes a number of the other layers for you and is quite detailed. // 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) // 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 // 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()); 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()]; 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( let instance = Instance::new(
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: extensions, enabled_extensions: extensions,
enabled_layers: layers, enabled_layers: layers,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
) )
.expect("failed to create Vulkan instance"); .expect("failed to create Vulkan instance");
/////////////////////////////////////////////////////////////////////////////////////////////////////////////// // After creating the instance we must register the debug callback.
// 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.
// Note: If you let this debug_callback binding fall out of scope then the callback will stop providing events
let _debug_callback = unsafe { let _debug_callback = unsafe {
DebugUtilsMessenger::new( DebugUtilsMessenger::new(
instance.clone(), instance.clone(),
@ -130,10 +128,7 @@ fn main() {
.ok() .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 { let device_extensions = DeviceExtensions {
..DeviceExtensions::empty() ..DeviceExtensions::empty()
}; };
@ -198,5 +193,6 @@ fn main() {
) )
.unwrap(); .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!)
} }

View File

@ -122,14 +122,13 @@ impl AmbientLightingSystem {
/// - `color_input` is an image containing the albedo of each object of the scene. It is the /// - `color_input` is an image containing the albedo of each object of the scene. It is the
/// result of the deferred pass. /// result of the deferred pass.
/// - `ambient_color` is the color to apply. /// - `ambient_color` is the color to apply.
///
pub fn draw( pub fn draw(
&self, &self,
viewport_dimensions: [u32; 2], viewport_dimensions: [u32; 2],
color_input: Arc<dyn ImageViewAbstract + 'static>, color_input: Arc<dyn ImageViewAbstract + 'static>,
ambient_color: [f32; 3], ambient_color: [f32; 3],
) -> SecondaryAutoCommandBuffer { ) -> SecondaryAutoCommandBuffer {
let push_constants = fs::ty::PushConstants { let push_constants = fs::PushConstants {
color: [ambient_color[0], ambient_color[1], ambient_color[2], 1.0], color: [ambient_color[0], ambient_color[1], ambient_color[2], 1.0],
}; };
@ -177,38 +176,40 @@ impl AmbientLightingSystem {
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
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_Position = vec4(position, 0.0, 1.0);
}" }
",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
// The `color_input` parameter of the `draw` method. // The `color_input` parameter of the `draw` method.
layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput u_diffuse; layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput u_diffuse;
layout(push_constant) uniform PushConstants { layout(push_constant) uniform PushConstants {
// The `ambient_color` parameter of the `draw` method. // The `ambient_color` parameter of the `draw` method.
vec4 color; vec4 color;
} push_constants; } push_constants;
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
void main() { void main() {
// Load the value at the current pixel. // Load the value at the current pixel.
vec3 in_diffuse = subpassLoad(u_diffuse).rgb; vec3 in_diffuse = subpassLoad(u_diffuse).rgb;
f_color.rgb = push_constants.color.rgb * in_diffuse; f_color.rgb = push_constants.color.rgb * in_diffuse;
f_color.a = 1.0; f_color.a = 1.0;
}", }
",
} }
} }

View File

@ -132,7 +132,6 @@ impl DirectionalLightingSystem {
/// result of the deferred pass. /// result of the deferred pass.
/// - `direction` is the direction of the light in world coordinates. /// - `direction` is the direction of the light in world coordinates.
/// - `color` is the color to apply. /// - `color` is the color to apply.
///
pub fn draw( pub fn draw(
&self, &self,
viewport_dimensions: [u32; 2], viewport_dimensions: [u32; 2],
@ -141,7 +140,7 @@ impl DirectionalLightingSystem {
direction: Vector3<f32>, direction: Vector3<f32>,
color: [f32; 3], color: [f32; 3],
) -> SecondaryAutoCommandBuffer { ) -> SecondaryAutoCommandBuffer {
let push_constants = fs::ty::PushConstants { let push_constants = fs::PushConstants {
color: [color[0], color[1], color[2], 1.0], color: [color[0], color[1], color[2], 1.0],
direction: direction.extend(0.0).into(), direction: direction.extend(0.0).into(),
}; };
@ -193,49 +192,53 @@ impl DirectionalLightingSystem {
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
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_Position = vec4(position, 0.0, 1.0);
}" }
",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
// The `color_input` parameter of the `draw` method. // The `color_input` parameter of the `draw` method.
layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput u_diffuse; layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput u_diffuse;
// The `normals_input` parameter of the `draw` method. // The `normals_input` parameter of the `draw` method.
layout(input_attachment_index = 1, set = 0, binding = 1) uniform subpassInput u_normals; layout(input_attachment_index = 1, set = 0, binding = 1) uniform subpassInput u_normals;
layout(push_constant) uniform PushConstants { layout(push_constant) uniform PushConstants {
// The `color` parameter of the `draw` method. // The `color` parameter of the `draw` method.
vec4 color; vec4 color;
// The `direction` parameter of the `draw` method. // The `direction` parameter of the `draw` method.
vec4 direction; vec4 direction;
} push_constants; } push_constants;
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
void main() { void main() {
vec3 in_normal = normalize(subpassLoad(u_normals).rgb); 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);
vec3 in_diffuse = subpassLoad(u_diffuse).rgb; // If the normal is perpendicular to the direction of the lighting, then
f_color.rgb = light_percent * push_constants.color.rgb * in_diffuse; // `light_percent` will be 0. If the normal is parallel to the direction of the
f_color.a = 1.0; // 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;
}
",
} }
} }

View File

@ -12,8 +12,7 @@
// The main code is in the `system` module, while the other modules implement the different kinds // The main code is in the `system` module, while the other modules implement the different kinds
// of lighting sources. // of lighting sources.
use bytemuck::{Pod, Zeroable}; use vulkano::{buffer::BufferContents, pipeline::graphics::vertex_input::Vertex};
use vulkano::pipeline::graphics::vertex_input::Vertex;
pub use self::system::{DrawPass, Frame, FrameSystem, LightingPass, Pass}; pub use self::system::{DrawPass, Frame, FrameSystem, LightingPass, Pass};
@ -22,8 +21,8 @@ mod directional_lighting_system;
mod point_lighting_system; mod point_lighting_system;
mod system; mod system;
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct LightingVertex { struct LightingVertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],

View File

@ -138,7 +138,6 @@ impl PointLightingSystem {
/// coordinates of each pixel being processed. /// coordinates of each pixel being processed.
/// - `position` is the position of the spot light in world coordinates. /// - `position` is the position of the spot light in world coordinates.
/// - `color` is the color of the light. /// - `color` is the color of the light.
///
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn draw( pub fn draw(
&self, &self,
@ -150,7 +149,7 @@ impl PointLightingSystem {
position: Vector3<f32>, position: Vector3<f32>,
color: [f32; 3], color: [f32; 3],
) -> SecondaryAutoCommandBuffer { ) -> SecondaryAutoCommandBuffer {
let push_constants = fs::ty::PushConstants { let push_constants = fs::PushConstants {
screen_to_world: screen_to_world.into(), screen_to_world: screen_to_world.into(),
color: [color[0], color[1], color[2], 1.0], color: [color[0], color[1], color[2], 1.0],
position: position.extend(0.0).into(), position: position.extend(0.0).into(),
@ -204,68 +203,73 @@ impl PointLightingSystem {
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 position; layout(location = 0) in vec2 position;
layout(location = 0) out vec2 v_screen_coords; layout(location = 0) out vec2 v_screen_coords;
void main() { void main() {
v_screen_coords = position; v_screen_coords = position;
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
}" }
",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
// The `color_input` parameter of the `draw` method. // The `color_input` parameter of the `draw` method.
layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput u_diffuse; layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput u_diffuse;
// The `normals_input` parameter of the `draw` method. // The `normals_input` parameter of the `draw` method.
layout(input_attachment_index = 1, set = 0, binding = 1) uniform subpassInput u_normals; layout(input_attachment_index = 1, set = 0, binding = 1) uniform subpassInput u_normals;
// The `depth_input` parameter of the `draw` method. // The `depth_input` parameter of the `draw` method.
layout(input_attachment_index = 2, set = 0, binding = 2) uniform subpassInput u_depth; layout(input_attachment_index = 2, set = 0, binding = 2) uniform subpassInput u_depth;
layout(push_constant) uniform PushConstants { layout(push_constant) uniform PushConstants {
// The `screen_to_world` parameter of the `draw` method. // The `screen_to_world` parameter of the `draw` method.
mat4 screen_to_world; mat4 screen_to_world;
// The `color` parameter of the `draw` method. // The `color` parameter of the `draw` method.
vec4 color; vec4 color;
// The `position` parameter of the `draw` method. // The `position` parameter of the `draw` method.
vec4 position; vec4 position;
} push_constants; } push_constants;
layout(location = 0) in vec2 v_screen_coords; layout(location = 0) in vec2 v_screen_coords;
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
void main() { void main() {
float in_depth = subpassLoad(u_depth).x; 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;
vec3 in_normal = normalize(subpassLoad(u_normals).rgb); // Any depth superior or equal to 1.0 means that the pixel has been untouched by
vec3 light_direction = normalize(push_constants.position.xyz - world.xyz); // the deferred pass. We don't want to deal with them.
// Calculate the percent of lighting that is received based on the orientation of the normal if (in_depth >= 1.0) {
// and the direction of the light. discard;
float light_percent = max(-dot(light_direction, in_normal), 0.0); }
float light_distance = length(push_constants.position.xyz - world.xyz); // Find the world coordinates of the current pixel.
// Further decrease light_percent based on the distance with the light position. vec4 world = push_constants.screen_to_world * vec4(v_screen_coords, in_depth, 1.0);
light_percent *= 1.0 / exp(light_distance); world /= world.w;
vec3 in_diffuse = subpassLoad(u_diffuse).rgb; vec3 in_normal = normalize(subpassLoad(u_normals).rgb);
f_color.rgb = push_constants.color.rgb * light_percent * in_diffuse; vec3 light_direction = normalize(push_constants.position.xyz - world.xyz);
f_color.a = 1.0;
}", // 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;
}
",
} }
} }

View File

@ -69,7 +69,6 @@ impl FrameSystem {
/// - `final_output_format` is the format of the image that will later be passed to the /// - `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 /// `frame()` method. We need to know that in advance. If that format ever changes, we have
/// to create a new `FrameSystem`. /// to create a new `FrameSystem`.
///
pub fn new( pub fn new(
gfx_queue: Arc<Queue>, gfx_queue: Arc<Queue>,
final_output_format: Format, final_output_format: Format,
@ -151,8 +150,8 @@ impl FrameSystem {
) )
.unwrap(); .unwrap();
// For now we create three temporary images with a dimension of 1 by 1 pixel. // For now we create three temporary images with a dimension of 1 by 1 pixel. These images
// These images will be replaced the first time we call `frame()`. // will be replaced the first time we call `frame()`.
let diffuse_buffer = ImageView::new_default( let diffuse_buffer = ImageView::new_default(
AttachmentImage::with_usage( AttachmentImage::with_usage(
&memory_allocator, &memory_allocator,
@ -188,8 +187,8 @@ impl FrameSystem {
gfx_queue.device().clone(), gfx_queue.device().clone(),
)); ));
// Initialize the three lighting systems. // Initialize the three lighting systems. Note that we need to pass to them the subpass
// Note that we need to pass to them the subpass where they will be executed. // where they will be executed.
let lighting_subpass = Subpass::from(render_pass.clone(), 1).unwrap(); let lighting_subpass = Subpass::from(render_pass.clone(), 1).unwrap();
let ambient_lighting_system = AmbientLightingSystem::new( let ambient_lighting_system = AmbientLightingSystem::new(
gfx_queue.clone(), gfx_queue.clone(),
@ -245,7 +244,6 @@ impl FrameSystem {
/// - `final_image` is the image we are going to draw to. /// - `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 /// - `world_to_framebuffer` is the matrix that will be used to convert from 3D coordinates in
/// the world into 2D coordinates on the framebuffer. /// the world into 2D coordinates on the framebuffer.
///
pub fn frame<F>( pub fn frame<F>(
&mut self, &mut self,
before_future: F, before_future: F,
@ -454,7 +452,6 @@ pub struct DrawPass<'f, 's: 'f> {
impl<'f, 's: 'f> DrawPass<'f, 's> { impl<'f, 's: 'f> DrawPass<'f, 's> {
/// Appends a command that executes a secondary command buffer that performs drawing. /// Appends a command that executes a secondary command buffer that performs drawing.
#[inline]
pub fn execute<C>(&mut self, command_buffer: C) pub fn execute<C>(&mut self, command_buffer: C)
where where
C: SecondaryCommandBufferAbstract + 'static, C: SecondaryCommandBufferAbstract + 'static,
@ -468,14 +465,12 @@ impl<'f, 's: 'f> DrawPass<'f, 's> {
} }
/// Returns the dimensions in pixels of the viewport. /// Returns the dimensions in pixels of the viewport.
#[inline]
pub fn viewport_dimensions(&self) -> [u32; 2] { pub fn viewport_dimensions(&self) -> [u32; 2] {
self.frame.framebuffer.extent() self.frame.framebuffer.extent()
} }
/// Returns the 4x4 matrix that turns world coordinates into 2D coordinates on the framebuffer. /// Returns the 4x4 matrix that turns world coordinates into 2D coordinates on the framebuffer.
#[allow(dead_code)] #[allow(dead_code)]
#[inline]
pub fn world_to_framebuffer_matrix(&self) -> Matrix4<f32> { pub fn world_to_framebuffer_matrix(&self) -> Matrix4<f32> {
self.frame.world_to_framebuffer self.frame.world_to_framebuffer
} }

View File

@ -11,15 +11,15 @@
// //
// The idea behind deferred lighting is to render the scene in two steps. // 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 // First you draw all the objects of the scene. But instead of calculating the color they will have
// have on the screen, you output their characteristics such as their diffuse color and their // on the screen, you output their characteristics such as their diffuse color and their normals,
// normals, and write this to images. // and write this to images.
// //
// After all the objects are drawn, you should obtain several images that contain the // After all the objects are drawn, you should obtain several images that contain the
// characteristics of each pixel. // characteristics of each pixel.
// //
// Then you apply lighting to the scene. In other words you draw to the final image by taking // Then you apply lighting to the scene. In other words you draw to the final image by taking these
// these intermediate images and the various lights of the scene as input. // 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 // 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 // expensive otherwise. It has some drawbacks, which are the fact that transparent objects must be
@ -66,7 +66,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -109,7 +108,7 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
@ -216,7 +215,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, 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 let new_images = new_images
.into_iter() .into_iter()
@ -235,7 +234,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -285,7 +284,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
} }

View File

@ -7,10 +7,9 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use bytemuck::{Pod, Zeroable};
use std::sync::Arc; use std::sync::Arc;
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage, Subbuffer}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage, Subbuffer},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder,
CommandBufferInheritanceInfo, CommandBufferUsage, SecondaryAutoCommandBuffer, CommandBufferInheritanceInfo, CommandBufferUsage, SecondaryAutoCommandBuffer,
@ -120,8 +119,8 @@ impl TriangleDrawSystem {
} }
} }
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct TriangleVertex { struct TriangleVertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -130,29 +129,31 @@ struct TriangleVertex {
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
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_Position = vec4(position, 0.0, 1.0);
}" }
",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
layout(location = 1) out vec3 f_normal; layout(location = 1) out vec3 f_normal;
void main() { void main() {
f_color = vec4(1.0, 1.0, 1.0, 1.0); f_color = vec4(1.0, 1.0, 1.0, 1.0);
f_normal = vec3(0.0, 0.0, 1.0); f_normal = vec3(0.0, 0.0, 1.0);
}" }
",
} }
} }

View File

@ -9,10 +9,9 @@
// This example demonstrates how to use dynamic uniform buffers. // This example demonstrates how to use dynamic uniform buffers.
// //
// Dynamic uniform and storage buffers store buffer data for different // Dynamic uniform and storage buffers store buffer data for different calls in one large buffer.
// calls in one large buffer. Each draw or dispatch call can specify an // Each draw or dispatch call can specify an offset into the buffer to read object data from,
// offset into the buffer to read object data from, without having to // without having to rebind descriptor sets.
// rebind descriptor sets.
use std::{iter::repeat, mem::size_of}; use std::{iter::repeat, mem::size_of};
use vulkano::{ use vulkano::{
@ -40,7 +39,6 @@ fn main() {
let instance = Instance::new( let instance = Instance::new(
library, library,
InstanceCreateInfo { InstanceCreateInfo {
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -74,7 +72,7 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
@ -94,29 +92,29 @@ fn main() {
mod shader { mod shader {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #version 450
layout(local_size_x = 12) in; layout(local_size_x = 12) in;
// Uniform Buffer Object // Uniform buffer.
layout(set = 0, binding = 0) uniform InData { layout(set = 0, binding = 0) uniform InData {
uint data; uint index;
} ubo; } ub;
// Output Buffer // Output buffer.
layout(set = 0, binding = 1) buffer OutData { layout(set = 0, binding = 1) buffer OutData {
uint data[]; 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() { void main() {
uint index = gl_GlobalInvocationID.x; uint index = gl_GlobalInvocationID.x;
if(index == ubo.data) { if (index == ub.index) {
data.data[index] = index; data[index] = index;
} }
} }
" ",
} }
} }
@ -138,29 +136,31 @@ fn main() {
let command_buffer_allocator = let command_buffer_allocator =
StandardCommandBufferAllocator::new(device.clone(), Default::default()); StandardCommandBufferAllocator::new(device.clone(), Default::default());
// Declare input buffer. // Create the input buffer. Data in a dynamic buffer **MUST** be aligned to
// Data in a dynamic buffer **MUST** be aligned to min_uniform_buffer_offset_align // `min_uniform_buffer_offset_align` or `min_storage_buffer_offset_align`, depending on the
// or min_storage_buffer_offset_align, depending on the type of buffer. // type of buffer.
let data: Vec<u8> = vec![3, 11, 7]; let data: Vec<u32> = vec![3, 11, 7];
let min_dynamic_align = device let min_dynamic_align = device
.physical_device() .physical_device()
.properties() .properties()
.min_uniform_buffer_offset_alignment as usize; .min_uniform_buffer_offset_alignment as usize;
println!("Minimum uniform buffer offset alignment: {min_dynamic_align}"); println!("Minimum uniform buffer offset alignment: {min_dynamic_align}");
println!("Input: {data:?}"); println!("Input: {data:?}");
// Round size up to the next multiple of align. // Round size up to the next multiple of align.
let align = (size_of::<u32>() + min_dynamic_align - 1) & !(min_dynamic_align - 1); let align = (size_of::<u32>() + min_dynamic_align - 1) & !(min_dynamic_align - 1);
let aligned_data = { let aligned_data = {
let mut aligned_data = Vec::with_capacity(align * data.len()); let mut aligned_data = Vec::with_capacity(align * data.len());
for elem in data { for elem in data {
let bytes = elem.to_ne_bytes(); let bytes = elem.to_ne_bytes();
// Fill up the buffer with data // Fill up the buffer with data.
for b in bytes { aligned_data.extend(bytes);
aligned_data.push(b); // Zero out any padding needed for alignment.
}
// Zero out any padding needed for alignment
aligned_data.extend(repeat(0).take(align - bytes.len())); aligned_data.extend(repeat(0).take(align - bytes.len()));
} }
aligned_data aligned_data
}; };
@ -196,7 +196,7 @@ fn main() {
WriteDescriptorSet::buffer_with_range( WriteDescriptorSet::buffer_with_range(
0, 0,
input_buffer, input_buffer,
0..size_of::<shader::ty::InData>() as DeviceSize, 0..size_of::<shader::InData>() as DeviceSize,
), ),
WriteDescriptorSet::buffer(1, output_buffer.clone()), WriteDescriptorSet::buffer(1, output_buffer.clone()),
], ],

View File

@ -7,12 +7,11 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
// This example demonstrates how to compute and load Compute Shader local size // This example demonstrates how to define the compute shader local size layout at runtime through
// layout in runtime through specialization constants using Physical Device metadata. // specialization constants while considering the physical device properties.
// //
// Workgroup parallelism capabilities are varying between GPUs and setting them // Workgroup parallelism capabilities vary between GPUs and setting them properly is important to
// properly is important to achieve maximal performance that particular device // achieve the maximal performance that particular device can provide.
// can provide.
use std::{fs::File, io::BufWriter, path::Path}; use std::{fs::File, io::BufWriter, path::Path};
use vulkano::{ use vulkano::{
@ -43,13 +42,11 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: InstanceExtensions { enabled_extensions: InstanceExtensions {
// This extension is required to obtain physical device metadata // This extension is required to obtain physical device metadata about the device
// about the device workgroup size limits // workgroup size limits.
khr_get_physical_device_properties2: true, khr_get_physical_device_properties2: true,
..InstanceExtensions::empty() ..InstanceExtensions::empty()
}, },
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -82,7 +79,7 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
@ -102,23 +99,20 @@ fn main() {
mod cs { mod cs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #version 450
// We set local_size_x and local_size_y to be variable configurable // We set `local_size_x` and `local_size_y` to be variables configurable values
// values through Specialization Constants. Values 1 and 2 define // through specialization constants. Values `1` and `2` both define a constant ID
// constant_id (1 and 2 correspondingly) and default values of // as well as a default value of 1 and 2 of the constants respecively. The
// the constants both. The `local_size_z = 1` here is an ordinary // `local_size_z = 1` here is an ordinary constant of the local size on the Z axis.
// built-in value of the local size in Z axis.
// //
// Unfortunately current GLSL language capabilities doesn't let us // Unfortunately current GLSL language capabilities doesn't let us define exact
// define exact names of the constants so we will have to use // names of the constants so we will have to use anonymous constants instead. See
// anonymous constants instead. See below on how to provide their // below for how to provide their values at runtime.
// values in run time.
// //
// Please NOTE that the constant_id in local_size layout must be // NOTE: The constant ID in `local_size` layout must be positive values. Zeros lead
// positive values. Zero value lead to runtime failure on nVidia // to runtime failure on NVIDIA devices due to a known bug in the driver.
// devices due to a known bug in nVidia driver.
layout(local_size_x_id = 1, local_size_y_id = 2, local_size_z = 1) in; 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 // 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; layout(set = 0, binding = 0, rgba8) uniform writeonly image2D img;
void main() { void main() {
// Colorful Mandelbrot fractal // Colorful Mandelbrot fractal.
vec2 norm_coordinates = (gl_GlobalInvocationID.xy + vec2(0.5)) / vec2(imageSize(img)); 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); 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); imageStore(img, ivec2(gl_GlobalInvocationID.xy), to_write);
} }
" ",
} }
} }
let shader = cs::load(device.clone()).unwrap(); let shader = cs::load(device.clone()).unwrap();
// Fetching subgroup size from the Physical Device metadata to compute appropriate // Fetching subgroup size from the physical device properties to determine an appropriate
// Compute Shader local size properties. // compute shader local size.
// //
// Most of the drivers provide this metadata, but some of the drivers don't. // Most of the drivers provide this property, but some of the drivers don't. In that case we
// In this case we can find appropriate value in this table: https://vulkan.gpuinfo.org/ // can find an appropriate value using this tool: https://vulkan.gpuinfo.org, or just use a
// or just use fallback constant for simplicity, but failure to set proper // fallback constant for simplicity, but failure to set a proper local size can lead to a
// local size can lead to significant performance penalty. // significant performance penalty.
let (local_size_x, local_size_y) = match device.physical_device().properties().subgroup_size { let (local_size_x, local_size_y) = match device.physical_device().properties().subgroup_size {
Some(subgroup_size) => { Some(subgroup_size) => {
println!("Subgroup size is {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) (8, subgroup_size / 8)
} }
None => { None => {
println!("This Vulkan driver doesn't provide physical device Subgroup information"); println!("This Vulkan driver doesn't provide physical device Subgroup information");
// Using fallback constant // Using a fallback constant.
(8, 8) (8, 8)
} }
}; };
@ -185,7 +179,8 @@ fn main() {
red: 0.2, red: 0.2,
green: 0.5, green: 0.5,
blue: 1.0, 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, constant_2: local_size_y,
}; };
let pipeline = ComputePipeline::new( let pipeline = ComputePipeline::new(
@ -247,11 +242,8 @@ fn main() {
0, 0,
set, set,
) )
.dispatch([ // Note that dispatch dimensions must be proportional to the local size.
1024 / local_size_x, // Note that dispatch dimensions must be .dispatch([1024 / local_size_x, 1024 / local_size_y, 1])
1024 / local_size_y, // proportional to local size
1,
])
.unwrap() .unwrap()
.copy_image_to_buffer(CopyImageToBufferInfo::image_buffer(image, buf.clone())) .copy_image_to_buffer(CopyImageToBufferInfo::image_buffer(image, buf.clone()))
.unwrap(); .unwrap();

View File

@ -8,14 +8,13 @@ fn main() {
// TODO: Can this be demonstrated for other platforms as well? // TODO: Can this be demonstrated for other platforms as well?
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
mod linux { mod linux {
use bytemuck::{Pod, Zeroable};
use glium::glutin::{self, platform::unix::HeadlessContextExt}; use glium::glutin::{self, platform::unix::HeadlessContextExt};
use std::{ use std::{
sync::{Arc, Barrier}, sync::{Arc, Barrier},
time::Instant, time::Instant,
}; };
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage, Subbuffer}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage, Subbuffer},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder,
CommandBufferUsage, RenderPassBeginInfo, SemaphoreSubmitInfo, SubmitInfo, CommandBufferUsage, RenderPassBeginInfo, SemaphoreSubmitInfo, SubmitInfo,
@ -69,7 +68,7 @@ mod linux {
pub fn main() { pub fn main() {
let event_loop = EventLoop::new(); 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() let hrb = glutin::ContextBuilder::new()
.with_gl_debug_flag(true) .with_gl_debug_flag(true)
.with_gl(glutin::GlRequest::Latest) .with_gl(glutin::GlRequest::Latest)
@ -82,11 +81,13 @@ mod linux {
.build_surfaceless(&event_loop) .build_surfaceless(&event_loop)
.unwrap(); .unwrap();
// Used for checking device and driver UUIDs.
let display = glium::HeadlessRenderer::with_debug( let display = glium::HeadlessRenderer::with_debug(
hrb_vk, hrb_vk,
glium::debug::DebugCallbackBehavior::PrintAll, glium::debug::DebugCallbackBehavior::PrintAll,
) )
.unwrap(); // Used for checking device and driver UUIDs .unwrap();
let ( let (
device, device,
_instance, _instance,
@ -293,7 +294,7 @@ mod linux {
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => { Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => {
return return
} }
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -312,7 +313,7 @@ mod linux {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -375,7 +376,7 @@ mod linux {
previous_frame_end = Some(vulkano::sync::now(device.clone()).boxed()); previous_frame_end = Some(vulkano::sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(vulkano::sync::now(device.clone()).boxed()); previous_frame_end = Some(vulkano::sync::now(device.clone()).boxed());
} }
}; };
@ -386,8 +387,8 @@ mod linux {
}); });
} }
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct MyVertex { struct MyVertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -427,7 +428,6 @@ mod linux {
} }
.union(&required_extensions), .union(&required_extensions),
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
@ -444,7 +444,7 @@ mod linux {
msg.layer_prefix.unwrap_or("unknown"), msg.layer_prefix.unwrap_or("unknown"),
msg.ty, msg.ty,
msg.severity, msg.severity,
msg.description msg.description,
); );
})), })),
) )
@ -695,28 +695,30 @@ mod linux {
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 position; layout(location = 0) in vec2 position;
layout(location = 0) out vec2 tex_coords; layout(location = 0) out vec2 tex_coords;
void main() { void main() {
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
tex_coords = position + vec2(0.5); tex_coords = position + vec2(0.5);
}" }
",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 tex_coords; layout(location = 0) in vec2 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() { void main() {
f_color = texture(tex, tex_coords); f_color = texture(tex, tex_coords);
}" }
",
} }
} }
} }

View File

@ -7,10 +7,9 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use bytemuck::{Pod, Zeroable};
use std::{io::Cursor, sync::Arc}; use std::{io::Cursor, sync::Arc};
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, BlitImageInfo, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, BlitImageInfo,
BufferImageCopy, ClearColorImageInfo, CommandBufferUsage, CopyBufferToImageInfo, BufferImageCopy, ClearColorImageInfo, CommandBufferUsage, CopyBufferToImageInfo,
@ -57,8 +56,8 @@ use winit::{
}; };
fn main() { fn main() {
// The start of this example is exactly the same as `triangle`. You should read the // The start of this example is exactly the same as `triangle`. You should read the `triangle`
// `triangle` example if you haven't done so yet. // example if you haven't done so yet.
let library = VulkanLibrary::new().unwrap(); let library = VulkanLibrary::new().unwrap();
let required_extensions = vulkano_win::required_extensions(&library); let required_extensions = vulkano_win::required_extensions(&library);
@ -66,7 +65,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -161,8 +159,8 @@ fn main() {
let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -255,12 +253,12 @@ fn main() {
) )
.unwrap(); .unwrap();
// here, we perform image copying and blitting on the same image // Here, we perform image copying and blitting on the same image.
uploads uploads
// clear the image buffer // Clear the image buffer.
.clear_color_image(ClearColorImageInfo::image(image.clone())) .clear_color_image(ClearColorImageInfo::image(image.clone()))
.unwrap() .unwrap()
// put our image in the top left corner // Put our image in the top left corner.
.copy_buffer_to_image(CopyBufferToImageInfo { .copy_buffer_to_image(CopyBufferToImageInfo {
regions: [BufferImageCopy { regions: [BufferImageCopy {
image_subresource: image.subresource_layers(), image_subresource: image.subresource_layers(),
@ -271,7 +269,7 @@ fn main() {
..CopyBufferToImageInfo::buffer_image(buffer, image.clone()) ..CopyBufferToImageInfo::buffer_image(buffer, image.clone())
}) })
.unwrap() .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 { .copy_image(CopyImageInfo {
// Copying within the same image requires the General layout if the source and // Copying within the same image requires the General layout if the source and
// destination subresources overlap. // destination subresources overlap.
@ -289,9 +287,9 @@ fn main() {
..CopyImageInfo::images(image.clone(), image.clone()) ..CopyImageInfo::images(image.clone(), image.clone())
}) })
.unwrap() .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 { .blit_image(BlitImageInfo {
// Same for blitting. // Same as above applies for blitting.
src_image_layout: ImageLayout::General, src_image_layout: ImageLayout::General,
dst_image_layout: ImageLayout::General, dst_image_layout: ImageLayout::General,
regions: [ImageBlit { regions: [ImageBlit {
@ -301,7 +299,7 @@ fn main() {
[img_size[0] * 2, img_size[1] * 2, 1], [img_size[0] * 2, img_size[1] * 2, 1],
], ],
dst_subresource: image.subresource_layers(), dst_subresource: image.subresource_layers(),
// swapping the two corners results in flipped image // Swapping the two corners results in flipped image.
dst_offsets: [ dst_offsets: [
[img_size[0] * 2 - 1, img_size[1] - 1, 0], [img_size[0] * 2 - 1, img_size[1] - 1, 0],
[img_size[0], 0, 1], [img_size[0], 0, 1],
@ -394,7 +392,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -410,7 +408,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -470,7 +468,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,
@ -507,32 +505,34 @@ fn window_size_dependent_setup(
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 position; layout(location = 0) in vec2 position;
layout(location = 0) out vec2 tex_coords; layout(location = 0) out vec2 tex_coords;
void main() { void main() {
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
tex_coords = position + vec2(0.5); tex_coords = position + vec2(0.5);
}" }
",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 tex_coords; layout(location = 0) in vec2 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() { void main() {
f_color = texture(tex, tex_coords); f_color = texture(tex, tex_coords);
}" }
",
} }
} }

View File

@ -7,10 +7,9 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use bytemuck::{Pod, Zeroable};
use std::{io::Cursor, sync::Arc}; use std::{io::Cursor, sync::Arc};
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents,
@ -55,8 +54,8 @@ use winit::{
}; };
fn main() { fn main() {
// The start of this example is exactly the same as `triangle`. You should read the // The start of this example is exactly the same as `triangle`. You should read the `triangle`
// `triangle` example if you haven't done so yet. // example if you haven't done so yet.
let library = VulkanLibrary::new().unwrap(); let library = VulkanLibrary::new().unwrap();
let required_extensions = vulkano_win::required_extensions(&library); let required_extensions = vulkano_win::required_extensions(&library);
@ -64,7 +63,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -159,8 +157,8 @@ fn main() {
let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -323,7 +321,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -339,7 +337,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -399,7 +397,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,
@ -436,32 +434,34 @@ fn window_size_dependent_setup(
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 position; layout(location = 0) in vec2 position;
layout(location = 0) out vec2 tex_coords; layout(location = 0) out vec2 tex_coords;
void main() { void main() {
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
tex_coords = position + vec2(0.5); tex_coords = position + vec2(0.5);
}" }
",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 tex_coords; layout(location = 0) in vec2 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() { void main() {
f_color = texture(tex, tex_coords); f_color = texture(tex, tex_coords);
}" }
",
} }
} }

View File

@ -7,19 +7,18 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
// An immutable sampler is a sampler that is integrated into the descriptor set layout // An immutable sampler is a sampler that is integrated into the descriptor set layout (and thus
// (and thus pipeline layout), instead of being written to an individual descriptor set. // pipeline layout), instead of being written to an individual descriptor set. Consequently, all
// Consequently, all descriptor sets with this layout will share the same sampler. // 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 // This example is almost identical to the image example, but with two differences, which have
// been commented: // been commented:
// - The sampler is added to the descriptor set layout at pipeline creation. // - The sampler is added to the descriptor set layout at pipeline creation.
// - No sampler is included when building a descriptor set. // - No sampler is included when building a descriptor set.
use bytemuck::{Pod, Zeroable};
use std::{io::Cursor, sync::Arc}; use std::{io::Cursor, sync::Arc};
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents,
@ -70,7 +69,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -165,8 +163,8 @@ fn main() {
let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -273,8 +271,7 @@ fn main() {
.color_blend_state(ColorBlendState::new(subpass.num_color_attachments()).blend_alpha()) .color_blend_state(ColorBlendState::new(subpass.num_color_attachments()).blend_alpha())
.render_pass(subpass) .render_pass(subpass)
.with_auto_layout(device.clone(), |layout_create_infos| { .with_auto_layout(device.clone(), |layout_create_infos| {
// Modify the auto-generated layout by setting an immutable sampler to // Modify the auto-generated layout by setting an immutable sampler to set 0 binding 0.
// set 0 binding 0.
let binding = layout_create_infos[0].bindings.get_mut(&0).unwrap(); let binding = layout_create_infos[0].bindings.get_mut(&0).unwrap();
binding.immutable_samplers = vec![sampler]; binding.immutable_samplers = vec![sampler];
}) })
@ -282,7 +279,8 @@ fn main() {
let layout = pipeline.layout().set_layouts().get(0).unwrap(); 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( let set = PersistentDescriptorSet::new(
&descriptor_set_allocator, &descriptor_set_allocator,
layout.clone(), layout.clone(),
@ -336,7 +334,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -352,7 +350,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -412,7 +410,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,
@ -449,32 +447,34 @@ fn window_size_dependent_setup(
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 position; layout(location = 0) in vec2 position;
layout(location = 0) out vec2 tex_coords; layout(location = 0) out vec2 tex_coords;
void main() { void main() {
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
tex_coords = position + vec2(0.5); tex_coords = position + vec2(0.5);
}" }
",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 tex_coords; layout(location = 0) in vec2 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() { void main() {
f_color = texture(tex, tex_coords); f_color = texture(tex, tex_coords);
}" }
",
} }
} }

View File

@ -12,24 +12,22 @@
// Indirect draw calls allow us to issue a draw without needing to know the number of vertices // 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. // 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 // This is used in situations where vertices are being generated on the GPU, such as a GPU particle
// particle simulation, and the exact number of output vertices cannot be known until // simulation, and the exact number of output vertices cannot be known until the compute shader has
// the compute shader has run. // run.
// //
// In this example the compute shader is trivial and the number of vertices does not change. // 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 // However is does demonstrate that each compute instance atomically updates the vertex counter
// counter before filling the vertex buffer. // before filling the vertex buffer.
// //
// For an explanation of how the rendering of the triangles takes place see the `triangle.rs` // For an explanation of how the rendering of the triangles takes place see the `triangle.rs`
// example. // example.
//
use bytemuck::{Pod, Zeroable};
use std::sync::Arc; use std::sync::Arc;
use vulkano::{ use vulkano::{
buffer::{ buffer::{
allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
BufferUsage, BufferContents, BufferUsage,
}, },
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
@ -76,7 +74,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -174,7 +171,7 @@ fn main() {
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
// The triangle vertex positions. // The triangle vertex positions.
@ -183,14 +180,14 @@ fn main() {
void main() { void main() {
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
} }
" ",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r#"
#version 450 #version 450
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
@ -198,16 +195,17 @@ fn main() {
void main() { void main() {
f_color = vec4(1.0, 0.0, 0.0, 1.0); 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 // A simple compute shader that generates vertices. It has two buffers bound: the first is
// is the IndirectDrawArgs struct we passed the draw_indirect so we can set the number to vertices to draw // 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 { mod cs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #version 450
layout(local_size_x = 16, local_size_y = 1, local_size_z = 1) in; layout(local_size_x = 16, local_size_y = 1, local_size_z = 1) in;
@ -226,9 +224,10 @@ fn main() {
void main() { void main() {
uint idx = gl_GlobalInvocationID.x; uint idx = gl_GlobalInvocationID.x;
// each thread of compute shader is going to increment the counter, so we need to use atomic // Each invocation of the compute shader is going to increment the counter, so
// operations for safety. The previous value of the counter is returned so that gives us // we need to use atomic operations for safety. The previous value of the
// the offset into the vertex buffer this thread can write it's vertices into. // 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); uint offset = atomicAdd(vertices, 6);
vec2 center = vec2(-0.8, -0.8) + idx * vec2(0.1, 0.1); 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 + 4] = center + vec2(0.025, 0.01725);
triangles.pos[offset + 5] = center + vec2(-0.025, 0.01725); triangles.pos[offset + 5] = center + vec2(-0.025, 0.01725);
} }
" ",
} }
} }
@ -292,10 +291,10 @@ fn main() {
) )
.unwrap(); .unwrap();
// # Vertex Types // `Vertex` is the vertex type that will be output from the compute shader and be input to the
// `Vertex` is the vertex type that will be output from the compute shader and be input to the vertex shader. // vertex shader.
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -355,7 +354,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -374,15 +373,16 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
recreate_swapchain = true; recreate_swapchain = true;
} }
// Allocate a GPU buffer to hold the arguments for this frames draw call. The compute // 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. // shader will only update `vertex_count`, so set the other parameters correctly
// here.
let indirect_commands = [DrawIndirectCommand { let indirect_commands = [DrawIndirectCommand {
vertex_count: 0, vertex_count: 0,
instance_count: 1, instance_count: 1,
@ -397,15 +397,15 @@ fn main() {
.unwrap() .unwrap()
.copy_from_slice(&indirect_commands); .copy_from_slice(&indirect_commands);
// Allocate a GPU buffer to hold this frames vertices. This needs to be large enough to hold // Allocate a buffer to hold this frame's vertices. This needs to be large enough
// the worst case number of vertices generated by the compute shader // 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 iter = (0..(6 * 16)).map(|_| Vertex { position: [0.0; 2] });
let vertices = vertex_pool.allocate_slice(iter.len() as _).unwrap(); let vertices = vertex_pool.allocate_slice(iter.len() as _).unwrap();
for (o, i) in vertices.write().unwrap().iter_mut().zip(iter) { for (o, i) in vertices.write().unwrap().iter_mut().zip(iter) {
*o = i; *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 layout = compute_pipeline.layout().set_layouts().get(0).unwrap();
let cs_desciptor_set = PersistentDescriptorSet::new( let cs_desciptor_set = PersistentDescriptorSet::new(
&descriptor_set_allocator, &descriptor_set_allocator,
@ -424,8 +424,8 @@ fn main() {
) )
.unwrap(); .unwrap();
// First in the command buffer we dispatch the compute shader to generate the vertices and fill out the draw // First in the command buffer we dispatch the compute shader to generate the
// call arguments // vertices and fill out the draw call arguments.
builder builder
.bind_pipeline_compute(compute_pipeline.clone()) .bind_pipeline_compute(compute_pipeline.clone())
.bind_descriptor_sets( .bind_descriptor_sets(
@ -446,11 +446,11 @@ fn main() {
SubpassContents::Inline, SubpassContents::Inline,
) )
.unwrap() .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()]) .set_viewport(0, [viewport.clone()])
.bind_pipeline_graphics(render_pipeline.clone()) .bind_pipeline_graphics(render_pipeline.clone())
.bind_vertex_buffers(0, vertices) .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) .draw_indirect(indirect_buffer)
.unwrap() .unwrap()
.end_render_pass() .end_render_pass()
@ -478,7 +478,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,

View File

@ -12,10 +12,9 @@
// This is a simple, modified version of the `triangle.rs` example that demonstrates how we can use // 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. // the "instancing" technique with vulkano to draw many instances of the triangle.
use bytemuck::{Pod, Zeroable};
use std::sync::Arc; use std::sync::Arc;
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
RenderPassBeginInfo, SubpassContents, RenderPassBeginInfo, SubpassContents,
@ -51,22 +50,17 @@ use winit::{
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
// # Vertex Types /// The vertex type that we will be used to describe the triangle's geometry.
// #[derive(BufferContents, Vertex)]
// 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.
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct TriangleVertex { struct TriangleVertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], 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)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct InstanceData { struct InstanceData {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position_offset: [f32; 2], position_offset: [f32; 2],
@ -81,7 +75,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -177,8 +170,8 @@ fn main() {
let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
// We now create a buffer that will store the shape of our triangle. // We now create a buffer that will store the shape of our triangle. This triangle is identical
// This triangle is identical to the one in the `triangle.rs` example. // to the one in the `triangle.rs` example.
let vertices = [ let vertices = [
TriangleVertex { TriangleVertex {
position: [-0.5, -0.25], position: [-0.5, -0.25],
@ -202,8 +195,8 @@ fn main() {
.unwrap() .unwrap()
}; };
// Now we create another buffer that will store the unique data per instance. // Now we create another buffer that will store the unique data per instance. For this example,
// For this example, we'll have the instances form a 10x10 grid that slowly gets larger. // we'll have the instances form a 10x10 grid that slowly gets larger.
let instances = { let instances = {
let rows = 10; let rows = 10;
let cols = 10; let cols = 10;
@ -238,7 +231,7 @@ fn main() {
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
// The triangle vertex positions. // The triangle vertex positions.
@ -252,14 +245,14 @@ fn main() {
// Apply the scale and offset for the instance. // Apply the scale and offset for the instance.
gl_Position = vec4(position * scale + position_offset, 0.0, 1.0); gl_Position = vec4(position * scale + position_offset, 0.0, 1.0);
} }
" ",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
@ -267,7 +260,7 @@ fn main() {
void main() { void main() {
f_color = vec4(1.0, 0.0, 0.0, 1.0); f_color = vec4(1.0, 0.0, 0.0, 1.0);
} }
" ",
} }
} }
@ -292,8 +285,8 @@ fn main() {
.unwrap(); .unwrap();
let pipeline = GraphicsPipeline::start() let pipeline = GraphicsPipeline::start()
// Use the `BuffersDefinition` to describe to vulkano how the two vertex types // Use the implementations of the `Vertex` trait to describe to vulkano how the two vertex
// are expected to be used. // types are expected to be used.
.vertex_input_state([TriangleVertex::per_vertex(), InstanceData::per_instance()]) .vertex_input_state([TriangleVertex::per_vertex(), InstanceData::per_instance()])
.vertex_shader(vs.entry_point("main").unwrap(), ()) .vertex_shader(vs.entry_point("main").unwrap(), ())
.input_assembly_state(InputAssemblyState::new()) .input_assembly_state(InputAssemblyState::new())
@ -346,7 +339,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -365,7 +358,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -425,7 +418,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,

View File

@ -7,49 +7,52 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use crate::fractal_compute_pipeline::FractalComputePipeline; use crate::{
use crate::place_over_frame::RenderPassPlaceOverFrame; fractal_compute_pipeline::FractalComputePipeline, place_over_frame::RenderPassPlaceOverFrame,
};
use cgmath::Vector2; use cgmath::Vector2;
use std::sync::Arc; use std::{sync::Arc, time::Instant};
use std::time::Instant; use vulkano::{
use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; command_buffer::allocator::StandardCommandBufferAllocator,
use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; descriptor_set::allocator::StandardDescriptorSetAllocator, device::Queue,
use vulkano::device::Queue; memory::allocator::StandardMemoryAllocator, sync::GpuFuture,
use vulkano::memory::allocator::StandardMemoryAllocator; };
use vulkano::sync::GpuFuture; use vulkano_util::{
use vulkano_util::renderer::{DeviceImageView, VulkanoWindowRenderer}; renderer::{DeviceImageView, VulkanoWindowRenderer},
use vulkano_util::window::WindowDescriptor; window::WindowDescriptor,
use winit::window::Fullscreen; };
use winit::{ use winit::{
dpi::PhysicalPosition, dpi::PhysicalPosition,
event::{ event::{
ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, VirtualKeyCode, ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, VirtualKeyCode,
WindowEvent, WindowEvent,
}, },
window::Fullscreen,
}; };
const MAX_ITERS_INIT: u32 = 200; const MAX_ITERS_INIT: u32 = 200;
const MOVE_SPEED: f32 = 0.5; const MOVE_SPEED: f32 = 0.5;
/// App for exploring Julia and Mandelbrot fractals /// App for exploring Julia and Mandelbrot fractals.
pub struct FractalApp { 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, fractal_pipeline: FractalComputePipeline,
/// Our render pipeline (pass) /// Our render pipeline (pass).
pub place_over_frame: RenderPassPlaceOverFrame, pub place_over_frame: RenderPassPlaceOverFrame,
/// Toggle that flips between julia and mandelbrot /// Toggle that flips between Julia and Mandelbrot.
pub is_julia: bool, pub is_julia: bool,
/// Togglet thats stops the movement on Julia /// Toggle that stops the movement on Julia.
is_c_paused: bool, is_c_paused: bool,
/// C is a constant input to Julia escape time algorithm (mouse position). /// C is a constant input to Julia escape time algorithm (mouse position).
c: Vector2<f32>, c: Vector2<f32>,
/// Our zoom level /// Our zoom level.
scale: Vector2<f32>, scale: Vector2<f32>,
/// Our translation on the complex plane /// Our translation on the complex plane.
translation: Vector2<f32>, translation: Vector2<f32>,
/// 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, pub max_iters: u32,
/// Time tracking, useful for frame independent movement /// Time tracking, useful for frame independent movement.
time: Instant, time: Instant,
dt: f32, dt: f32,
dt_sum: f32, dt_sum: f32,
@ -113,11 +116,11 @@ Usage:
F: Toggle full-screen F: Toggle full-screen
Right mouse: Stop movement in Julia (mouse position determines c) Right mouse: Stop movement in Julia (mouse position determines c)
Esc: Quit\ 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<dyn GpuFuture> { pub fn compute(&self, image_target: DeviceImageView) -> Box<dyn GpuFuture> {
self.fractal_pipeline.compute( self.fractal_pipeline.compute(
image_target, 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 { pub fn is_running(&self) -> bool {
!self.input_state.should_quit !self.input_state.should_quit
} }
/// Return average fps /// Returns the average FPS.
pub fn avg_fps(&self) -> f32 { pub fn avg_fps(&self) -> f32 {
self.avg_fps self.avg_fps
} }
/// Delta time in milliseconds /// Returns the delta time in milliseconds.
pub fn dt(&self) -> f32 { pub fn dt(&self) -> f32 {
self.dt * 1000.0 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) { 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 { if self.dt_sum > 1.0 {
self.avg_fps = self.frame_count / self.dt_sum; self.avg_fps = self.frame_count / self.dt_sum;
self.frame_count = 0.0; self.frame_count = 0.0;
@ -158,17 +161,19 @@ Usage:
self.time = Instant::now(); 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) { 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. { if self.input_state.scroll_delta > 0. {
self.scale /= 1.05; self.scale /= 1.05;
} else if self.input_state.scroll_delta < 0. { } else if self.input_state.scroll_delta < 0. {
self.scale *= 1.05; 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; let move_speed = MOVE_SPEED * self.dt * self.scale.x;
// Panning
// Panning.
if self.input_state.pan_up { if self.input_state.pan_up {
self.translation += Vector2::new(0.0, move_speed); self.translation += Vector2::new(0.0, move_speed);
} }
@ -181,22 +186,27 @@ Usage:
if self.input_state.pan_left { if self.input_state.pan_left {
self.translation += Vector2::new(-move_speed, 0.0); self.translation += Vector2::new(-move_speed, 0.0);
} }
// Toggle between julia and mandelbrot
// Toggle between Julia and Mandelbrot.
if self.input_state.toggle_julia { if self.input_state.toggle_julia {
self.is_julia = !self.is_julia; self.is_julia = !self.is_julia;
} }
// Toggle c
// Toggle c.
if self.input_state.toggle_c { if self.input_state.toggle_c {
self.is_c_paused = !self.is_c_paused; self.is_c_paused = !self.is_c_paused;
} }
// Update c
// Update c.
if !self.is_c_paused { 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); 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; 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 { if self.input_state.increase_iterations {
self.max_iters += 1; self.max_iters += 1;
} }
@ -207,11 +217,13 @@ Usage:
self.max_iters -= 1; self.max_iters -= 1;
} }
} }
// Randomize our palette
// Randomize our palette.
if self.input_state.randomize_palette { if self.input_state.randomize_palette {
self.fractal_pipeline.randomize_palette(); self.fractal_pipeline.randomize_palette();
} }
// Toggle full-screen
// Toggle full-screen.
if self.input_state.toggle_full_screen { if self.input_state.toggle_full_screen {
let is_full_screen = renderer.window().fullscreen().is_some(); let is_full_screen = renderer.window().fullscreen().is_some();
renderer.window().set_fullscreen(if !is_full_screen { 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<()>) { pub fn handle_input(&mut self, window_size: [f32; 2], event: &Event<()>) {
self.input_state.handle_input(window_size, 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) { pub fn reset_input_state(&mut self) {
self.input_state.reset() self.input_state.reset()
} }
@ -240,9 +252,9 @@ fn state_is_pressed(state: ElementState) -> bool {
} }
} }
/// Just a very simple input state (mappings). /// Just a very simple input state (mappings). Winit only has `Pressed` and `Released` events, thus
/// Winit only has Pressed and Released events, thus continuous movement needs toggles. /// continuous movement needs toggles. Panning is one of those things where continuous movement
/// Panning is one of those where continuous movement feels better. /// feels better.
struct InputState { struct InputState {
pub window_size: [f32; 2], pub window_size: [f32; 2],
pub pan_up: bool, 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) { fn reset(&mut self) {
*self = InputState { *self = InputState {
scroll_delta: 0.0, 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) { fn on_keyboard_event(&mut self, input: &KeyboardInput) {
if let Some(key_code) = input.virtual_keycode { if let Some(key_code) = input.virtual_keycode {
match key_code { 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) { fn on_mouse_wheel_event(&mut self, delta: &MouseScrollDelta) {
let change = match delta { let change = match delta {
MouseScrollDelta::LineDelta(_x, y) => *y, MouseScrollDelta::LineDelta(_x, y) => *y,

View File

@ -45,7 +45,7 @@ impl FractalComputePipeline {
command_buffer_allocator: Arc<StandardCommandBufferAllocator>, command_buffer_allocator: Arc<StandardCommandBufferAllocator>,
descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>, descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
) -> FractalComputePipeline { ) -> FractalComputePipeline {
// Initial colors // Initial colors.
let colors = vec![ let colors = vec![
[1.0, 0.0, 0.0, 1.0], [1.0, 0.0, 0.0, 1.0],
[1.0, 1.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) { pub fn randomize_palette(&mut self) {
let mut colors = vec![]; let mut colors = vec![];
for _ in 0..self.palette_size { for _ in 0..self.palette_size {
@ -120,7 +120,7 @@ impl FractalComputePipeline {
max_iters: u32, max_iters: u32,
is_julia: bool, is_julia: bool,
) -> Box<dyn GpuFuture> { ) -> Box<dyn GpuFuture> {
// Resize image if needed // Resize image if needed.
let img_dims = image.image().dimensions().width_height(); let img_dims = image.image().dimensions().width_height();
let pipeline_layout = self.pipeline.layout(); let pipeline_layout = self.pipeline.layout();
let desc_layout = pipeline_layout.set_layouts().get(0).unwrap(); let desc_layout = pipeline_layout.set_layouts().get(0).unwrap();
@ -140,15 +140,14 @@ impl FractalComputePipeline {
) )
.unwrap(); .unwrap();
let push_constants = cs::ty::PushConstants { let push_constants = cs::PushConstants {
end_color: self.end_color,
c: c.into(), c: c.into(),
scale: scale.into(), scale: scale.into(),
translation: translation.into(), translation: translation.into(),
end_color: self.end_color,
palette_size: self.palette_size, palette_size: self.palette_size,
max_iters: max_iters as i32, max_iters: max_iters as i32,
is_julia: is_julia as u32, is_julia: is_julia as u32,
_dummy0: [0u8; 8], // Required for alignment
}; };
builder builder
.bind_pipeline_compute(self.pipeline.clone()) .bind_pipeline_compute(self.pipeline.clone())
@ -165,101 +164,104 @@ impl FractalComputePipeline {
mod cs { mod cs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #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 // Image to which we'll write our fractal
layout(set = 0, binding = 0, rgba8) uniform writeonly image2D img; layout(set = 0, binding = 0, rgba8) uniform writeonly image2D img;
// Our palette as a dynamic buffer // Our palette as a dynamic buffer
layout(set = 0, binding = 1) buffer Palette { layout(set = 0, binding = 1) buffer Palette {
vec4 data[]; vec4 data[];
} palette; } palette;
// Our variable inputs as push constants // Our variable inputs as push constants
layout(push_constant) uniform PushConstants { layout(push_constant) uniform PushConstants {
vec2 c; vec4 end_color;
vec2 scale; vec2 c;
vec2 translation; vec2 scale;
vec4 end_color; vec2 translation;
int palette_size; int palette_size;
int max_iters; int max_iters;
bool is_julia; bool is_julia;
} push_constants; } push_constants;
// Gets smooth color between current color (determined by iterations) and the next color in the palette // Gets smooth color between current color (determined by iterations) and the next
// by linearly interpolating the colors based on: https://linas.org/art-gallery/escape/smooth.html // color in the palette by linearly interpolating the colors based on:
vec4 get_color( // https://linas.org/art-gallery/escape/smooth.html
int palette_size, vec4 get_color(
vec4 end_color, int palette_size,
int i, vec4 end_color,
int max_iters, int i,
float len_z int max_iters,
) { float len_z
if (i < max_iters) { ) {
float iters_float = float(i) + 1.0 - log(log(len_z)) / log(2.0f); if (i < max_iters) {
float iters_floor = floor(iters_float); float iters_float = float(i) + 1.0 - log(log(len_z)) / log(2.0f);
float remainder = iters_float - iters_floor; float iters_floor = floor(iters_float);
vec4 color_start = palette.data[int(iters_floor) % push_constants.palette_size]; float remainder = iters_float - iters_floor;
vec4 color_end = palette.data[(int(iters_floor) + 1) % push_constants.palette_size]; vec4 color_start = palette.data[int(iters_floor) % push_constants.palette_size];
return mix(color_start, color_end, remainder); vec4 color_end = palette.data[(int(iters_floor) + 1) % push_constants.palette_size];
} return mix(color_start, color_end, remainder);
return end_color; }
} return end_color;
}
void main() { void main() {
// Scale image pixels to range // Scale image pixels to range
vec2 dims = vec2(imageSize(img)); vec2 dims = vec2(imageSize(img));
float ar = dims.x / dims.y; float ar = dims.x / dims.y;
float x_over_width = (gl_GlobalInvocationID.x / dims.x); float x_over_width = (gl_GlobalInvocationID.x / dims.x);
float y_over_height = (gl_GlobalInvocationID.y / dims.y); 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 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; 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 // Julia is like mandelbrot, but instead changing the constant `c` will change the
// you'll see. Thus we want to bind the c to mouse position. // 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 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. // With julia, c = any value between the interesting range (-2.0 - 2.0),
vec2 c; // Z = scaled xy position of the image.
vec2 z; vec2 c;
if (push_constants.is_julia) { vec2 z;
c = push_constants.c; if (push_constants.is_julia) {
z = vec2(x0, y0); c = push_constants.c;
} else { z = vec2(x0, y0);
c = vec2(x0, y0); } else {
z = vec2(0.0, 0.0); c = vec2(x0, y0);
} z = vec2(0.0, 0.0);
}
// Escape time algorithm: // Escape time algorithm:
// https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set // 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 // It's an iterative algorithm where the bailout point (number of iterations) will
// the color we choose from the palette // determine the color we choose from the palette.
int i; int i;
float len_z; float len_z;
for (i = 0; i < push_constants.max_iters; i += 1) { for (i = 0; i < push_constants.max_iters; i += 1) {
z = vec2( z = vec2(
z.x * z.x - z.y * z.y + c.x, z.x * z.x - z.y * z.y + c.x,
z.y * z.x + z.x * z.y + c.y z.y * z.x + z.x * z.y + c.y
); );
len_z = length(z); len_z = length(z);
// Using 8.0 for bailout limit give a little nicer colors with smooth colors // Using 8.0 for bailout limit give a little nicer colors with smooth colors
// 2.0 is enough to 'determine' an escape will happen // 2.0 is enough to 'determine' an escape will happen.
if (len_z > 8.0) { if (len_z > 8.0) {
break; break;
} }
} }
vec4 write_color = get_color( vec4 write_color = get_color(
push_constants.palette_size, push_constants.palette_size,
push_constants.end_color, push_constants.end_color,
i, i,
push_constants.max_iters, push_constants.max_iters,
len_z len_z
); );
imageStore(img, ivec2(gl_GlobalInvocationID.xy), write_color); imageStore(img, ivec2(gl_GlobalInvocationID.xy), write_color);
}", }
",
} }
} }

View File

@ -7,13 +7,25 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // 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 crate::app::FractalApp;
use vulkano::image::ImageUsage; use vulkano::{image::ImageUsage, swapchain::PresentMode, sync::GpuFuture};
use vulkano::swapchain::PresentMode; use vulkano_util::{
use vulkano::sync::GpuFuture; context::{VulkanoConfig, VulkanoContext},
use vulkano_util::context::{VulkanoConfig, VulkanoContext}; renderer::{VulkanoWindowRenderer, DEFAULT_IMAGE_FORMAT},
use vulkano_util::renderer::{VulkanoWindowRenderer, DEFAULT_IMAGE_FORMAT}; window::{VulkanoWindows, WindowDescriptor},
use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; };
use winit::{ use winit::{
event::{Event, WindowEvent}, event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
@ -25,17 +37,8 @@ mod fractal_compute_pipeline;
mod pixels_draw_pipeline; mod pixels_draw_pipeline;
mod place_over_frame; 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() { fn main() {
// Create event loop // Create the event loop.
let mut event_loop = EventLoop::new(); let mut event_loop = EventLoop::new();
let context = VulkanoContext::new(VulkanoConfig::default()); let context = VulkanoContext::new(VulkanoConfig::default());
let mut windows = VulkanoWindows::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. // Add our render target image onto which we'll be rendering our fractals.
let render_target_id = 0; let render_target_id = 0;
let primary_window_renderer = windows.get_primary_renderer_mut().unwrap(); let primary_window_renderer = windows.get_primary_renderer_mut().unwrap();
// Make sure the image usage is correct (based on your pipeline). // Make sure the image usage is correct (based on your pipeline).
primary_window_renderer.add_additional_image_view( primary_window_renderer.add_additional_image_view(
render_target_id, render_target_id,
@ -60,16 +64,18 @@ fn main() {
ImageUsage::SAMPLED | ImageUsage::STORAGE | ImageUsage::TRANSFER_DST, 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(); 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( let mut app = FractalApp::new(
gfx_queue.clone(), gfx_queue.clone(),
primary_window_renderer.swapchain_format(), primary_window_renderer.swapchain_format(),
); );
app.print_guide(); app.print_guide();
// Basic loop for our runtime // Basic loop for our runtime:
// 1. Handle events // 1. Handle events
// 2. Update state based on events // 2. Update state based on events
// 3. Compute & Render // 3. Compute & Render
@ -82,7 +88,7 @@ fn main() {
match primary_window_renderer.window_size() { match primary_window_renderer.window_size() {
[w, h] => { [w, h] => {
// Skip this frame when minimized // Skip this frame when minimized.
if w == 0.0 || h == 0.0 { if w == 0.0 || h == 0.0 {
continue; 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( fn handle_events(
event_loop: &mut EventLoop<()>, event_loop: &mut EventLoop<()>,
renderer: &mut VulkanoWindowRenderer, renderer: &mut VulkanoWindowRenderer,
app: &mut FractalApp, app: &mut FractalApp,
) -> bool { ) -> bool {
let mut is_running = true; let mut is_running = true;
event_loop.run_return(|event, _, control_flow| { event_loop.run_return(|event, _, control_flow| {
*control_flow = ControlFlow::Wait; *control_flow = ControlFlow::Wait;
match &event { match &event {
Event::WindowEvent { event, .. } => match event { Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => is_running = false, WindowEvent::CloseRequested => is_running = false,
@ -123,19 +131,21 @@ fn handle_events(
Event::MainEventsCleared => *control_flow = ControlFlow::Exit, 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); app.handle_input(renderer.window_size(), &event);
}); });
is_running && app.is_running() is_running && app.is_running()
} }
/// Orchestrate rendering here /// Orchestrates rendering.
fn compute_then_render( fn compute_then_render(
renderer: &mut VulkanoWindowRenderer, renderer: &mut VulkanoWindowRenderer,
app: &mut FractalApp, app: &mut FractalApp,
target_image_id: usize, target_image_id: usize,
) { ) {
// Start frame // Start the frame.
let before_pipeline_future = match renderer.acquire() { let before_pipeline_future = match renderer.acquire() {
Err(e) => { Err(e) => {
println!("{e}"); println!("{e}");
@ -143,15 +153,19 @@ fn compute_then_render(
} }
Ok(future) => future, Ok(future) => future,
}; };
// Retrieve target image
// Retrieve the target image.
let image = renderer.get_additional_image_view(target_image_id); let image = renderer.get_additional_image_view(target_image_id);
// Compute our fractal (writes to target image). Join future with `before_pipeline_future`. // Compute our fractal (writes to target image). Join future with `before_pipeline_future`.
let after_compute = app.compute(image.clone()).join(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 = let after_renderpass_future =
app.place_over_frame app.place_over_frame
.render(after_compute, image, renderer.swapchain_image_view()); .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); renderer.present(after_renderpass_future, true);
} }

View File

@ -7,10 +7,9 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use bytemuck::{Pod, Zeroable};
use std::sync::Arc; use std::sync::Arc;
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage, Subbuffer}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage, Subbuffer},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder,
CommandBufferInheritanceInfo, CommandBufferUsage, SecondaryAutoCommandBuffer, CommandBufferInheritanceInfo, CommandBufferUsage, SecondaryAutoCommandBuffer,
@ -33,9 +32,9 @@ use vulkano::{
sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerMipmapMode}, sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerMipmapMode},
}; };
/// Vertex for textured quads /// Vertex for textured quads.
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
pub struct TexturedVertex { pub struct TexturedVertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
pub position: [f32; 2], pub position: [f32; 2],
@ -67,7 +66,7 @@ pub fn textured_quad(width: f32, height: f32) -> (Vec<TexturedVertex>, Vec<u32>)
) )
} }
/// A subpass pipeline that fills a quad over frame /// A subpass pipeline that fills a quad over frame.
pub struct PixelsDrawPipeline { pub struct PixelsDrawPipeline {
gfx_queue: Arc<Queue>, gfx_queue: Arc<Queue>,
subpass: Subpass, subpass: Subpass,
@ -160,7 +159,7 @@ impl PixelsDrawPipeline {
.unwrap() .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( pub fn draw(
&self, &self,
viewport_dimensions: [u32; 2], viewport_dimensions: [u32; 2],
@ -204,35 +203,35 @@ impl PixelsDrawPipeline {
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
layout(location=0) in vec2 position; layout(location=0) in vec2 position;
layout(location=1) in vec2 tex_coords; 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() { void main() {
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
f_tex_coords = tex_coords; f_tex_coords = tex_coords;
} }
" ",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 v_tex_coords; 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() { void main() {
f_color = texture(tex, v_tex_coords); f_color = texture(tex, v_tex_coords);
} }
" ",
} }
} }

View File

@ -24,7 +24,7 @@ use vulkano::{
}; };
use vulkano_util::renderer::{DeviceImageView, SwapchainImageView}; 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 { pub struct RenderPassPlaceOverFrame {
gfx_queue: Arc<Queue>, gfx_queue: Arc<Queue>,
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,
@ -72,8 +72,8 @@ impl RenderPassPlaceOverFrame {
} }
} }
/// Place view exactly over swapchain image target. /// Places the view exactly over the target swapchain image. The texture draw pipeline uses a
/// Texture draw pipeline uses a quad onto which it places the view. /// quad onto which it places the view.
pub fn render<F>( pub fn render<F>(
&self, &self,
before_future: F, before_future: F,
@ -83,9 +83,10 @@ impl RenderPassPlaceOverFrame {
where where
F: GpuFuture + 'static, F: GpuFuture + 'static,
{ {
// Get dimensions // Get dimensions.
let img_dims = target.image().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( let framebuffer = Framebuffer::new(
self.render_pass.clone(), self.render_pass.clone(),
FramebufferCreateInfo { FramebufferCreateInfo {
@ -94,14 +95,16 @@ impl RenderPassPlaceOverFrame {
}, },
) )
.unwrap(); .unwrap();
// Create primary command buffer builder
// Create primary command buffer builder.
let mut command_buffer_builder = AutoCommandBufferBuilder::primary( let mut command_buffer_builder = AutoCommandBufferBuilder::primary(
&self.command_buffer_allocator, &self.command_buffer_allocator,
self.gfx_queue.queue_family_index(), self.gfx_queue.queue_family_index(),
CommandBufferUsage::OneTimeSubmit, CommandBufferUsage::OneTimeSubmit,
) )
.unwrap(); .unwrap();
// Begin render pass
// Begin render pass.
command_buffer_builder command_buffer_builder
.begin_render_pass( .begin_render_pass(
RenderPassBeginInfo { RenderPassBeginInfo {
@ -111,17 +114,22 @@ impl RenderPassPlaceOverFrame {
SubpassContents::SecondaryCommandBuffers, SubpassContents::SecondaryCommandBuffers,
) )
.unwrap(); .unwrap();
// Create secondary command buffer from texture pipeline & send draw commands
// Create secondary command buffer from texture pipeline & send draw commands.
let cb = self let cb = self
.pixels_draw_pipeline .pixels_draw_pipeline
.draw(img_dims.width_height(), view); .draw(img_dims.width_height(), view);
// Execute above commands (subpass)
// Execute above commands (subpass).
command_buffer_builder.execute_commands(cb).unwrap(); command_buffer_builder.execute_commands(cb).unwrap();
// End render pass
// End render pass.
command_buffer_builder.end_render_pass().unwrap(); command_buffer_builder.end_render_pass().unwrap();
// Build command buffer
// Build command buffer.
let command_buffer = command_buffer_builder.build().unwrap(); let command_buffer = command_buffer_builder.build().unwrap();
// Execute primary command buffer
// Execute primary command buffer.
let after_future = before_future let after_future = before_future
.then_execute(self.gfx_queue.clone(), command_buffer) .then_execute(self.gfx_queue.clone(), command_buffer)
.unwrap(); .unwrap();

View File

@ -7,67 +7,64 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
//! Multisampling anti-aliasing example, using a render pass resolve. // Multisampling anti-aliasing example, using a render pass resolve.
//! //
//! # Introduction to multisampling // # Introduction to multisampling
//! //
//! When you draw an object on an image, this object occupies a certain set of pixels. Each pixel // When you draw an object on an image, this object occupies a certain set of pixels. Each pixel of
//! of the image is either fully covered by the object, or not covered at all. There is no such // the image is either fully covered by the object, or not covered at all. There is no such thing
//! thing as a pixel that is half-covered by the object that you're drawing. What this means is // as a pixel that is half-covered by the object that you're drawing. What this means is that you
//! that you will sometimes see a "staircase effect" at the border of your object, also called // will sometimes see a "staircase effect" at the border of your object, also called aliasing.
//! aliasing. //
//! // The root cause of aliasing is that the resolution of the image is not high enough. If you
//! 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
//! increase the size of the image you're drawing to, this effect will still exist but will be // less visible.
//! much less visible. //
//! // In order to decrease aliasing, some games and programs use what we call "SuperSample Anti-
//! 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
//! 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
//! 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
//! nearby pixels. Since the intermediate image is 4 times larger than the destination, this would // be 4x SSAA.
//! be x4 SSAA. //
//! // However this technique is very expensive in terms of GPU power. The fragment shader and all its
//! However this technique is very expensive in terms of GPU power. The fragment shader and all // calculations has to run four times more often.
//! its calculations has to run four times more often. //
//! // So instead of SSAA, a common alternative is MSAA (MultiSample Anti-Aliasing). The base principle
//! So instead of SSAA, a common alternative is MSAA (MultiSampling Anti Aliasing). The base // is more or less the same: you draw to an image of a larger dimension, and then at the end you
//! principle is more or less the same: you draw to an image of a larger dimension, and then at // scale it down to the final size. The difference is that the fragment shader is only run once per
//! the end you scale it down to the final size. The difference is that the fragment shader is // pixel of the final size, and its value is duplicated to fill to all the pixels of the
//! only run once per pixel of the final size, and its value is duplicated to fill to all the // intermediate image that are covered by the object.
//! 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
//! For example, let's say that you use x4 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
//! 4096x4096, and your object covers the whole image. With MSAA, the fragment shader will only // run 1,048,576 times (1024 * 1024), compared to 4,194,304 times (2048 * 2048) with 4x SSAA. Then
//! be 1,048,576 times (1024 * 1024), compared to 16,777,216 times (4096 * 4096) with 4x SSAA. // the output of each fragment shader invocation is copied in each of the four pixels of the
//! 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.
//! 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
//! 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
//! pixels of the intermediate image that are covered by the object will receive the output of the // fragment shader.
//! fragment shader. //
//! // Because of the way it works, this technique requires direct support from the hardware, contrary
//! Because of the way it works, this technique requires direct support from the hardware, // to SSAA which can be done on any machine.
//! contrary to SSAA which can be done on any machine. //
//! // # Multisampled images
//! # Multisampled images //
//! // Using MSAA with Vulkan is done by creating a regular image, but with a number of samples per
//! 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
//! 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
//! 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
//! 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.
//! 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
//! 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.
//! 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
//! 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
//! non-multisampled image. This operation is not a regular blit (blitting a multisampled image is // an error), instead it is called *resolving* the image.
//! an error), instead it is called *resolving* the image.
//!
use bytemuck::{Pod, Zeroable};
use std::{fs::File, io::BufWriter, path::Path}; use std::{fs::File, io::BufWriter, path::Path};
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
CopyImageToBufferInfo, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents, CopyImageToBufferInfo, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents,
@ -101,7 +98,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -135,7 +131,7 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
@ -195,14 +191,16 @@ fn main() {
load: Clear, load: Clear,
store: DontCare, store: DontCare,
format: Format::R8G8B8A8_UNORM, 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. // The second framebuffer attachment is the final image.
color: { color: {
load: DontCare, load: DontCare,
store: Store, store: Store,
format: Format::R8G8B8A8_UNORM, format: Format::R8G8B8A8_UNORM,
samples: 1, // Same here, this has to match. // Same here, this has to match.
samples: 1,
} }
}, },
pass: { pass: {
@ -230,30 +228,30 @@ fn main() {
.unwrap(); .unwrap();
// Here is the "end" of the multisampling example, as starting from here everything is the same // Here is the "end" of the multisampling example, as starting from here everything is the same
// as in any other example. // as in any other example. The pipeline, vertex buffer, and command buffer are created in
// The pipeline, vertex buffer, and command buffer are created in exactly the same way as // exactly the same way as without multisampling. At the end of the example, we copy the
// without multisampling. // content of `image` (ie. the final image) to a buffer, then read the content of that buffer
// At the end of the example, we copy the content of `image` (ie. the final image) to a buffer, // and save it to a PNG file.
// then read the content of that buffer and save it to a PNG file.
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
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_Position = vec4(position, 0.0, 1.0);
}" }
} ",
}
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
@ -261,15 +259,15 @@ fn main() {
void main() { void main() {
f_color = vec4(1.0, 0.0, 0.0, 1.0); f_color = vec4(1.0, 0.0, 0.0, 1.0);
} }
" ",
} }
} }
let vs = vs::load(device.clone()).unwrap(); let vs = vs::load(device.clone()).unwrap();
let fs = fs::load(device.clone()).unwrap(); let fs = fs::load(device.clone()).unwrap();
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],

View File

@ -12,14 +12,13 @@
// This is the only example that is entirely detailed. All the other examples avoid code // This is the only example that is entirely detailed. All the other examples avoid code
// duplication by using helper functions. // duplication by using helper functions.
// //
// This example assumes that you are already more or less familiar with graphics programming // This example assumes that you are already more or less familiar with graphics programming and
// and that you want to learn Vulkan. This means that for example it won't go into details about // that you want to learn Vulkan. This means that for example it won't go into details about what a
// what a vertex or a shader is. // vertex or a shader is.
use bytemuck::{Pod, Zeroable};
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
RenderPassBeginInfo, SubpassContents, RenderPassBeginInfo, SubpassContents,
@ -54,7 +53,7 @@ use winit::{
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
// A struct to contain resources related to a window /// A struct to contain resources related to a window.
struct WindowSurface { struct WindowSurface {
surface: Arc<Surface>, surface: Arc<Surface>,
swapchain: Arc<Swapchain>, swapchain: Arc<Swapchain>,
@ -70,7 +69,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -78,18 +76,20 @@ fn main() {
.unwrap(); .unwrap();
let event_loop = EventLoop::new(); 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 mut window_surfaces = HashMap::new();
let surface = WindowBuilder::new() let surface = WindowBuilder::new()
.build_vk_surface(&event_loop, instance.clone()) .build_vk_surface(&event_loop, instance.clone())
.unwrap(); .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::<Window>().unwrap(); let window = surface.object().unwrap().downcast_ref::<Window>().unwrap();
let window_id = window.id(); let window_id = window.id();
// Find the device and a queue. // 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, queue, surface_caps) = {
let device_extensions = DeviceExtensions { let device_extensions = DeviceExtensions {
@ -123,7 +123,7 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
@ -147,8 +147,7 @@ fn main() {
(device, queues.next().unwrap(), surface_capabilities) (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 (swapchain, images) = {
let image_format = Some( let image_format = Some(
device device
@ -180,8 +179,8 @@ fn main() {
let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -211,7 +210,7 @@ fn main() {
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 position; layout(location = 0) in vec2 position;
@ -219,14 +218,14 @@ fn main() {
void main() { void main() {
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
} }
" ",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
@ -234,7 +233,7 @@ fn main() {
void main() { void main() {
f_color = vec4(1.0, 0.0, 0.0, 1.0); f_color = vec4(1.0, 0.0, 0.0, 1.0);
} }
" ",
} }
} }
@ -379,11 +378,11 @@ fn main() {
} }
Event::RedrawRequested(window_id) => { Event::RedrawRequested(window_id) => {
let WindowSurface { let WindowSurface {
ref surface, surface,
ref mut swapchain, swapchain,
ref mut recreate_swapchain, recreate_swapchain,
ref mut framebuffers, framebuffers,
ref mut previous_frame_end, previous_frame_end,
} = window_surfaces.get_mut(&window_id).unwrap(); } = window_surfaces.get_mut(&window_id).unwrap();
let window = surface.object().unwrap().downcast_ref::<Window>().unwrap(); let window = surface.object().unwrap().downcast_ref::<Window>().unwrap();
@ -401,7 +400,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
*swapchain = new_swapchain; *swapchain = new_swapchain;
@ -417,7 +416,7 @@ fn main() {
*recreate_swapchain = true; *recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -472,7 +471,7 @@ fn main() {
*previous_frame_end = Some(sync::now(device.clone()).boxed()); *previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
*previous_frame_end = Some(sync::now(device.clone()).boxed()); *previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
} }

View File

@ -12,12 +12,15 @@ use crate::{
WINDOW2_HEIGHT, WINDOW2_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW2_HEIGHT, WINDOW2_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH,
}; };
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use vulkano::command_buffer::allocator::StandardCommandBufferAllocator; use vulkano::{
use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator; command_buffer::allocator::StandardCommandBufferAllocator,
use vulkano::memory::allocator::StandardMemoryAllocator; descriptor_set::allocator::StandardDescriptorSetAllocator, device::Queue, format::Format,
use vulkano::{device::Queue, format::Format}; memory::allocator::StandardMemoryAllocator,
use vulkano_util::context::{VulkanoConfig, VulkanoContext}; };
use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; use vulkano_util::{
context::{VulkanoConfig, VulkanoContext},
window::{VulkanoWindows, WindowDescriptor},
};
use winit::{event_loop::EventLoop, window::WindowId}; use winit::{event_loop::EventLoop, window::WindowId};
pub struct RenderPipeline { pub struct RenderPipeline {
@ -68,7 +71,7 @@ pub struct App {
impl App { impl App {
pub fn open(&mut self, event_loop: &EventLoop<()>) { pub fn open(&mut self, event_loop: &EventLoop<()>) {
// Create windows & pipelines // Create windows & pipelines.
let id1 = self.windows.create_window( let id1 = self.windows.create_window(
event_loop, event_loop,
&self.context, &self.context,
@ -94,7 +97,7 @@ impl App {
self.pipelines.insert( self.pipelines.insert(
id1, id1,
RenderPipeline::new( RenderPipeline::new(
// Use same queue.. for synchronization // Use same queue.. for synchronization.
self.context.graphics_queue().clone(), self.context.graphics_queue().clone(),
self.context.graphics_queue().clone(), self.context.graphics_queue().clone(),
[ [

View File

@ -10,28 +10,28 @@
use cgmath::Vector2; use cgmath::Vector2;
use rand::Rng; use rand::Rng;
use std::sync::Arc; 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::{ use vulkano::{
buffer::{Buffer, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferUsage, Subbuffer},
command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer}, command_buffer::{
descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet}, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
PrimaryAutoCommandBuffer,
},
descriptor_set::{
allocator::StandardDescriptorSetAllocator, PersistentDescriptorSet, WriteDescriptorSet,
},
device::Queue, device::Queue,
format::Format, format::Format,
image::ImageAccess, image::{ImageAccess, ImageUsage, StorageImage},
memory::allocator::MemoryAllocator,
pipeline::{ComputePipeline, Pipeline, PipelineBindPoint}, pipeline::{ComputePipeline, Pipeline, PipelineBindPoint},
sync::GpuFuture, sync::GpuFuture,
}; };
use vulkano_util::renderer::DeviceImageView; use vulkano_util::renderer::DeviceImageView;
/// Pipeline holding double buffered grid & color image. /// Pipeline holding double buffered grid & color image. Grids are used to calculate the state, and
/// Grids are used to calculate the state, and color image is used to show the output. /// color image is used to show the output. Because on each step we determine state in parallel, we
/// Because each step we determine state in parallel, we need to write the output to /// need to write the output to another grid. Otherwise the state would not be correctly determined
/// another grid. Otherwise the state would not be correctly determined as one thread might read /// as one shader invocation might read data that was just written by another shader invocation.
/// data that was just written by another thread
pub struct GameOfLifeComputePipeline { pub struct GameOfLifeComputePipeline {
compute_queue: Arc<Queue>, compute_queue: Arc<Queue>,
compute_life_pipeline: Arc<ComputePipeline>, compute_life_pipeline: Arc<ComputePipeline>,
@ -126,13 +126,15 @@ impl GameOfLifeComputePipeline {
) )
.unwrap(); .unwrap();
// Dispatch will mutate the builder adding commands which won't be sent before we build the command buffer // Dispatch will mutate the builder adding commands which won't be sent before we build the
// after dispatches. This will minimize the commands we send to the GPU. For example, we could be doing // command buffer after dispatches. This will minimize the commands we send to the GPU. For
// tens of dispatches here depending on our needs. Maybe we wanted to simulate 10 steps at a time... // 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); 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); self.dispatch(&mut builder, life_color, dead_color, 1);
let command_buffer = builder.build().unwrap(); let command_buffer = builder.build().unwrap();
@ -141,13 +143,13 @@ impl GameOfLifeComputePipeline {
.unwrap(); .unwrap();
let after_pipeline = finished.then_signal_fence_and_flush().unwrap().boxed(); 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); std::mem::swap(&mut self.life_in, &mut self.life_out);
after_pipeline after_pipeline
} }
/// Build the command for a dispatch. /// Builds the command for a dispatch.
fn dispatch( fn dispatch(
&self, &self,
builder: &mut AutoCommandBufferBuilder< builder: &mut AutoCommandBufferBuilder<
@ -156,10 +158,10 @@ impl GameOfLifeComputePipeline {
>, >,
life_color: [f32; 4], life_color: [f32; 4],
dead_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, step: i32,
) { ) {
// Resize image if needed // Resize image if needed.
let img_dims = self.image.image().dimensions().width_height(); let img_dims = self.image.image().dimensions().width_height();
let pipeline_layout = self.compute_life_pipeline.layout(); let pipeline_layout = self.compute_life_pipeline.layout();
let desc_layout = pipeline_layout.set_layouts().get(0).unwrap(); let desc_layout = pipeline_layout.set_layouts().get(0).unwrap();
@ -174,7 +176,7 @@ impl GameOfLifeComputePipeline {
) )
.unwrap(); .unwrap();
let push_constants = compute_life_cs::ty::PushConstants { let push_constants = compute_life_cs::PushConstants {
life_color, life_color,
dead_color, dead_color,
step, step,
@ -191,79 +193,81 @@ impl GameOfLifeComputePipeline {
mod compute_life_cs { mod compute_life_cs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #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 = 0, rgba8) uniform writeonly image2D img;
layout(set = 0, binding = 1) buffer LifeInBuffer { uint life_in[]; }; layout(set = 0, binding = 1) buffer LifeInBuffer { uint life_in[]; };
layout(set = 0, binding = 2) buffer LifeOutBuffer { uint life_out[]; }; layout(set = 0, binding = 2) buffer LifeOutBuffer { uint life_out[]; };
layout(push_constant) uniform PushConstants { layout(push_constant) uniform PushConstants {
vec4 life_color; vec4 life_color;
vec4 dead_color; vec4 dead_color;
int step; int step;
} push_constants; } push_constants;
int get_index(ivec2 pos) { int get_index(ivec2 pos) {
ivec2 dims = ivec2(imageSize(img)); ivec2 dims = ivec2(imageSize(img));
return pos.y * dims.x + pos.x; return pos.y * dims.x + pos.x;
} }
// https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life // https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
void compute_life() { void compute_life() {
ivec2 pos = ivec2(gl_GlobalInvocationID.xy); ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
int index = get_index(pos); int index = get_index(pos);
ivec2 up_left = pos + ivec2(-1, 1); ivec2 up_left = pos + ivec2(-1, 1);
ivec2 up = pos + ivec2(0, 1); ivec2 up = pos + ivec2(0, 1);
ivec2 up_right = pos + ivec2(1, 1); ivec2 up_right = pos + ivec2(1, 1);
ivec2 right = pos + ivec2(1, 0); ivec2 right = pos + ivec2(1, 0);
ivec2 down_right = pos + ivec2(1, -1); ivec2 down_right = pos + ivec2(1, -1);
ivec2 down = pos + ivec2(0, -1); ivec2 down = pos + ivec2(0, -1);
ivec2 down_left = pos + ivec2(-1, -1); ivec2 down_left = pos + ivec2(-1, -1);
ivec2 left = pos + ivec2(-1, 0); ivec2 left = pos + ivec2(-1, 0);
int alive_count = 0; int alive_count = 0;
if (life_out[get_index(up_left)] == 1) { alive_count += 1; } 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)] == 1) { alive_count += 1; }
if (life_out[get_index(up_right)] == 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(right)] == 1) { alive_count += 1; }
if (life_out[get_index(down_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)] == 1) { alive_count += 1; }
if (life_out[get_index(down_left)] == 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; } if (life_out[get_index(left)] == 1) { alive_count += 1; }
// Dead becomes alive // Dead becomes alive.
if (life_out[index] == 0 && alive_count == 3) { if (life_out[index] == 0 && alive_count == 3) {
life_out[index] = 1; life_out[index] = 1;
} // Becomes dead }
else if (life_out[index] == 1 && alive_count < 2 || alive_count > 3) { // Becomes dead.
life_out[index] = 0; else if (life_out[index] == 1 && alive_count < 2 || alive_count > 3) {
} // Else Do nothing life_out[index] = 0;
else { }
// 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) {
void compute_color() { imageStore(img, pos, push_constants.life_color);
ivec2 pos = ivec2(gl_GlobalInvocationID.xy); } else {
int index = get_index(pos); imageStore(img, pos, push_constants.dead_color);
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 {
void main() { compute_color();
if (push_constants.step == 0) { }
compute_life(); }
} else { ",
compute_color();
}
}",
} }
} }

View File

@ -7,6 +7,15 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // 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 app;
mod game_of_life; mod game_of_life;
mod pixels_draw; mod pixels_draw;
@ -23,13 +32,6 @@ use winit::{
platform::run_return::EventLoopExtRunReturn, 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_WIDTH: f32 = 1024.0;
pub const WINDOW_HEIGHT: f32 = 1024.0; pub const WINDOW_HEIGHT: f32 = 1024.0;
pub const WINDOW2_WIDTH: f32 = 512.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; pub const SCALING: f32 = 2.0;
fn main() { fn main() {
println!("Welcome to Vulkano Game of Life\n Use Mouse to draw life on the grid(s)\n"); println!("Welcome to Vulkano Game of Life\nUse the mouse to draw life on the grid(s)\n");
// Create event loop
// Create event loop.
let mut event_loop = EventLoop::new(); let mut event_loop = EventLoop::new();
// Create app with vulkano context
// Create app with vulkano context.
let mut app = App::default(); let mut app = App::default();
app.open(&event_loop); app.open(&event_loop);
// Time & inputs... // Time & inputs...
let mut time = Instant::now(); let mut time = Instant::now();
let mut cursor_pos = Vector2::new(0.0, 0.0); 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_w1 = false;
let mut mouse_is_pressed_w2 = false; let mut mouse_is_pressed_w2 = false;
loop { loop {
// Event handling // Event handling.
if !handle_events( if !handle_events(
&mut event_loop, &mut event_loop,
&mut app, &mut app,
@ -61,14 +67,16 @@ fn main() {
) { ) {
break; break;
} }
// Draw life on windows if mouse is down
// Draw life on windows if mouse is down.
draw_life( draw_life(
&mut app, &mut app,
cursor_pos, cursor_pos,
mouse_is_pressed_w1, mouse_is_pressed_w1,
mouse_is_pressed_w2, mouse_is_pressed_w2,
); );
// Compute life & render 60fps
// Compute life & render 60fps.
if (Instant::now() - time).as_secs_f64() > 1.0 / 60.0 { if (Instant::now() - time).as_secs_f64() > 1.0 / 60.0 {
compute_then_render_per_window(&mut app); compute_then_render_per_window(&mut app);
time = Instant::now(); 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( fn handle_events(
event_loop: &mut EventLoop<()>, event_loop: &mut EventLoop<()>,
app: &mut App, app: &mut App,
@ -85,6 +93,7 @@ fn handle_events(
mouse_pressed_w2: &mut bool, mouse_pressed_w2: &mut bool,
) -> bool { ) -> bool {
let mut is_running = true; let mut is_running = true;
event_loop.run_return(|event, _, control_flow| { event_loop.run_return(|event, _, control_flow| {
*control_flow = ControlFlow::Poll; *control_flow = ControlFlow::Poll;
match &event { match &event {
@ -95,20 +104,21 @@ fn handle_events(
if *window_id == app.windows.primary_window_id().unwrap() { if *window_id == app.windows.primary_window_id().unwrap() {
is_running = false; is_running = false;
} else { } else {
// Destroy window by removing its renderer... // Destroy window by removing its renderer.
app.windows.remove_renderer(*window_id); app.windows.remove_renderer(*window_id);
app.pipelines.remove(window_id); app.pipelines.remove(window_id);
} }
} }
// Resize window and its images... // Resize window and its images.
WindowEvent::Resized(..) | WindowEvent::ScaleFactorChanged { .. } => { WindowEvent::Resized(..) | WindowEvent::ScaleFactorChanged { .. } => {
let vulkano_window = app.windows.get_renderer_mut(*window_id).unwrap(); let vulkano_window = app.windows.get_renderer_mut(*window_id).unwrap();
vulkano_window.resize(); vulkano_window.resize();
} }
// Handle mouse position events.
WindowEvent::CursorMoved { position, .. } => { WindowEvent::CursorMoved { position, .. } => {
*cursor_pos = Vector2::new(position.x as f32, position.y as f32) *cursor_pos = Vector2::new(position.x as f32, position.y as f32)
} }
// Mouse button event // Handle mouse button events.
WindowEvent::MouseInput { state, button, .. } => { WindowEvent::MouseInput { state, button, .. } => {
let mut mouse_pressed = false; let mut mouse_pressed = false;
if button == &MouseButton::Left && state == &ElementState::Pressed { if button == &MouseButton::Left && state == &ElementState::Pressed {
@ -129,6 +139,7 @@ fn handle_events(
_ => (), _ => (),
} }
}); });
is_running is_running
} }
@ -146,13 +157,15 @@ fn draw_life(
if id != &primary_window_id && !mouse_is_pressed_w2 { if id != &primary_window_id && !mouse_is_pressed_w2 {
continue; continue;
} }
let window_size = window.window_size(); let window_size = window.window_size();
let compute_pipeline = &mut app.pipelines.get_mut(id).unwrap().compute; let compute_pipeline = &mut app.pipelines.get_mut(id).unwrap().compute;
let mut normalized_pos = Vector2::new( let mut normalized_pos = Vector2::new(
(cursor_pos.x / window_size[0]).clamp(0.0, 1.0), (cursor_pos.x / window_size[0]).clamp(0.0, 1.0),
(cursor_pos.y / window_size[1]).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; normalized_pos.y = 1.0 - normalized_pos.y;
let image_size = compute_pipeline let image_size = compute_pipeline
.color_image() .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) { fn compute_then_render_per_window(app: &mut App) {
let primary_window_id = app.windows.primary_window_id().unwrap(); let primary_window_id = app.windows.primary_window_id().unwrap();
for (window_id, window_renderer) in app.windows.iter_mut() { 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( fn compute_then_render(
window_renderer: &mut VulkanoWindowRenderer, window_renderer: &mut VulkanoWindowRenderer,
pipeline: &mut RenderPipeline, pipeline: &mut RenderPipeline,
life_color: [f32; 4], life_color: [f32; 4],
dead_color: [f32; 4], dead_color: [f32; 4],
) { ) {
// Skip this window when minimized // Skip this window when minimized.
match window_renderer.window_size() { match window_renderer.window_size() {
[w, h] => { [w, h] => {
if w == 0.0 || h == 0.0 { 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() { let before_pipeline_future = match window_renderer.acquire() {
Err(e) => { Err(e) => {
println!("{e}"); println!("{e}");
@ -204,12 +217,12 @@ fn compute_then_render(
Ok(future) => future, Ok(future) => future,
}; };
// Compute // Compute.
let after_compute = pipeline let after_compute = pipeline
.compute .compute
.compute(before_pipeline_future, life_color, dead_color); .compute(before_pipeline_future, life_color, dead_color);
// Render // Render.
let color_image = pipeline.compute.color_image(); let color_image = pipeline.compute.color_image();
let target_image = window_renderer.swapchain_image_view(); let target_image = window_renderer.swapchain_image_view();
@ -217,6 +230,6 @@ fn compute_then_render(
.place_over_frame .place_over_frame
.render(after_compute, color_image, target_image); .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); window_renderer.present(after_render, true);
} }

View File

@ -7,10 +7,9 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use bytemuck::{Pod, Zeroable};
use std::sync::Arc; use std::sync::Arc;
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage, Subbuffer}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage, Subbuffer},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder,
CommandBufferInheritanceInfo, CommandBufferUsage, SecondaryAutoCommandBuffer, CommandBufferInheritanceInfo, CommandBufferUsage, SecondaryAutoCommandBuffer,
@ -33,9 +32,9 @@ use vulkano::{
sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerMipmapMode}, sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerMipmapMode},
}; };
/// Vertex for textured quads /// Vertex for textured quads.
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
pub struct TexturedVertex { pub struct TexturedVertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
pub position: [f32; 2], pub position: [f32; 2],
@ -67,7 +66,7 @@ pub fn textured_quad(width: f32, height: f32) -> (Vec<TexturedVertex>, Vec<u32>)
) )
} }
/// A subpass pipeline that fills a quad over frame /// A subpass pipeline that fills a quad over the frame.
pub struct PixelsDrawPipeline { pub struct PixelsDrawPipeline {
gfx_queue: Arc<Queue>, gfx_queue: Arc<Queue>,
subpass: Subpass, subpass: Subpass,
@ -160,7 +159,7 @@ impl PixelsDrawPipeline {
.unwrap() .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( pub fn draw(
&self, &self,
viewport_dimensions: [u32; 2], viewport_dimensions: [u32; 2],
@ -204,35 +203,35 @@ impl PixelsDrawPipeline {
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
layout(location=0) in vec2 position; layout(location=0) in vec2 position;
layout(location=1) in vec2 tex_coords; 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() { void main() {
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
f_tex_coords = tex_coords; f_tex_coords = tex_coords;
} }
" ",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 v_tex_coords; 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() { void main() {
f_color = texture(tex, v_tex_coords); f_color = texture(tex, v_tex_coords);
} }
" ",
} }
} }

View File

@ -24,7 +24,7 @@ use vulkano::{
}; };
use vulkano_util::renderer::{DeviceImageView, SwapchainImageView}; 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 { pub struct RenderPassPlaceOverFrame {
gfx_queue: Arc<Queue>, gfx_queue: Arc<Queue>,
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,
@ -72,8 +72,8 @@ impl RenderPassPlaceOverFrame {
} }
} }
/// Place view exactly over swapchain image target. /// Places the view exactly over the target swapchain image. The texture draw pipeline uses a
/// Texture draw pipeline uses a quad onto which it places the view. /// quad onto which it places the view.
pub fn render<F>( pub fn render<F>(
&self, &self,
before_future: F, before_future: F,
@ -83,9 +83,10 @@ impl RenderPassPlaceOverFrame {
where where
F: GpuFuture + 'static, F: GpuFuture + 'static,
{ {
// Get dimensions // Get the dimensions.
let img_dims = target.image().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( let framebuffer = Framebuffer::new(
self.render_pass.clone(), self.render_pass.clone(),
FramebufferCreateInfo { FramebufferCreateInfo {
@ -94,14 +95,16 @@ impl RenderPassPlaceOverFrame {
}, },
) )
.unwrap(); .unwrap();
// Create primary command buffer builder
// Create a primary command buffer builder.
let mut command_buffer_builder = AutoCommandBufferBuilder::primary( let mut command_buffer_builder = AutoCommandBufferBuilder::primary(
&self.command_buffer_allocator, &self.command_buffer_allocator,
self.gfx_queue.queue_family_index(), self.gfx_queue.queue_family_index(),
CommandBufferUsage::OneTimeSubmit, CommandBufferUsage::OneTimeSubmit,
) )
.unwrap(); .unwrap();
// Begin render pass
// Begin the render pass.
command_buffer_builder command_buffer_builder
.begin_render_pass( .begin_render_pass(
RenderPassBeginInfo { RenderPassBeginInfo {
@ -111,17 +114,22 @@ impl RenderPassPlaceOverFrame {
SubpassContents::SecondaryCommandBuffers, SubpassContents::SecondaryCommandBuffers,
) )
.unwrap(); .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 let cb = self
.pixels_draw_pipeline .pixels_draw_pipeline
.draw(img_dims.width_height(), view); .draw(img_dims.width_height(), view);
// Execute above commands (subpass)
// Execute above commands (subpass).
command_buffer_builder.execute_commands(cb).unwrap(); command_buffer_builder.execute_commands(cb).unwrap();
// End render pass
// End the render pass.
command_buffer_builder.end_render_pass().unwrap(); command_buffer_builder.end_render_pass().unwrap();
// Build command buffer
// Build the command buffer.
let command_buffer = command_buffer_builder.build().unwrap(); let command_buffer = command_buffer_builder.build().unwrap();
// Execute primary command buffer
// Execute primary command buffer.
let after_future = before_future let after_future = before_future
.then_execute(self.gfx_queue.clone(), command_buffer) .then_execute(self.gfx_queue.clone(), command_buffer)
.unwrap(); .unwrap();

View File

@ -7,16 +7,14 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
//! This example demonstrates using the `VK_KHR_multiview` extension to render to multiple // This example demonstrates using the `VK_KHR_multiview` extension to render to multiple layers of
//! layers of the framebuffer in one render pass. This can significantly improve performance // the framebuffer in one render pass. This can significantly improve performance in cases where
//! in cases where multiple perspectives or cameras are very similar like in virtual reality // multiple perspectives or cameras are very similar like in virtual reality or other types of
//! or other types of stereoscopic rendering where the left and right eye only differ // stereoscopic rendering where the left and right eye only differ in a small position offset.
//! in a small position offset.
use bytemuck::{Pod, Zeroable};
use std::{fs::File, io::BufWriter, path::Path}; use std::{fs::File, io::BufWriter, path::Path};
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage, Subbuffer}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage, Subbuffer},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, BufferImageCopy, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, BufferImageCopy,
CommandBufferUsage, CopyImageToBufferInfo, RenderPassBeginInfo, SubpassContents, CommandBufferUsage, CopyImageToBufferInfo, RenderPassBeginInfo, SubpassContents,
@ -54,10 +52,10 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: InstanceExtensions { 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() ..InstanceExtensions::empty()
}, },
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -68,25 +66,23 @@ fn main() {
..DeviceExtensions::empty() ..DeviceExtensions::empty()
}; };
let features = Features { let features = Features {
// enabling the `multiview` feature will use the `VK_KHR_multiview` extension on // enabling the `multiview` feature will use the `VK_KHR_multiview` extension on Vulkan 1.0
// Vulkan 1.0 and the device feature on Vulkan 1.1+ // and the device feature on Vulkan 1.1+.
multiview: true, multiview: true,
..Features::empty() ..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| { .filter(|p| {
p.supported_extensions().contains(&device_extensions) // 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
.filter(|p| { // on a device that doesn't support two views will lead to a runtime error when
p.supported_features().contains(&features) // creating the `RenderPass`. The `max_multiview_view_count` function will return
}) // `None` when the `VK_KHR_get_physical_device_properties2` instance extension has not
.filter(|p| { // 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 p.properties().max_multiview_view_count.unwrap_or(0) >= 2
}) })
.filter_map(|p| { .filter_map(|p| {
@ -103,14 +99,17 @@ fn main() {
PhysicalDeviceType::Other => 4, PhysicalDeviceType::Other => 4,
_ => 5, _ => 5,
}) })
// A real application should probably fall back to rendering the framebuffer layers // A real application should probably fall back to rendering the framebuffer layers in
// in multiple passes when multiview isn't supported. // 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"); .expect(
"no device supports two multiview views or the \
`VK_KHR_get_physical_device_properties2` instance extension has not been loaded",
);
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
@ -147,8 +146,8 @@ fn main() {
let image_view = ImageView::new_default(image.clone()).unwrap(); let image_view = ImageView::new_default(image.clone()).unwrap();
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -175,40 +174,39 @@ fn main() {
) )
.unwrap(); .unwrap();
// Note the `#extension GL_EXT_multiview : enable` that enables the multiview extension // Note the `#extension GL_EXT_multiview : enable` that enables the multiview extension for the
// for the shader and the use of `gl_ViewIndex` which contains a value based on which // shader and the use of `gl_ViewIndex` which contains a value based on which view the shader
// view the shader is being invoked for. // is being invoked for. In this example `gl_ViewIndex` is used to toggle a hardcoded offset
// In this example `gl_ViewIndex` is used toggle a hardcoded offset for vertex positions // for vertex positions but in a VR application you could easily use it as an index to a
// but in a VR application you could easily use it as an index to a uniform array // uniform array that contains the transformation matrices for the left and right eye.
// that contains the transformation matrices for the left and right eye.
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
#extension GL_EXT_multiview : enable #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); gl_Position = vec4(position, 0.0, 1.0) + gl_ViewIndex * vec4(0.25, 0.25, 0.0, 0.0);
} }
" ",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
void main() { void main() {
f_color = vec4(1.0, 0.0, 0.0, 1.0); f_color = vec4(1.0, 0.0, 0.0, 1.0);
} }
" ",
} }
} }
@ -228,8 +226,8 @@ fn main() {
..Default::default() ..Default::default()
}], }],
subpasses: vec![SubpassDescription { subpasses: vec![SubpassDescription {
// the view mask indicates which layers of the framebuffer should be rendered for each // The view mask indicates which layers of the framebuffer should be rendered for each
// subpass // subpass.
view_mask: 0b11, view_mask: 0b11,
color_attachments: vec![Some(AttachmentReference { color_attachments: vec![Some(AttachmentReference {
attachment: 0, attachment: 0,
@ -238,8 +236,8 @@ fn main() {
})], })],
..Default::default() ..Default::default()
}], }],
// the correlated view masks indicate sets of views that may be more efficient to render // The correlated view masks indicate sets of views that may be more efficient to render
// concurrently // concurrently.
correlated_view_masks: vec![0b11], correlated_view_masks: vec![0b11],
..Default::default() ..Default::default()
}; };
@ -299,8 +297,8 @@ fn main() {
) )
.unwrap(); .unwrap();
// drawing commands are broadcast to each view in the view mask of the active renderpass // Drawing commands are broadcast to each view in the view mask of the active renderpass which
// which means only a single draw call is needed to draw to multiple layers of the framebuffer // means only a single draw call is needed to draw to multiple layers of the framebuffer.
builder builder
.begin_render_pass( .begin_render_pass(
RenderPassBeginInfo { RenderPassBeginInfo {
@ -317,7 +315,7 @@ fn main() {
.end_render_pass() .end_render_pass()
.unwrap(); .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 builder
.copy_image_to_buffer(CopyImageToBufferInfo { .copy_image_to_buffer(CopyImageToBufferInfo {
regions: [BufferImageCopy { regions: [BufferImageCopy {
@ -356,7 +354,7 @@ fn main() {
future.wait(None).unwrap(); future.wait(None).unwrap();
// write each layer to its own file // Write each layer to its own file.
write_image_buffer_to_file( write_image_buffer_to_file(
buffer1, buffer1,
"multiview1.png", "multiview1.png",

View File

@ -7,14 +7,13 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
// This is a modification of the triangle example, that demonstrates the basics of occlusion queries. // This is a modification of the triangle example, that demonstrates the basics of occlusion
// Occlusion queries allow you to query whether, and sometimes how many, pixels pass the depth test // queries. Occlusion queries allow you to query whether, and sometimes how many, pixels pass the
// in a range of draw calls. // depth test in a range of draw calls.
use bytemuck::{Pod, Zeroable};
use std::sync::Arc; use std::sync::Arc;
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
RenderPassBeginInfo, SubpassContents, RenderPassBeginInfo, SubpassContents,
@ -59,7 +58,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -154,8 +152,8 @@ fn main() {
let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32B32_SFLOAT)] #[format(R32G32B32_SFLOAT)]
position: [f32; 3], position: [f32; 3],
@ -177,10 +175,9 @@ fn main() {
position: [0.25, -0.1, 0.5], position: [0.25, -0.1, 0.5],
color: [1.0, 0.0, 0.0], color: [1.0, 0.0, 0.0],
}, },
// The second triangle (cyan) is the same shape and position as the first, // The second triangle (cyan) is the same shape and position as the first, but smaller, and
// but smaller, and moved behind a bit. // moved behind a bit. It should be completely occluded by the first triangle. (You can
// It should be completely occluded by the first triangle. // lower its z value to put it in front.)
// (You can lower its z value to put it in front)
Vertex { Vertex {
position: [-0.25, -0.125, 0.6], position: [-0.25, -0.125, 0.6],
color: [0.0, 1.0, 1.0], color: [0.0, 1.0, 1.0],
@ -193,9 +190,8 @@ fn main() {
position: [0.125, -0.05, 0.6], position: [0.125, -0.05, 0.6],
color: [0.0, 1.0, 1.0], color: [0.0, 1.0, 1.0],
}, },
// The third triangle (green) is the same shape and size as the first, // The third triangle (green) is the same shape and size as the first, but moved to the
// but moved to the left and behind the second. // left and behind the second. It is partially occluded by the first two.
// It is partially occluded by the first two.
Vertex { Vertex {
position: [-0.25, -0.25, 0.7], position: [-0.25, -0.25, 0.7],
color: [0.0, 1.0, 0.0], color: [0.0, 1.0, 0.0],
@ -234,46 +230,45 @@ fn main() {
) )
.unwrap(); .unwrap();
// Create a buffer on the CPU to hold the results of the three queries. // Create a buffer on the CPU to hold the results of the three queries. Query results are
// Query results are always represented as either `u32` or `u64`. // always represented as either `u32` or `u64`. For occlusion queries, you always need one
// For occlusion queries, you always need one element per query. You can ask for the number of // element per query. You can ask for the number of elements needed at runtime by calling
// elements needed at runtime by calling `QueryType::result_len`. // `QueryType::result_len`. If you retrieve query results with `with_availability` enabled,
// If you retrieve query results with `with_availability` enabled, then this array needs to // then this array needs to be 6 elements long instead of 3.
// be 6 elements long instead of 3.
let mut query_results = [0u32; 3]; let mut query_results = [0u32; 3];
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec3 position; layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color; layout(location = 1) in vec3 color;
layout(location = 0) out vec3 v_color; layout(location = 0) out vec3 v_color;
void main() { void main() {
v_color = color; v_color = color;
gl_Position = vec4(position, 1.0); gl_Position = vec4(position, 1.0);
} }
" ",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec3 v_color; layout(location = 0) in vec3 v_color;
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
void main() { void main() {
f_color = vec4(v_color, 1.0); f_color = vec4(v_color, 1.0);
} }
" ",
} }
} }
@ -310,9 +305,9 @@ fn main() {
.viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant()) .viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant())
.fragment_shader(fs.entry_point("main").unwrap(), ()) .fragment_shader(fs.entry_point("main").unwrap(), ())
.render_pass(Subpass::from(render_pass.clone(), 0).unwrap()) .render_pass(Subpass::from(render_pass.clone(), 0).unwrap())
// Enable depth testing, which is needed for occlusion queries to make sense at all. // Enable depth testing, which is needed for occlusion queries to make sense at all. If you
// If you disable depth testing, every pixel is considered to pass the depth test, so // disable depth testing, every pixel is considered to pass the depth test, so every query
// every query will return a nonzero result. // will return a nonzero result.
.depth_stencil_state(DepthStencilState::simple_depth_test()) .depth_stencil_state(DepthStencilState::simple_depth_test())
.build(device.clone()) .build(device.clone())
.unwrap(); .unwrap();
@ -365,7 +360,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -385,7 +380,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -402,8 +397,8 @@ fn main() {
// Beginning or resetting a query is unsafe for now. // Beginning or resetting a query is unsafe for now.
unsafe { unsafe {
builder builder
// A query must be reset before each use, including the first use. // A query must be reset before each use, including the first use. This must be
// This must be done outside a render pass. // done outside a render pass.
.reset_query_pool(query_pool.clone(), 0..3) .reset_query_pool(query_pool.clone(), 0..3)
.unwrap() .unwrap()
.set_viewport(0, [viewport.clone()]) .set_viewport(0, [viewport.clone()])
@ -418,14 +413,14 @@ fn main() {
SubpassContents::Inline, SubpassContents::Inline,
) )
.unwrap() .unwrap()
// Begin query 0, then draw the red triangle. // Begin query 0, then draw the red triangle. Enabling the
// Enabling the `QueryControlFlags::PRECISE` flag would give exact numeric // `QueryControlFlags::PRECISE` flag would give exact numeric results. This
// results. This needs the `occlusion_query_precise` feature to be enabled on // needs the `occlusion_query_precise` feature to be enabled on the device.
// the device.
.begin_query( .begin_query(
query_pool.clone(), query_pool.clone(),
0, 0,
QueryControlFlags::empty(), // QueryControlFlags::PRECISE QueryControlFlags::empty(),
// QueryControlFlags::PRECISE,
) )
.unwrap() .unwrap()
.bind_vertex_buffers(0, triangle1.clone()) .bind_vertex_buffers(0, triangle1.clone())
@ -477,16 +472,15 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
} }
// Retrieve the query results. // Retrieve the query results. This copies the results to a variable on the CPU. You
// This copies the results to a variable on the CPU. You can also use the // can also use the `copy_query_pool_results` function on a command buffer to write
// `copy_query_pool_results` function on a command buffer to write results to a // results to a Vulkano buffer. This could then be used to influence draw operations
// Vulkano buffer. This could then be used to influence draw operations further down // further down the line, either in the same frame or a future frame.
// the line, either in the same frame or a future frame.
#[rustfmt::skip] #[rustfmt::skip]
query_pool query_pool
.queries_range(0..3) .queries_range(0..3)
@ -494,21 +488,21 @@ fn main() {
.get_results( .get_results(
&mut query_results, &mut query_results,
// Block the function call until the results are available. // Block the function call until the results are available.
// Note: if not all the queries have actually been executed, then this // NOTE: If not all the queries have actually been executed, then this will
// will wait forever for something that never happens! // wait forever for something that never happens!
QueryResultFlags::WAIT QueryResultFlags::WAIT
// Enable this flag to give partial results if available, instead of waiting // Enable this flag to give partial results if available, instead of waiting
// for the full results. // for the full results.
// | QueryResultFlags::PARTIAL // | QueryResultFlags::PARTIAL
// Blocking and waiting will ensure the results are always available after // Blocking and waiting will ensure the results are always available after the
// the function returns. // function returns.
// //
// If you disable waiting, then this flag can be enabled to include the // 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 // availability of each query's results. You need one extra element per query
// query in your `query_results` buffer for this. This element will // in your `query_results` buffer for this. This element will be filled with a
// be filled with a zero/nonzero value indicating availability. // zero/nonzero value indicating availability.
// | QueryResultFlags::WITH_AVAILABILITY // | QueryResultFlags::WITH_AVAILABILITY
) )
.unwrap(); .unwrap();

View File

@ -9,23 +9,20 @@
// This example demonstrates how to use pipeline caching. // This example demonstrates how to use pipeline caching.
// //
// Using a PipelineCache can improve performance significantly, // Using a `PipelineCache` can improve performance significantly, by checking if the requested
// by checking if the requested pipeline exists in the cache and if so, // pipeline exists in the cache and if so, return that pipeline directly or insert that new
// return that pipeline directly or insert that new pipeline into the // pipeline into the cache.
// cache.
// //
// You can retrieve the data in the cache as a `Vec<u8>` and // You can retrieve the data in the cache as a `Vec<u8>` and save that to a binary file. Later you
// save that to a binary file. Later you can load that file and build a // can load that file and build a PipelineCache with the given data. Be aware that the Vulkan
// 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.
// implementation does not check if the data is valid and vulkano // Invalid data can lead to driver crashes or worse. Using the same cache data with a different GPU
// currently does not either. Invalid data can lead to driver crashes // probably won't work, a simple driver update can lead to invalid data as well. To check if your
// or worse. Using the same cache data with a different GPU probably // data is valid you can find inspiration here:
// won't work, a simple driver update can lead to invalid data as well. // https://zeux.io/2019/07/17/serializing-pipeline-cache/
// 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 // In the future, vulkano might implement those safety checks, but for now, you would have to do
// now, you would have to do that yourself or trust the data and the user. // that yourself or trust the data and the user.
use std::{ use std::{
fs::{remove_file, rename, File}, fs::{remove_file, rename, File},
@ -47,7 +44,6 @@ fn main() {
let instance = Instance::new( let instance = Instance::new(
library, library,
InstanceCreateInfo { InstanceCreateInfo {
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -82,7 +78,7 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
// Now initializing the device. // Now initializing the device.
@ -102,33 +98,33 @@ fn main() {
// We are creating an empty PipelineCache to start somewhere. // We are creating an empty PipelineCache to start somewhere.
let pipeline_cache = PipelineCache::empty(device.clone()).unwrap(); let pipeline_cache = PipelineCache::empty(device.clone()).unwrap();
// We need to create the compute pipeline that describes our operation. We are using the // We need to create the compute pipeline that describes our operation. We are using the shader
// shader from the basic-compute-shader example. // from the basic-compute-shader example.
// //
// If you are familiar with graphics pipeline, the principle is the same except that compute // If you are familiar with graphics pipeline, the principle is the same except that compute
// pipelines are much simpler to create. // pipelines are much simpler to create.
// //
// Pass the PipelineCache as an optional parameter to the ComputePipeline constructor. // Pass the `PipelineCache` as an optional parameter to the `ComputePipeline` constructor. For
// For GraphicPipelines you can use the GraphicPipelineBuilder that has a method // `GraphicPipeline`s you can use the `GraphicPipelineBuilder` that has a method
// `build_with_cache(cache: Arc<PipelineCache>)` // `build_with_cache(cache: Arc<PipelineCache>)`.
let _pipeline = { let _pipeline = {
mod cs { mod cs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #version 450
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout(set = 0, binding = 0) buffer Data { layout(set = 0, binding = 0) buffer Data {
uint data[]; uint data[];
} data; };
void main() { void main() {
uint idx = gl_GlobalInvocationID.x; uint idx = gl_GlobalInvocationID.x;
data.data[idx] *= 12; data[idx] *= 12;
} }
" ",
} }
} }
let shader = cs::load(device.clone()).unwrap(); let shader = cs::load(device.clone()).unwrap();
@ -142,13 +138,12 @@ fn main() {
.unwrap() .unwrap()
}; };
// Normally you would use your pipeline for computing, but we just want to focus on the // Normally you would use your pipeline for computing, but we just want to focus on the cache
// cache functionality. // functionality. The cache works the same for a `GraphicsPipeline`, a `ComputePipeline` is
// The cache works the same for a GraphicsPipeline, a ComputePipeline is just simpler to // just simpler to build.
// build.
// //
// We are now going to retrieve the cache data into a Vec<u8> and save that to a file on // We are now going to retrieve the cache data into a Vec<u8> and save that to a file on our
// our disk. // disk.
if let Ok(data) = pipeline_cache.get_data() { if let Ok(data) = pipeline_cache.get_data() {
if let Ok(mut file) = File::create("pipeline_cache.bin.tmp") { 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 // The `PipelineCache` is now saved to disk and can be loaded the next time the application is
// is started. This way, the pipelines do not have to be rebuild and pipelines that might // started. This way, the pipelines do not have to be rebuild and pipelines that might exist in
// exist in the cache can be build far quicker. // the cache can be build far quicker.
// //
// To load the cache from the file, we just need to load the data into a Vec<u8> and build // To load the cache from the file, we just need to load the data into a Vec<u8> and build the
// the PipelineCache from that. Note that this function is currently unsafe as there are // `PipelineCache` from that. Note that this function is currently unsafe as there are no
// no checks, as it was mentioned at the start of this example. // checks, as it was mentioned at the start of this example.
let data = { let data = {
if let Ok(mut file) = File::open("pipeline_cache.bin") { if let Ok(mut file) = File::open("pipeline_cache.bin") {
let mut data = Vec::new(); let mut data = Vec::new();
@ -187,14 +182,13 @@ fn main() {
PipelineCache::empty(device).unwrap() PipelineCache::empty(device).unwrap()
}; };
// As the PipelineCache of the Vulkan implementation saves an opaque blob of data, // As the `PipelineCache` of the Vulkan implementation saves an opaque blob of data, there is
// there is no real way to know if the data is correct. There might be differences // no real way to know if the data is correct. There might be differences in the byte blob
// in the byte blob here, but it should still work. // here, but it should still work. If it doesn't, please check if there is an issue describing
// If it doesn't, please check if there is an issue describing this problem, and if // this problem, and if not open a new one, on the GitHub page.
// not open a new one, on the GitHub page.
assert_eq!( assert_eq!(
pipeline_cache.get_data().unwrap(), pipeline_cache.get_data().unwrap(),
second_cache.get_data().unwrap() second_cache.get_data().unwrap(),
); );
println!("Success"); println!("Success");
} }

View File

@ -7,10 +7,10 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
// Push constants are a small bank of values written directly to the command buffer // Push constants are a small bank of values written directly to the command buffer and accessible
// and accessible in shaders. They allow the application to set values used in shaders // in shaders. They allow the application to set values used in shaders without creating buffers or
// without creating buffers or modifying and binding descriptor sets for each update. // modifying and binding descriptor sets for each update. As a result, they are expected to
// As a result, they are expected to outperform such memory-backed resource updates. // outperform such memory-backed resource updates.
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferUsage},
@ -36,7 +36,6 @@ fn main() {
let instance = Instance::new( let instance = Instance::new(
library, library,
InstanceCreateInfo { InstanceCreateInfo {
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -70,7 +69,7 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
@ -90,26 +89,26 @@ fn main() {
mod cs { mod cs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #version 450
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout(push_constant) uniform PushConstantData { layout(push_constant) uniform PushConstantData {
int multiple; int multiple;
float addend; float addend;
bool enable; bool enable;
} pc; } pc;
layout(set = 0, binding = 0) buffer Data { layout(set = 0, binding = 0) buffer Data {
uint data[]; uint data[];
} data; };
void main() { void main() {
uint idx = gl_GlobalInvocationID.x; uint idx = gl_GlobalInvocationID.x;
if (pc.enable) { if (pc.enable) {
data.data[idx] *= pc.multiple; data[idx] *= pc.multiple;
data.data[idx] += uint(pc.addend); data[idx] += uint(pc.addend);
} }
} }
", ",
@ -152,18 +151,20 @@ fn main() {
) )
.unwrap(); .unwrap();
// The `vulkano_shaders::shaders!` macro generates a struct with the correct representation of the push constants struct specified in the shader. // The `vulkano_shaders::shaders!` macro generates a struct with the correct representation of
// Here we create an instance of the generated struct. // the push constants struct specified in the shader. Here we create an instance of the
let push_constants = cs::ty::PushConstantData { // generated struct.
let push_constants = cs::PushConstantData {
multiple: 1, multiple: 1,
addend: 1.0, addend: 1.0,
enable: 1, enable: 1,
}; };
// For a compute pipeline, push constants are passed to the `dispatch` method. // For a compute pipeline, push constants are passed to the `dispatch` method. For a graphics
// For a graphics pipeline, push constants are passed to the `draw` and `draw_indexed` methods. // 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. // 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( let mut builder = AutoCommandBufferBuilder::primary(
&command_buffer_allocator, &command_buffer_allocator,
queue.queue_family_index(), queue.queue_family_index(),

View File

@ -7,10 +7,9 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use bytemuck::{Pod, Zeroable};
use std::{io::Cursor, sync::Arc}; use std::{io::Cursor, sync::Arc};
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents,
@ -59,7 +58,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -98,7 +96,7 @@ fn main() {
PhysicalDeviceType::Other => 4, PhysicalDeviceType::Other => 4,
_ => 5, _ => 5,
}) })
.expect("No suitable physical device found"); .expect("no suitable physical device found");
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
@ -155,8 +153,8 @@ fn main() {
let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -315,7 +313,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -331,7 +329,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -391,7 +389,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,
@ -428,32 +426,34 @@ fn window_size_dependent_setup(
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 position; layout(location = 0) in vec2 position;
layout(location = 0) out vec2 tex_coords; layout(location = 0) out vec2 tex_coords;
void main() { void main() {
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
tex_coords = position + vec2(0.5); tex_coords = position + vec2(0.5);
}" }
",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 tex_coords; layout(location = 0) in vec2 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() { void main() {
f_color = texture(tex, tex_coords); f_color = texture(tex, tex_coords);
}" }
",
} }
} }

View File

@ -6,23 +6,24 @@
// at your option. All files in the project carrying such // at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // 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 std::{fs::File, io::Read, sync::Arc};
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
RenderPassBeginInfo, SubpassContents, RenderPassBeginInfo, SubpassContents,
@ -66,7 +67,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -109,7 +109,7 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
@ -177,10 +177,13 @@ fn main() {
.unwrap(); .unwrap();
let vs = { let vs = {
let mut f = File::open("src/bin/runtime-shader/vert.spv") let mut f = File::open("src/bin/runtime-shader/vert.spv").expect(
.expect("Can't find file src/bin/runtime-shader/vert.spv This example needs to be run from the root of the example crate."); "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![]; let mut v = vec![];
f.read_to_end(&mut v).unwrap(); f.read_to_end(&mut v).unwrap();
// Create a ShaderModule on a device the same Shader::load does it. // Create a ShaderModule on a device the same Shader::load does it.
// NOTE: You will have to verify correctness of the data by yourself! // NOTE: You will have to verify correctness of the data by yourself!
unsafe { ShaderModule::from_bytes(device.clone(), &v) }.unwrap() unsafe { ShaderModule::from_bytes(device.clone(), &v) }.unwrap()
@ -188,9 +191,10 @@ fn main() {
let fs = { let fs = {
let mut f = File::open("src/bin/runtime-shader/frag.spv") 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![]; let mut v = vec![];
f.read_to_end(&mut v).unwrap(); f.read_to_end(&mut v).unwrap();
unsafe { ShaderModule::from_bytes(device.clone(), &v) }.unwrap() unsafe { ShaderModule::from_bytes(device.clone(), &v) }.unwrap()
}; };
@ -213,8 +217,8 @@ fn main() {
let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
pub struct Vertex { pub struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
pub position: [f32; 2], 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: 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 // note that passing wrong types, providing sets at wrong indexes will cause
// descriptor set builder to return Err! // descriptor set builder to return Err!
// TODO: Outdated ^
let mut viewport = Viewport { let mut viewport = Viewport {
origin: [0.0, 0.0], origin: [0.0, 0.0],
@ -290,7 +295,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -306,7 +311,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -360,7 +365,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,

View File

@ -7,10 +7,9 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use bytemuck::{Pod, Zeroable};
use std::{io::Cursor, sync::Arc}; use std::{io::Cursor, sync::Arc};
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents,
@ -59,8 +58,8 @@ use winit::{
}; };
fn main() { fn main() {
// The start of this example is exactly the same as `triangle`. You should read the // The start of this example is exactly the same as `triangle`. You should read the `triangle`
// `triangle` example if you haven't done so yet. // example if you haven't done so yet.
let library = VulkanLibrary::new().unwrap(); let library = VulkanLibrary::new().unwrap();
let required_extensions = vulkano_win::required_extensions(&library); let required_extensions = vulkano_win::required_extensions(&library);
@ -68,7 +67,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -170,8 +168,8 @@ fn main() {
let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -356,7 +354,7 @@ fn main() {
.descriptor_binding_requirements(), .descriptor_binding_requirements(),
); );
// Set 0, Binding 0 // Set 0, Binding 0.
let binding = layout_create_infos[0].bindings.get_mut(&0).unwrap(); let binding = layout_create_infos[0].bindings.get_mut(&0).unwrap();
binding.variable_descriptor_count = true; binding.variable_descriptor_count = true;
binding.descriptor_count = 2; binding.descriptor_count = 2;
@ -457,7 +455,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -473,7 +471,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -533,7 +531,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,
@ -572,21 +570,22 @@ mod vs {
ty: "vertex", ty: "vertex",
vulkan_version: "1.2", vulkan_version: "1.2",
spirv_version: "1.5", spirv_version: "1.5",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 position; layout(location = 0) in vec2 position;
layout(location = 1) in uint tex_i; layout(location = 1) in uint tex_i;
layout(location = 2) in vec2 coords; layout(location = 2) in vec2 coords;
layout(location = 0) out flat uint out_tex_i; layout(location = 0) out flat uint out_tex_i;
layout(location = 1) out vec2 out_coords; layout(location = 1) out vec2 out_coords;
void main() { void main() {
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
out_tex_i = tex_i; out_tex_i = tex_i;
out_coords = coords; out_coords = coords;
}" }
",
} }
} }
@ -595,20 +594,21 @@ mod fs {
ty: "fragment", ty: "fragment",
vulkan_version: "1.2", vulkan_version: "1.2",
spirv_version: "1.5", spirv_version: "1.5",
src: " src: r"
#version 450 #version 450
#extension GL_EXT_nonuniform_qualifier : enable #extension GL_EXT_nonuniform_qualifier : enable
layout(location = 0) in flat uint tex_i; layout(location = 0) in flat uint tex_i;
layout(location = 1) in vec2 coords; 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() { void main() {
f_color = texture(nonuniformEXT(tex[tex_i]), coords); f_color = texture(nonuniformEXT(tex[tex_i]), coords);
}" }
",
} }
} }

View File

@ -8,7 +8,8 @@
// according to those terms. // according to those terms.
// This example is a copy of `basic-compute-shaders.rs`, but initalizes half of the input buffer // 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::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferUsage},
@ -35,7 +36,6 @@ fn main() {
let instance = Instance::new( let instance = Instance::new(
library, library,
InstanceCreateInfo { InstanceCreateInfo {
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -69,7 +69,7 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
@ -91,23 +91,24 @@ fn main() {
mod cs { mod cs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #version 450
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout(set = 0, binding = 0) buffer Data { layout(set = 0, binding = 0) buffer Data {
uint data[]; uint data[];
} data; };
void main() { void main() {
uint idx = gl_GlobalInvocationID.x; uint idx = gl_GlobalInvocationID.x;
data.data[idx] *= 12; data[idx] *= 12;
} }
" ",
} }
} }
let shader = cs::load(device.clone()).unwrap(); let shader = cs::load(device.clone()).unwrap();
ComputePipeline::new( ComputePipeline::new(
device.clone(), device.clone(),
shader.entry_point("main").unwrap(), shader.entry_point("main").unwrap(),
@ -123,21 +124,19 @@ fn main() {
let command_buffer_allocator = let command_buffer_allocator =
StandardCommandBufferAllocator::new(device.clone(), Default::default()); StandardCommandBufferAllocator::new(device.clone(), Default::default());
let data_buffer = { let data_buffer = Buffer::from_iter(
// we intitialize half of the array and leave the other half to 0, we will use copy later to fill it &memory_allocator,
let data_iter = (0..65536u32).map(|n| if n < 65536 / 2 { n } else { 0 }); BufferAllocateInfo {
Buffer::from_iter( buffer_usage: BufferUsage::STORAGE_BUFFER
&memory_allocator, | BufferUsage::TRANSFER_SRC
BufferAllocateInfo { | BufferUsage::TRANSFER_DST,
buffer_usage: BufferUsage::STORAGE_BUFFER ..Default::default()
| BufferUsage::TRANSFER_SRC },
| BufferUsage::TRANSFER_DST, // We intitialize half of the array and leave the other half at 0, we will use the copy
..Default::default() // command later to fill it.
}, (0..65536u32).map(|n| if n < 65536 / 2 { n } else { 0 }),
data_iter, )
) .unwrap();
.unwrap()
};
let layout = pipeline.layout().set_layouts().get(0).unwrap(); let layout = pipeline.layout().set_layouts().get(0).unwrap();
let set = PersistentDescriptorSet::new( let set = PersistentDescriptorSet::new(
@ -154,7 +153,8 @@ fn main() {
) )
.unwrap(); .unwrap();
builder 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 { .copy_buffer(CopyBufferInfoTyped {
regions: [BufferCopy { regions: [BufferCopy {
src_offset: 0, src_offset: 0,
@ -187,9 +187,9 @@ fn main() {
let data_buffer_content = data_buffer.read().unwrap(); 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 { 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], n * 12);
assert_eq!(data_buffer_content[n as usize + 65536 / 2], n * 12); assert_eq!(data_buffer_content[n as usize + 65536 / 2], n * 12);
} }

View File

@ -7,9 +7,9 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
// This example demonstrates how to use the standard and relative include directives within // This example demonstrates how to use the standard and relative include directives within shader
// shader source code. The boilerplate is taken from the "basic-compute-shader.rs" example, where // source code. The boilerplate is taken from the "basic-compute-shader.rs" example, where most of
// most of the boilerplate is explained. // the boilerplate is explained.
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferUsage},
@ -35,7 +35,6 @@ fn main() {
let instance = Instance::new( let instance = Instance::new(
library, library,
InstanceCreateInfo { InstanceCreateInfo {
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -69,7 +68,7 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
@ -92,27 +91,28 @@ fn main() {
ty: "compute", ty: "compute",
// We declare what directories to search for when using the `#include <...>` // We declare what directories to search for when using the `#include <...>`
// syntax. Specified directories have descending priorities based on their order. // syntax. Specified directories have descending priorities based on their order.
include: [ "src/bin/shader-include/standard-shaders" ], include: ["src/bin/shader-include/standard-shaders"],
src: " src: r#"
#version 450 #version 450
// Substitutes this line with the contents of the file `common.glsl` found in one of the standard
// `include` directories specified above. // Substitutes this line with the contents of the file `common.glsl` found in
// Note, that relative inclusion (`#include \"...\"`), although it falls back to standard // one of the standard `include` directories specified above.
// inclusion, should not be used for **embedded** shader source, as it may be misleading and/or // Note that relative inclusion (`#include "..."`), although it falls back to
// confusing. // standard inclusion, should not be used for **embedded** shader source, as it
// may be misleading and/or confusing.
#include <common.glsl> #include <common.glsl>
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout(set = 0, binding = 0) buffer Data { layout(set = 0, binding = 0) buffer Data {
uint data[]; uint data[];
} data; };
void main() { void main() {
uint idx = gl_GlobalInvocationID.x; 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(); let shader = cs::load(device.clone()).unwrap();

View File

@ -7,38 +7,26 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
// This example demonstrates how to put derives onto generated Rust structs from // This example demonstrates how to put derives onto Rust structs generated from the shader types
// the Shader types through the "types-meta" options of // through the `custom_derives` option of the `shader!` macro.
// `shader!` macro.
// Vulkano Shader macro is capable to generate Rust structs representing each // The `shader!` macro is capable of generating Rust structs representing each type found in the
// type found in the shader source. These structs appear in the `ty` module // shader source. These structs are generated in the same module where the macro was called.
// generated in the same module where the macro was called.
// //
// By default each type has only `Clone` and `Copy` implementations. For // By default each type only has `Clone` and `Copy` derives. For ergonomic purposes you may want to
// ergonomic purposes developer may want to implement more traits on top of each // add more derives for each type. For example built-in derive macros such as `Default` or `Debug`.
// type. For example "standard" traits such as `Default` or `Debug`.
// //
// One way to do so is implementing them manually, but it would be hard to do, // The only way we can add derive macros to these generated types is if the `shader!` macro
// and complicates code maintenances. // generates the derive attribute with the wanted derives, hence there's a macro option for it.
//
// 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.
use ron::{ use ron::ser::PrettyConfig;
from_str, use serde::{Deserialize, Serialize};
ser::{to_string_pretty, PrettyConfig}, use std::fmt::{Debug, Display, Error as FmtError, Formatter};
}; use vulkano::padded::Padded;
use std::fmt::{Debug, Display, Error, Formatter};
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #version 450
struct Foo { struct Foo {
@ -61,62 +49,43 @@ vulkano_shaders::shader! {
void main() {} void main() {}
", ",
types_meta: { custom_derives: [Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize],
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize)]
impl Eq
}
} }
// In the example above the macro generated `Clone`, `Copy`, `PartialEq`, // In the example above the macro generates `Clone`, `Copy`, `Debug`, `Default`, `PartialEq`,
// `Debug` and `Default` implementations for each declared // derives for each declared type (`PushConstantData`, `Foo` and `Bar`) in the shader, and it also
// type(`PushConstantData`, `Foo` and `Bar`) in the shader, and applied // applies derives of the `Serialize` and `Deserialize` traits from serde. However, it doesn't
// `impl Eq` for each of them too. And it also applied derives of // apply any of these to `Bars` since that type does not a have size known at compile time.
// `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.
impl Display for crate::ty::Foo { // Some traits are not meant to be derived, such as `Display`, but we can still implement them
fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), Error> { // manually.
impl Display for Foo {
fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), FmtError> {
Debug::fmt(self, formatter) Debug::fmt(self, formatter)
} }
} }
fn main() { fn main() {
use crate::ty::*; // Prints "Foo { x: 0.0, z: [100.0, 200.0, 300.0] }".
// Prints "Foo { x: 0.0, z: [100.0, 200.0, 300.0] }" skipping "_dummyX" fields.
println!( println!(
"{}", "{:?}",
Foo { Foo {
z: [100.0, 200.0, 300.0], z: [100.0, 200.0, 300.0],
..Default::default() ..Default::default()
} },
); );
let mut bar = Bar { let mut bar = Bar {
y: [5.1, 6.2], // The `Padded` wrapper here is padding the following field, `foo`.
y: Padded([5.1, 6.2]),
// Fills all fields with zeroes including "_dummyX" fields, so we don't // Fills all fields with zeroes.
// have to maintain them manually anymore.
..Default::default() ..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!( assert_eq!(
Bar { Bar {
y: [5.1, 6.2], // `Padded<T, N>` implementes `From<T>`, so you can construct it this way as well.
y: [5.1, 6.2].into(),
..Default::default() ..Default::default()
}, },
bar, bar,
@ -124,16 +93,17 @@ fn main() {
assert_ne!(Bar::default(), bar); 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 // Since we put `Serialize` and `Deserialize` traits to the derives list we can serialize and
// serialize and deserialize Shader data // 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}"); println!("Serialized Bar: {serialized}");
let deserialized = from_str::<Bar>(&serialized).unwrap(); let deserialized = ron::from_str::<Bar>(&serialized).unwrap();
assert_eq!(deserialized.foo.x, 125.0); assert_eq!(*deserialized.foo.x, 125.0);
} }

View File

@ -7,25 +7,23 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
// This example demonstrates how to compile several shaders together using vulkano-shaders macro, // This example demonstrates how to compile several shaders together using the `shader!` macro,
// such that the macro generates unique Shader types per each compiled shader, but generates common // such that the macro doesn't generate unique shader types for each compiled shader, but generates
// shareable set of Rust structs representing corresponding structs in the source glsl code. // 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 // Normally, each `shader!` macro invocation among other things generates all Rust types for each
// containing all Rust types per each "struct" declaration of glsl code. Using this submodule // `struct` declaration of the GLSL code. Using these the user can organize type-safe
// the user can organize type-safe interoperability between Rust code and the shader interface // interoperability between Rust code and the shader input/output interface tied to these structs.
// input/output data tied to these structs. However, if the user compiles several shaders in // However, if the user compiles several shaders in independent Rust modules, each of these modules
// independent Rust modules, each of these modules would contain independent `ty` submodule with // would contain an independent set of Rust types. So, even if both shaders contain the same (or
// each own set of Rust types. So, even if both shaders contain the same(or partially intersecting) // partially intersecting) GLSL structs they will be duplicated by each macro invocation and
// glsl structs they will be duplicated in each generated `ty` submodule and treated by Rust as // treated by Rust as independent types. As such it would be tricky to organize interoperability
// independent types. As such it would be tricky to organize interoperability between shader // between shader interfaces in Rust.
// interfaces in Rust.
// //
// To solve this problem the user can use "shared" generation mode of the macro. In this mode the // 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 // 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 // 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 // with the same names, and it will not generate duplicates.
// `ty` submodule.
use std::sync::Arc; use std::sync::Arc;
use vulkano::{ use vulkano::{
@ -52,7 +50,6 @@ fn main() {
let instance = Instance::new( let instance = Instance::new(
library, library,
InstanceCreateInfo { InstanceCreateInfo {
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -109,91 +106,91 @@ fn main() {
// their layout interfaces. // their layout interfaces.
// //
// First one is just multiplying each value from the input array of ints to provided // 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 // value in push constants struct. And the second one in turn adds this value instead
// multiplying. // of multiplying.
// //
// However both shaders declare glsl struct `Parameters` for push constants in each // 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 // shader. Since each of the struct has exactly the same interface, they will be
// treated by the macro as "shared". // treated by the macro as "shared".
// //
// Also, note that glsl code duplications between shader sources is not necessary too. // 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 // 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 files using "#include" // such types, and include it in each shader entry-point file using the `#include`
// directive. // directive.
shaders: { shaders: {
// Generate single unique `SpecializationConstants` struct for all shaders since // Generate single unique `SpecializationConstants` struct for all shaders, since
// their specialization interfaces are the same. This option is turned off // their specialization interfaces are the same. This option is turned off by
// by default and the macro by default producing unique // default and the macro by default produces unique structs
// structs(`MultSpecializationConstants`, `AddSpecializationConstants`) // (`MultSpecializationConstants` and `AddSpecializationConstants` in this case).
shared_constants: true, shared_constants: true,
mult: { mult: {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #version 450
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout(constant_id = 0) const bool enabled = true; layout(constant_id = 0) const bool enabled = true;
layout(push_constant) uniform Parameters { layout(push_constant) uniform Parameters {
int value; int value;
} pc; } pc;
layout(set = 0, binding = 0) buffer Data { layout(set = 0, binding = 0) buffer Data {
uint data[]; uint data[];
} data; };
void main() { void main() {
if (!enabled) { if (!enabled) {
return; return;
} }
uint idx = gl_GlobalInvocationID.x; uint idx = gl_GlobalInvocationID.x;
data.data[idx] *= pc.value; data[idx] *= pc.value;
} }
" ",
}, },
add: { add: {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #version 450
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout(constant_id = 0) const bool enabled = true; layout(constant_id = 0) const bool enabled = true;
layout(push_constant) uniform Parameters { layout(push_constant) uniform Parameters {
int value; int value;
} pc; } pc;
layout(set = 0, binding = 0) buffer Data { layout(set = 0, binding = 0) buffer Data {
uint data[]; uint data[];
} data; };
void main() { void main() {
if (!enabled) { if (!enabled) {
return; return;
} }
uint idx = gl_GlobalInvocationID.x; uint idx = gl_GlobalInvocationID.x;
data.data[idx] += pc.value; data[idx] += pc.value;
} }
" ",
} },
}, },
} }
// The macro will create the following things in this module: // The macro will create the following things in this module:
// - `ShaderMult` for the first shader loader/entry-point. // - `load_mult` for the first shader loader/entry-point.
// - `ShaderAdd` for the second shader loader/entry-point. // - `load_add` for the second shader loader/entry-point.
// `SpecializationConstants` Rust struct for both shader's specialization constants. // - `SpecializationConstants` struct for both shaders' specialization constants.
// `ty` submodule with `Parameters` Rust struct common for both shaders. // - `Parameters` struct common for both shaders.
} }
// We introducing generic function responsible for running any of the shaders above with /// We are introducing a generic function responsible for running any of the shaders above with
// provided Push Constants parameter. /// the provided push constants parameter. Note that the shaders' interface `parameters` here
// Note that shader's interface `parameter` here is shader-independent. /// are shader-independent.
fn run_shader( fn run_shader(
pipeline: Arc<ComputePipeline>, pipeline: Arc<ComputePipeline>,
queue: Arc<Queue>, queue: Arc<Queue>,
data_buffer: Subbuffer<[u32]>, data_buffer: Subbuffer<[u32]>,
parameters: shaders::ty::Parameters, parameters: shaders::Parameters,
command_buffer_allocator: &StandardCommandBufferAllocator, command_buffer_allocator: &StandardCommandBufferAllocator,
descriptor_set_allocator: &StandardDescriptorSetAllocator, descriptor_set_allocator: &StandardDescriptorSetAllocator,
) { ) {
@ -238,21 +235,18 @@ fn main() {
StandardCommandBufferAllocator::new(device.clone(), Default::default()); StandardCommandBufferAllocator::new(device.clone(), Default::default());
let descriptor_set_allocator = StandardDescriptorSetAllocator::new(device.clone()); let descriptor_set_allocator = StandardDescriptorSetAllocator::new(device.clone());
// Preparing test data array `[0, 1, 2, 3....]` // Prepare test array `[0, 1, 2, 3....]`.
let data_buffer = { let data_buffer = Buffer::from_iter(
let data_iter = 0..65536u32; &memory_allocator,
Buffer::from_iter( BufferAllocateInfo {
&memory_allocator, buffer_usage: BufferUsage::STORAGE_BUFFER,
BufferAllocateInfo { ..Default::default()
buffer_usage: BufferUsage::STORAGE_BUFFER, },
..Default::default() 0..65536u32,
}, )
data_iter, .unwrap();
)
.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( let mult_pipeline = ComputePipeline::new(
device.clone(), device.clone(),
shaders::load_mult(device.clone()) shaders::load_mult(device.clone())
@ -265,7 +259,7 @@ fn main() {
) )
.unwrap(); .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( let add_pipeline = ComputePipeline::new(
device.clone(), device.clone(),
shaders::load_add(device) shaders::load_add(device)
@ -278,32 +272,32 @@ fn main() {
) )
.unwrap(); .unwrap();
// Multiply each value by 2 // Multiply each value by 2.
run_shader( run_shader(
mult_pipeline.clone(), mult_pipeline.clone(),
queue.clone(), queue.clone(),
data_buffer.clone(), data_buffer.clone(),
shaders::ty::Parameters { value: 2 }, shaders::Parameters { value: 2 },
&command_buffer_allocator, &command_buffer_allocator,
&descriptor_set_allocator, &descriptor_set_allocator,
); );
// Then add 1 to each value // Then add 1 to each value.
run_shader( run_shader(
add_pipeline, add_pipeline,
queue.clone(), queue.clone(),
data_buffer.clone(), data_buffer.clone(),
shaders::ty::Parameters { value: 1 }, shaders::Parameters { value: 1 },
&command_buffer_allocator, &command_buffer_allocator,
&descriptor_set_allocator, &descriptor_set_allocator,
); );
// Then multiply each value by 3 // Then multiply each value by 3.
run_shader( run_shader(
mult_pipeline, mult_pipeline,
queue, queue,
data_buffer.clone(), data_buffer.clone(),
shaders::ty::Parameters { value: 3 }, shaders::Parameters { value: 3 },
&command_buffer_allocator, &command_buffer_allocator,
&descriptor_set_allocator, &descriptor_set_allocator,
); );

View File

@ -7,15 +7,14 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
//! A minimal particle-sandbox to demonstrate a reasonable use-case for a device-local buffer. // A minimal particle-sandbox to demonstrate a reasonable use-case for a device-local buffer. We
//! We gain significant runtime performance by writing the inital vertex values to the GPU using // gain significant runtime performance by writing the inital vertex values to the GPU using a
//! a staging buffer and then copying the data to a device-local buffer to be accessed solely // staging buffer and then copying the data to a device-local buffer to be accessed solely by the
//! by the GPU through the compute shader and as a vertex array. // GPU through the compute shader and as a vertex array.
use bytemuck::{Pod, Zeroable};
use std::{sync::Arc, time::SystemTime}; use std::{sync::Arc, time::SystemTime};
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
CopyBufferInfo, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents, CopyBufferInfo, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents,
@ -39,8 +38,10 @@ use vulkano::{
GraphicsPipeline, PipelineBindPoint, GraphicsPipeline, PipelineBindPoint,
}, },
render_pass::{Framebuffer, FramebufferCreateInfo, Subpass}, render_pass::{Framebuffer, FramebufferCreateInfo, Subpass},
swapchain::{PresentMode, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo}, swapchain::{
sync::{future::FenceSignalFuture, GpuFuture}, acquire_next_image, PresentMode, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo,
},
sync::{self, future::FenceSignalFuture, GpuFuture},
VulkanLibrary, VulkanLibrary,
}; };
use vulkano_win::VkSurfaceBuild; use vulkano_win::VkSurfaceBuild;
@ -56,15 +57,14 @@ const WINDOW_HEIGHT: u32 = 600;
const PARTICLE_COUNT: usize = 100_000; const PARTICLE_COUNT: usize = 100_000;
fn main() { fn main() {
// The usual Vulkan initialization. // The usual Vulkan initialization. Largely the same as example `triangle.rs` until further
// Largely the same as example `triangle.rs` until further commentation is provided. // commentation is provided.
let library = VulkanLibrary::new().unwrap(); let library = VulkanLibrary::new().unwrap();
let required_extensions = vulkano_win::required_extensions(&library); let required_extensions = vulkano_win::required_extensions(&library);
let instance = Instance::new( let instance = Instance::new(
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -73,7 +73,8 @@ fn main() {
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let surface = WindowBuilder::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_title("simple particles")
.with_inner_size(winit::dpi::PhysicalSize::new(WINDOW_WIDTH, WINDOW_HEIGHT)) .with_inner_size(winit::dpi::PhysicalSize::new(WINDOW_WIDTH, WINDOW_HEIGHT))
.build_vk_surface(&event_loop, instance.clone()) .build_vk_surface(&event_loop, instance.clone())
@ -110,8 +111,9 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
physical_device, physical_device,
DeviceCreateInfo { DeviceCreateInfo {
@ -196,7 +198,7 @@ fn main() {
mod cs { mod cs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #version 450
layout(local_size_x = 128, local_size_y = 1, local_size_z = 1) in; 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. // Allow push constants to define a parameters of compute.
layout (push_constant) uniform PushConstants layout (push_constant) uniform PushConstants {
{
vec2 attractor; vec2 attractor;
float attractor_strength; float attractor_strength;
float delta_time; float delta_time;
} push; } 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 minLength = 0.02;
const float friction = -2.0; const float friction = -2.0;
@ -232,15 +235,15 @@ fn main() {
vec2 pos = verticies[index].pos + push.delta_time * vel; vec2 pos = verticies[index].pos + push.delta_time * vel;
// Bounce particle off screen-border. // 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); 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); 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); 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); pos.y = sign(pos.y);
} }
} }
@ -248,11 +251,11 @@ fn main() {
// Simple inverse-square force. // Simple inverse-square force.
vec2 t = push.attractor - pos; vec2 t = push.attractor - pos;
float r = max(length(t), minLength); 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. // Update velocity, enforcing a maximum speed.
vel += push.delta_time * force; vel += push.delta_time * force;
if(length(vel) > maxSpeed) { if (length(vel) > maxSpeed) {
vel = maxSpeed*normalize(vel); 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 { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 pos; layout(location = 0) in vec2 pos;
@ -276,23 +280,30 @@ fn main() {
layout(location = 0) out vec4 outColor; 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() { void main() {
gl_Position = vec4(pos, 0.0, 1.0); gl_Position = vec4(pos, 0.0, 1.0);
gl_PointSize = 1.0; gl_PointSize = 1.0;
// Mix colors based on position and velocity. // 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 { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec4 outColor; layout(location = 0) in vec4 outColor;
@ -302,7 +313,7 @@ fn main() {
void main() { void main() {
fragColor = outColor; fragColor = outColor;
} }
" ",
} }
} }
@ -315,8 +326,8 @@ fn main() {
let command_buffer_allocator = let command_buffer_allocator =
StandardCommandBufferAllocator::new(device.clone(), Default::default()); StandardCommandBufferAllocator::new(device.clone(), Default::default());
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
pos: [f32; 2], pos: [f32; 2],
@ -349,7 +360,8 @@ fn main() {
) )
.unwrap(); .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::<Vertex>( let device_local_buffer = Buffer::new_slice::<Vertex>(
&memory_allocator, &memory_allocator,
BufferAllocateInfo { BufferAllocateInfo {
@ -390,7 +402,7 @@ fn main() {
device_local_buffer 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( let compute_pipeline = vulkano::pipeline::ComputePipeline::new(
device.clone(), device.clone(),
cs.entry_point("main").unwrap(), cs.entry_point("main").unwrap(),
@ -398,20 +410,21 @@ fn main() {
None, 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. // Create a new descriptor set for binding vertices as a storage buffer.
use vulkano::pipeline::Pipeline; // Required to access layout() method of pipeline. use vulkano::pipeline::Pipeline; // Required to access the `layout` method of pipeline.
let descriptor_set = PersistentDescriptorSet::new( let descriptor_set = PersistentDescriptorSet::new(
&descriptor_set_allocator, &descriptor_set_allocator,
compute_pipeline compute_pipeline
.layout() .layout()
.set_layouts() .set_layouts()
.get(0) // 0 is the index of the descriptor set. // 0 is the index of the descriptor set.
.get(0)
.unwrap() .unwrap()
.clone(), .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()), WriteDescriptorSet::buffer(0, vertex_buffer.clone()),
], ],
) )
@ -428,7 +441,8 @@ fn main() {
let graphics_pipeline = GraphicsPipeline::start() let graphics_pipeline = GraphicsPipeline::start()
.vertex_input_state(Vertex::per_vertex()) .vertex_input_state(Vertex::per_vertex())
.vertex_shader(vs.entry_point("main").unwrap(), ()) .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])) .viewport_state(ViewportState::viewport_fixed_scissor_irrelevant([viewport]))
.fragment_shader(fs.entry_point("main").unwrap(), ()) .fragment_shader(fs.entry_point("main").unwrap(), ())
.render_pass(Subpass::from(render_pass, 0).unwrap()) .render_pass(Subpass::from(render_pass, 0).unwrap())
@ -462,41 +476,42 @@ fn main() {
last_frame_time = now; last_frame_time = now;
// Create push contants to be passed to compute shader. // 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: [0.75 * (3. * time).cos(), 0.6 * (0.75 * time).sin()],
attractor_strength: 1.2 * (2. * time).cos(), attractor_strength: 1.2 * (2. * time).cos(),
delta_time, delta_time,
}; };
// Acquire information on the next swapchain target. // Acquire information on the next swapchain target.
let (image_index, suboptimal, acquire_future) = let (image_index, suboptimal, acquire_future) = match acquire_next_image(
match vulkano::swapchain::acquire_next_image( swapchain.clone(),
swapchain.clone(), None, // timeout
None, /*timeout*/ ) {
) { Ok(tuple) => tuple,
Ok(tuple) => tuple, Err(e) => panic!("failed to acquire next image: {e}"),
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!( assert!(
!suboptimal, !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. // If this image buffer already has a future then attempt to cleanup fence
// Usually the future for this index will have completed by the time we are rendering it again. // 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] { if let Some(image_fence) = &mut fences[image_index as usize] {
image_fence.cleanup_finished() 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() { let previous_future = match fences[previous_fence_index as usize].clone() {
// Ensure current frame is synchronized with previous. // Ensure current frame is synchronized with previous.
Some(fence) => fence.boxed(), Some(fence) => fence.boxed(),
// Create new future to guarentee synchronization with (fake) previous frame. // 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( let mut builder = AutoCommandBufferBuilder::primary(
@ -553,7 +568,7 @@ fn main() {
Ok(future) => Some(Arc::new(future)), Ok(future) => Some(Arc::new(future)),
// Unknown failure. // Unknown failure.
Err(e) => panic!("Failed to flush future: {e:?}"), Err(e) => panic!("failed to flush future: {e}"),
}; };
previous_fence_index = image_index; previous_fence_index = image_index;
} }

View File

@ -7,7 +7,7 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // 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::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferUsage},
@ -33,7 +33,6 @@ fn main() {
let instance = Instance::new( let instance = Instance::new(
library, library,
InstanceCreateInfo { InstanceCreateInfo {
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -65,9 +64,9 @@ fn main() {
.unwrap(); .unwrap();
println!( println!(
"Using device: {} (type: {:?})", "using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
@ -87,27 +86,27 @@ fn main() {
mod cs { mod cs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "compute", ty: "compute",
src: " src: r"
#version 450 #version 450
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 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 = 0) const int multiple = 64;
layout(constant_id = 1) const float addend = 64; layout(constant_id = 1) const float addend = 64;
layout(constant_id = 2) const bool enable = true; 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 { layout(set = 0, binding = 0) buffer Data {
uint data[]; uint data[];
} data; };
void main() { void main() {
uint idx = gl_GlobalInvocationID.x; uint idx = gl_GlobalInvocationID.x;
if (enable) { if (enable) {
data.data[idx] *= multiple; data[idx] *= multiple;
data.data[idx] += uint(addend); data[idx] += uint(addend);
} }
} }
" ",
} }
} }

View File

@ -56,8 +56,8 @@ use winit::{
}; };
fn main() { fn main() {
// The start of this example is exactly the same as `triangle`. You should read the // The start of this example is exactly the same as `triangle`. You should read the `triangle`
// `triangle` example if you haven't done so yet. // example if you haven't done so yet.
let library = VulkanLibrary::new().unwrap(); let library = VulkanLibrary::new().unwrap();
let required_extensions = vulkano_win::required_extensions(&library); let required_extensions = vulkano_win::required_extensions(&library);
@ -65,7 +65,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -264,7 +263,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -303,7 +302,7 @@ fn main() {
); );
let scale = Matrix4::from_scale(0.01); let scale = Matrix4::from_scale(0.01);
let uniform_data = vs::ty::Data { let uniform_data = vs::Data {
world: Matrix4::from(rotation).into(), world: Matrix4::from(rotation).into(),
view: (view * scale).into(), view: (view * scale).into(),
proj: proj.into(), proj: proj.into(),
@ -330,7 +329,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -393,7 +392,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); 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( fn window_size_dependent_setup(
memory_allocator: &StandardMemoryAllocator, memory_allocator: &StandardMemoryAllocator,
vs: &ShaderModule, vs: &ShaderModule,
@ -433,9 +432,9 @@ fn window_size_dependent_setup(
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// In the triangle example we use a dynamic viewport, as its a simple example. // In the triangle example we use a dynamic viewport, as its a simple example. However in the
// However in the teapot example, we recreate the pipelines with a hardcoded viewport instead. // teapot example, we recreate the pipelines with a hardcoded viewport instead. This allows the
// This allows the driver to optimize things, at the cost of slower window resizes. // driver to optimize things, at the cost of slower window resizes.
// https://computergraphics.stackexchange.com/questions/5742/vulkan-best-way-of-updating-pipeline-viewport // https://computergraphics.stackexchange.com/questions/5742/vulkan-best-way-of-updating-pipeline-viewport
let pipeline = GraphicsPipeline::start() let pipeline = GraphicsPipeline::start()
.vertex_input_state([Position::per_vertex(), Normal::per_vertex()]) .vertex_input_state([Position::per_vertex(), Normal::per_vertex()])
@ -467,6 +466,6 @@ mod vs {
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
path: "src/bin/teapot/frag.glsl" path: "src/bin/teapot/frag.glsl",
} }
} }

View File

@ -8,20 +8,22 @@
// according to those terms. // according to those terms.
// Some relevant documentation: // 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 overview https://www.khronos.org/opengl/wiki/Tessellation
// * Tessellation Evaluation Shader https://www.khronos.org/opengl/wiki/Tessellation_Evaluation_Shader // - Tessellation Control Shader https://www.khronos.org/opengl/wiki/Tessellation_Control_Shader
// * Tessellation real-world usage 1 http://ogldev.atspace.co.uk/www/tutorial30/tutorial30.html // - Tessellation Evaluation Shader https://www.khronos.org/opengl/wiki/Tessellation_Evaluation_Shader
// * Tessellation real-world usage 2 https://prideout.net/blog/?p=48 // - 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: // 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 std::sync::Arc;
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
RenderPassBeginInfo, SubpassContents, RenderPassBeginInfo, SubpassContents,
@ -61,7 +63,7 @@ use winit::{
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 position; layout(location = 0) in vec2 position;
@ -69,64 +71,66 @@ mod vs {
void main() { void main() {
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
} }
" ",
} }
} }
mod tcs { mod tcs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "tess_ctrl", ty: "tess_ctrl",
src: " src: r"
#version 450 #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) void main(void) {
{ // Save the position of the patch, so the TES can access it. We could define our
// save the position of the patch, so the tes can access it // own output variables for this, but `gl_out` is handily provided.
// 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_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
gl_TessLevelInner[0] = 10; // many triangles are generated in the center // Many triangles are generated in the center.
gl_TessLevelOuter[0] = 1; // no triangles are generated for this edge gl_TessLevelInner[0] = 10;
gl_TessLevelOuter[1] = 10; // many triangles are generated for this edge // No triangles are generated for this edge.
gl_TessLevelOuter[2] = 10; // many triangles are generated for this edge gl_TessLevelOuter[0] = 1;
// gl_TessLevelInner[1] = only used when tes uses layout(quads) // Many triangles are generated for this edge.
// gl_TessLevelOuter[3] = only used when tes uses layout(quads) 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
// There is a stage in between tcs and tes called Primitive Generation (PG) // defined for it. It takes `gl_TessLevelInner` and `gl_TessLevelOuter` and uses them to generate
// Shaders cannot be defined for it. // positions within the patch and pass them to TES via `gl_TessCoord`.
// 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. // When TES uses `layout(triangles)` then `gl_TessCoord` is in Barycentric coordinates. If
// if layout(quads) is used then gl_TessCoord is in cartesian coordinates. // `layout(quads)` is used then `gl_TessCoord` is in Cartesian coordinates. Barycentric coordinates
// Barrycentric coordinates are of the form (x, y, z) where x + y + z = 1 // are of the form (x, y, z) where x + y + z = 1 and the values x, y and z represent the distance
// and the values x, y and z represent the distance from a vertex of the triangle. // from a vertex of the triangle.
// https://mathworld.wolfram.com/BarycentricCoordinates.html // https://mathworld.wolfram.com/BarycentricCoordinates.html
mod tes { mod tes {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "tess_eval", ty: "tess_eval",
src: " src: r"
#version 450 #version 450
layout(triangles, equal_spacing, cw) in; layout(triangles, equal_spacing, cw) in;
void main(void) void main(void) {
{ // Retrieve the vertex positions set by the TCS.
// retrieve the vertex positions set by the tcs
vec4 vert_x = gl_in[0].gl_Position; vec4 vert_x = gl_in[0].gl_Position;
vec4 vert_y = gl_in[1].gl_Position; vec4 vert_y = gl_in[1].gl_Position;
vec4 vert_z = gl_in[2].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_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.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, 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 1.0
); );
} }
" ",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
@ -149,7 +153,7 @@ mod fs {
void main() { void main() {
f_color = vec4(1.0, 1.0, 1.0, 1.0); f_color = vec4(1.0, 1.0, 1.0, 1.0);
} }
" ",
} }
} }
@ -160,7 +164,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -209,7 +212,7 @@ fn main() {
println!( println!(
"Using device: {} (type: {:?})", "Using device: {} (type: {:?})",
physical_device.properties().device_name, physical_device.properties().device_name,
physical_device.properties().device_type physical_device.properties().device_type,
); );
let (device, mut queues) = Device::new( let (device, mut queues) = Device::new(
@ -262,7 +265,7 @@ fn main() {
let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)] #[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
@ -396,7 +399,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -412,7 +415,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -466,7 +469,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,

View File

@ -7,10 +7,9 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use bytemuck::{Pod, Zeroable};
use std::{io::Cursor, sync::Arc}; use std::{io::Cursor, sync::Arc};
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents,
@ -66,7 +65,6 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, enabled_extensions: required_extensions,
// Enable enumerating devices that use non-conformant vulkan implementations. (ex. MoltenVK)
enumerate_portability: true, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -161,8 +159,8 @@ fn main() {
let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -239,11 +237,14 @@ fn main() {
image_data image_data
}) })
.collect(); .collect();
// Replace with your actual image array dimensions.
let dimensions = ImageDimensions::Dim2d { let dimensions = ImageDimensions::Dim2d {
width: 128, width: 128,
height: 128, height: 128,
array_layers: 3, array_layers: 3,
}; // Replace with your actual image array dimensions };
let image = ImmutableImage::from_iter( let image = ImmutableImage::from_iter(
&memory_allocator, &memory_allocator,
image_array_data, image_array_data,
@ -253,6 +254,7 @@ fn main() {
&mut uploads, &mut uploads,
) )
.unwrap(); .unwrap();
ImageView::new_default(image).unwrap() ImageView::new_default(image).unwrap()
}; };
@ -324,7 +326,7 @@ fn main() {
}) { }) {
Ok(r) => r, Ok(r) => r,
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
@ -340,7 +342,7 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; return;
} }
Err(e) => panic!("Failed to acquire next image: {e:?}"), Err(e) => panic!("failed to acquire next image: {e}"),
}; };
if suboptimal { if suboptimal {
@ -400,7 +402,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,
@ -437,38 +439,40 @@ fn window_size_dependent_setup(
mod vs { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 position; layout(location = 0) in vec2 position;
layout(location = 0) out vec2 tex_coords; layout(location = 0) out vec2 tex_coords;
layout(location = 1) out uint layer; layout(location = 1) out uint layer;
const float x[4] = float[](0.0, 0.0, 1.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); const float y[4] = float[](0.0, 1.0, 0.0, 1.0);
void main() { void main() {
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
tex_coords = vec2(x[gl_VertexIndex], y[gl_VertexIndex]); tex_coords = vec2(x[gl_VertexIndex], y[gl_VertexIndex]);
layer = gl_InstanceIndex; layer = gl_InstanceIndex;
}" }
",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) in vec2 tex_coords; layout(location = 0) in vec2 tex_coords;
layout(location = 1) flat in uint layer; layout(location = 1) flat in uint layer;
layout(location = 0) out vec4 f_color; 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() { void main() {
f_color = texture(tex, vec3(tex_coords, layer)); f_color = texture(tex, vec3(tex_coords, layer));
}" }
",
} }
} }

View File

@ -12,19 +12,18 @@
// This is the only example that is entirely detailed. All the other examples avoid code // This is the only example that is entirely detailed. All the other examples avoid code
// duplication by using helper functions. // duplication by using helper functions.
// //
// This example assumes that you are already more or less familiar with graphics programming // This example assumes that you are already more or less familiar with graphics programming and
// and that you want to learn Vulkan. This means that for example it won't go into details about // that you want to learn Vulkan. This means that for example it won't go into details about what a
// what a vertex or a shader is. // vertex or a shader is.
// //
// This version of the triangle example is written using dynamic rendering instead of render pass // 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 // 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 // `khr_dynamic_rendering` extension, or if you want to see how to support older versions, see the
// original triangle example. // original triangle example.
use bytemuck::{Pod, Zeroable};
use std::sync::Arc; use std::sync::Arc;
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
RenderingAttachmentInfo, RenderingInfo, RenderingAttachmentInfo, RenderingInfo,
@ -61,14 +60,15 @@ use winit::{
}; };
fn main() { fn main() {
let library = VulkanLibrary::new().unwrap();
// The first step of any Vulkan program is to create an instance. // 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. // 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 // All the window-drawing functionalities are part of non-core extensions that we need to
// to enable manually. To do so, we ask the `vulkano_win` crate for the list of extensions // enable manually. To do so, we ask the `vulkano_win` crate for the list of extensions
// required to draw to a window. // required to draw to a window.
let library = VulkanLibrary::new().unwrap();
let required_extensions = vulkano_win::required_extensions(&library); let required_extensions = vulkano_win::required_extensions(&library);
// Now creating the instance. // Now creating the instance.
@ -76,7 +76,8 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, 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, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -91,16 +92,15 @@ fn main() {
// ever get an error about `build_vk_surface` being undefined in one of your projects, this // 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. // probably means that you forgot to import this trait.
// //
// This returns a `vulkano::swapchain::Surface` object that contains both a cross-platform winit // This returns a `vulkano::swapchain::Surface` object that contains both a cross-platform
// window and a cross-platform Vulkan surface that represents the surface of the window. // winit window and a cross-platform Vulkan surface that represents the surface of the window.
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let surface = WindowBuilder::new() let surface = WindowBuilder::new()
.build_vk_surface(&event_loop, instance.clone()) .build_vk_surface(&event_loop, instance.clone())
.unwrap(); .unwrap();
// Choose device extensions that we're going to use. // Choose device extensions that we're going to use. In order to present images to a surface,
// In order to present images to a surface, we need a `Swapchain`, which is provided by the // we need a `Swapchain`, which is provided by the `khr_swapchain` extension.
// `khr_swapchain` extension.
let mut device_extensions = DeviceExtensions { let mut device_extensions = DeviceExtensions {
khr_swapchain: true, khr_swapchain: true,
..DeviceExtensions::empty() ..DeviceExtensions::empty()
@ -128,11 +128,11 @@ fn main() {
// //
// Devices can provide multiple queues to run commands in parallel (for example a draw // 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 // 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 // have to manage manually in Vulkan. Queues of the same type belong to the same queue
// queue family. // family.
// //
// Here, we look for a single queue family that is suitable for our purposes. In a // 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 // handle data transfers in parallel with graphics operations. You may also need a
// separate queue for compute operations, if your application uses those. // separate queue for compute operations, if your application uses those.
p.queue_family_properties() p.queue_family_properties()
@ -140,8 +140,8 @@ fn main() {
.enumerate() .enumerate()
.position(|(i, q)| { .position(|(i, q)| {
// We select a queue family that supports graphics operations. When drawing to // 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 // a window surface, as we do in this example, we also need to check that
// in this queue family are capable of presenting images to the surface. // queues in this queue family are capable of presenting images to the surface.
q.queue_flags.intersects(QueueFlags::GRAPHICS) q.queue_flags.intersects(QueueFlags::GRAPHICS)
&& p.surface_support(i as u32, &surface).unwrap_or(false) && p.surface_support(i as u32, &surface).unwrap_or(false)
}) })
@ -151,13 +151,12 @@ fn main() {
.map(|i| (p, i as u32)) .map(|i| (p, i as u32))
}) })
// All the physical devices that pass the filters above are suitable for the application. // 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 // However, not every device is equal, some are preferred over others. Now, we assign each
// each physical device a score, and pick the device with the // physical device a score, and pick the device with the lowest ("best") score.
// lowest ("best") score.
// //
// In this example, we simply select the best-scoring device to use in the application. // 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 // In a real-world setting, you may want to use the best-scoring device only as a "default"
// "default" or "recommended" device, and let the user choose the device themselves. // or "recommended" device, and let the user choose the device themself.
.min_by_key(|(p, _)| { .min_by_key(|(p, _)| {
// We assign a lower score to device types that are likely to be faster/better. // We assign a lower score to device types that are likely to be faster/better.
match p.properties().device_type { match p.properties().device_type {
@ -169,7 +168,7 @@ fn main() {
_ => 5, _ => 5,
} }
}) })
.expect("No suitable physical device found"); .expect("no suitable physical device found");
// Some little debug infos. // Some little debug infos.
println!( println!(
@ -189,7 +188,7 @@ fn main() {
// Now initializing the device. This is probably the most important object of Vulkan. // 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( let (device, mut queues) = Device::new(
// Which physical device to connect to. // Which physical device to connect to.
physical_device, physical_device,
@ -223,17 +222,17 @@ fn main() {
) )
.unwrap(); .unwrap();
// Since we can request multiple queues, the `queues` variable is in fact an iterator. We // Since we can request multiple queues, the `queues` variable is in fact an iterator. We only
// only use one queue in this example, so we just retrieve the first and only element of the // use one queue in this example, so we just retrieve the first and only element of the
// iterator. // iterator.
let queue = queues.next().unwrap(); let queue = queues.next().unwrap();
// Before we can draw on the surface, we have to create what is called a swapchain. Creating // Before we can draw on the surface, we have to create what is called a swapchain. Creating a
// a swapchain allocates the color buffers that will contain the image that will ultimately // swapchain allocates the color buffers that will contain the image that will ultimately be
// be visible on the screen. These images are returned alongside the swapchain. // visible on the screen. These images are returned alongside the swapchain.
let (mut swapchain, images) = { let (mut swapchain, images) = {
// Querying the capabilities of the surface. When we create the swapchain we can only // Querying the capabilities of the surface. When we create the swapchain we can only pass
// pass values that are allowed by the capabilities. // values that are allowed by the capabilities.
let surface_capabilities = device let surface_capabilities = device
.physical_device() .physical_device()
.surface_capabilities(&surface, Default::default()) .surface_capabilities(&surface, Default::default())
@ -257,17 +256,17 @@ fn main() {
min_image_count: surface_capabilities.min_image_count, min_image_count: surface_capabilities.min_image_count,
image_format, image_format,
// The dimensions of the window, only used to initially setup the swapchain. // The dimensions of the window, only used to initially setup the swapchain.
//
// NOTE: // NOTE:
// On some drivers the swapchain dimensions are specified by // On some drivers the swapchain dimensions are specified by
// `surface_capabilities.current_extent` and the swapchain size must use these // `surface_capabilities.current_extent` and the swapchain size must use these
// dimensions. // dimensions. These dimensions are always the same as the window dimensions.
// These dimensions are always the same as the window dimensions.
// //
// However, other drivers don't specify a value, i.e. // However, other drivers don't specify a value, i.e.
// `surface_capabilities.current_extent` is `None`. These drivers will allow // `surface_capabilities.current_extent` is `None`. These drivers will allow
// anything, but the only sensible value is the window // anything, but the only sensible value is the window dimensions.
// dimensions.
// //
// Both of these cases need the swapchain to use the window dimensions, so we just // Both of these cases need the swapchain to use the window dimensions, so we just
// use that. // use that.
@ -291,11 +290,11 @@ fn main() {
let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
// We now create a buffer that will store the shape of our triangle. // We now create a buffer that will store the shape of our triangle. We use `#[repr(C)]` here
// We use #[repr(C)] here to force rustc to not do anything funky with our data, although for this // to force rustc to use a defined layout for our data, as the default representation has *no
// particular example, it doesn't actually change the in-memory representation. // guarantees*.
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -324,47 +323,45 @@ fn main() {
// The next step is to create the shaders. // The next step is to create the shaders.
// //
// The raw shader creation API provided by the vulkano library is unsafe for various // The raw shader creation API provided by the vulkano library is unsafe for various reasons,
// reasons, so The `shader!` macro provides a way to generate a Rust module from GLSL // so The `shader!` macro provides a way to generate a Rust module from GLSL source - in the
// source - in the example below, the source is provided as a string input directly to // example below, the source is provided as a string input directly to the shader, but a path
// the shader, but a path to a source file can be provided as well. Note that the user // to a source file can be provided as well. Note that the user must specify the type of shader
// must specify the type of shader (e.g., "vertex," "fragment, etc.") using the `ty` // (e.g. "vertex", "fragment", etc.) using the `ty` option of the macro.
// option of the macro.
// //
// The module generated by the `shader!` macro includes a `load` function which loads // The items generated by the `shader!` macro include a `load` function which loads the shader
// the shader using an input logical device. The module also includes type definitions // using an input logical device. The module also includes type definitions for layout
// for layout structures defined in the shader source, for example, uniforms and push // structures defined in the shader source, for example uniforms and push constants.
// constants.
// //
// A more detailed overview of what the `shader!` macro generates can be found in the // 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 { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
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_Position = vec4(position, 0.0, 1.0);
} }
" ",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
void main() { void main() {
f_color = vec4(1.0, 0.0, 0.0, 1.0); f_color = vec4(1.0, 0.0, 0.0, 1.0);
} }
" ",
} }
} }
@ -392,8 +389,8 @@ fn main() {
.vertex_input_state(Vertex::per_vertex()) .vertex_input_state(Vertex::per_vertex())
// The content of the vertex buffer describes a list of triangles. // The content of the vertex buffer describes a list of triangles.
.input_assembly_state(InputAssemblyState::new()) .input_assembly_state(InputAssemblyState::new())
// A Vulkan shader can in theory contain multiple entry points, so we have to specify // A Vulkan shader can in theory contain multiple entry points, so we have to specify which
// which one. // one.
.vertex_shader(vs.entry_point("main").unwrap(), ()) .vertex_shader(vs.entry_point("main").unwrap(), ())
// Use a resizable viewport set to draw over the entire window // Use a resizable viewport set to draw over the entire window
.viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant()) .viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant())
@ -403,7 +400,7 @@ fn main() {
.build(device.clone()) .build(device.clone())
.unwrap(); .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. // Otherwise we would have to recreate the whole pipeline.
let mut viewport = Viewport { let mut viewport = Viewport {
origin: [0.0, 0.0], 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. // 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. // 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. // To continue rendering, we need to recreate the swapchain by creating a new swapchain. Here,
// Here, we remember that we need to do this for the next loop iteration. // we remember that we need to do this for the next loop iteration.
let mut recreate_swapchain = false; let mut recreate_swapchain = false;
// In the loop below we are going to submit commands to the GPU. Submitting a command produces // 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; recreate_swapchain = true;
} }
Event::RedrawEventsCleared => { Event::RedrawEventsCleared => {
// It is important to call this function from time to time, otherwise resources will keep // It is important to call this function from time to time, otherwise resources
// accumulating and you will eventually reach an out of memory error. // 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 // Calling this function polls various fences in order to determine what the GPU
// already processed, and frees the resources that are no longer needed. // has already processed, and frees the resources that are no longer needed.
previous_frame_end.as_mut().unwrap().cleanup_finished(); previous_frame_end.as_mut().unwrap().cleanup_finished();
// Whenever the window resizes we need to recreate everything dependent on the window size. // Whenever the window resizes we need to recreate everything dependent on the
// In this example that includes the swapchain, the framebuffers and the dynamic state viewport. // window size. In this example that includes the swapchain, the framebuffers and
// the dynamic state viewport.
if recreate_swapchain { if recreate_swapchain {
// Get the new dimensions of the window. // Get the new dimensions of the window.
let window = surface.object().unwrap().downcast_ref::<Window>().unwrap(); let window = surface.object().unwrap().downcast_ref::<Window>().unwrap();
@ -478,27 +476,30 @@ fn main() {
..swapchain.create_info() ..swapchain.create_info()
}) { }) {
Ok(r) => r, Ok(r) => r,
// This error tends to happen when the user is manually resizing the window. // This error tends to happen when the user is manually resizing the
// Simply restarting the loop is the easiest way to fix this issue. // window. Simply restarting the loop is the easiest way to fix this
// issue.
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; swapchain = new_swapchain;
// Now that we have new swapchain images, we must create new image views from // Now that we have new swapchain images, we must create new image views from
// them as well. // them as well.
attachment_image_views = attachment_image_views =
window_size_dependent_setup(&new_images, &mut viewport); window_size_dependent_setup(&new_images, &mut viewport);
recreate_swapchain = false; recreate_swapchain = false;
} }
// Before we can draw on the output, we have to *acquire* an image from the swapchain. If // Before we can draw on the output, we have to *acquire* an image from the
// no image is available (which happens if you submit draw commands too quickly), then the // swapchain. If no image is available (which happens if you submit draw commands
// function will block. // too quickly), then the function will block. This operation returns the index of
// This operation returns the index of the image that we are allowed to draw upon. // the image that we are allowed to draw upon.
// //
// This function can block if no image is available. The parameter is an optional timeout // This function can block if no image is available. The parameter is an optional
// after which the function call will return an error. // timeout after which the function call will return an error.
let (image_index, suboptimal, acquire_future) = let (image_index, suboptimal, acquire_future) =
match acquire_next_image(swapchain.clone(), None) { match acquire_next_image(swapchain.clone(), None) {
Ok(r) => r, Ok(r) => r,
@ -506,25 +507,26 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; 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 // `acquire_next_image` can be successful, but suboptimal. This means that the
// will still work, but it may not display correctly. With some drivers this can be when // swapchain image will still work, but it may not display correctly. With some
// the window resizes, but it may not cause the swapchain to become out of date. // drivers this can be when the window resizes, but it may not cause the swapchain
// to become out of date.
if suboptimal { if suboptimal {
recreate_swapchain = true; recreate_swapchain = true;
} }
// In order to draw, we have to build a *command buffer*. The command buffer object holds // In order to draw, we have to build a *command buffer*. The command buffer object
// the list of commands that are going to be executed. // holds the list of commands that are going to be executed.
// //
// Building a command buffer is an expensive operation (usually a few hundred // 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 // microseconds), but it is known to be a hot path in the driver and is expected to
// optimized. // be optimized.
// //
// Note that we have to pass a queue family when we create the command buffer. The command // Note that we have to pass a queue family when we create the command buffer. The
// buffer will only be executable on that given queue family. // command buffer will only be executable on that given queue family.
let mut builder = AutoCommandBufferBuilder::primary( let mut builder = AutoCommandBufferBuilder::primary(
&command_buffer_allocator, &command_buffer_allocator,
queue.queue_family_index(), queue.queue_family_index(),
@ -537,20 +539,20 @@ fn main() {
// attachments we are going to use for rendering here, which needs to match // attachments we are going to use for rendering here, which needs to match
// what was previously specified when creating the pipeline. // what was previously specified when creating the pipeline.
.begin_rendering(RenderingInfo { .begin_rendering(RenderingInfo {
// As before, we specify one color attachment, but now we specify // As before, we specify one color attachment, but now we specify the image
// the image view to use as well as how it should be used. // view to use as well as how it should be used.
color_attachments: vec![Some(RenderingAttachmentInfo { color_attachments: vec![Some(RenderingAttachmentInfo {
// `Clear` means that we ask the GPU to clear the content of this // `Clear` means that we ask the GPU to clear the content of this
// attachment at the start of rendering. // attachment at the start of rendering.
load_op: LoadOp::Clear, load_op: LoadOp::Clear,
// `Store` means that we ask the GPU to store the rendered output // `Store` means that we ask the GPU to store the rendered output in
// in the attachment image. We could also ask it to discard the result. // the attachment image. We could also ask it to discard the result.
store_op: StoreOp::Store, store_op: StoreOp::Store,
// The value to clear the attachment with. Here we clear it with a // The value to clear the attachment with. Here we clear it with a blue
// blue color. // color.
// //
// Only attachments that have `LoadOp::Clear` are provided with // Only attachments that have `LoadOp::Clear` are provided with clear
// clear values, any others should use `None` as the clear value. // values, any others should use `None` as the clear value.
clear_value: Some([0.0, 0.0, 1.0, 1.0].into()), clear_value: Some([0.0, 0.0, 1.0, 1.0].into()),
..RenderingAttachmentInfo::image_view( ..RenderingAttachmentInfo::image_view(
// We specify image view corresponding to the currently acquired // We specify image view corresponding to the currently acquired
@ -561,13 +563,13 @@ fn main() {
..Default::default() ..Default::default()
}) })
.unwrap() .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. // TODO: Document state setting and how it affects subsequent draw commands.
// Since we used an `EmptyPipeline` object, the objects have to be `()`.
.set_viewport(0, [viewport.clone()]) .set_viewport(0, [viewport.clone()])
.bind_pipeline_graphics(pipeline.clone()) .bind_pipeline_graphics(pipeline.clone())
.bind_vertex_buffers(0, vertex_buffer.clone()) .bind_vertex_buffers(0, vertex_buffer.clone())
// We add a draw command.
.draw(vertex_buffer.len() as u32, 1, 0, 0) .draw(vertex_buffer.len() as u32, 1, 0, 0)
.unwrap() .unwrap()
// We leave the render pass. // We leave the render pass.
@ -583,12 +585,14 @@ fn main() {
.join(acquire_future) .join(acquire_future)
.then_execute(queue.clone(), command_buffer) .then_execute(queue.clone(), command_buffer)
.unwrap() .unwrap()
// The color output is now expected to contain our triangle. But in order to show it on // The color output is now expected to contain our triangle. But in order to
// the screen, we have to *present* the image by calling `present`. // 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 // This function does not actually present the image immediately. Instead it
// present command at the end of the queue. This means that it will only be presented once // submits a present command at the end of the queue. This means that it will
// the GPU has finished executing the command buffer that draws the triangle. // only be presented once the GPU has finished executing the command buffer
// that draws the triangle.
.then_swapchain_present( .then_swapchain_present(
queue.clone(), queue.clone(),
SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index), SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index),
@ -604,7 +608,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
println!("Failed to flush future: {e:?}"); println!("failed to flush future: {e}");
previous_frame_end = Some(sync::now(device.clone()).boxed()); 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
viewport: &mut Viewport, viewport: &mut Viewport,

View File

@ -12,14 +12,13 @@
// This is the only example that is entirely detailed. All the other examples avoid code // This is the only example that is entirely detailed. All the other examples avoid code
// duplication by using helper functions. // duplication by using helper functions.
// //
// This example assumes that you are already more or less familiar with graphics programming // This example assumes that you are already more or less familiar with graphics programming and
// and that you want to learn Vulkan. This means that for example it won't go into details about // that you want to learn Vulkan. This means that for example it won't go into details about what a
// what a vertex or a shader is. // vertex or a shader is.
use bytemuck::{Pod, Zeroable};
use std::sync::Arc; use std::sync::Arc;
use vulkano::{ use vulkano::{
buffer::{Buffer, BufferAllocateInfo, BufferUsage}, buffer::{Buffer, BufferAllocateInfo, BufferContents, BufferUsage},
command_buffer::{ command_buffer::{
allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage,
RenderPassBeginInfo, SubpassContents, RenderPassBeginInfo, SubpassContents,
@ -55,14 +54,15 @@ use winit::{
}; };
fn main() { fn main() {
let library = VulkanLibrary::new().unwrap();
// The first step of any Vulkan program is to create an instance. // 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. // 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 // All the window-drawing functionalities are part of non-core extensions that we need to
// to enable manually. To do so, we ask the `vulkano_win` crate for the list of extensions // enable manually. To do so, we ask the `vulkano_win` crate for the list of extensions
// required to draw to a window. // required to draw to a window.
let library = VulkanLibrary::new().unwrap();
let required_extensions = vulkano_win::required_extensions(&library); let required_extensions = vulkano_win::required_extensions(&library);
// Now creating the instance. // Now creating the instance.
@ -70,7 +70,8 @@ fn main() {
library, library,
InstanceCreateInfo { InstanceCreateInfo {
enabled_extensions: required_extensions, 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, enumerate_portability: true,
..Default::default() ..Default::default()
}, },
@ -85,16 +86,15 @@ fn main() {
// ever get an error about `build_vk_surface` being undefined in one of your projects, this // 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. // probably means that you forgot to import this trait.
// //
// This returns a `vulkano::swapchain::Surface` object that contains both a cross-platform winit // This returns a `vulkano::swapchain::Surface` object that contains both a cross-platform
// window and a cross-platform Vulkan surface that represents the surface of the window. // winit window and a cross-platform Vulkan surface that represents the surface of the window.
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let surface = WindowBuilder::new() let surface = WindowBuilder::new()
.build_vk_surface(&event_loop, instance.clone()) .build_vk_surface(&event_loop, instance.clone())
.unwrap(); .unwrap();
// Choose device extensions that we're going to use. // Choose device extensions that we're going to use. In order to present images to a surface,
// In order to present images to a surface, we need a `Swapchain`, which is provided by the // we need a `Swapchain`, which is provided by the `khr_swapchain` extension.
// `khr_swapchain` extension.
let device_extensions = DeviceExtensions { let device_extensions = DeviceExtensions {
khr_swapchain: true, khr_swapchain: true,
..DeviceExtensions::empty() ..DeviceExtensions::empty()
@ -117,11 +117,11 @@ fn main() {
// //
// Devices can provide multiple queues to run commands in parallel (for example a draw // 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 // 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 // have to manage manually in Vulkan. Queues of the same type belong to the same queue
// queue family. // family.
// //
// Here, we look for a single queue family that is suitable for our purposes. In a // 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 // handle data transfers in parallel with graphics operations. You may also need a
// separate queue for compute operations, if your application uses those. // separate queue for compute operations, if your application uses those.
p.queue_family_properties() p.queue_family_properties()
@ -129,8 +129,8 @@ fn main() {
.enumerate() .enumerate()
.position(|(i, q)| { .position(|(i, q)| {
// We select a queue family that supports graphics operations. When drawing to // 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 // a window surface, as we do in this example, we also need to check that
// in this queue family are capable of presenting images to the surface. // queues in this queue family are capable of presenting images to the surface.
q.queue_flags.intersects(QueueFlags::GRAPHICS) q.queue_flags.intersects(QueueFlags::GRAPHICS)
&& p.surface_support(i as u32, &surface).unwrap_or(false) && p.surface_support(i as u32, &surface).unwrap_or(false)
}) })
@ -140,13 +140,12 @@ fn main() {
.map(|i| (p, i as u32)) .map(|i| (p, i as u32))
}) })
// All the physical devices that pass the filters above are suitable for the application. // 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 // However, not every device is equal, some are preferred over others. Now, we assign each
// each physical device a score, and pick the device with the // physical device a score, and pick the device with the lowest ("best") score.
// lowest ("best") score.
// //
// In this example, we simply select the best-scoring device to use in the application. // 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 // In a real-world setting, you may want to use the best-scoring device only as a "default"
// "default" or "recommended" device, and let the user choose the device themselves. // or "recommended" device, and let the user choose the device themself.
.min_by_key(|(p, _)| { .min_by_key(|(p, _)| {
// We assign a lower score to device types that are likely to be faster/better. // We assign a lower score to device types that are likely to be faster/better.
match p.properties().device_type { match p.properties().device_type {
@ -158,7 +157,7 @@ fn main() {
_ => 5, _ => 5,
} }
}) })
.expect("No suitable physical device found"); .expect("no suitable physical device found");
// Some little debug infos. // Some little debug infos.
println!( println!(
@ -169,7 +168,7 @@ fn main() {
// Now initializing the device. This is probably the most important object of Vulkan. // 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( let (device, mut queues) = Device::new(
// Which physical device to connect to. // Which physical device to connect to.
physical_device, physical_device,
@ -192,17 +191,17 @@ fn main() {
) )
.unwrap(); .unwrap();
// Since we can request multiple queues, the `queues` variable is in fact an iterator. We // Since we can request multiple queues, the `queues` variable is in fact an iterator. We only
// only use one queue in this example, so we just retrieve the first and only element of the // use one queue in this example, so we just retrieve the first and only element of the
// iterator. // iterator.
let queue = queues.next().unwrap(); let queue = queues.next().unwrap();
// Before we can draw on the surface, we have to create what is called a swapchain. Creating // Before we can draw on the surface, we have to create what is called a swapchain. Creating a
// a swapchain allocates the color buffers that will contain the image that will ultimately // swapchain allocates the color buffers that will contain the image that will ultimately be
// be visible on the screen. These images are returned alongside the swapchain. // visible on the screen. These images are returned alongside the swapchain.
let (mut swapchain, images) = { let (mut swapchain, images) = {
// Querying the capabilities of the surface. When we create the swapchain we can only // Querying the capabilities of the surface. When we create the swapchain we can only pass
// pass values that are allowed by the capabilities. // values that are allowed by the capabilities.
let surface_capabilities = device let surface_capabilities = device
.physical_device() .physical_device()
.surface_capabilities(&surface, Default::default()) .surface_capabilities(&surface, Default::default())
@ -226,17 +225,17 @@ fn main() {
min_image_count: surface_capabilities.min_image_count, min_image_count: surface_capabilities.min_image_count,
image_format, image_format,
// The dimensions of the window, only used to initially setup the swapchain. // The dimensions of the window, only used to initially setup the swapchain.
//
// NOTE: // NOTE:
// On some drivers the swapchain dimensions are specified by // On some drivers the swapchain dimensions are specified by
// `surface_capabilities.current_extent` and the swapchain size must use these // `surface_capabilities.current_extent` and the swapchain size must use these
// dimensions. // dimensions. These dimensions are always the same as the window dimensions.
// These dimensions are always the same as the window dimensions.
// //
// However, other drivers don't specify a value, i.e. // However, other drivers don't specify a value, i.e.
// `surface_capabilities.current_extent` is `None`. These drivers will allow // `surface_capabilities.current_extent` is `None`. These drivers will allow
// anything, but the only sensible value is the window // anything, but the only sensible value is the window dimensions.
// dimensions.
// //
// Both of these cases need the swapchain to use the window dimensions, so we just // Both of these cases need the swapchain to use the window dimensions, so we just
// use that. // use that.
@ -260,11 +259,11 @@ fn main() {
let memory_allocator = StandardMemoryAllocator::new_default(device.clone()); let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
// We now create a buffer that will store the shape of our triangle. // We now create a buffer that will store the shape of our triangle. We use `#[repr(C)]` here
// We use #[repr(C)] here to force rustc to not do anything funky with our data, although for this // to force rustc to use a defined layout for our data, as the default representation has *no
// particular example, it doesn't actually change the in-memory representation. // guarantees*.
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
struct Vertex { struct Vertex {
#[format(R32G32_SFLOAT)] #[format(R32G32_SFLOAT)]
position: [f32; 2], position: [f32; 2],
@ -293,47 +292,45 @@ fn main() {
// The next step is to create the shaders. // The next step is to create the shaders.
// //
// The raw shader creation API provided by the vulkano library is unsafe for various // The raw shader creation API provided by the vulkano library is unsafe for various reasons,
// reasons, so The `shader!` macro provides a way to generate a Rust module from GLSL // so The `shader!` macro provides a way to generate a Rust module from GLSL source - in the
// source - in the example below, the source is provided as a string input directly to // example below, the source is provided as a string input directly to the shader, but a path
// the shader, but a path to a source file can be provided as well. Note that the user // to a source file can be provided as well. Note that the user must specify the type of shader
// must specify the type of shader (e.g., "vertex," "fragment, etc.") using the `ty` // (e.g. "vertex", "fragment", etc.) using the `ty` option of the macro.
// option of the macro.
// //
// The module generated by the `shader!` macro includes a `load` function which loads // The items generated by the `shader!` macro include a `load` function which loads the shader
// the shader using an input logical device. The module also includes type definitions // using an input logical device. The module also includes type definitions for layout
// for layout structures defined in the shader source, for example, uniforms and push // structures defined in the shader source, for example uniforms and push constants.
// constants.
// //
// A more detailed overview of what the `shader!` macro generates can be found in the // 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 { mod vs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "vertex", ty: "vertex",
src: " src: r"
#version 450 #version 450
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_Position = vec4(position, 0.0, 1.0);
} }
" ",
} }
} }
mod fs { mod fs {
vulkano_shaders::shader! { vulkano_shaders::shader! {
ty: "fragment", ty: "fragment",
src: " src: r"
#version 450 #version 450
layout(location = 0) out vec4 f_color; layout(location = 0) out vec4 f_color;
void main() { void main() {
f_color = vec4(1.0, 0.0, 0.0, 1.0); f_color = vec4(1.0, 0.0, 0.0, 1.0);
} }
" ",
} }
} }
@ -345,27 +342,28 @@ fn main() {
// manually. // manually.
// The next step is to create a *render pass*, which is an object that describes where the // 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 // output of the graphics pipeline will go. It describes the layout of the images where the
// where the colors, depth and/or stencil information will be written. // colors, depth and/or stencil information will be written.
let render_pass = vulkano::single_pass_renderpass!( let render_pass = vulkano::single_pass_renderpass!(
device.clone(), device.clone(),
attachments: { attachments: {
// `color` is a custom name we give to the first and only attachment. // `color` is a custom name we give to the first and only attachment.
color: { color: {
// `load: Clear` means that we ask the GPU to clear the content of this // `load: Clear` means that we ask the GPU to clear the content of this attachment
// attachment at the start of the drawing. // at the start of the drawing.
load: Clear, load: Clear,
// `store: Store` means that we ask the GPU to store the output of the draw // `store: Store` means that we ask the GPU to store the output of the draw in the
// in the actual image. We could also ask it to discard the result. // actual image. We could also ask it to discard the result.
store: Store, store: Store,
// `format: <ty>` indicates the type of the format of the image. This has to // `format: <ty>` indicates the type of the format of the image. This has to be one
// be one of the types of the `vulkano::format` module (or alternatively one // of the types of the `vulkano::format` module (or alternatively one of your
// of your structs that implements the `FormatDesc` trait). Here we use the // structs that implements the `FormatDesc` trait). Here we use the same format as
// same format as the swapchain. // the swapchain.
format: swapchain.image_format(), format: swapchain.image_format(),
// `samples: 1` means that we ask the GPU to use one sample to determine the value // `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) // of each pixel in the color attachment. We could use a larger value
// for antialiasing. An example of this can be found in msaa-renderpass.rs. // (multisampling) for antialiasing. An example of this can be found in
// msaa-renderpass.rs.
samples: 1, samples: 1,
} }
}, },
@ -399,7 +397,7 @@ fn main() {
.build(device.clone()) .build(device.clone())
.unwrap(); .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. // Otherwise we would have to recreate the whole pipeline.
let mut viewport = Viewport { let mut viewport = Viewport {
origin: [0.0, 0.0], 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. // 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. // 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. // To continue rendering, we need to recreate the swapchain by creating a new swapchain. Here,
// Here, we remember that we need to do this for the next loop iteration. // we remember that we need to do this for the next loop iteration.
let mut recreate_swapchain = false; let mut recreate_swapchain = false;
// In the loop below we are going to submit commands to the GPU. Submitting a command produces // 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; recreate_swapchain = true;
} }
Event::RedrawEventsCleared => { Event::RedrawEventsCleared => {
// Do not draw frame when screen dimensions are zero. // Do not draw the frame when the screen dimensions are zero. On Windows, this can
// On Windows, this can occur from minimizing the application. // occur when minimizing the application.
let window = surface.object().unwrap().downcast_ref::<Window>().unwrap(); let window = surface.object().unwrap().downcast_ref::<Window>().unwrap();
let dimensions = window.inner_size(); let dimensions = window.inner_size();
if dimensions.width == 0 || dimensions.height == 0 { if dimensions.width == 0 || dimensions.height == 0 {
return; return;
} }
// It is important to call this function from time to time, otherwise resources will keep // It is important to call this function from time to time, otherwise resources
// accumulating and you will eventually reach an out of memory error. // 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 // Calling this function polls various fences in order to determine what the GPU
// already processed, and frees the resources that are no longer needed. // has already processed, and frees the resources that are no longer needed.
previous_frame_end.as_mut().unwrap().cleanup_finished(); previous_frame_end.as_mut().unwrap().cleanup_finished();
// Whenever the window resizes we need to recreate everything dependent on the window size. // Whenever the window resizes we need to recreate everything dependent on the
// In this example that includes the swapchain, the framebuffers and the dynamic state viewport. // window size. In this example that includes the swapchain, the framebuffers and
// the dynamic state viewport.
if recreate_swapchain { if recreate_swapchain {
// Use the new dimensions of the window. // Use the new dimensions of the window.
@ -481,30 +480,33 @@ fn main() {
..swapchain.create_info() ..swapchain.create_info()
}) { }) {
Ok(r) => r, Ok(r) => r,
// This error tends to happen when the user is manually resizing the window. // This error tends to happen when the user is manually resizing the
// Simply restarting the loop is the easiest way to fix this issue. // window. Simply restarting the loop is the easiest way to fix this
// issue.
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
Err(e) => panic!("Failed to recreate swapchain: {e:?}"), Err(e) => panic!("failed to recreate swapchain: {e}"),
}; };
swapchain = new_swapchain; 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. // recreate framebuffers as well.
framebuffers = window_size_dependent_setup( framebuffers = window_size_dependent_setup(
&new_images, &new_images,
render_pass.clone(), render_pass.clone(),
&mut viewport, &mut viewport,
); );
recreate_swapchain = false; recreate_swapchain = false;
} }
// Before we can draw on the output, we have to *acquire* an image from the swapchain. If // Before we can draw on the output, we have to *acquire* an image from the
// no image is available (which happens if you submit draw commands too quickly), then the // swapchain. If no image is available (which happens if you submit draw commands
// function will block. // too quickly), then the function will block. This operation returns the index of
// This operation returns the index of the image that we are allowed to draw upon. // the image that we are allowed to draw upon.
// //
// This function can block if no image is available. The parameter is an optional timeout // This function can block if no image is available. The parameter is an optional
// after which the function call will return an error. // timeout after which the function call will return an error.
let (image_index, suboptimal, acquire_future) = let (image_index, suboptimal, acquire_future) =
match acquire_next_image(swapchain.clone(), None) { match acquire_next_image(swapchain.clone(), None) {
Ok(r) => r, Ok(r) => r,
@ -512,25 +514,26 @@ fn main() {
recreate_swapchain = true; recreate_swapchain = true;
return; 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 // `acquire_next_image` can be successful, but suboptimal. This means that the
// will still work, but it may not display correctly. With some drivers this can be when // swapchain image will still work, but it may not display correctly. With some
// the window resizes, but it may not cause the swapchain to become out of date. // drivers this can be when the window resizes, but it may not cause the swapchain
// to become out of date.
if suboptimal { if suboptimal {
recreate_swapchain = true; recreate_swapchain = true;
} }
// In order to draw, we have to build a *command buffer*. The command buffer object holds // In order to draw, we have to build a *command buffer*. The command buffer object
// the list of commands that are going to be executed. // holds the list of commands that are going to be executed.
// //
// Building a command buffer is an expensive operation (usually a few hundred // 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 // microseconds), but it is known to be a hot path in the driver and is expected to
// optimized. // be optimized.
// //
// Note that we have to pass a queue family when we create the command buffer. The command // Note that we have to pass a queue family when we create the command buffer. The
// buffer will only be executable on that given queue family. // command buffer will only be executable on that given queue family.
let mut builder = AutoCommandBufferBuilder::primary( let mut builder = AutoCommandBufferBuilder::primary(
&command_buffer_allocator, &command_buffer_allocator,
queue.queue_family_index(), queue.queue_family_index(),
@ -543,12 +546,13 @@ fn main() {
.begin_render_pass( .begin_render_pass(
RenderPassBeginInfo { RenderPassBeginInfo {
// A list of values to clear the attachments with. This list contains // A list of values to clear the attachments with. This list contains
// one item for each attachment in the render pass. In this case, // one item for each attachment in the render pass. In this case, there
// there is only one attachment, and we clear it with a blue color. // is only one attachment, and we clear it with a blue color.
// //
// Only attachments that have `LoadOp::Clear` are provided with clear // Only attachments that have `LoadOp::Clear` are provided with clear
// values, any others should use `ClearValue::None` as the clear value. // values, any others should use `ClearValue::None` as the clear value.
clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into())], clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into())],
..RenderPassBeginInfo::framebuffer( ..RenderPassBeginInfo::framebuffer(
framebuffers[image_index as usize].clone(), framebuffers[image_index as usize].clone(),
) )
@ -559,17 +563,17 @@ fn main() {
SubpassContents::Inline, SubpassContents::Inline,
) )
.unwrap() .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. // TODO: Document state setting and how it affects subsequent draw commands.
// Since we used an `EmptyPipeline` object, the objects have to be `()`.
.set_viewport(0, [viewport.clone()]) .set_viewport(0, [viewport.clone()])
.bind_pipeline_graphics(pipeline.clone()) .bind_pipeline_graphics(pipeline.clone())
.bind_vertex_buffers(0, vertex_buffer.clone()) .bind_vertex_buffers(0, vertex_buffer.clone())
// We add a draw command.
.draw(vertex_buffer.len() as u32, 1, 0, 0) .draw(vertex_buffer.len() as u32, 1, 0, 0)
.unwrap() .unwrap()
// We leave the render pass. Note that if we had multiple // We leave the render pass. Note that if we had multiple subpasses we could
// subpasses we could have called `next_subpass` to jump to the next subpass. // have called `next_subpass` to jump to the next subpass.
.end_render_pass() .end_render_pass()
.unwrap(); .unwrap();
@ -582,12 +586,14 @@ fn main() {
.join(acquire_future) .join(acquire_future)
.then_execute(queue.clone(), command_buffer) .then_execute(queue.clone(), command_buffer)
.unwrap() .unwrap()
// The color output is now expected to contain our triangle. But in order to show it on // The color output is now expected to contain our triangle. But in order to
// the screen, we have to *present* the image by calling `present`. // 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 // This function does not actually present the image immediately. Instead it
// present command at the end of the queue. This means that it will only be presented once // submits a present command at the end of the queue. This means that it will
// the GPU has finished executing the command buffer that draws the triangle. // only be presented once the GPU has finished executing the command buffer
// that draws the triangle.
.then_swapchain_present( .then_swapchain_present(
queue.clone(), queue.clone(),
SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index), SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index),
@ -603,7 +609,7 @@ fn main() {
previous_frame_end = Some(sync::now(device.clone()).boxed()); previous_frame_end = Some(sync::now(device.clone()).boxed());
} }
Err(e) => { Err(e) => {
panic!("Failed to flush future: {e:?}"); panic!("failed to flush future: {e}");
// previous_frame_end = Some(sync::now(device.clone()).boxed()); // 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( fn window_size_dependent_setup(
images: &[Arc<SwapchainImage>], images: &[Arc<SwapchainImage>],
render_pass: Arc<RenderPass>, render_pass: Arc<RenderPass>,

View File

@ -7,11 +7,10 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use bytemuck::{Pod, Zeroable}; use vulkano::{buffer::BufferContents, pipeline::graphics::vertex_input::Vertex};
use vulkano::pipeline::graphics::vertex_input::Vertex;
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
pub struct Position { pub struct Position {
#[format(R32G32B32_SFLOAT)] #[format(R32G32B32_SFLOAT)]
position: [f32; 3], position: [f32; 3],
@ -1614,8 +1613,8 @@ pub const POSITIONS: [Position; 531] = [
}, },
]; ];
#[derive(BufferContents, Vertex)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
pub struct Normal { pub struct Normal {
#[format(R32G32B32_SFLOAT)] #[format(R32G32B32_SFLOAT)]
normal: [f32; 3], normal: [f32; 3],

View File

@ -2,7 +2,10 @@
name = "vulkano-shaders" name = "vulkano-shaders"
version = "0.32.0" version = "0.32.0"
edition = "2021" edition = "2021"
authors = ["Pierre Krieger <pierre.krieger1708@gmail.com>", "The vulkano contributors"] authors = [
"Pierre Krieger <pierre.krieger1708@gmail.com>",
"The vulkano contributors",
]
repository = "https://github.com/vulkano-rs/vulkano" repository = "https://github.com/vulkano-rs/vulkano"
description = "Shaders rust code generation macro" description = "Shaders rust code generation macro"
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
@ -24,7 +27,5 @@ syn = { version = "1.0", features = ["full", "extra-traits"] }
vulkano = { version = "0.32.0", path = "../vulkano" } vulkano = { version = "0.32.0", path = "../vulkano" }
[features] [features]
cgmath = []
nalgebra = []
shaderc-build-from-source = ["shaderc/build-from-source"] shaderc-build-from-source = ["shaderc/build-from-source"]
shaderc-debug = [] shaderc-debug = []

View File

@ -7,27 +7,31 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use crate::{entry_point, read_file_to_string, structs, LinAlgType, RegisteredType, TypesMeta}; use crate::{
use ahash::HashMap; entry_point,
structs::{self, TypeRegistry},
MacroInput,
};
use heck::ToSnakeCase;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
pub use shaderc::{CompilationArtifact, IncludeType, ResolvedInclude, ShaderKind}; pub use shaderc::{CompilationArtifact, IncludeType, ResolvedInclude, ShaderKind};
use shaderc::{CompileOptions, Compiler, EnvVersion, SpirvVersion, TargetEnv}; use shaderc::{CompileOptions, Compiler, EnvVersion, TargetEnv};
use std::{ use std::{
cell::{RefCell, RefMut}, cell::RefCell,
io::Error as IoError, fs,
iter::Iterator, iter::Iterator,
path::Path, path::{Path, PathBuf},
}; };
use vulkano::shader::{ use syn::{Error, LitStr};
reflect, use vulkano::{
spirv::{Spirv, SpirvError}, shader::{reflect, spirv::Spirv},
Version,
}; };
pub(super) fn path_to_str(path: &Path) -> &str { pub struct Shader {
path.to_str().expect( pub source: LitStr,
"Could not stringify the file to be included. Make sure the path consists of \ pub name: String,
valid unicode characters.", pub spirv: Spirv,
)
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -36,40 +40,39 @@ fn include_callback(
directive_type: IncludeType, directive_type: IncludeType,
contained_within_path_raw: &str, contained_within_path_raw: &str,
recursion_depth: usize, recursion_depth: usize,
include_directories: &[impl AsRef<Path>], include_directories: &[PathBuf],
root_source_has_path: bool, root_source_has_path: bool,
base_path: &impl AsRef<Path>, base_path: &Path,
mut includes_tracker: RefMut<'_, Vec<String>>, includes: &mut Vec<String>,
) -> Result<ResolvedInclude, String> { ) -> Result<ResolvedInclude, String> {
let file_to_include = match directive_type { let file_to_include = match directive_type {
IncludeType::Relative => { IncludeType::Relative => {
let requested_source_path = Path::new(requested_source_path_raw); let requested_source_path = Path::new(requested_source_path_raw);
// Is embedded current shader source embedded within a rust macro? // If the shader source is embedded within the macro, abort unless we get an absolute
// If so, abort unless absolute path. // path.
if !root_source_has_path && recursion_depth == 1 && !requested_source_path.is_absolute() if !root_source_has_path && recursion_depth == 1 && !requested_source_path.is_absolute()
{ {
let requested_source_name = requested_source_path let requested_source_name = requested_source_path
.file_name() .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(); .to_string_lossy();
let requested_source_directory = requested_source_path let requested_source_directory = requested_source_path
.parent() .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(); .to_string_lossy();
return Err(format!( return Err(format!(
"Usage of relative paths in imports in embedded GLSL is not \ "usage of relative paths in imports in embedded GLSL is not allowed, try \
allowed, try using `#include <{}>` and adding the directory \ using `#include <{}>` and adding the directory `{}` to the `include` array in \
`{}` to the `include` array in your `shader!` macro call \ your `shader!` macro call instead",
instead.", requested_source_name, requested_source_directory,
requested_source_name, requested_source_directory
)); ));
} }
let mut resolved_path = if recursion_depth == 1 { let mut resolved_path = if recursion_depth == 1 {
Path::new(contained_within_path_raw) Path::new(contained_within_path_raw)
.parent() .parent()
.map(|parent| base_path.as_ref().join(parent)) .map(|parent| base_path.join(parent))
} else { } else {
Path::new(contained_within_path_raw) Path::new(contained_within_path_raw)
.parent() .parent()
@ -77,17 +80,17 @@ fn include_callback(
} }
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!( panic!(
"The file `{}` does not reside in a directory. This is \ "the file `{}` does not reside in a directory, this is an implementation \
an implementation error.", error",
contained_within_path_raw contained_within_path_raw,
) )
}); });
resolved_path.push(requested_source_path); resolved_path.push(requested_source_path);
if !resolved_path.is_file() { if !resolved_path.is_file() {
return Err(format!( return Err(format!(
"Invalid inclusion path `{}`, the path does not point to a file.", "invalid inclusion path `{}`, the path does not point to a file",
requested_source_path_raw requested_source_path_raw,
)); ));
} }
@ -101,79 +104,78 @@ fn include_callback(
// in the relative include directive or when using absolute paths in a standard // in the relative include directive or when using absolute paths in a standard
// include directive. // include directive.
return Err(format!( return Err(format!(
"No such file found, as specified by the absolute path. \ "no such file found as specified by the absolute path; keep in mind that \
Keep in mind, that absolute paths cannot be used with \ absolute paths cannot be used with inclusion from standard directories \
inclusion from standard directories (`#include <...>`), try \ (`#include <...>`), try using `#include \"...\"` instead; requested path: {}",
using `#include \"...\"` instead. Requested path: {}", requested_source_path_raw,
requested_source_path_raw
)); ));
} }
let found_requested_source_path = include_directories let found_requested_source_path = include_directories
.iter() .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()); .find(|resolved_requested_source_path| resolved_requested_source_path.is_file());
if let Some(found_requested_source_path) = found_requested_source_path { if let Some(found_requested_source_path) = found_requested_source_path {
found_requested_source_path found_requested_source_path
} else { } else {
return Err(format!( return Err(format!(
"Could not include the file `{}` from any include directories.", "failed to include the file `{}` from any include directories",
requested_source_path_raw requested_source_path_raw,
)); ));
} }
} }
}; };
let file_to_include_string = path_to_str(file_to_include.as_path()).to_string(); let content = fs::read_to_string(file_to_include.as_path()).map_err(|err| {
let content = read_file_to_string(file_to_include.as_path()).map_err(|_| {
format!( format!(
"Could not read the contents of file `{}` to be included in the \ "failed to read the contents of file `{file_to_include:?}` to be included in the \
shader source.", shader source: {err}",
&file_to_include_string
) )
})?; })?;
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 { Ok(ResolvedInclude {
resolved_name: file_to_include_string, resolved_name,
content, content,
}) })
} }
#[allow(clippy::too_many_arguments)] pub(super) fn compile(
pub fn compile( input: &MacroInput,
path: Option<String>, path: Option<String>,
base_path: &impl AsRef<Path>, base_path: &Path,
code: &str, code: &str,
ty: ShaderKind, shader_kind: ShaderKind,
include_directories: &[impl AsRef<Path>],
macro_defines: &[(impl AsRef<str>, impl AsRef<str>)],
vulkan_version: Option<EnvVersion>,
spirv_version: Option<SpirvVersion>,
) -> Result<(CompilationArtifact, Vec<String>), String> { ) -> Result<(CompilationArtifact, Vec<String>), 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 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( compile_options.set_target_env(
TargetEnv::Vulkan, 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); compile_options.set_target_spirv(spirv_version);
} }
let root_source_path = if let &Some(ref path) = &path { let root_source_path = path.as_deref().unwrap_or(
path // An arbitrary placeholder file name for embedded shaders.
} else { "shader.glsl",
// 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( compile_options.set_include_callback(
|requested_source_path, directive_type, contained_within_path, recursion_depth| { |requested_source_path, directive_type, contained_within_path, recursion_depth| {
include_callback( include_callback(
@ -181,52 +183,68 @@ pub fn compile(
directive_type, directive_type,
contained_within_path, contained_within_path,
recursion_depth, recursion_depth,
include_directories, &input.include_directories,
path.is_some(), path.is_some(),
base_path, base_path,
includes_tracker.borrow_mut(), &mut includes.borrow_mut(),
) )
}, },
); );
for (macro_name, macro_value) in macro_defines.iter() { for (macro_name, macro_value) in &input.macro_defines {
compile_options.add_macro_definition(macro_name.as_ref(), Some(macro_value.as_ref())); compile_options.add_macro_definition(macro_name, Some(macro_value));
} }
#[cfg(feature = "shaderc-debug")] #[cfg(feature = "shaderc-debug")]
compile_options.set_generate_debug_info(); compile_options.set_generate_debug_info();
let content = compiler let content = compiler
.compile_into_spirv(code, ty, root_source_path, "main", Some(&compile_options)) .compile_into_spirv(
.map_err(|e| e.to_string())?; 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>( pub(super) fn reflect(
prefix: &'a str, input: &MacroInput,
source: LitStr,
name: String,
words: &[u32], words: &[u32],
types_meta: &TypesMeta, input_paths: Vec<String>,
input_paths: impl IntoIterator<Item = &'a str>, type_registry: &mut TypeRegistry,
shared_constants: bool,
types_registry: &'a mut HashMap<String, RegisteredType>,
) -> Result<(TokenStream, TokenStream), Error> { ) -> 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| { let include_bytes = input_paths.into_iter().map(|s| {
quote! { 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. // The bytes themselves can be optimized out by the compiler as they are unused.
::std::include_bytes!( #s ) ::std::include_bytes!( #s )
} }
}); });
let spirv_version = { let spirv_version = {
let major = spirv.version().major; let Version {
let minor = spirv.version().minor; major,
let patch = spirv.version().patch; minor,
patch,
} = shader.spirv.version();
quote! { quote! {
::vulkano::Version { ::vulkano::Version {
major: #major, 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)); let name = format_ident!("{}", format!("{:?}", capability));
quote! { &::vulkano::shader::spirv::Capability::#name } quote! { &::vulkano::shader::spirv::Capability::#name }
}); });
let spirv_extensions = reflect::spirv_extensions(&spirv); let spirv_extensions = reflect::spirv_extensions(&shader.spirv);
let entry_points = reflect::entry_points(&spirv) let entry_points = reflect::entry_points(&shader.spirv)
.map(|(name, model, info)| entry_point::write_entry_point(&name, model, &info)); .map(|(name, model, info)| entry_point::write_entry_point(&name, model, &info));
let specialization_constants = structs::write_specialization_constants::<L>( let specialization_constants =
prefix, structs::write_specialization_constants(input, &shader, type_registry)?;
&spirv,
shared_constants,
types_registry,
);
let load_name = if prefix.is_empty() { let load_name = if shader.name.is_empty() {
format_ident!("load") format_ident!("load")
} else { } else {
format_ident!("load_{}", prefix) format_ident!("load_{}", shader.name.to_snake_case())
}; };
let shader_code = quote! { let shader_code = quote! {
/// Loads the shader in Vulkan as a `ShaderModule`. /// Loads the shader as a `ShaderModule`.
#[inline]
#[allow(unsafe_code)] #[allow(unsafe_code)]
pub fn #load_name(device: ::std::sync::Arc<::vulkano::device::Device>) #[inline]
-> Result<::std::sync::Arc<::vulkano::shader::ShaderModule>, ::vulkano::shader::ShaderCreationError> pub fn #load_name(
{ device: ::std::sync::Arc<::vulkano::device::Device>,
let _bytes = ( #( #include_bytes),* ); ) -> ::std::result::Result<
::std::sync::Arc<::vulkano::shader::ShaderModule>,
::vulkano::shader::ShaderCreationError,
> {
let _bytes = ( #( #include_bytes ),* );
static WORDS: &[u32] = &[ #( #words ),* ]; static WORDS: &[u32] = &[ #( #words ),* ];
@ -272,9 +289,9 @@ pub(super) fn reflect<'a, L: LinAlgType>(
device, device,
WORDS, WORDS,
#spirv_version, #spirv_version,
[#(#spirv_capabilities),*], [ #( #spirv_capabilities ),* ],
[#(#spirv_extensions),*], [ #( #spirv_extensions ),* ],
[#(#entry_points),*], [ #( #entry_points ),* ],
) )
} }
} }
@ -282,51 +299,19 @@ pub(super) fn reflect<'a, L: LinAlgType>(
#specialization_constants #specialization_constants
}; };
let structs = structs::write_structs::<L>(prefix, &spirv, types_meta, types_registry); let structs = structs::write_structs(input, &shader, type_registry)?;
Ok((shader_code, structs)) Ok((shader_code, structs))
} }
#[derive(Debug)]
pub enum Error {
IoError(IoError),
SpirvError(SpirvError),
}
impl From<IoError> for Error {
fn from(err: IoError) -> Error {
Error::IoError(err)
}
}
impl From<SpirvError> for Error {
fn from(err: SpirvError) -> Error {
Error::SpirvError(err)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; 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"))] fn convert_paths(root_path: &Path, paths: &[PathBuf]) -> Vec<String> {
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<String> {
paths paths
.iter() .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() .collect()
} }
@ -344,194 +329,88 @@ mod tests {
} }
#[test] #[test]
fn test_bad_alignment() { fn include_resolution() {
// 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::<StdArray>(
"",
&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::<StdArray>(
"",
&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::<StdArray>(
"",
&spirv,
&TypesMeta::default(),
&mut HashMap::default(),
);
}
#[test]
fn test_include_resolution() {
let root_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let root_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let empty_includes: [PathBuf; 0] = [];
let defines: [(String, String); 0] = [];
let (_compile_relative, _) = compile( let (_compile_relative, _) = compile(
&MacroInput::empty(),
Some(String::from("tests/include_test.glsl")), Some(String::from("tests/include_test.glsl")),
&root_path, &root_path,
" r#"
#version 450 #version 450
#include \"include_dir_a/target_a.glsl\" #include "include_dir_a/target_a.glsl"
#include \"include_dir_b/target_b.glsl\" #include "include_dir_b/target_b.glsl"
void main() {} void main() {}
", "#,
ShaderKind::Vertex, ShaderKind::Vertex,
&empty_includes,
&defines,
None,
None,
) )
.expect("Cannot resolve include files"); .expect("cannot resolve include files");
let (_compile_include_paths, includes) = compile( 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")), Some(String::from("tests/include_test.glsl")),
&root_path, &root_path,
" r#"
#version 450 #version 450
#include <target_a.glsl> #include <target_a.glsl>
#include <target_b.glsl> #include <target_b.glsl>
void main() {} void main() {}
", "#,
ShaderKind::Vertex, 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!( assert_eq!(
includes, includes,
convert_paths( convert_paths(
&root_path, &root_path,
&[ &[
vec!["tests", "include_dir_a", "target_a.glsl"].join(path_separator()), ["tests", "include_dir_a", "target_a.glsl"]
vec!["tests", "include_dir_b", "target_b.glsl"].join(path_separator()), .into_iter()
] .collect(),
) ["tests", "include_dir_b", "target_b.glsl"]
.into_iter()
.collect(),
],
),
); );
let (_compile_include_paths_with_relative, includes_with_relative) = compile( 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")), Some(String::from("tests/include_test.glsl")),
&root_path, &root_path,
" r#"
#version 450 #version 450
#include <target_a.glsl> #include <target_a.glsl>
#include <../include_dir_b/target_b.glsl> #include <../include_dir_b/target_b.glsl>
void main() {} void main() {}
", "#,
ShaderKind::Vertex, 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!( assert_eq!(
includes_with_relative, includes_with_relative,
convert_paths( convert_paths(
&root_path, &root_path,
&[ &[
vec!["tests", "include_dir_a", "target_a.glsl"].join(path_separator()), ["tests", "include_dir_a", "target_a.glsl"]
vec!["tests", "include_dir_a", "../include_dir_b/target_b.glsl"] .into_iter()
.join(path_separator()), .collect(),
] ["tests", "include_dir_a", "../include_dir_b/target_b.glsl"]
) .into_iter()
.collect(),
],
),
); );
let absolute_path = root_path let absolute_path = root_path
@ -540,99 +419,99 @@ mod tests {
.join("target_a.glsl"); .join("target_a.glsl");
let absolute_path_str = absolute_path let absolute_path_str = absolute_path
.to_str() .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( let (_compile_absolute_path, includes_absolute_path) = compile(
&MacroInput::empty(),
Some(String::from("tests/include_test.glsl")), Some(String::from("tests/include_test.glsl")),
&root_path, &root_path,
&format!( &format!(
" r#"
#version 450 #version 450
#include \"{}\" #include "{absolute_path_str}"
void main() {{}} void main() {{}}
", "#,
absolute_path_str
), ),
ShaderKind::Vertex, ShaderKind::Vertex,
&empty_includes,
&defines,
None,
None,
) )
.expect("Cannot resolve include files"); .expect("cannot resolve include files");
assert_eq!( assert_eq!(
includes_absolute_path, includes_absolute_path,
convert_paths( convert_paths(
&root_path, &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( 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")), Some(String::from("tests/include_test.glsl")),
&root_path, &root_path,
" r#"
#version 450 #version 450
#include <target_c.glsl> #include <target_c.glsl>
void main() {} void main() {}
", "#,
ShaderKind::Vertex, 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!( assert_eq!(
includes_recursive, includes_recursive,
convert_paths( convert_paths(
&root_path, &root_path,
&[ &[
vec!["tests", "include_dir_c", "target_c.glsl"].join(path_separator()), ["tests", "include_dir_c", "target_c.glsl"]
vec!["tests", "include_dir_c", "../include_dir_a/target_a.glsl"] .into_iter()
.join(path_separator()), .collect(),
vec!["tests", "include_dir_b", "target_b.glsl"].join(path_separator()), ["tests", "include_dir_c", "../include_dir_a/target_a.glsl"]
] .into_iter()
) .collect(),
["tests", "include_dir_b", "target_b.glsl"]
.into_iter()
.collect(),
],
),
); );
} }
#[test] #[test]
fn test_macros() { fn macros() {
let empty_includes: [PathBuf; 0] = []; let need_defines = r#"
let defines = vec![("NAME1", ""), ("NAME2", "58")]; #version 450
let no_defines: [(String, String); 0] = []; #if defined(NAME1) && NAME2 > 29
let need_defines = " void main() {}
#version 450 #endif
#if defined(NAME1) && NAME2 > 29 "#;
void main() {}
#endif
";
let compile_no_defines = compile( let compile_no_defines = compile(
&MacroInput::empty(),
None, None,
&Path::new(""), Path::new(""),
need_defines, need_defines,
ShaderKind::Vertex, ShaderKind::Vertex,
&empty_includes,
&no_defines,
None,
None,
); );
assert!(compile_no_defines.is_err()); 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, None,
&Path::new(""), Path::new(""),
need_defines, need_defines,
ShaderKind::Vertex, ShaderKind::Vertex,
&empty_includes, )
&defines, .expect("setting shader macros did not work");
None,
None,
);
compile_defines.expect("Setting shader macros did not work");
} }
/// `entrypoint1.frag.glsl`: /// `entrypoint1.frag.glsl`:
@ -699,7 +578,7 @@ mod tests {
/// spirv-link entrypoint1.spv entrypoint2.spv -o multiple_entrypoints.spv /// spirv-link entrypoint1.spv entrypoint2.spv -o multiple_entrypoints.spv
/// ``` /// ```
#[test] #[test]
fn test_descriptor_calculation_with_multiple_entrypoints() { fn descriptor_calculation_with_multiple_entrypoints() {
let data = include_bytes!("../tests/multiple_entrypoints.spv"); let data = include_bytes!("../tests/multiple_entrypoints.spv");
let instructions: Vec<u32> = data let instructions: Vec<u32> = data
.chunks(4) .chunks(4)
@ -715,11 +594,12 @@ mod tests {
} }
// Check first entrypoint // 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(); let mut e1_bindings = Vec::new();
for loc in e1_descriptors.keys() { for loc in e1_descriptors.keys() {
e1_bindings.push(*loc); e1_bindings.push(*loc);
} }
assert_eq!(e1_bindings.len(), 5); assert_eq!(e1_bindings.len(), 5);
assert!(e1_bindings.contains(&(0, 0))); assert!(e1_bindings.contains(&(0, 0)));
assert!(e1_bindings.contains(&(0, 1))); assert!(e1_bindings.contains(&(0, 1)));
@ -728,11 +608,12 @@ mod tests {
assert!(e1_bindings.contains(&(0, 4))); assert!(e1_bindings.contains(&(0, 4)));
// Check second entrypoint // 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(); let mut e2_bindings = Vec::new();
for loc in e2_descriptors.keys() { for loc in e2_descriptors.keys() {
e2_bindings.push(*loc); e2_bindings.push(*loc);
} }
assert_eq!(e2_bindings.len(), 3); assert_eq!(e2_bindings.len(), 3);
assert!(e2_bindings.contains(&(0, 0))); assert!(e2_bindings.contains(&(0, 0)));
assert!(e2_bindings.contains(&(0, 1))); assert!(e2_bindings.contains(&(0, 1)));
@ -740,43 +621,38 @@ mod tests {
} }
#[test] #[test]
fn test_descriptor_calculation_with_multiple_functions() { fn descriptor_calculation_with_multiple_functions() {
let includes: [PathBuf; 0] = [];
let defines: [(String, String); 0] = [];
let (comp, _) = compile( let (comp, _) = compile(
&MacroInput::empty(),
None, None,
&Path::new(""), Path::new(""),
" r#"
#version 450 #version 450
layout(set = 1, binding = 0) buffer Buffer { layout(set = 1, binding = 0) buffer Buffer {
vec3 data; vec3 data;
} bo; } bo;
layout(set = 2, binding = 0) uniform Uniform { layout(set = 2, binding = 0) uniform Uniform {
float data; float data;
} ubo; } ubo;
layout(set = 3, binding = 1) uniform sampler textureSampler; layout(set = 3, binding = 1) uniform sampler textureSampler;
layout(set = 3, binding = 2) uniform texture2D imageTexture; layout(set = 3, binding = 2) uniform texture2D imageTexture;
float withMagicSparkles(float data) { float withMagicSparkles(float data) {
return texture(sampler2D(imageTexture, textureSampler), vec2(data, data)).x; return texture(sampler2D(imageTexture, textureSampler), vec2(data, data)).x;
} }
vec3 makeSecretSauce() { vec3 makeSecretSauce() {
return vec3(withMagicSparkles(ubo.data)); return vec3(withMagicSparkles(ubo.data));
} }
void main() { void main() {
bo.data = makeSecretSauce(); bo.data = makeSecretSauce();
} }
", "#,
ShaderKind::Vertex, ShaderKind::Vertex,
&includes,
&defines,
None,
None,
) )
.unwrap(); .unwrap();
let spirv = Spirv::new(comp.as_binary()).unwrap(); let spirv = Spirv::new(comp.as_binary()).unwrap();
@ -786,6 +662,7 @@ mod tests {
for (loc, _reqs) in info.descriptor_binding_requirements { for (loc, _reqs) in info.descriptor_binding_requirements {
bindings.push(loc); bindings.push(loc);
} }
assert_eq!(bindings.len(), 4); assert_eq!(bindings.len(), 4);
assert!(bindings.contains(&(1, 0))); assert!(bindings.contains(&(1, 0)));
assert!(bindings.contains(&(2, 0))); assert!(bindings.contains(&(2, 0)));
@ -794,6 +671,6 @@ mod tests {
return; return;
} }
panic!("Could not find entrypoint"); panic!("could not find entrypoint");
} }
} }

View File

@ -8,7 +8,7 @@
// according to those terms. // according to those terms.
use ahash::HashMap; use ahash::HashMap;
use proc_macro2::TokenStream; use proc_macro2::{Ident, Span, TokenStream};
use vulkano::{ use vulkano::{
pipeline::layout::PushConstantRange, pipeline::layout::PushConstantRange,
shader::{ shader::{
@ -25,11 +25,7 @@ pub(super) fn write_entry_point(
info: &EntryPointInfo, info: &EntryPointInfo,
) -> TokenStream { ) -> TokenStream {
let execution = write_shader_execution(&info.execution); let execution = write_shader_execution(&info.execution);
let model = syn::parse_str::<syn::Path>(&format!( let model = Ident::new(&format!("{:?}", model), Span::call_site());
"::vulkano::shader::spirv::ExecutionModel::{:?}",
model
))
.unwrap();
let descriptor_binding_requirements = let descriptor_binding_requirements =
write_descriptor_binding_requirements(&info.descriptor_binding_requirements); write_descriptor_binding_requirements(&info.descriptor_binding_requirements);
let push_constant_requirements = let push_constant_requirements =
@ -42,7 +38,7 @@ pub(super) fn write_entry_point(
quote! { quote! {
( (
#name.to_owned(), #name.to_owned(),
#model, ::vulkano::shader::spirv::ExecutionModel::#model,
::vulkano::shader::EntryPointInfo { ::vulkano::shader::EntryPointInfo {
execution: #execution, execution: #execution,
descriptor_binding_requirements: #descriptor_binding_requirements.into_iter().collect(), descriptor_binding_requirements: #descriptor_binding_requirements.into_iter().collect(),
@ -194,7 +190,9 @@ fn write_descriptor_binding_requirements(
sampler_compare: #sampler_compare, sampler_compare: #sampler_compare,
sampler_no_unnormalized_coordinates: #sampler_no_unnormalized_coordinates, sampler_no_unnormalized_coordinates: #sampler_no_unnormalized_coordinates,
sampler_no_ycbcr_conversion: #sampler_no_ycbcr_conversion, 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, storage_image_atomic: #storage_image_atomic,
} }
) )
@ -205,22 +203,22 @@ fn write_descriptor_binding_requirements(
( (
(#set_num, #binding_num), (#set_num, #binding_num),
::vulkano::shader::DescriptorBindingRequirements { ::vulkano::shader::DescriptorBindingRequirements {
descriptor_types: vec![#(#descriptor_types_items),*], descriptor_types: vec![ #( #descriptor_types_items ),* ],
descriptor_count: #descriptor_count, descriptor_count: #descriptor_count,
image_format: #image_format, image_format: #image_format,
image_multisampled: #image_multisampled, image_multisampled: #image_multisampled,
image_scalar_type: #image_scalar_type, image_scalar_type: #image_scalar_type,
image_view_type: #image_view_type, image_view_type: #image_view_type,
stages: #stages, stages: #stages,
descriptors: [#(#descriptor_items),*].into_iter().collect(), descriptors: [ #( #descriptor_items ),* ].into_iter().collect(),
}, },
), )
} }
}); });
quote! { quote! {
[ [
#( #descriptor_binding_requirements )* #( #descriptor_binding_requirements ),*
] ]
} }
} }
@ -264,13 +262,13 @@ fn write_specialization_constant_requirements(
::vulkano::shader::SpecializationConstantRequirements { ::vulkano::shader::SpecializationConstantRequirements {
size: #size, size: #size,
}, },
), )
} }
}); });
quote! { quote! {
[ [
#( #specialization_constant_requirements )* #( #specialization_constant_requirements ),*
] ]
} }
} }
@ -301,15 +299,15 @@ fn write_interface(interface: &ShaderInterface) -> TokenStream {
num_elements: #num_elements, num_elements: #num_elements,
is_64bit: #is_64bit, is_64bit: #is_64bit,
}, },
name: Some(::std::borrow::Cow::Borrowed(#name)) name: ::std::option::Option::Some(::std::borrow::Cow::Borrowed(#name)),
}, }
} }
}, },
); );
quote! { quote! {
::vulkano::shader::ShaderInterface::new_unchecked(vec![ ::vulkano::shader::ShaderInterface::new_unchecked(vec![
#( #items )* #( #items ),*
]) ])
} }
} }
@ -363,6 +361,6 @@ fn stages_to_items(stages: ShaderStages) -> TokenStream {
.into_iter() .into_iter()
.flatten(); .flatten();
quote! { #(#stages_items)|* } quote! { #( #stages_items )|* }
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -21,18 +21,15 @@ ahash = "0.8"
# When updating Ash, also update vk.xml to the same Vulkan patch version that Ash uses. # 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. # All versions of vk.xml can be found at https://github.com/KhronosGroup/Vulkan-Headers/commits/main/registry/vk.xml.
ash = "^0.37.1" ash = "^0.37.1"
bytemuck = { version = "1.7", features = [ bytemuck = "1.7"
"derive",
"extern_crate_std",
"min_const_generics",
] }
cgmath = { version = "0.18.0", optional = true } cgmath = { version = "0.18.0", optional = true }
crossbeam-queue = "0.3" crossbeam-queue = "0.3"
half = "2" half = { version = "2", features = ["bytemuck"] }
libloading = "0.7" libloading = "0.7"
nalgebra = { version = "0.31.0", optional = true } nalgebra = { version = "0.31.0", optional = true }
once_cell = "1.16" once_cell = "1.16"
parking_lot = { version = "0.12", features = ["send_guard"] } parking_lot = { version = "0.12", features = ["send_guard"] }
serde = { version = "1.0", optional = true }
smallvec = "1.8" smallvec = "1.8"
thread_local = "1.1" thread_local = "1.1"
vulkano-macros = { path = "macros", version = "0.32.0" } vulkano-macros = { path = "macros", version = "0.32.0" }

View File

@ -21,3 +21,6 @@ syn = "1.0"
quote = "1.0" quote = "1.0"
proc-macro2 = "1.0" proc-macro2 = "1.0"
proc-macro-crate = "1.2" proc-macro-crate = "1.2"
[dev-dependencies]
vulkano = { path = ".." }

View File

@ -0,0 +1,329 @@
// Copyright (c) 2017 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// 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<TokenStream> {
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::<Vec<WherePredicate>>();
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<T: ?Sized> {
components: PtrComponents,
ptr: *mut T,
}
#[derive(Clone, Copy)]
#[repr(C)]
struct PtrComponents {
data: *mut ::std::ffi::c_void,
len: usize,
}
let alignment = <Self as ::#crate_ident::buffer::BufferContents>::LAYOUT
.alignment()
.as_devicesize() as usize;
::std::debug_assert!(data as usize % alignment == 0);
let head_size = <Self as ::#crate_ident::buffer::BufferContents>::LAYOUT
.head_size() as usize;
let element_size = <Self as ::#crate_ident::buffer::BufferContents>::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<TokenStream> {
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::<Self>()
)
};
}
// 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<T: ::#crate_ident::buffer::BufferContents + ?Sized>() {}
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());
}
}

View File

@ -7,14 +7,13 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use proc_macro::TokenStream; use crate::bail;
use proc_macro2::Span; use proc_macro2::{Span, TokenStream};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote; use quote::quote;
use syn::{ use syn::{
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
punctuated::Punctuated, 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<TokenStream> { pub fn derive_vertex(ast: syn::DeriveInput) -> Result<TokenStream> {
@ -25,25 +24,14 @@ pub fn derive_vertex(ast: syn::DeriveInput) -> Result<TokenStream> {
fields: Fields::Named(fields), fields: Fields::Named(fields),
.. ..
}) => &fields.named, }) => &fields.named,
_ => { _ => bail!("expected a struct with named fields"),
return Err(Error::new_spanned(
ast,
"Expected a struct with named fields",
));
}
}; };
let found_crate = crate_name("vulkano").expect("vulkano is present in `Cargo.toml`"); let crate_ident = crate::crate_ident();
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 mut members = quote! { let mut members = quote! {
let mut offset = 0; let mut offset = 0;
let mut members = HashMap::default(); let mut members = ::std::collections::HashMap::default();
}; };
for field in fields.iter() { for field in fields.iter() {
@ -64,30 +52,30 @@ pub fn derive_vertex(ast: syn::DeriveInput) -> Result<TokenStream> {
} else if attr_ident == "format" { } else if attr_ident == "format" {
let format_ident = attr.parse_args_with(Ident::parse)?; let format_ident = attr.parse_args_with(Ident::parse)?;
format = quote! { format = quote! {
let format = Format::#format_ident; let format = ::#crate_ident::format::Format::#format_ident;
}; };
} }
} }
if format.is_empty() { if format.is_empty() {
return Err(Error::new( bail!(
field_name.span(), field_name,
"Expected `#[format(...)]`-attribute with valid `vulkano::format::Format`", "expected `#[format(...)]`-attribute with valid `vulkano::format::Format`",
)); );
} }
for name in &names { for name in &names {
members = quote! { members = quote! {
#members #members
let field_size = std::mem::size_of::<#field_ty>() as u32; let field_size = ::std::mem::size_of::<#field_ty>() as u32;
{ {
#format #format
let format_size = format.block_size().expect("no block size for format") as u32; let format_size = format.block_size().expect("no block size for format") as u32;
let num_elements = field_size / format_size; let num_elements = field_size / format_size;
let remainder = 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( members.insert(
#name.to_string(), #name.to_string(),
VertexMemberInfo { ::#crate_ident::pipeline::graphics::vertex_input::VertexMemberInfo {
offset, offset,
format, format,
num_elements, num_elements,
@ -100,37 +88,34 @@ pub fn derive_vertex(ast: syn::DeriveInput) -> Result<TokenStream> {
} }
let function_body = quote! { 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 #members
#crate_ident::pipeline::graphics::vertex_input::VertexBufferDescription { ::#crate_ident::pipeline::graphics::vertex_input::VertexBufferDescription {
members, members,
stride: std::mem::size_of::<#struct_name>() as u32, stride: ::std::mem::size_of::<#struct_name>() as u32,
input_rate: VertexInputRate::Vertex, input_rate: ::#crate_ident::pipeline::graphics::vertex_input::VertexInputRate::Vertex,
} }
}; };
Ok(TokenStream::from(quote! { Ok(quote! {
#[allow(unsafe_code)] #[allow(unsafe_code)]
unsafe impl #crate_ident::pipeline::graphics::vertex_input::Vertex for #struct_name { unsafe impl ::#crate_ident::pipeline::graphics::vertex_input::Vertex for #struct_name {
#[inline(always)] #[inline(always)]
fn per_vertex() -> #crate_ident::pipeline::graphics::vertex_input::VertexBufferDescription { fn per_vertex() -> ::#crate_ident::pipeline::graphics::vertex_input::VertexBufferDescription {
#function_body #function_body
} }
#[inline(always)]
fn per_instance() -> #crate_ident::pipeline::graphics::vertex_input::VertexBufferDescription { #[inline(always)]
#function_body.per_instance() 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 {
#function_body.per_instance_with_divisor(divisor) #[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 { struct NameMeta {

View File

@ -8,12 +8,47 @@
// according to those terms. // according to those terms.
use proc_macro::TokenStream; 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; mod derive_vertex;
#[proc_macro_derive(Vertex, attributes(name, format))] #[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); 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;

View File

@ -9,7 +9,7 @@
//! Efficiently suballocates buffers into smaller subbuffers. //! Efficiently suballocates buffers into smaller subbuffers.
use super::{Buffer, BufferError, BufferMemory, BufferUsage, Subbuffer}; use super::{Buffer, BufferContents, BufferError, BufferMemory, BufferUsage, Subbuffer};
use crate::{ use crate::{
buffer::BufferAllocateInfo, buffer::BufferAllocateInfo,
device::{Device, DeviceOwned}, device::{Device, DeviceOwned},
@ -17,11 +17,10 @@ use crate::{
align_up, AllocationCreationError, DeviceAlignment, DeviceLayout, MemoryAllocator, align_up, AllocationCreationError, DeviceAlignment, DeviceLayout, MemoryAllocator,
MemoryUsage, StandardMemoryAllocator, MemoryUsage, StandardMemoryAllocator,
}, },
DeviceSize, DeviceSize, NonZeroDeviceSize,
}; };
use crossbeam_queue::ArrayQueue; use crossbeam_queue::ArrayQueue;
use std::{ use std::{
alloc::Layout,
cell::UnsafeCell, cell::UnsafeCell,
cmp, cmp,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
@ -196,36 +195,50 @@ where
} }
/// Allocates a subbuffer for sized data. /// Allocates a subbuffer for sized data.
/// pub fn allocate_sized<T>(&self) -> Result<Subbuffer<T>, AllocationCreationError>
/// # Panics where
/// T: BufferContents,
/// - Panics if `T` has zero size. {
/// - Panics if `T` has an alignment greater than `64`. let layout = T::LAYOUT.unwrap_sized();
pub fn allocate_sized<T>(&self) -> Result<Subbuffer<T>, AllocationCreationError> {
let layout = DeviceLayout::from_layout(Layout::new::<T>())
.expect("can't allocate memory for zero-sized types");
self.allocate(layout) unsafe { &mut *self.state.get() }
.map(|subbuffer| unsafe { subbuffer.reinterpret() }) .allocate(layout)
.map(|subbuffer| unsafe { subbuffer.reinterpret_unchecked() })
} }
/// Allocates a subbuffer for a slice. /// Allocates a subbuffer for a slice.
/// ///
/// # Panics /// # Panics
/// ///
/// - Panics if `T` has zero size.
/// - Panics if `T` has an alignment greater than `64`.
/// - Panics if `len` is zero. /// - Panics if `len` is zero.
pub fn allocate_slice<T>( pub fn allocate_slice<T>(
&self, &self,
len: DeviceSize, len: DeviceSize,
) -> Result<Subbuffer<[T]>, AllocationCreationError> { ) -> Result<Subbuffer<[T]>, AllocationCreationError>
let layout = where
DeviceLayout::from_layout(Layout::array::<T>(len.try_into().unwrap()).unwrap()) T: BufferContents,
.expect("can't allocate memory for zero-sized types"); {
self.allocate_unsized(len)
}
self.allocate(layout) /// Allocates a subbuffer for unsized data.
.map(|subbuffer| unsafe { subbuffer.reinterpret() }) ///
/// # Panics
///
/// - Panics if `len` is zero.
pub fn allocate_unsized<T>(
&self,
len: DeviceSize,
) -> Result<Subbuffer<T>, 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`. /// Allocates a subbuffer with the given `layout`.
@ -239,11 +252,7 @@ where
) -> Result<Subbuffer<[u8]>, AllocationCreationError> { ) -> Result<Subbuffer<[u8]>, AllocationCreationError> {
assert!(layout.alignment().as_devicesize() <= 64); assert!(layout.alignment().as_devicesize() <= 64);
let state = unsafe { &mut *self.state.get() }; unsafe { &mut *self.state.get() }.allocate(layout)
let offset = state.allocate(layout)?;
let arena = state.arena.as_ref().unwrap().clone();
Ok(Subbuffer::from_arena(arena, offset, layout.size()))
} }
} }
@ -277,7 +286,10 @@ impl<A> SubbufferAllocatorState<A>
where where
A: MemoryAllocator, A: MemoryAllocator,
{ {
fn allocate(&mut self, layout: DeviceLayout) -> Result<DeviceSize, AllocationCreationError> { fn allocate(
&mut self,
layout: DeviceLayout,
) -> Result<Subbuffer<[u8]>, AllocationCreationError> {
let size = layout.size(); let size = layout.size();
let alignment = cmp::max(layout.alignment(), self.buffer_alignment); let alignment = cmp::max(layout.alignment(), self.buffer_alignment);
@ -310,7 +322,7 @@ where
let offset = offset - arena_offset; let offset = offset - arena_offset;
self.free_start = offset + size; 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. // We reached the end of the arena, grab the next one.

View File

@ -27,8 +27,8 @@
//! `VkBuffer`, and as such doesn't hold onto any memory. //! `VkBuffer`, and as such doesn't hold onto any memory.
//! - [`Buffer`] is a `RawBuffer` with memory bound to it, and with state tracking. //! - [`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 //! - [`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 //! a reference to a portion of a `Buffer`. `Subbuffer` also has a type parameter, which is a
//! for how the data in the portion of the buffer is going to be interpreted. //! hint for how the data in the portion of the buffer is going to be interpreted.
//! //!
//! # `Subbuffer` allocation //! # `Subbuffer` allocation
//! //!
@ -98,7 +98,10 @@
//! [the `view` module]: self::view //! [the `view` module]: self::view
//! [the `shader` module documentation]: crate::shader //! [the `shader` module documentation]: crate::shader
pub use self::{subbuffer::Subbuffer, usage::BufferUsage}; pub use self::{
subbuffer::{BufferContents, BufferContentsLayout, Subbuffer},
usage::BufferUsage,
};
use self::{ use self::{
subbuffer::{ReadLockError, WriteLockError}, subbuffer::{ReadLockError, WriteLockError},
sys::{BufferCreateInfo, RawBuffer}, sys::{BufferCreateInfo, RawBuffer},
@ -119,15 +122,13 @@ use crate::{
DeviceSize, NonZeroDeviceSize, RequirementNotMet, RequiresOneOf, Version, VulkanError, DeviceSize, NonZeroDeviceSize, RequirementNotMet, RequiresOneOf, Version, VulkanError,
VulkanObject, VulkanObject,
}; };
use bytemuck::{Pod, PodCastError};
use parking_lot::{Mutex, MutexGuard}; use parking_lot::{Mutex, MutexGuard};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
alloc::Layout,
error::Error, error::Error,
fmt::{Display, Error as FmtError, Formatter}, fmt::{Display, Error as FmtError, Formatter},
hash::{Hash, Hasher}, hash::{Hash, Hasher},
mem::{size_of, size_of_val}, mem::size_of_val,
ops::Range, ops::Range,
ptr, ptr,
sync::Arc, sync::Arc,
@ -282,8 +283,6 @@ impl Buffer {
/// ///
/// # Panics /// # Panics
/// ///
/// - Panics if `T` has zero size.
/// - Panics if `T` has an alignment greater than `64`.
/// - Panics if `iter` is empty. /// - Panics if `iter` is empty.
pub fn from_iter<T, I>( pub fn from_iter<T, I>(
allocator: &(impl MemoryAllocator + ?Sized), allocator: &(impl MemoryAllocator + ?Sized),
@ -291,7 +290,7 @@ impl Buffer {
iter: I, iter: I,
) -> Result<Subbuffer<[T]>, BufferError> ) -> Result<Subbuffer<[T]>, BufferError>
where where
[T]: BufferContents, T: BufferContents,
I: IntoIterator<Item = T>, I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator, I::IntoIter: ExactSizeIterator,
{ {
@ -307,20 +306,17 @@ impl Buffer {
/// Creates a new uninitialized `Buffer` for sized data. Returns a [`Subbuffer`] spanning the /// Creates a new uninitialized `Buffer` for sized data. Returns a [`Subbuffer`] spanning the
/// whole buffer. /// whole buffer.
///
/// # Panics
///
/// - Panics if `T` has zero size.
/// - Panics if `T` has an alignment greater than `64`.
pub fn new_sized<T>( pub fn new_sized<T>(
allocator: &(impl MemoryAllocator + ?Sized), allocator: &(impl MemoryAllocator + ?Sized),
allocate_info: BufferAllocateInfo, allocate_info: BufferAllocateInfo,
) -> Result<Subbuffer<T>, BufferError> { ) -> Result<Subbuffer<T>, BufferError>
let layout = Layout::new::<T>() where
.try_into() T: BufferContents,
.expect("can't allocate memory for zero-sized types"); {
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 /// Creates a new uninitialized `Buffer` for a slice. Returns a [`Subbuffer`] spanning the
@ -328,20 +324,37 @@ impl Buffer {
/// ///
/// # Panics /// # Panics
/// ///
/// - Panics if `T` has zero size.
/// - Panics if `T` has an alignment greater than `64`.
/// - Panics if `len` is zero. /// - Panics if `len` is zero.
pub fn new_slice<T>( pub fn new_slice<T>(
allocator: &(impl MemoryAllocator + ?Sized), allocator: &(impl MemoryAllocator + ?Sized),
allocate_info: BufferAllocateInfo, allocate_info: BufferAllocateInfo,
len: DeviceSize, len: DeviceSize,
) -> Result<Subbuffer<[T]>, BufferError> { ) -> Result<Subbuffer<[T]>, BufferError>
let layout = Layout::array::<T>(len.try_into().unwrap()) where
.unwrap() T: BufferContents,
.try_into() {
.expect("can't allocate memory for zero-sized types"); 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<T>(
allocator: &(impl MemoryAllocator + ?Sized),
allocate_info: BufferAllocateInfo,
len: DeviceSize,
) -> Result<Subbuffer<T>, 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`. /// 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<T> 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<T> 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::<T>() as DeviceSize
}
}
/// The buffer configuration to query in /// The buffer configuration to query in
/// [`PhysicalDevice::external_buffer_properties`](crate::device::physical::PhysicalDevice::external_buffer_properties). /// [`PhysicalDevice::external_buffer_properties`](crate::device::physical::PhysicalDevice::external_buffer_properties).
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]

View File

@ -7,9 +7,12 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // 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::{ use crate::{
device::{Device, DeviceOwned}, device::{Device, DeviceOwned},
macros::try_opt,
memory::{ memory::{
self, self,
allocator::{align_down, align_up, DeviceAlignment, DeviceLayout}, allocator::{align_down, align_up, DeviceAlignment, DeviceLayout},
@ -17,19 +20,23 @@ use crate::{
}, },
DeviceSize, NonZeroDeviceSize, DeviceSize, NonZeroDeviceSize,
}; };
use bytemuck::PodCastError; use bytemuck::{AnyBitPattern, PodCastError};
use std::{ use std::{
alloc::Layout, alloc::Layout,
cmp, cmp,
error::Error, error::Error,
ffi::c_void,
fmt::{Display, Error as FmtError, Formatter}, fmt::{Display, Error as FmtError, Formatter},
hash::{Hash, Hasher}, hash::{Hash, Hasher},
marker::PhantomData, marker::PhantomData,
mem::{self, align_of, size_of}, mem::{self, align_of, size_of},
ops::{Deref, DerefMut, Range, RangeBounds}, ops::{Deref, DerefMut, Range, RangeBounds},
ptr::{self, NonNull},
sync::Arc, sync::Arc,
}; };
pub use vulkano_macros::BufferContents;
/// A subpart of a buffer. /// A subpart of a buffer.
/// ///
/// This type doesn't correspond to any Vulkan object, it exists for API convenience. Most Vulkan /// This type doesn't correspond to any Vulkan object, it exists for API convenience. Most Vulkan
@ -69,15 +76,6 @@ enum SubbufferParent {
} }
impl<T: ?Sized> Subbuffer<T> { impl<T: ?Sized> Subbuffer<T> {
pub(super) fn from_buffer(buffer: Arc<Buffer>) -> Self {
Subbuffer {
offset: 0,
size: buffer.size(),
parent: SubbufferParent::Buffer(buffer),
marker: PhantomData,
}
}
pub(super) fn from_arena(arena: Arc<Arena>, offset: DeviceSize, size: DeviceSize) -> Self { pub(super) fn from_arena(arena: Arc<Arena>, offset: DeviceSize, size: DeviceSize) -> Self {
Subbuffer { Subbuffer {
offset, offset,
@ -120,6 +118,20 @@ impl<T: ?Sized> Subbuffer<T> {
} }
} }
/// 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<NonNull<c_void>> {
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. /// Returns the device address for this subbuffer.
pub fn device_address(&self) -> Result<NonZeroDeviceSize, BufferError> { pub fn device_address(&self) -> Result<NonZeroDeviceSize, BufferError> {
self.buffer().device_address().map(|ptr| { self.buffer().device_address().map(|ptr| {
@ -130,46 +142,31 @@ impl<T: ?Sized> Subbuffer<T> {
}) })
} }
/// 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<U: ?Sized>(self) -> Subbuffer<U> {
// SAFETY: All `Subbuffer`s share the same layout.
mem::transmute::<Subbuffer<T>, Subbuffer<U>>(self)
}
/// Same as [`reinterpret`], except it works with a reference to the subbuffer.
///
/// [`reinterpret`]: Self::reinterpret
pub unsafe fn reinterpret_ref<U: ?Sized>(&self) -> &Subbuffer<U> {
assert!(size_of::<Subbuffer<T>>() == size_of::<Subbuffer<U>>());
assert!(align_of::<Subbuffer<T>>() == align_of::<Subbuffer<U>>());
// SAFETY: All `Subbuffer`s share the same layout.
mem::transmute::<&Subbuffer<T>, &Subbuffer<U>>(self)
}
/// Casts the subbuffer to a slice of raw bytes. /// Casts the subbuffer to a slice of raw bytes.
pub fn into_bytes(self) -> Subbuffer<[u8]> { 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. /// Same as [`into_bytes`], except it works with a reference to the subbuffer.
/// ///
/// [`into_bytes`]: Self::into_bytes /// [`into_bytes`]: Self::into_bytes
pub fn as_bytes(&self) -> &Subbuffer<[u8]> { pub fn as_bytes(&self) -> &Subbuffer<[u8]> {
unsafe { self.reinterpret_ref() } unsafe { self.reinterpret_ref_unchecked_inner() }
}
#[inline(always)]
unsafe fn reinterpret_unchecked_inner<U: ?Sized>(self) -> Subbuffer<U> {
// SAFETY: All `Subbuffer`s share the same layout.
mem::transmute::<Subbuffer<T>, Subbuffer<U>>(self)
}
#[inline(always)]
unsafe fn reinterpret_ref_unchecked_inner<U: ?Sized>(&self) -> &Subbuffer<U> {
assert!(size_of::<Subbuffer<T>>() == size_of::<Subbuffer<U>>());
assert!(align_of::<Subbuffer<T>>() == align_of::<Subbuffer<U>>());
// SAFETY: All `Subbuffer`s share the same layout.
mem::transmute::<&Subbuffer<T>, &Subbuffer<U>>(self)
} }
} }
@ -177,6 +174,53 @@ impl<T> Subbuffer<T>
where where
T: BufferContents + ?Sized, 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<U>(self) -> Subbuffer<U>
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<U>(&self) -> &Subbuffer<U>
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. /// 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 /// 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"), BufferMemory::Sparse => todo!("`Subbuffer::read` doesn't support sparse binding yet"),
}; };
let range = self.range(); let range = if let Some(atom_size) = allocation.atom_size() {
let aligned_range = if let Some(atom_size) = allocation.atom_size() {
// This works because the suballocators align allocations to the non-coherent 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. // when the memory is host-visible but not host-coherent.
let start = align_down(self.offset, atom_size); let start = align_down(self.offset, atom_size);
@ -222,12 +264,12 @@ where
Range { start, end } Range { start, end }
} else { } else {
range.clone() self.range()
}; };
let mut state = self.buffer().state(); let mut state = self.buffer().state();
state.check_cpu_read(aligned_range.clone())?; state.check_cpu_read(range.clone())?;
unsafe { state.cpu_read_lock(aligned_range.clone()) }; unsafe { state.cpu_read_lock(range.clone()) };
if allocation.atom_size().is_some() { if allocation.atom_size().is_some() {
// If there are other read locks being held at this point, they also called // 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. // 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 // 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. // 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 mapped_ptr = self.mapped_ptr().ok_or(BufferError::MemoryNotHostVisible)?;
let data = T::from_bytes(bytes).unwrap(); // 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 { Ok(BufferReadGuard {
subbuffer: self, subbuffer: self,
data, data,
range: aligned_range, range,
}) })
} }
@ -279,9 +322,7 @@ where
BufferMemory::Sparse => todo!("`Subbuffer::write` doesn't support sparse binding yet"), BufferMemory::Sparse => todo!("`Subbuffer::write` doesn't support sparse binding yet"),
}; };
let range = self.range(); let range = if let Some(atom_size) = allocation.atom_size() {
let aligned_range = if let Some(atom_size) = allocation.atom_size() {
// This works because the suballocators align allocations to the non-coherent 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. // when the memory is host-visible but not host-coherent.
let start = align_down(self.offset, atom_size); let start = align_down(self.offset, atom_size);
@ -292,24 +333,25 @@ where
Range { start, end } Range { start, end }
} else { } else {
range.clone() self.range()
}; };
let mut state = self.buffer().state(); let mut state = self.buffer().state();
state.check_cpu_write(aligned_range.clone())?; state.check_cpu_write(range.clone())?;
unsafe { state.cpu_write_lock(aligned_range.clone()) }; unsafe { state.cpu_write_lock(range.clone()) };
if allocation.atom_size().is_some() { 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 mapped_ptr = self.mapped_ptr().ok_or(BufferError::MemoryNotHostVisible)?;
let data = T::from_bytes_mut(bytes).unwrap(); // 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 { Ok(BufferWriteGuard {
subbuffer: self, subbuffer: self,
data, data,
range: aligned_range, range,
}) })
} }
} }
@ -317,7 +359,14 @@ where
impl<T> Subbuffer<T> { impl<T> Subbuffer<T> {
/// Converts the subbuffer to a slice of one element. /// Converts the subbuffer to a slice of one element.
pub fn into_slice(self) -> Subbuffer<[T]> { 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, T: BufferContents,
{ {
/// Tries to cast a subbuffer of raw bytes to a `Subbuffer<T>`. /// Tries to cast a subbuffer of raw bytes to a `Subbuffer<T>`.
///
/// # 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<Self, PodCastError> { pub fn try_from_bytes(subbuffer: Subbuffer<[u8]>) -> Result<Self, PodCastError> {
assert_valid_type_param::<T>();
if subbuffer.size() != size_of::<T>() as DeviceSize { if subbuffer.size() != size_of::<T>() as DeviceSize {
Err(PodCastError::SizeMismatch) Err(PodCastError::SizeMismatch)
} else if !is_aligned(subbuffer.memory_offset(), DeviceAlignment::of::<T>()) { } else if !is_aligned(subbuffer.memory_offset(), DeviceAlignment::of::<T>()) {
Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
} else { } else {
Ok(unsafe { subbuffer.reinterpret() }) Ok(unsafe { subbuffer.reinterpret_unchecked() })
} }
} }
/// Tries to cast the subbuffer to a different type. /// 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<U>(self) -> Result<Subbuffer<U>, PodCastError> pub fn try_cast<U>(self) -> Result<Subbuffer<U>, PodCastError>
where where
U: BufferContents, U: BufferContents,
{ {
assert_valid_type_param::<U>();
if size_of::<U>() != size_of::<T>() { if size_of::<U>() != size_of::<T>() {
Err(PodCastError::SizeMismatch) Err(PodCastError::SizeMismatch)
} else if align_of::<U>() > align_of::<T>() } else if align_of::<U>() > align_of::<T>()
@ -362,7 +397,7 @@ where
{ {
Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
} else { } else {
Ok(unsafe { self.reinterpret() }) Ok(unsafe { self.reinterpret_unchecked() })
} }
} }
} }
@ -370,9 +405,7 @@ where
impl<T> Subbuffer<[T]> { impl<T> Subbuffer<[T]> {
/// Returns the number of elements in the slice. /// Returns the number of elements in the slice.
pub fn len(&self) -> DeviceSize { pub fn len(&self) -> DeviceSize {
assert_valid_type_param::<T>(); debug_assert!(self.size % size_of::<T>() as DeviceSize == 0);
debug_assert!(self.size() % size_of::<T>() as DeviceSize == 0);
self.size / size_of::<T>() as DeviceSize self.size / size_of::<T>() as DeviceSize
} }
@ -381,7 +414,7 @@ impl<T> Subbuffer<[T]> {
/// ///
/// # Panics /// # Panics
/// ///
/// - Panics if `index` is out of range. /// - Panics if `index` is out of bounds.
pub fn index(self, index: DeviceSize) -> Subbuffer<T> { pub fn index(self, index: DeviceSize) -> Subbuffer<T> {
assert!(index <= self.len()); assert!(index <= self.len());
@ -403,11 +436,13 @@ impl<T> Subbuffer<[T]> {
/// # Panics /// # Panics
/// ///
/// - Panics if `range` is out of bounds. /// - Panics if `range` is out of bounds.
/// - Panics if `range` is empty.
pub fn slice(mut self, range: impl RangeBounds<DeviceSize>) -> Subbuffer<[T]> { pub fn slice(mut self, range: impl RangeBounds<DeviceSize>) -> Subbuffer<[T]> {
let Range { start, end } = memory::range(range, ..self.len()).unwrap(); let Range { start, end } = memory::range(range, ..self.len()).unwrap();
self.offset += start * size_of::<T>() as DeviceSize; self.offset += start * size_of::<T>() as DeviceSize;
self.size = (end - start) * size_of::<T>() as DeviceSize; self.size = (end - start) * size_of::<T>() as DeviceSize;
assert!(self.size != 0);
self self
} }
@ -418,6 +453,7 @@ impl<T> Subbuffer<[T]> {
self.offset += start * size_of::<T>() as DeviceSize; self.offset += start * size_of::<T>() as DeviceSize;
self.size = (end - start) * size_of::<T>() as DeviceSize; self.size = (end - start) * size_of::<T>() as DeviceSize;
debug_assert!(self.size != 0);
self self
} }
@ -426,9 +462,10 @@ impl<T> Subbuffer<[T]> {
/// ///
/// # Panics /// # 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]>) { 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) } unsafe { self.split_at_unchecked(mid) }
} }
@ -443,6 +480,17 @@ impl<T> Subbuffer<[T]> {
} }
impl Subbuffer<[u8]> { impl Subbuffer<[u8]> {
/// Creates a new `Subbuffer<[u8]>` spanning the whole buffer.
#[inline]
pub fn new(buffer: Arc<Buffer>) -> 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. /// 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 /// 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
/// ///
/// - Panics if the aligned offset would be out of bounds. /// - Panics if the aligned offset would be out of bounds.
/// - Panics if `T` has zero size. pub fn cast_aligned<T>(self) -> Subbuffer<[T]>
/// - Panics if `T` has an alignment greater than `64`. where
pub fn cast_aligned<T>(self) -> Subbuffer<[T]> { T: BufferContents,
{
let layout = DeviceLayout::from_layout(Layout::new::<T>()).unwrap(); let layout = DeviceLayout::from_layout(Layout::new::<T>()).unwrap();
let aligned = self.align_to(layout); 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 /// 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 the aligned offset would be out of bounds.
/// - Panics if `layout.alignment()` exceeds `64`. /// - Panics if `layout.alignment()` exceeds `64`.
#[inline]
pub fn align_to(mut self, layout: DeviceLayout) -> Subbuffer<[u8]> { pub fn align_to(mut self, layout: DeviceLayout) -> Subbuffer<[u8]> {
assert!(layout.alignment().as_devicesize() <= 64); assert!(layout.alignment().as_devicesize() <= 64);
@ -484,20 +534,13 @@ impl Subbuffer<[u8]> {
impl<T> Subbuffer<[T]> impl<T> Subbuffer<[T]>
where where
[T]: BufferContents, T: BufferContents,
{ {
/// Tries to cast the slice to a different element type. /// 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<U>(self) -> Result<Subbuffer<[U]>, PodCastError> pub fn try_cast_slice<U>(self) -> Result<Subbuffer<[U]>, PodCastError>
where where
[U]: BufferContents, U: BufferContents,
{ {
assert_valid_type_param::<U>();
if size_of::<U>() != size_of::<T>() && self.size() % size_of::<U>() as DeviceSize != 0 { if size_of::<U>() != size_of::<T>() && self.size() % size_of::<U>() as DeviceSize != 0 {
Err(PodCastError::OutputSliceWouldHaveSlop) Err(PodCastError::OutputSliceWouldHaveSlop)
} else if align_of::<U>() > align_of::<T>() } else if align_of::<U>() > align_of::<T>()
@ -505,21 +548,15 @@ where
{ {
Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
} else { } else {
Ok(unsafe { self.reinterpret() }) Ok(unsafe { self.reinterpret_unchecked() })
} }
} }
} }
#[inline(always)]
fn assert_valid_type_param<T>() {
assert!(size_of::<T>() != 0);
assert!(align_of::<T>() <= 64);
}
impl From<Arc<Buffer>> for Subbuffer<[u8]> { impl From<Arc<Buffer>> for Subbuffer<[u8]> {
#[inline] #[inline]
fn from(buffer: Arc<Buffer>) -> Self { fn from(buffer: Arc<Buffer>) -> 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<T, U> {
/// x: T,
/// y: T,
/// slice: [U],
/// }
/// ```
///
/// This even works with dependently-sized types:
///
/// ```
/// # use vulkano::buffer::BufferContents;
/// #[derive(BufferContents)]
/// #[repr(C)]
/// struct MyData<T>
/// 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<T> BufferContents for T
where
T: AnyBitPattern + Send + Sync,
{
const LAYOUT: BufferContentsLayout =
if let Some(layout) = BufferContentsLayout::from_sized(Layout::new::<T>()) {
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::<T>());
debug_assert!(data as usize % align_of::<T>() == 0);
data.cast()
}
}
unsafe impl<T> 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::<T>() == 0);
debug_assert!(data as usize % align_of::<T>() == 0);
let len = range / size_of::<T>();
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<DeviceLayout>,
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<DeviceSize> {
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<DeviceLayout> {
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<Self> {
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<Self> {
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<Self> {
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<Self> {
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)] #[cfg(test)]
mod tests { mod tests {
use super::*; 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::<Test1>());
assert_eq!(Test1::LAYOUT.element_size(), None);
assert_eq!(
Test1::LAYOUT.alignment().as_devicesize() as usize,
align_of::<Test1>(),
);
#[derive(BufferContents)]
#[repr(C)]
struct Composite1(Test1, [f32; 10], Test1);
assert_eq!(
Composite1::LAYOUT.head_size() as usize,
size_of::<Composite1>(),
);
assert_eq!(Composite1::LAYOUT.element_size(), None);
assert_eq!(
Composite1::LAYOUT.alignment().as_devicesize() as usize,
align_of::<Composite1>(),
);
#[derive(BufferContents)]
#[repr(C)]
struct Test2(u64, u8, [u32]);
assert_eq!(
Test2::LAYOUT.head_size() as usize,
size_of::<u64>() + size_of::<u32>(),
);
assert_eq!(
Test2::LAYOUT.element_size().unwrap() as usize,
size_of::<u32>(),
);
assert_eq!(
Test2::LAYOUT.alignment().as_devicesize() as usize,
align_of::<u64>(),
);
#[derive(BufferContents)]
#[repr(C)]
struct Composite2(Test1, [f32; 10], Test2);
assert_eq!(
Composite2::LAYOUT.head_size() as usize,
size_of::<Test1>() + size_of::<[f32; 10]>() + size_of::<u64>() + size_of::<u32>(),
);
assert_eq!(
Composite2::LAYOUT.element_size().unwrap() as usize,
size_of::<u32>(),
);
assert_eq!(
Composite2::LAYOUT.alignment().as_devicesize() as usize,
align_of::<u64>(),
);
}
#[test] #[test]
fn split_at() { fn split_at() {
let (device, _) = gfx_dev_and_queue!(); let (device, _) = gfx_dev_and_queue!();
@ -714,13 +1232,17 @@ mod tests {
} }
{ {
let (left, right) = buffer.clone().split_at(6); let (left, right) = buffer.clone().split_at(5);
assert!(left.len() == 6); assert!(left.len() == 5);
assert!(right.len() == 0); 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) });
} }
} }

View File

@ -1197,7 +1197,7 @@ impl UnsafeCommandBufferBuilder {
stages.into(), stages.into(),
offset, offset,
size, size,
data.as_bytes().as_ptr() as *const _, data as *const _ as *const _,
); );
} }

View File

@ -400,7 +400,7 @@ where
assert_eq!(device, dst_buffer.device()); assert_eq!(device, dst_buffer.device());
// VUID-vkCmdFillBuffer-size-00026 // VUID-vkCmdFillBuffer-size-00026
assert!(dst_buffer.size() != 0); // Guaranteed by `Subbuffer`
// VUID-vkCmdFillBuffer-dstBuffer-00029 // VUID-vkCmdFillBuffer-dstBuffer-00029
if !dst_buffer if !dst_buffer
@ -438,7 +438,10 @@ where
D: BufferContents + ?Sized, D: BufferContents + ?Sized,
Dd: SafeDeref<Target = D> + Send + Sync + 'static, Dd: SafeDeref<Target = D> + 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 { unsafe {
self.inner.update_buffer(dst_buffer, data)?; self.inner.update_buffer(dst_buffer, data)?;
@ -450,7 +453,7 @@ where
fn validate_update_buffer( fn validate_update_buffer(
&self, &self,
dst_buffer: &Subbuffer<[u8]>, dst_buffer: &Subbuffer<[u8]>,
data: &[u8], data_size: DeviceSize,
) -> Result<(), ClearError> { ) -> Result<(), ClearError> {
let device = self.device(); let device = self.device();
@ -473,7 +476,7 @@ where
assert_eq!(device, dst_buffer.device()); assert_eq!(device, dst_buffer.device());
// VUID-vkCmdUpdateBuffer-dataSize-arraylength // VUID-vkCmdUpdateBuffer-dataSize-arraylength
assert!(size_of_val(data) != 0); assert!(data_size != 0);
// VUID-vkCmdUpdateBuffer-dstBuffer-00034 // VUID-vkCmdUpdateBuffer-dstBuffer-00034
if !dst_buffer if !dst_buffer
@ -488,10 +491,10 @@ where
// VUID-vkCmdUpdateBuffer-dstOffset-00032 // VUID-vkCmdUpdateBuffer-dstOffset-00032
// VUID-vkCmdUpdateBuffer-dataSize-00033 // VUID-vkCmdUpdateBuffer-dataSize-00033
if size_of_val(data) as DeviceSize > dst_buffer.size() { if data_size > dst_buffer.size() {
return Err(ClearError::RegionOutOfBufferBounds { return Err(ClearError::RegionOutOfBufferBounds {
region_index: 0, region_index: 0,
offset_range_end: size_of_val(data) as DeviceSize, offset_range_end: data_size,
buffer_size: dst_buffer.size(), buffer_size: dst_buffer.size(),
}); });
} }
@ -506,18 +509,18 @@ where
} }
// VUID-vkCmdUpdateBuffer-dataSize-00037 // VUID-vkCmdUpdateBuffer-dataSize-00037
if size_of_val(data) > 65536 { if data_size > 65536 {
return Err(ClearError::DataTooLarge { return Err(ClearError::DataTooLarge {
size: size_of_val(data) as DeviceSize, size: data_size,
max: 65536, max: 65536,
}); });
} }
// VUID-vkCmdUpdateBuffer-dataSize-00038 // VUID-vkCmdUpdateBuffer-dataSize-00038
if size_of_val(data) % 4 != 0 { if data_size % 4 != 0 {
return Err(ClearError::SizeNotAlignedForBuffer { return Err(ClearError::SizeNotAlignedForBuffer {
region_index: 0, region_index: 0,
size: size_of_val(data) as DeviceSize, size: data_size,
required_alignment: 4, required_alignment: 4,
}); });
} }
@ -736,12 +739,12 @@ impl SyncCommandBufferBuilder {
D: BufferContents + ?Sized, D: BufferContents + ?Sized,
Dd: SafeDeref<Target = D> + Send + Sync + 'static, Dd: SafeDeref<Target = D> + Send + Sync + 'static,
{ {
struct Cmd<Dd> { struct Cmd<D: ?Sized, Dd> {
dst_buffer: Subbuffer<[u8]>, dst_buffer: Subbuffer<D>,
data: Dd, data: Dd,
} }
impl<D, Dd> Command for Cmd<Dd> impl<D, Dd> Command for Cmd<D, Dd>
where where
D: BufferContents + ?Sized, D: BufferContents + ?Sized,
Dd: SafeDeref<Target = D> + Send + Sync + 'static, Dd: SafeDeref<Target = D> + Send + Sync + 'static,
@ -751,11 +754,10 @@ impl SyncCommandBufferBuilder {
} }
unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { 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_index = self.commands.len();
let command_name = "update_buffer"; let command_name = "update_buffer";
let resources = [( let resources = [(
@ -766,7 +768,7 @@ impl SyncCommandBufferBuilder {
secondary_use_ref: None, secondary_use_ref: None,
}, },
Resource::Buffer { Resource::Buffer {
buffer: dst_buffer.clone(), buffer: dst_buffer.as_bytes().clone(),
range: 0..size_of_val(data.deref()) as DeviceSize, range: 0..size_of_val(data.deref()) as DeviceSize,
memory: PipelineMemoryAccess { memory: PipelineMemoryAccess {
stages: PipelineStages::ALL_TRANSFER, stages: PipelineStages::ALL_TRANSFER,
@ -887,7 +889,7 @@ impl UnsafeCommandBufferBuilder {
dst_buffer.buffer().handle(), dst_buffer.buffer().handle(),
dst_buffer.offset(), dst_buffer.offset(),
size_of_val(data) as DeviceSize, size_of_val(data) as DeviceSize,
data.as_bytes().as_ptr() as *const _, data as *const _ as *const _,
); );
} }
} }

View File

@ -71,11 +71,10 @@
//! use vulkano::command_buffer::PrimaryCommandBufferAbstract; //! use vulkano::command_buffer::PrimaryCommandBufferAbstract;
//! use vulkano::command_buffer::SubpassContents; //! use vulkano::command_buffer::SubpassContents;
//! //!
//! # use vulkano::pipeline::graphics::vertex_input::Vertex; //! # use vulkano::{buffer::BufferContents, pipeline::graphics::vertex_input::Vertex};
//! # use bytemuck::{Pod, Zeroable};
//! //!
//! # #[derive(BufferContents, Vertex)]
//! # #[repr(C)] //! # #[repr(C)]
//! # #[derive(Clone, Copy, Debug, Default, Zeroable, Pod, Vertex)]
//! # struct PosVertex { //! # struct PosVertex {
//! # #[format(R32G32B32_SFLOAT)] //! # #[format(R32G32B32_SFLOAT)]
//! # position: [f32; 3] //! # position: [f32; 3]

View File

@ -28,7 +28,7 @@ use crate::{
DeviceSize, RequiresOneOf, VulkanObject, DeviceSize, RequiresOneOf, VulkanObject,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{cmp::min, sync::Arc}; use std::{cmp::min, mem::size_of_val, os::raw::c_void, sync::Arc};
impl<L, A> CommandBufferBuilder<L, A> impl<L, A> CommandBufferBuilder<L, A>
where where
@ -627,15 +627,9 @@ where
&mut self, &mut self,
pipeline_layout: Arc<PipelineLayout>, pipeline_layout: Arc<PipelineLayout>,
offset: u32, offset: u32,
push_constants: &impl BufferContents, push_constants: &(impl BufferContents + ?Sized),
) -> &mut Self { ) -> &mut Self {
let push_constants = push_constants.as_bytes(); self.validate_push_constants(&pipeline_layout, offset, size_of_val(push_constants) as u32)
if push_constants.is_empty() {
return self;
}
self.validate_push_constants(&pipeline_layout, offset, push_constants)
.unwrap(); .unwrap();
unsafe { self.push_constants_unchecked(pipeline_layout, offset, push_constants) } unsafe { self.push_constants_unchecked(pipeline_layout, offset, push_constants) }
@ -645,18 +639,18 @@ where
&self, &self,
pipeline_layout: &PipelineLayout, pipeline_layout: &PipelineLayout,
offset: u32, offset: u32,
push_constants: &[u8], data_size: u32,
) -> Result<(), BindPushError> { ) -> Result<(), BindPushError> {
if offset % 4 != 0 { if offset % 4 != 0 {
return Err(BindPushError::PushConstantsOffsetNotAligned); return Err(BindPushError::PushConstantsOffsetNotAligned);
} }
if push_constants.len() % 4 != 0 { if data_size % 4 != 0 {
return Err(BindPushError::PushConstantsSizeNotAligned); return Err(BindPushError::PushConstantsSizeNotAligned);
} }
let mut current_offset = offset; 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 for range in pipeline_layout
.push_constant_ranges_disjoint() .push_constant_ranges_disjoint()
@ -695,9 +689,8 @@ where
offset: u32, offset: u32,
push_constants: &(impl BufferContents + ?Sized), push_constants: &(impl BufferContents + ?Sized),
) -> &mut Self { ) -> &mut Self {
let push_constants = push_constants.as_bytes();
let mut current_offset = offset; 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(); 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 // 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 push_size = min(remaining_size, range.offset + range.size - current_offset);
let data_offset = (current_offset - offset) as usize; 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)( (fns.v1_0.cmd_push_constants)(
self.handle(), self.handle(),
pipeline_layout.handle(), pipeline_layout.handle(),
range.stages.into(), range.stages.into(),
current_offset, current_offset,
values.len() as u32, push_size,
values.as_ptr() as *const _, (push_constants as *const _ as *const c_void).add(data_offset),
); );
current_offset += push_size; current_offset += push_size;
@ -743,7 +735,7 @@ where
// https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/2711 // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/2711
self.builder_state self.builder_state
.push_constants .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.builder_state.push_constants_pipeline_layout = Some(pipeline_layout.clone());
self.resources.push(Box::new(pipeline_layout)); self.resources.push(Box::new(pipeline_layout));

View File

@ -532,7 +532,7 @@ where
assert_eq!(device, dst_buffer.device()); assert_eq!(device, dst_buffer.device());
// VUID-vkCmdFillBuffer-size-00026 // VUID-vkCmdFillBuffer-size-00026
assert!(dst_buffer.size() != 0); // Guaranteed by `Subbuffer`
// VUID-vkCmdFillBuffer-dstBuffer-00029 // VUID-vkCmdFillBuffer-dstBuffer-00029
if !dst_buffer if !dst_buffer
@ -614,15 +614,15 @@ where
where where
D: BufferContents + ?Sized, 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( fn validate_update_buffer(
&self, &self,
dst_buffer: &Subbuffer<[u8]>, dst_buffer: &Subbuffer<[u8]>,
data: &[u8], data_size: DeviceSize,
) -> Result<(), ClearError> { ) -> Result<(), ClearError> {
let device = self.device(); let device = self.device();
@ -645,7 +645,7 @@ where
assert_eq!(device, dst_buffer.device()); assert_eq!(device, dst_buffer.device());
// VUID-vkCmdUpdateBuffer-dataSize-arraylength // VUID-vkCmdUpdateBuffer-dataSize-arraylength
assert!(size_of_val(data) != 0); assert!(data_size != 0);
// VUID-vkCmdUpdateBuffer-dstBuffer-00034 // VUID-vkCmdUpdateBuffer-dstBuffer-00034
if !dst_buffer if !dst_buffer
@ -660,10 +660,10 @@ where
// VUID-vkCmdUpdateBuffer-dstOffset-00032 // VUID-vkCmdUpdateBuffer-dstOffset-00032
// VUID-vkCmdUpdateBuffer-dataSize-00033 // VUID-vkCmdUpdateBuffer-dataSize-00033
if size_of_val(data) as DeviceSize > dst_buffer.size() { if data_size > dst_buffer.size() {
return Err(ClearError::RegionOutOfBufferBounds { return Err(ClearError::RegionOutOfBufferBounds {
region_index: 0, region_index: 0,
offset_range_end: size_of_val(data) as DeviceSize, offset_range_end: data_size,
buffer_size: dst_buffer.size(), buffer_size: dst_buffer.size(),
}); });
} }
@ -678,18 +678,18 @@ where
} }
// VUID-vkCmdUpdateBuffer-dataSize-00037 // VUID-vkCmdUpdateBuffer-dataSize-00037
if size_of_val(data) > 65536 { if data_size > 65536 {
return Err(ClearError::DataTooLarge { return Err(ClearError::DataTooLarge {
size: size_of_val(data) as DeviceSize, size: data_size,
max: 65536, max: 65536,
}); });
} }
// VUID-vkCmdUpdateBuffer-dataSize-00038 // VUID-vkCmdUpdateBuffer-dataSize-00038
if size_of_val(data) % 4 != 0 { if data_size % 4 != 0 {
return Err(ClearError::SizeNotAlignedForBuffer { return Err(ClearError::SizeNotAlignedForBuffer {
region_index: 0, region_index: 0,
size: size_of_val(data) as DeviceSize, size: data_size,
required_alignment: 4, required_alignment: 4,
}); });
} }
@ -714,7 +714,7 @@ where
dst_buffer.buffer().handle(), dst_buffer.buffer().handle(),
dst_buffer.offset(), dst_buffer.offset(),
size_of_val(data) as DeviceSize, 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; let command_index = self.next_command_index;

View File

@ -184,7 +184,7 @@ impl ImmutableImage {
command_buffer_builder: &mut AutoCommandBufferBuilder<L, A>, command_buffer_builder: &mut AutoCommandBufferBuilder<L, A>,
) -> Result<Arc<Self>, ImmutableImageCreationError> ) -> Result<Arc<Self>, ImmutableImageCreationError>
where where
[Px]: BufferContents, Px: BufferContents,
I: IntoIterator<Item = Px>, I: IntoIterator<Item = Px>,
I::IntoIter: ExactSizeIterator, I::IntoIter: ExactSizeIterator,
A: CommandBufferAllocator, A: CommandBufferAllocator,

View File

@ -17,6 +17,7 @@
//! | `document_unchecked` | Include `_unchecked` functions in the generated documentation. | //! | `document_unchecked` | Include `_unchecked` functions in the generated documentation. |
//! | `cgmath` | Generate additional definitions and functions using the [`cgmath`] library. | //! | `cgmath` | Generate additional definitions and functions using the [`cgmath`] library. |
//! | `nalgebra` | Generate additional definitions and functions using the [`nalgebra`] library. | //! | `nalgebra` | Generate additional definitions and functions using the [`nalgebra`] library. |
//! | `serde` | Enables (de)serialization of certain types using [`serde`]. |
//! //!
//! # Starting off with Vulkano //! # Starting off with Vulkano
//! //!
@ -104,6 +105,7 @@
//! //!
//! [`cgmath`]: https://crates.io/crates/cgmath //! [`cgmath`]: https://crates.io/crates/cgmath
//! [`nalgebra`]: https://crates.io/crates/nalgebra //! [`nalgebra`]: https://crates.io/crates/nalgebra
//! [`serde`]: https://crates.io/crates/serde
//! [`VulkanLibrary`]: crate::VulkanLibrary //! [`VulkanLibrary`]: crate::VulkanLibrary
//! [`Instance`]: crate::instance::Instance //! [`Instance`]: crate::instance::Instance
//! [`Surface`]: crate::swapchain::Surface //! [`Surface`]: crate::swapchain::Surface
@ -182,6 +184,7 @@ pub mod instance;
pub mod library; pub mod library;
mod macros; mod macros;
pub mod memory; pub mod memory;
pub mod padded;
pub mod pipeline; pub mod pipeline;
pub mod query; pub mod query;
mod range_map; mod range_map;

View File

@ -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};

View File

@ -8,10 +8,10 @@
// according to those terms. // according to those terms.
use super::align_up; use super::align_up;
use crate::{DeviceSize, NonZeroDeviceSize}; use crate::{macros::try_opt, DeviceSize, NonZeroDeviceSize};
use std::{ use std::{
alloc::Layout, alloc::Layout,
cmp::{self, Ordering}, cmp::Ordering,
error::Error, error::Error,
fmt::{Debug, Display, Formatter, Result as FmtResult}, fmt::{Debug, Display, Formatter, Result as FmtResult},
hash::{Hash, Hasher}, hash::{Hash, Hasher},
@ -40,7 +40,7 @@ impl DeviceLayout {
/// zero size. /// zero size.
#[inline] #[inline]
pub const fn from_layout(layout: Layout) -> Result<Self, TryFromLayoutError> { pub const fn from_layout(layout: Layout) -> Result<Self, TryFromLayoutError> {
let (size, alignment) = (layout.size(), layout.align()); let (size, alignment) = Self::size_alignment_from_layout(&layout);
#[cfg(any( #[cfg(any(
target_pointer_width = "64", target_pointer_width = "64",
@ -51,10 +51,7 @@ impl DeviceLayout {
const _: () = assert!(size_of::<DeviceSize>() >= size_of::<usize>()); const _: () = assert!(size_of::<DeviceSize>() >= size_of::<usize>());
const _: () = assert!(DeviceLayout::MAX_SIZE >= isize::MAX as DeviceSize); const _: () = assert!(DeviceLayout::MAX_SIZE >= isize::MAX as DeviceSize);
if let Some(size) = NonZeroDeviceSize::new(size as DeviceSize) { if let Some(size) = NonZeroDeviceSize::new(size) {
// SAFETY: `Layout`'s alignment-invariant guarantees that it is a power of two.
let alignment = unsafe { DeviceAlignment::new_unchecked(alignment as DeviceSize) };
// SAFETY: Under the precondition that `usize` can't overflow `DeviceSize`, which // 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 // we checked above, `Layout`'s overflow-invariant is the same if not stricter than
// that of `DeviceLayout`. // that of `DeviceLayout`.
@ -102,14 +99,10 @@ impl DeviceLayout {
/// exceed [`DeviceLayout::MAX_SIZE`] when rounded up to the nearest multiple of `alignment`. /// exceed [`DeviceLayout::MAX_SIZE`] when rounded up to the nearest multiple of `alignment`.
#[inline] #[inline]
pub const fn from_size_alignment(size: DeviceSize, alignment: DeviceSize) -> Option<Self> { pub const fn from_size_alignment(size: DeviceSize, alignment: DeviceSize) -> Option<Self> {
if let (Some(size), Some(alignment)) = ( let size = try_opt!(NonZeroDeviceSize::new(size));
NonZeroDeviceSize::new(size), let alignment = try_opt!(DeviceAlignment::new(alignment));
DeviceAlignment::new(alignment),
) { DeviceLayout::new(size, alignment)
DeviceLayout::new(size, alignment)
} else {
None
}
} }
/// Creates a new `DeviceLayout` from the given `size` and `alignment` without doing any /// 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. /// - `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 /// - `size`, when rounded up to the nearest multiple of `alignment`, must not exceed
/// [`DeviceLayout::MAX_SIZE`]. /// [`DeviceLayout::MAX_SIZE`].
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
#[inline] #[inline]
pub const unsafe fn from_size_alignment_unchecked( pub const unsafe fn from_size_alignment_unchecked(
size: DeviceSize, size: DeviceSize,
@ -159,6 +153,7 @@ impl DeviceLayout {
/// ///
/// - `size`, when rounded up to the nearest multiple of `alignment`, must not exceed /// - `size`, when rounded up to the nearest multiple of `alignment`, must not exceed
/// [`DeviceLayout::MAX_SIZE`]. /// [`DeviceLayout::MAX_SIZE`].
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
#[inline] #[inline]
pub const unsafe fn new_unchecked(size: NonZeroDeviceSize, alignment: DeviceAlignment) -> Self { pub const unsafe fn new_unchecked(size: NonZeroDeviceSize, alignment: DeviceAlignment) -> Self {
debug_assert!(size.get() <= Self::max_size_for_alignment(alignment)); 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 /// Returns [`None`] if `self.size()` would overflow [`DeviceLayout::MAX_SIZE`] when rounded up
/// to the nearest multiple of `alignment`. /// to the nearest multiple of `alignment`.
#[inline] #[inline]
pub fn align_to(&self, alignment: DeviceAlignment) -> Option<Self> { pub const fn align_to(&self, alignment: DeviceAlignment) -> Option<Self> {
DeviceLayout::new(self.size, cmp::max(self.alignment, alignment)) 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 /// 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 /// returns [`None`] on arithmetic overflow or when the total size would exceed
/// [`DeviceLayout::MAX_SIZE`]. /// [`DeviceLayout::MAX_SIZE`].
#[inline] #[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 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 /// 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 /// [`pad_to_alignment`]: Self::pad_to_alignment
#[inline] #[inline]
pub fn extend(&self, next: Self) -> Option<(Self, DeviceSize)> { pub const fn extend(&self, next: Self) -> Option<(Self, DeviceSize)> {
let padding = self.padding_needed_for(next.alignment); self.extend_inner(next.size(), next.alignment)
let offset = self.size.checked_add(padding)?; }
let size = offset.checked_add(next.size())?;
let alignment = cmp::max(self.alignment, 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::<DeviceSize>() >= size_of::<usize>());
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 { pub const fn log2(self) -> u32 {
self.as_nonzero().trailing_zeros() 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 { impl Debug for DeviceAlignment {

View File

@ -204,28 +204,6 @@ impl MemoryAlloc {
.map(|ptr| slice::from_raw_parts_mut(ptr.as_ptr().cast(), self.size as usize)) .map(|ptr| slice::from_raw_parts_mut(ptr.as_ptr().cast(), self.size as usize))
} }
pub(crate) unsafe fn read(&self, range: Range<DeviceSize>) -> 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<DeviceSize>) -> 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<DeviceAlignment> { pub(crate) fn atom_size(&self) -> Option<DeviceAlignment> {
self.atom_size self.atom_size
} }

338
vulkano/src/padded.rs Normal file
View File

@ -0,0 +1,338 @@
// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// 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<i32, 12>,
/// 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<i32, 12>; 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<T, const N: usize> {
value: T,
_padding: [MaybeUninit<u8>; N],
}
#[allow(non_snake_case)]
#[doc(hidden)]
#[inline(always)]
pub const fn Padded<T, const N: usize>(value: T) -> Padded<T, N> {
Padded {
value,
_padding: [MaybeUninit::uninit(); N],
}
}
impl<T, const N: usize> AsRef<T> for Padded<T, N> {
fn as_ref(&self) -> &T {
&self.value
}
}
impl<T, const N: usize> AsMut<T> for Padded<T, N> {
fn as_mut(&mut self) -> &mut T {
&mut self.value
}
}
impl<T, const N: usize> Clone for Padded<T, N>
where
T: Clone,
{
fn clone(&self) -> Self {
Padded(self.value.clone())
}
}
impl<T, const N: usize> Copy for Padded<T, N> where T: Copy {}
impl<T, const N: usize> Debug for Padded<T, N>
where
T: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
self.value.fmt(f)
}
}
impl<T, const N: usize> Default for Padded<T, N>
where
T: Default,
{
fn default() -> Self {
Padded(T::default())
}
}
impl<T, const N: usize> Deref for Padded<T, N> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<T, const N: usize> DerefMut for Padded<T, N> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.value
}
}
impl<T, const N: usize> Display for Padded<T, N>
where
T: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
self.value.fmt(f)
}
}
impl<T, const N: usize> From<T> for Padded<T, N> {
fn from(value: T) -> Self {
Padded(value)
}
}
impl<T, const N: usize> PartialEq for Padded<T, N>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T, const N: usize> Eq for Padded<T, N> where T: Eq {}
impl<T, const N: usize> Hash for Padded<T, N>
where
T: Hash,
{
fn hash<H: Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
impl<T, const N: usize> PartialOrd for Padded<T, N>
where
T: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.value.partial_cmp(&other.value)
}
}
impl<T, const N: usize> Ord for Padded<T, N>
where
T: Ord,
{
fn cmp(&self, other: &Self) -> Ordering {
self.value.cmp(&other.value)
}
}
unsafe impl<T, const N: usize> BufferContents for Padded<T, N>
where
T: BufferContents,
{
const LAYOUT: BufferContentsLayout =
if let Some(layout) = BufferContentsLayout::from_sized(Layout::new::<Self>()) {
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::<Self>());
debug_assert!(data as usize % align_of::<Self>() == 0);
data.cast()
}
}
#[cfg(feature = "serde")]
impl<T, const N: usize> Serialize for Padded<T, N>
where
T: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.value.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, T, const N: usize> Deserialize<'de> for Padded<T, N>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
T::deserialize(deserializer).map(Padded)
}
}

View File

@ -10,11 +10,11 @@
//! Configures how input vertices are assembled into primitives. //! Configures how input vertices are assembled into primitives.
use crate::{ use crate::{
buffer::BufferContents,
macros::vulkan_enum, macros::vulkan_enum,
pipeline::{PartialStateMode, StateMode}, pipeline::{PartialStateMode, StateMode},
DeviceSize, DeviceSize,
}; };
use bytemuck::Pod;
/// The state in a graphics pipeline describing how the input assembly stage should behave. /// The state in a graphics pipeline describing how the input assembly stage should behave.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -199,7 +199,7 @@ impl PrimitiveTopologyClass {
} }
/// Trait for types that can be used as indices by the GPU. /// 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. /// Returns the type of data.
fn ty() -> IndexType; fn ty() -> IndexType;
} }

View File

@ -40,7 +40,7 @@ impl<T: ?Sized> VertexBuffersCollection for Vec<Subbuffer<T>> {
} }
} }
impl<T, const N: usize> VertexBuffersCollection for [Subbuffer<T>; N] { impl<T: ?Sized, const N: usize> VertexBuffersCollection for [Subbuffer<T>; N] {
fn into_vec(self) -> Vec<Subbuffer<[u8]>> { fn into_vec(self) -> Vec<Subbuffer<[u8]>> {
self.into_iter().map(Subbuffer::into_bytes).collect() self.into_iter().map(Subbuffer::into_bytes).collect()
} }

View File

@ -14,10 +14,10 @@ use crate::format::Format;
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// # use bytemuck::{Zeroable, Pod}; /// # use vulkano::buffer::BufferContents;
/// #[derive(BufferContents, Default)]
/// #[repr(C)] /// #[repr(C)]
/// #[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] /// struct Vertex {
/// struct Vertex{
/// position: [f32; 3], /// position: [f32; 3],
/// color: [f32; 4], /// color: [f32; 4],
/// } /// }
@ -26,7 +26,7 @@ use crate::format::Format;
/// ``` /// ```
#[deprecated( #[deprecated(
since = "0.33.0", 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_export]
macro_rules! impl_vertex { macro_rules! impl_vertex {

View File

@ -8,8 +8,7 @@
// according to those terms. // according to those terms.
use super::VertexInputRate; use super::VertexInputRate;
use crate::format::Format; use crate::{buffer::BufferContents, format::Format};
use bytemuck::Pod;
use std::collections::HashMap; use std::collections::HashMap;
pub use vulkano_macros::Vertex; 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` /// The vertex trait can be derived and the format has to be specified using the `format`
/// field-level attribute: /// 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)] /// #[repr(C)]
/// #[derive(Clone, Copy, Debug, Default, Pod, Zeroable, Vertex)]
/// struct MyVertex { /// struct MyVertex {
/// // Every field needs to explicitly state the desired shader input format /// // Every field needs to explicitly state the desired shader input format
/// #[format(R32G32B32_SFLOAT)] /// #[format(R32G32B32_SFLOAT)]
@ -38,10 +37,12 @@ pub use vulkano_macros::Vertex;
/// proj: [f32; 16], /// proj: [f32; 16],
/// } /// }
/// ``` /// ```
pub unsafe trait Vertex: Pod + Send + Sync + 'static { pub unsafe trait Vertex: BufferContents + Sized {
/// Returns the information about this Vertex type. /// Returns the information about this Vertex type.
fn per_vertex() -> VertexBufferDescription; fn per_vertex() -> VertexBufferDescription;
fn per_instance() -> VertexBufferDescription; fn per_instance() -> VertexBufferDescription;
fn per_instance_with_divisor(divisor: u32) -> VertexBufferDescription; fn per_instance_with_divisor(divisor: u32) -> VertexBufferDescription;
} }
@ -100,6 +101,7 @@ pub struct VertexMemberInfo {
} }
impl VertexMemberInfo { impl VertexMemberInfo {
#[inline]
pub fn num_components(&self) -> u32 { pub fn num_components(&self) -> u32 {
self.format self.format
.components() .components()

View File

@ -14,11 +14,11 @@
//! pool and the slot id within that query pool. //! pool and the slot id within that query pool.
use crate::{ use crate::{
buffer::BufferContents,
device::{Device, DeviceOwned}, device::{Device, DeviceOwned},
macros::vulkan_bitflags, macros::vulkan_bitflags,
DeviceSize, OomError, RequirementNotMet, RequiresOneOf, VulkanError, VulkanObject, DeviceSize, OomError, RequirementNotMet, RequiresOneOf, VulkanError, VulkanObject,
}; };
use bytemuck::Pod;
use std::{ use std::{
error::Error, error::Error,
ffi::c_void, ffi::c_void,
@ -513,7 +513,7 @@ impl From<RequirementNotMet> for GetResultsError {
/// # Safety /// # Safety
/// This is implemented for `u32` and `u64`. Unless you really know what you're doing, you should /// 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. /// 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; const FLAG: ash::vk::QueryResultFlags;
} }