diff --git a/examples/async-update/main.rs b/examples/async-update/main.rs index 4f1808674e..27211a2f48 100644 --- a/examples/async-update/main.rs +++ b/examples/async-update/main.rs @@ -42,10 +42,7 @@ use std::{ }; use vulkano::{ buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer}, - command_buffer::{ - sys::RawRecordingCommandBuffer, BufferImageCopy, ClearColorImageInfo, - CopyBufferToImageInfo, RenderPassBeginInfo, - }, + command_buffer::RenderPassBeginInfo, descriptor_set::{ allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet, }, @@ -57,7 +54,7 @@ use vulkano::{ image::{ sampler::{Sampler, SamplerCreateInfo}, view::ImageView, - Image, ImageCreateInfo, ImageType, ImageUsage, + Image, ImageAspects, ImageCreateInfo, ImageSubresourceLayers, ImageType, ImageUsage, }, instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}, memory::allocator::{AllocationCreateInfo, DeviceLayout, MemoryTypeFilter}, @@ -81,6 +78,9 @@ use vulkano::{ DeviceSize, Validated, VulkanError, VulkanLibrary, }; use vulkano_taskgraph::{ + command_buffer::{ + BufferImageCopy, ClearColorImageInfo, CopyBufferToImageInfo, RecordingCommandBuffer, + }, graph::{CompileInfo, ExecuteError, TaskGraph}, resource::{AccessType, Flight, HostAccessType, ImageLayoutType, Resources}, resource_map, Id, QueueFamilyType, Task, TaskContext, TaskResult, @@ -224,7 +224,7 @@ fn main() -> Result<(), impl Error> { {transfer_family_index} for transfers", ); - let resources = Resources::new(device.clone(), Default::default()); + let resources = Resources::new(&device, &Default::default()); let graphics_flight_id = resources.create_flight(MAX_FRAMES_IN_FLIGHT).unwrap(); let transfer_flight_id = resources.create_flight(1).unwrap(); @@ -343,16 +343,18 @@ fn main() -> Result<(), impl Error> { // Initialize the resources. unsafe { vulkano_taskgraph::execute( - graphics_queue.clone(), - resources.clone(), + &graphics_queue, + &resources, graphics_flight_id, |cbf, tcx| { tcx.write_buffer::<[MyVertex]>(vertex_buffer_id, ..)? .copy_from_slice(&vertices); for &texture_id in &texture_ids { - let texture = tcx.image(texture_id)?.image(); - cbf.clear_color_image(&ClearColorImageInfo::image(texture.clone()))?; + cbf.clear_color_image_unchecked(&ClearColorImageInfo { + image: texture_id, + ..Default::default() + }); } Ok(()) @@ -504,7 +506,7 @@ fn main() -> Result<(), impl Error> { framebuffers, }; - let mut task_graph = TaskGraph::new(resources.clone(), 1, 4); + let mut task_graph = TaskGraph::new(&resources, 1, 4); let virtual_swapchain_id = task_graph.add_swapchain(&SwapchainCreateInfo::default()); let virtual_texture_id = task_graph.add_image(&texture_create_info); @@ -543,9 +545,9 @@ fn main() -> Result<(), impl Error> { ); let task_graph = unsafe { - task_graph.compile(CompileInfo { - queues: vec![graphics_queue.clone()], - present_queue: Some(graphics_queue.clone()), + task_graph.compile(&CompileInfo { + queues: &[&graphics_queue], + present_queue: Some(&graphics_queue), flight_id: graphics_flight_id, ..Default::default() }) @@ -729,7 +731,7 @@ impl Task for RenderTask { unsafe fn execute( &self, - cbf: &mut RawRecordingCommandBuffer, + cbf: &mut RecordingCommandBuffer<'_>, tcx: &mut TaskContext<'_>, rcx: &Self::World, ) -> TaskResult { @@ -755,6 +757,8 @@ impl Task for RenderTask { }, }; + let cbf = cbf.raw_command_buffer(); + cbf.begin_render_pass( &RenderPassBeginInfo { clear_values: vec![Some([0.0, 0.0, 0.0, 1.0].into())], @@ -831,7 +835,7 @@ fn run_worker( .unwrap() }); - let mut task_graph = TaskGraph::new(resources.clone(), 1, 3); + let mut task_graph = TaskGraph::new(&resources, 1, 3); let virtual_front_staging_buffer_id = task_graph.add_buffer(&BufferCreateInfo::default()); let virtual_back_staging_buffer_id = task_graph.add_buffer(&BufferCreateInfo::default()); @@ -861,8 +865,8 @@ fn run_worker( ); let task_graph = unsafe { - task_graph.compile(CompileInfo { - queues: vec![transfer_queue], + task_graph.compile(&CompileInfo { + queues: &[&transfer_queue], flight_id: transfer_flight_id, ..Default::default() }) @@ -942,7 +946,7 @@ impl Task for UploadTask { unsafe fn execute( &self, - cbf: &mut RawRecordingCommandBuffer, + cbf: &mut RecordingCommandBuffer<'_>, tcx: &mut TaskContext<'_>, ¤t_corner: &Self::World, ) -> TaskResult { @@ -967,42 +971,38 @@ impl Task for UploadTask { tcx.write_buffer::<[_]>(self.front_staging_buffer_id, ..)? .fill(color); - let texture = tcx.image(self.texture_id)?.image(); - - cbf.copy_buffer_to_image(&CopyBufferToImageInfo { - regions: [BufferImageCopy { - image_subresource: texture.subresource_layers(), + cbf.copy_buffer_to_image_unchecked(&CopyBufferToImageInfo { + src_buffer: self.front_staging_buffer_id, + dst_image: self.texture_id, + regions: &[BufferImageCopy { + image_subresource: ImageSubresourceLayers { + aspects: ImageAspects::COLOR, + mip_level: 0, + array_layers: 0..1, + }, image_offset: CORNER_OFFSETS[current_corner % 4], image_extent: [TRANSFER_GRANULARITY, TRANSFER_GRANULARITY, 1], ..Default::default() - }] - .into(), - ..CopyBufferToImageInfo::buffer_image( - tcx.buffer(self.front_staging_buffer_id)? - .buffer() - .clone() - .into(), - texture.clone(), - ) - })?; + }], + ..Default::default() + }); if current_corner > 0 { - cbf.copy_buffer_to_image(&CopyBufferToImageInfo { - regions: [BufferImageCopy { - image_subresource: texture.subresource_layers(), + cbf.copy_buffer_to_image_unchecked(&CopyBufferToImageInfo { + src_buffer: self.back_staging_buffer_id, + dst_image: self.texture_id, + regions: &[BufferImageCopy { + image_subresource: ImageSubresourceLayers { + aspects: ImageAspects::COLOR, + mip_level: 0, + array_layers: 0..1, + }, image_offset: CORNER_OFFSETS[(current_corner - 1) % 4], image_extent: [TRANSFER_GRANULARITY, TRANSFER_GRANULARITY, 1], ..Default::default() - }] - .into(), - ..CopyBufferToImageInfo::buffer_image( - tcx.buffer(self.back_staging_buffer_id)? - .buffer() - .clone() - .into(), - texture.clone(), - ) - })?; + }], + ..Default::default() + }); } Ok(()) diff --git a/vulkano-taskgraph/src/command_buffer/commands/bind_push.rs b/vulkano-taskgraph/src/command_buffer/commands/bind_push.rs new file mode 100644 index 0000000000..eabdc4495e --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/bind_push.rs @@ -0,0 +1,209 @@ +use crate::{command_buffer::RecordingCommandBuffer, Id}; +use ash::vk; +use smallvec::SmallVec; +use std::{ffi::c_void, mem, ptr, sync::Arc}; +use vulkano::{ + self, + buffer::{Buffer, BufferContents, IndexType}, + device::DeviceOwned, + pipeline::{ComputePipeline, GraphicsPipeline, PipelineLayout}, + DeviceSize, Version, VulkanObject, +}; + +/// # Commands to bind or push state for pipeline execution commands +/// +/// These commands require a queue with a pipeline type that uses the given state. +impl RecordingCommandBuffer<'_> { + pub unsafe fn bind_index_buffer_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + index_type: IndexType, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_bind_index_buffer)( + self.handle(), + buffer.handle(), + offset, + index_type.into(), + ) + }; + + self + } + + pub unsafe fn bind_pipeline_compute_unchecked( + &mut self, + pipeline: &Arc, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_bind_pipeline)( + self.handle(), + vk::PipelineBindPoint::COMPUTE, + pipeline.handle(), + ) + }; + + self + } + + pub unsafe fn bind_pipeline_graphics_unchecked( + &mut self, + pipeline: &Arc, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_bind_pipeline)( + self.handle(), + vk::PipelineBindPoint::GRAPHICS, + pipeline.handle(), + ) + }; + + self + } + + pub unsafe fn bind_vertex_buffers_unchecked( + &mut self, + first_binding: u32, + buffers: &[Id], + offsets: &[DeviceSize], + sizes: &[DeviceSize], + strides: &[DeviceSize], + ) -> &mut Self { + if buffers.is_empty() { + return self; + } + + let buffers_vk = buffers + .iter() + .map(|&buffer| unsafe { self.accesses.buffer_unchecked(buffer) }.handle()) + .collect::>(); + + let device = self.device(); + + if device.api_version() >= Version::V1_3 + || device.enabled_extensions().ext_extended_dynamic_state + || device.enabled_extensions().ext_shader_object + { + let fns = self.device().fns(); + let cmd_bind_vertex_buffers2 = if device.api_version() >= Version::V1_3 { + fns.v1_3.cmd_bind_vertex_buffers2 + } else if device.enabled_extensions().ext_extended_dynamic_state { + fns.ext_extended_dynamic_state.cmd_bind_vertex_buffers2_ext + } else { + fns.ext_shader_object.cmd_bind_vertex_buffers2_ext + }; + + unsafe { + cmd_bind_vertex_buffers2( + self.handle(), + first_binding, + buffers_vk.len() as u32, + buffers_vk.as_ptr(), + offsets.as_ptr(), + if sizes.is_empty() { + ptr::null() + } else { + sizes.as_ptr() + }, + if strides.is_empty() { + ptr::null() + } else { + strides.as_ptr() + }, + ) + }; + } else { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_bind_vertex_buffers)( + self.handle(), + first_binding, + buffers_vk.len() as u32, + buffers_vk.as_ptr(), + offsets.as_ptr(), + ) + }; + } + + self + } + + pub unsafe fn push_constants_unchecked( + &mut self, + layout: &Arc, + offset: u32, + values: &(impl BufferContents + ?Sized), + ) -> &mut Self { + unsafe { + self.push_constants_unchecked_inner( + layout, + offset, + <*const _>::cast(values), + mem::size_of_val(values) as u32, + ) + } + } + + unsafe fn push_constants_unchecked_inner( + &mut self, + layout: &Arc, + offset: u32, + values: *const c_void, + size: u32, + ) -> &mut Self { + if size == 0 { + return self; + } + + let fns = self.device().fns(); + let mut current_offset = offset; + let mut remaining_size = size; + + for range in layout + .push_constant_ranges_disjoint() + .iter() + .skip_while(|range| range.offset + range.size <= offset) + { + // There is a gap between ranges, but the passed `values` contain some bytes in this + // gap; exit the loop and report an error. + if range.offset > current_offset { + break; + } + + // Push the minimum of the whole remaining data and the part until the end of this + // range. + let push_size = remaining_size.min(range.offset + range.size - current_offset); + let push_offset = (current_offset - offset) as usize; + debug_assert!(push_offset < size as usize); + let push_values = unsafe { values.add(push_offset) }; + + unsafe { + (fns.v1_0.cmd_push_constants)( + self.handle(), + layout.handle(), + range.stages.into(), + current_offset, + push_size, + push_values, + ) + }; + + current_offset += push_size; + remaining_size -= push_size; + + if remaining_size == 0 { + break; + } + } + + debug_assert_eq!(remaining_size, 0); + + self + } +} diff --git a/vulkano-taskgraph/src/command_buffer/commands/clear.rs b/vulkano-taskgraph/src/command_buffer/commands/clear.rs new file mode 100644 index 0000000000..248b80c243 --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/clear.rs @@ -0,0 +1,296 @@ +use crate::{ + command_buffer::RecordingCommandBuffer, + resource::{AccessType, ImageLayoutType}, + Id, +}; +use ash::vk; +use smallvec::SmallVec; +use std::mem; +use vulkano::{ + buffer::{Buffer, BufferContents}, + device::DeviceOwned, + format::{ClearColorValue, ClearDepthStencilValue}, + image::{Image, ImageSubresourceRange}, + DeviceSize, VulkanObject, +}; + +/// # Commands to fill resources with new data +impl RecordingCommandBuffer<'_> { + pub unsafe fn clear_color_image_unchecked( + &mut self, + clear_info: &ClearColorImageInfo<'_>, + ) -> &mut Self { + let &ClearColorImageInfo { + image, + image_layout, + clear_value, + regions, + _ne: _, + } = clear_info; + + let image = unsafe { self.accesses.image_unchecked(image) }; + let image_layout = AccessType::ClearTransferWrite.image_layout(image_layout); + + let fns = self.device().fns(); + let cmd_clear_color_image = fns.v1_0.cmd_clear_color_image; + + if regions.is_empty() { + let region_vk = image.subresource_range().into(); + + unsafe { + cmd_clear_color_image( + self.handle(), + image.handle(), + image_layout.into(), + &clear_value.into(), + 1, + ®ion_vk, + ) + }; + } else { + let regions_vk = regions + .iter() + .cloned() + .map(vk::ImageSubresourceRange::from) + .collect::>(); + + unsafe { + cmd_clear_color_image( + self.handle(), + image.handle(), + image_layout.into(), + &clear_value.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + + self + } + + pub unsafe fn clear_depth_stencil_image_unchecked( + &mut self, + clear_info: &ClearDepthStencilImageInfo<'_>, + ) -> &mut Self { + let &ClearDepthStencilImageInfo { + image, + image_layout, + clear_value, + regions, + _ne: _, + } = clear_info; + + let image = unsafe { self.accesses.image_unchecked(image) }; + let image_layout = AccessType::ClearTransferWrite.image_layout(image_layout); + + let fns = self.device().fns(); + let cmd_clear_depth_stencil_image = fns.v1_0.cmd_clear_depth_stencil_image; + + if regions.is_empty() { + let region_vk = image.subresource_range().into(); + + unsafe { + cmd_clear_depth_stencil_image( + self.handle(), + image.handle(), + image_layout.into(), + &clear_value.into(), + 1, + ®ion_vk, + ) + }; + } else { + let regions_vk = regions + .iter() + .cloned() + .map(vk::ImageSubresourceRange::from) + .collect::>(); + + unsafe { + cmd_clear_depth_stencil_image( + self.handle(), + image.handle(), + image_layout.into(), + &clear_value.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + + self + } + + pub unsafe fn fill_buffer_unchecked(&mut self, fill_info: &FillBufferInfo<'_>) -> &mut Self { + let &FillBufferInfo { + dst_buffer, + dst_offset, + mut size, + data, + _ne: _, + } = fill_info; + + let dst_buffer = unsafe { self.accesses.buffer_unchecked(dst_buffer) }; + + if size == 0 { + size = dst_buffer.size() & !3; + } + + let fns = self.device().fns(); + let cmd_fill_buffer = fns.v1_0.cmd_fill_buffer; + unsafe { cmd_fill_buffer(self.handle(), dst_buffer.handle(), dst_offset, size, data) }; + + self + } + + pub unsafe fn update_buffer_unchecked( + &mut self, + dst_buffer: Id, + dst_offset: DeviceSize, + data: &(impl BufferContents + ?Sized), + ) -> &mut Self { + let data_size = mem::size_of_val(data) as DeviceSize; + + if data_size == 0 { + return self; + } + + let dst_buffer = unsafe { self.accesses.buffer_unchecked(dst_buffer) }; + + let fns = self.device().fns(); + let cmd_update_buffer = fns.v1_0.cmd_update_buffer; + unsafe { + cmd_update_buffer( + self.handle(), + dst_buffer.handle(), + dst_offset, + data_size, + <*const _>::cast(data), + ) + }; + + self + } +} + +/// Parameters to clear a color image. +#[derive(Clone, Debug)] +pub struct ClearColorImageInfo<'a> { + /// The image to clear. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub image: Id, + + /// The layout used for `image` during the clear operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub image_layout: ImageLayoutType, + + /// The color value to clear the image to. + /// + /// The default value is `ClearColorValue::Float([0.0; 4])`. + pub clear_value: ClearColorValue, + + /// The subresource ranges of `image` to clear. + /// + /// The default value is a single region, covering the whole image. + pub regions: &'a [ImageSubresourceRange], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ClearColorImageInfo<'_> { + #[inline] + fn default() -> Self { + ClearColorImageInfo { + image: Id::INVALID, + image_layout: ImageLayoutType::Optimal, + clear_value: ClearColorValue::Float([0.0; 4]), + regions: &[], + _ne: crate::NE, + } + } +} + +/// Parameters to clear a depth/stencil image. +#[derive(Clone, Debug)] +pub struct ClearDepthStencilImageInfo<'a> { + /// The image to clear. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub image: Id, + + /// The layout used for `image` during the clear operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub image_layout: ImageLayoutType, + + /// The depth/stencil values to clear the image to. + /// + /// The default value is zero for both. + pub clear_value: ClearDepthStencilValue, + + /// The subresource ranges of `image` to clear. + /// + /// The default value is a single region, covering the whole image. + pub regions: &'a [ImageSubresourceRange], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ClearDepthStencilImageInfo<'_> { + #[inline] + fn default() -> Self { + ClearDepthStencilImageInfo { + image: Id::INVALID, + image_layout: ImageLayoutType::Optimal, + clear_value: ClearDepthStencilValue::default(), + regions: &[], + _ne: crate::NE, + } + } +} + +/// Parameters to fill a region of a buffer with repeated copies of a value. +#[derive(Clone, Debug)] +pub struct FillBufferInfo<'a> { + /// The buffer to fill. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_buffer: Id, + + /// The offset in bytes from the start of `dst_buffer` that filling will start from. + /// + /// This must be a multiple of 4. + /// + /// The default value is `0`. + pub dst_offset: DeviceSize, + + /// The number of bytes to fill. + /// + /// This must be a multiple of 4. + /// + /// The default value is the size of `dst_buffer`, rounded down to the nearest multiple of 4. + pub size: DeviceSize, + + /// The data to fill with. + /// + /// The default value is `0`. + pub data: u32, + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for FillBufferInfo<'_> { + #[inline] + fn default() -> Self { + FillBufferInfo { + dst_buffer: Id::INVALID, + dst_offset: 0, + size: 0, + data: 0, + _ne: crate::NE, + } + } +} diff --git a/vulkano-taskgraph/src/command_buffer/commands/copy.rs b/vulkano-taskgraph/src/command_buffer/commands/copy.rs new file mode 100644 index 0000000000..bcacb91daf --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/copy.rs @@ -0,0 +1,1450 @@ +use crate::{ + command_buffer::RecordingCommandBuffer, + resource::{AccessType, ImageLayoutType}, + Id, +}; +use ash::vk; +use smallvec::SmallVec; +use std::cmp; +use vulkano::{ + buffer::Buffer, + device::DeviceOwned, + image::{sampler::Filter, Image, ImageAspects, ImageSubresourceLayers}, + DeviceSize, Version, VulkanObject, +}; + +/// # Commands to transfer data between resources +impl RecordingCommandBuffer<'_> { + pub unsafe fn copy_buffer_unchecked( + &mut self, + copy_buffer_info: &CopyBufferInfo<'_>, + ) -> &mut Self { + let &CopyBufferInfo { + src_buffer, + dst_buffer, + regions, + _ne: _, + } = copy_buffer_info; + + let src_buffer = unsafe { self.accesses.buffer_unchecked(src_buffer) }; + let dst_buffer = unsafe { self.accesses.buffer_unchecked(dst_buffer) }; + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let cmd_copy_buffer2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_copy_buffer2 + } else { + fns.khr_copy_commands2.cmd_copy_buffer2_khr + }; + + if regions.is_empty() { + let regions_vk = [vk::BufferCopy2::default() + .src_offset(0) + .dst_offset(0) + .size(cmp::min(src_buffer.size(), dst_buffer.size()))]; + + let copy_buffer_info_vk = vk::CopyBufferInfo2::default() + .src_buffer(src_buffer.handle()) + .dst_buffer(dst_buffer.handle()) + .regions(®ions_vk); + + unsafe { cmd_copy_buffer2(self.handle(), ©_buffer_info_vk) }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &BufferCopy { + src_offset, + dst_offset, + size, + _ne, + } = region; + + vk::BufferCopy2::default() + .src_offset(src_offset) + .dst_offset(dst_offset) + .size(size) + }) + .collect::>(); + + let copy_buffer_info_vk = vk::CopyBufferInfo2::default() + .src_buffer(src_buffer.handle()) + .dst_buffer(dst_buffer.handle()) + .regions(®ions_vk); + + unsafe { cmd_copy_buffer2(self.handle(), ©_buffer_info_vk) }; + } + } else { + let cmd_copy_buffer = fns.v1_0.cmd_copy_buffer; + + if regions.is_empty() { + let region_vk = vk::BufferCopy { + src_offset: 0, + dst_offset: 0, + size: cmp::min(src_buffer.size(), dst_buffer.size()), + }; + + unsafe { + cmd_copy_buffer( + self.handle(), + src_buffer.handle(), + dst_buffer.handle(), + 1, + ®ion_vk, + ) + }; + } else { + let regions_vk = regions + .iter() + .map(|copy| { + let &BufferCopy { + src_offset, + dst_offset, + size, + _ne: _, + } = copy; + + vk::BufferCopy { + src_offset, + dst_offset, + size, + } + }) + .collect::>(); + + unsafe { + cmd_copy_buffer( + self.handle(), + src_buffer.handle(), + dst_buffer.handle(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + } + + self + } + + pub unsafe fn copy_image_unchecked( + &mut self, + copy_image_info: &CopyImageInfo<'_>, + ) -> &mut Self { + let &CopyImageInfo { + src_image, + src_image_layout, + dst_image, + dst_image_layout, + regions, + _ne: _, + } = copy_image_info; + + let src_image = unsafe { self.accesses.image_unchecked(src_image) }; + let src_image_layout = AccessType::CopyTransferRead.image_layout(src_image_layout); + let dst_image = unsafe { self.accesses.image_unchecked(dst_image) }; + let dst_image_layout = AccessType::CopyTransferWrite.image_layout(dst_image_layout); + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let cmd_copy_image2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_copy_image2 + } else { + fns.khr_copy_commands2.cmd_copy_image2_khr + }; + + if regions.is_empty() { + let min_array_layers = cmp::min(src_image.array_layers(), dst_image.array_layers()); + let src_extent = src_image.extent(); + let dst_extent = dst_image.extent(); + let regions_vk = [vk::ImageCopy2::default() + .src_subresource( + ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + ) + .src_offset(convert_offset([0; 3])) + .dst_subresource( + ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..dst_image.subresource_layers() + } + .into(), + ) + .dst_offset(convert_offset([0; 3])) + .extent(convert_extent([ + cmp::min(src_extent[0], dst_extent[0]), + cmp::min(src_extent[1], dst_extent[1]), + cmp::min(src_extent[2], dst_extent[2]), + ]))]; + + let copy_image_info_vk = vk::CopyImageInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk); + + unsafe { cmd_copy_image2(self.handle(), ©_image_info_vk) }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &ImageCopy { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne: _, + } = region; + + vk::ImageCopy2::default() + .src_subresource(src_subresource.into()) + .src_offset(convert_offset(src_offset)) + .dst_subresource(dst_subresource.into()) + .dst_offset(convert_offset(dst_offset)) + .extent(convert_extent(extent)) + }) + .collect::>(); + + let copy_image_info_vk = vk::CopyImageInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk); + + unsafe { cmd_copy_image2(self.handle(), ©_image_info_vk) }; + } + } else { + let cmd_copy_image = fns.v1_0.cmd_copy_image; + + if regions.is_empty() { + let min_array_layers = cmp::min(src_image.array_layers(), dst_image.array_layers()); + let src_extent = src_image.extent(); + let dst_extent = dst_image.extent(); + let region_vk = vk::ImageCopy { + src_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + src_offset: convert_offset([0; 3]), + dst_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..dst_image.subresource_layers() + } + .into(), + dst_offset: convert_offset([0; 3]), + extent: convert_extent([ + cmp::min(src_extent[0], dst_extent[0]), + cmp::min(src_extent[1], dst_extent[1]), + cmp::min(src_extent[2], dst_extent[2]), + ]), + }; + + unsafe { + cmd_copy_image( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_image.handle(), + dst_image_layout.into(), + 1, + ®ion_vk, + ) + }; + } else { + let regions_vk: SmallVec<[_; 8]> = regions + .iter() + .map(|region| { + let &ImageCopy { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne: _, + } = region; + + vk::ImageCopy { + src_subresource: src_subresource.into(), + src_offset: convert_offset(src_offset), + dst_subresource: dst_subresource.into(), + dst_offset: convert_offset(dst_offset), + extent: convert_extent(extent), + } + }) + .collect(); + + unsafe { + cmd_copy_image( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_image.handle(), + dst_image_layout.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + } + + self + } + + pub unsafe fn copy_buffer_to_image_unchecked( + &mut self, + copy_buffer_to_image_info: &CopyBufferToImageInfo<'_>, + ) -> &mut Self { + let &CopyBufferToImageInfo { + src_buffer, + dst_image, + dst_image_layout, + regions, + _ne: _, + } = copy_buffer_to_image_info; + + let src_buffer = unsafe { self.accesses.buffer_unchecked(src_buffer) }; + let dst_image = unsafe { self.accesses.image_unchecked(dst_image) }; + let dst_image_layout = AccessType::CopyTransferWrite.image_layout(dst_image_layout); + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let cmd_copy_buffer_to_image2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_copy_buffer_to_image2 + } else { + fns.khr_copy_commands2.cmd_copy_buffer_to_image2_khr + }; + + if regions.is_empty() { + let regions_vk = [vk::BufferImageCopy2::default() + .buffer_offset(0) + .buffer_row_length(0) + .buffer_image_height(0) + .image_subresource(dst_image.subresource_layers().into()) + .image_offset(convert_offset([0; 3])) + .image_extent(convert_extent(dst_image.extent()))]; + + let copy_buffer_to_image_info_vk = vk::CopyBufferToImageInfo2::default() + .src_buffer(src_buffer.handle()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk); + + unsafe { cmd_copy_buffer_to_image2(self.handle(), ©_buffer_to_image_info_vk) }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + + vk::BufferImageCopy2::default() + .buffer_offset(buffer_offset) + .buffer_row_length(buffer_row_length) + .buffer_image_height(buffer_image_height) + .image_subresource(image_subresource.into()) + .image_offset(convert_offset(image_offset)) + .image_extent(convert_extent(image_extent)) + }) + .collect::>(); + + let copy_buffer_to_image_info_vk = vk::CopyBufferToImageInfo2::default() + .src_buffer(src_buffer.handle()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk); + + unsafe { cmd_copy_buffer_to_image2(self.handle(), ©_buffer_to_image_info_vk) }; + } + } else { + let cmd_copy_buffer_to_image = fns.v1_0.cmd_copy_buffer_to_image; + + if regions.is_empty() { + let region_vk = vk::BufferImageCopy { + buffer_offset: 0, + buffer_row_length: 0, + buffer_image_height: 0, + image_subresource: dst_image.subresource_layers().into(), + image_offset: convert_offset([0; 3]), + image_extent: convert_extent(dst_image.extent()), + }; + + unsafe { + cmd_copy_buffer_to_image( + self.handle(), + src_buffer.handle(), + dst_image.handle(), + dst_image_layout.into(), + 1, + ®ion_vk, + ) + }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + + vk::BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + image_subresource: image_subresource.into(), + image_offset: convert_offset(image_offset), + image_extent: convert_extent(image_extent), + } + }) + .collect::>(); + + unsafe { + cmd_copy_buffer_to_image( + self.handle(), + src_buffer.handle(), + dst_image.handle(), + dst_image_layout.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + } + + self + } + + pub unsafe fn copy_image_to_buffer_unchecked( + &mut self, + copy_image_to_buffer_info: &CopyImageToBufferInfo<'_>, + ) -> &mut Self { + let &CopyImageToBufferInfo { + src_image, + src_image_layout, + dst_buffer, + regions, + _ne: _, + } = copy_image_to_buffer_info; + + let src_image = unsafe { self.accesses.image_unchecked(src_image) }; + let src_image_layout = AccessType::CopyTransferRead.image_layout(src_image_layout); + let dst_buffer = unsafe { self.accesses.buffer_unchecked(dst_buffer) }; + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let cmd_copy_image_to_buffer2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_copy_image_to_buffer2 + } else { + fns.khr_copy_commands2.cmd_copy_image_to_buffer2_khr + }; + + if regions.is_empty() { + let regions_vk = [vk::BufferImageCopy2::default() + .buffer_offset(0) + .buffer_row_length(0) + .buffer_image_height(0) + .image_subresource(src_image.subresource_layers().into()) + .image_offset(convert_offset([0; 3])) + .image_extent(convert_extent(src_image.extent()))]; + + let copy_image_to_buffer_info_vk = vk::CopyImageToBufferInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_buffer(dst_buffer.handle()) + .regions(®ions_vk); + + unsafe { cmd_copy_image_to_buffer2(self.handle(), ©_image_to_buffer_info_vk) }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + + vk::BufferImageCopy2::default() + .buffer_offset(buffer_offset) + .buffer_row_length(buffer_row_length) + .buffer_image_height(buffer_image_height) + .image_subresource(image_subresource.into()) + .image_offset(convert_offset(image_offset)) + .image_extent(convert_extent(image_extent)) + }) + .collect::>(); + + let copy_image_to_buffer_info_vk = vk::CopyImageToBufferInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_buffer(dst_buffer.handle()) + .regions(®ions_vk); + + unsafe { cmd_copy_image_to_buffer2(self.handle(), ©_image_to_buffer_info_vk) }; + } + } else { + let cmd_copy_image_to_buffer = fns.v1_0.cmd_copy_image_to_buffer; + + if regions.is_empty() { + let region_vk = vk::BufferImageCopy { + buffer_offset: 0, + buffer_row_length: 0, + buffer_image_height: 0, + image_subresource: src_image.subresource_layers().into(), + image_offset: convert_offset([0; 3]), + image_extent: convert_extent(src_image.extent()), + }; + + unsafe { + cmd_copy_image_to_buffer( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_buffer.handle(), + 1, + ®ion_vk, + ) + }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + ref image_subresource, + image_offset, + image_extent, + _ne: _, + } = region; + + vk::BufferImageCopy { + buffer_offset, + buffer_row_length, + buffer_image_height, + image_subresource: image_subresource.into(), + image_offset: convert_offset(image_offset), + image_extent: convert_extent(image_extent), + } + }) + .collect::>(); + + unsafe { + cmd_copy_image_to_buffer( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_buffer.handle(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + } + + self + } + + pub unsafe fn blit_image_unchecked( + &mut self, + blit_image_info: &BlitImageInfo<'_>, + ) -> &mut Self { + let &BlitImageInfo { + src_image, + src_image_layout, + dst_image, + dst_image_layout, + regions, + filter, + _ne: _, + } = blit_image_info; + + let src_image = unsafe { self.accesses.image_unchecked(src_image) }; + let src_image_layout = AccessType::BlitTransferRead.image_layout(src_image_layout); + let dst_image = unsafe { self.accesses.image_unchecked(dst_image) }; + let dst_image_layout = AccessType::BlitTransferWrite.image_layout(dst_image_layout); + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let cmd_blit_image2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_blit_image2 + } else { + fns.khr_copy_commands2.cmd_blit_image2_khr + }; + + if regions.is_empty() { + let min_array_layers = cmp::min(src_image.array_layers(), dst_image.array_layers()); + let regions_vk = [vk::ImageBlit2::default() + .src_subresource( + ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + ) + .src_offsets([[0; 3], src_image.extent()].map(convert_offset)) + .dst_subresource( + ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + ) + .dst_offsets([[0; 3], dst_image.extent()].map(convert_offset))]; + + let blit_image_info_vk = vk::BlitImageInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk) + .filter(filter.into()); + + unsafe { cmd_blit_image2(self.handle(), &blit_image_info_vk) }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &ImageBlit { + ref src_subresource, + src_offsets, + ref dst_subresource, + dst_offsets, + _ne: _, + } = region; + + vk::ImageBlit2::default() + .src_subresource(src_subresource.into()) + .src_offsets(src_offsets.map(convert_offset)) + .dst_subresource(dst_subresource.into()) + .dst_offsets(dst_offsets.map(convert_offset)) + }) + .collect::>(); + + let blit_image_info_vk = vk::BlitImageInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk) + .filter(filter.into()); + + unsafe { cmd_blit_image2(self.handle(), &blit_image_info_vk) }; + } + } else { + let cmd_blit_image = fns.v1_0.cmd_blit_image; + + if regions.is_empty() { + let min_array_layers = cmp::min(src_image.array_layers(), dst_image.array_layers()); + let region_vk = vk::ImageBlit { + src_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + src_offsets: [[0; 3], src_image.extent()].map(convert_offset), + dst_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..dst_image.subresource_layers() + } + .into(), + dst_offsets: [[0; 3], dst_image.extent()].map(convert_offset), + }; + + unsafe { + cmd_blit_image( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_image.handle(), + dst_image_layout.into(), + 1, + ®ion_vk, + filter.into(), + ) + }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &ImageBlit { + ref src_subresource, + src_offsets, + ref dst_subresource, + dst_offsets, + _ne: _, + } = region; + + vk::ImageBlit { + src_subresource: src_subresource.into(), + src_offsets: src_offsets.map(convert_offset), + dst_subresource: dst_subresource.into(), + dst_offsets: dst_offsets.map(convert_offset), + } + }) + .collect::>(); + + unsafe { + cmd_blit_image( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_image.handle(), + dst_image_layout.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + filter.into(), + ) + }; + } + } + + self + } + + pub unsafe fn resolve_image_unchecked( + &mut self, + resolve_image_info: &ResolveImageInfo<'_>, + ) -> &mut Self { + let &ResolveImageInfo { + src_image, + src_image_layout, + dst_image, + dst_image_layout, + regions, + _ne: _, + } = resolve_image_info; + + let src_image = unsafe { self.accesses.image_unchecked(src_image) }; + let src_image_layout = AccessType::ResolveTransferRead.image_layout(src_image_layout); + let dst_image = unsafe { self.accesses.image_unchecked(dst_image) }; + let dst_image_layout = AccessType::ResolveTransferRead.image_layout(dst_image_layout); + + let fns = self.device().fns(); + + if self.device().api_version() >= Version::V1_3 + || self.device().enabled_extensions().khr_copy_commands2 + { + let cmd_resolve_image2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_resolve_image2 + } else { + fns.khr_copy_commands2.cmd_resolve_image2_khr + }; + + if regions.is_empty() { + let min_array_layers = cmp::min(src_image.array_layers(), dst_image.array_layers()); + let src_extent = src_image.extent(); + let dst_extent = dst_image.extent(); + let regions_vk = [vk::ImageResolve2::default() + .src_subresource( + ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + ) + .src_offset(convert_offset([0; 3])) + .dst_subresource( + ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + ) + .dst_offset(convert_offset([0; 3])) + .extent(convert_extent([ + cmp::min(src_extent[0], dst_extent[0]), + cmp::min(src_extent[1], dst_extent[1]), + cmp::min(src_extent[2], dst_extent[2]), + ]))]; + + let resolve_image_info_vk = vk::ResolveImageInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk); + + unsafe { cmd_resolve_image2(self.handle(), &resolve_image_info_vk) }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &ImageResolve { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne: _, + } = region; + + vk::ImageResolve2::default() + .src_subresource(src_subresource.into()) + .src_offset(convert_offset(src_offset)) + .dst_subresource(dst_subresource.into()) + .dst_offset(convert_offset(dst_offset)) + .extent(convert_extent(extent)) + }) + .collect::>(); + + let resolve_image_info_vk = vk::ResolveImageInfo2::default() + .src_image(src_image.handle()) + .src_image_layout(src_image_layout.into()) + .dst_image(dst_image.handle()) + .dst_image_layout(dst_image_layout.into()) + .regions(®ions_vk); + + unsafe { cmd_resolve_image2(self.handle(), &resolve_image_info_vk) }; + } + } else { + let cmd_resolve_image = fns.v1_0.cmd_resolve_image; + + if regions.is_empty() { + let min_array_layers = cmp::min(src_image.array_layers(), dst_image.array_layers()); + let src_extent = src_image.extent(); + let dst_extent = dst_image.extent(); + let regions_vk = [vk::ImageResolve { + src_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..src_image.subresource_layers() + } + .into(), + src_offset: convert_offset([0; 3]), + dst_subresource: ImageSubresourceLayers { + array_layers: 0..min_array_layers, + ..dst_image.subresource_layers() + } + .into(), + dst_offset: convert_offset([0; 3]), + extent: convert_extent([ + cmp::min(src_extent[0], dst_extent[0]), + cmp::min(src_extent[1], dst_extent[1]), + cmp::min(src_extent[2], dst_extent[2]), + ]), + }]; + + unsafe { + cmd_resolve_image( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_image.handle(), + dst_image_layout.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } else { + let regions_vk = regions + .iter() + .map(|region| { + let &ImageResolve { + ref src_subresource, + src_offset, + ref dst_subresource, + dst_offset, + extent, + _ne: _, + } = region; + + vk::ImageResolve { + src_subresource: src_subresource.into(), + src_offset: convert_offset(src_offset), + dst_subresource: dst_subresource.into(), + dst_offset: convert_offset(dst_offset), + extent: convert_extent(extent), + } + }) + .collect::>(); + + unsafe { + cmd_resolve_image( + self.handle(), + src_image.handle(), + src_image_layout.into(), + dst_image.handle(), + dst_image_layout.into(), + regions_vk.len() as u32, + regions_vk.as_ptr(), + ) + }; + } + } + + self + } +} + +/// Parameters to copy data from a buffer to another buffer. +/// +/// The fields of `regions` represent bytes. +#[derive(Clone, Debug)] +pub struct CopyBufferInfo<'a> { + /// The buffer to copy from. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub src_buffer: Id, + + /// The buffer to copy to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_buffer: Id, + + /// The regions of both buffers to copy between, specified in bytes. + /// + /// The default value is a single region, with zero offsets and a `size` equal to the smallest + /// of the two buffers. + pub regions: &'a [BufferCopy<'a>], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for CopyBufferInfo<'_> { + #[inline] + fn default() -> Self { + CopyBufferInfo { + src_buffer: Id::INVALID, + dst_buffer: Id::INVALID, + regions: &[], + _ne: crate::NE, + } + } +} + +/// A region of data to copy between buffers. +#[derive(Clone, Debug)] +pub struct BufferCopy<'a> { + /// The offset in bytes or elements from the start of `src_buffer` that copying will start + /// from. + /// + /// The default value is `0`. + pub src_offset: DeviceSize, + + /// The offset in bytes or elements from the start of `dst_buffer` that copying will start + /// from. + /// + /// The default value is `0`. + pub dst_offset: DeviceSize, + + /// The number of bytes or elements to copy. + /// + /// The default value is `0`, which must be overridden. + pub size: DeviceSize, + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for BufferCopy<'_> { + #[inline] + fn default() -> Self { + Self { + src_offset: 0, + dst_offset: 0, + size: 0, + _ne: crate::NE, + } + } +} + +/// Parameters to copy data from an image to another image. +#[derive(Clone, Debug)] +pub struct CopyImageInfo<'a> { + /// The image to copy from. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub src_image: Id, + + /// The layout used for `src_image` during the copy operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub src_image_layout: ImageLayoutType, + + /// The image to copy to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_image: Id, + + /// The layout used for `dst_image` during the copy operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub dst_image_layout: ImageLayoutType, + + /// The regions of both images to copy between. + /// + /// The default value is a single region, covering the first mip level, and the smallest of the + /// array layers and extent of the two images. All aspects of each image are selected, or + /// `plane0` if the image is multi-planar. + pub regions: &'a [ImageCopy<'a>], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for CopyImageInfo<'_> { + #[inline] + fn default() -> Self { + CopyImageInfo { + src_image: Id::INVALID, + src_image_layout: ImageLayoutType::Optimal, + dst_image: Id::INVALID, + dst_image_layout: ImageLayoutType::Optimal, + regions: &[], + _ne: crate::NE, + } + } +} + +/// A region of data to copy between images. +#[derive(Clone, Debug)] +pub struct ImageCopy<'a> { + /// The subresource of `src_image` to copy from. + /// + /// The default value is empty, which must be overridden. + pub src_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `src_image` that copying will start from. + /// + /// The default value is `[0; 3]`. + pub src_offset: [u32; 3], + + /// The subresource of `dst_image` to copy to. + /// + /// The default value is empty, which must be overridden. + pub dst_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `dst_image` that copying will start from. + /// + /// The default value is `[0; 3]`. + pub dst_offset: [u32; 3], + + /// The extent of texels to copy. + /// + /// The default value is `[0; 3]`, which must be overridden. + pub extent: [u32; 3], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ImageCopy<'_> { + #[inline] + fn default() -> Self { + Self { + src_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + src_offset: [0; 3], + dst_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + dst_offset: [0; 3], + extent: [0; 3], + _ne: crate::NE, + } + } +} + +/// Parameters to copy data from a buffer to an image. +#[derive(Clone, Debug)] +pub struct CopyBufferToImageInfo<'a> { + /// The buffer to copy from. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub src_buffer: Id, + + /// The image to copy to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_image: Id, + + /// The layout used for `dst_image` during the copy operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub dst_image_layout: ImageLayoutType, + + /// The regions of the buffer and image to copy between. + /// + /// The default value is a single region, covering all of the buffer and the first mip level of + /// the image. All aspects of the image are selected, or `plane0` if the image is multi-planar. + pub regions: &'a [BufferImageCopy<'a>], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for CopyBufferToImageInfo<'_> { + #[inline] + fn default() -> Self { + CopyBufferToImageInfo { + src_buffer: Id::INVALID, + dst_image: Id::INVALID, + dst_image_layout: ImageLayoutType::Optimal, + regions: &[], + _ne: crate::NE, + } + } +} + +/// Parameters to copy data from an image to a buffer. +#[derive(Clone, Debug)] +pub struct CopyImageToBufferInfo<'a> { + /// The image to copy from. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub src_image: Id, + + /// The layout used for `src_image` during the copy operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub src_image_layout: ImageLayoutType, + + /// The buffer to copy to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_buffer: Id, + + /// The regions of the image and buffer to copy between. + /// + /// The default value is a single region, covering all of the buffer and the first mip level of + /// the image. All aspects of the image are selected, or `plane0` if the image is multi-planar. + pub regions: &'a [BufferImageCopy<'a>], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for CopyImageToBufferInfo<'_> { + #[inline] + fn default() -> Self { + CopyImageToBufferInfo { + src_image: Id::INVALID, + src_image_layout: ImageLayoutType::Optimal, + dst_buffer: Id::INVALID, + regions: &[], + _ne: crate::NE, + } + } +} + +/// A region of data to copy between a buffer and an image. +#[derive(Clone, Debug)] +pub struct BufferImageCopy<'a> { + /// The offset in bytes from the start of the buffer that copying will start from. + /// + /// The default value is `0`. + pub buffer_offset: DeviceSize, + + /// The number of texels between successive rows of image data in the buffer. + /// + /// If set to `0`, the width of the image is used. + /// + /// The default value is `0`. + pub buffer_row_length: u32, + + /// The number of rows between successive depth slices of image data in the buffer. + /// + /// If set to `0`, the height of the image is used. + /// + /// The default value is `0`. + pub buffer_image_height: u32, + + /// The subresource of the image to copy from/to. + /// + /// The default value is empty, which must be overridden. + pub image_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of the image that copying will start from. + /// + /// The default value is `[0; 3]`. + pub image_offset: [u32; 3], + + /// The extent of texels in the image to copy. + /// + /// The default value is `[0; 3]`, which must be overridden. + pub image_extent: [u32; 3], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for BufferImageCopy<'_> { + #[inline] + fn default() -> Self { + Self { + buffer_offset: 0, + buffer_row_length: 0, + buffer_image_height: 0, + image_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + image_offset: [0; 3], + image_extent: [0; 3], + _ne: crate::NE, + } + } +} + +/// Parameters to blit image data. +#[derive(Clone, Debug)] +pub struct BlitImageInfo<'a> { + /// The image to blit from. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub src_image: Id, + + /// The layout used for `src_image` during the blit operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub src_image_layout: ImageLayoutType, + + /// The image to blit to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_image: Id, + + /// The layout used for `dst_image` during the blit operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub dst_image_layout: ImageLayoutType, + + /// The regions of both images to blit between. + /// + /// The default value is a single region, covering the first mip level, and the smallest of the + /// array layers of the two images. The whole extent of each image is covered, scaling if + /// necessary. All aspects of each image are selected, or `plane0` if the image is + /// multi-planar. + pub regions: &'a [ImageBlit<'a>], + + /// The filter to use for sampling `src_image` when the `src_extent` and + /// `dst_extent` of a region are not the same size. + /// + /// The default value is [`Filter::Nearest`]. + pub filter: Filter, + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for BlitImageInfo<'_> { + #[inline] + fn default() -> Self { + BlitImageInfo { + src_image: Id::INVALID, + src_image_layout: ImageLayoutType::Optimal, + dst_image: Id::INVALID, + dst_image_layout: ImageLayoutType::Optimal, + regions: &[], + filter: Filter::Nearest, + _ne: crate::NE, + } + } +} + +/// A region of data to blit between images. +#[derive(Clone, Debug)] +pub struct ImageBlit<'a> { + /// The subresource of `src_image` to blit from. + /// + /// The default value is empty, which must be overridden. + pub src_subresource: ImageSubresourceLayers, + + /// The offsets from the zero coordinate of `src_image`, defining two corners of the region + /// to blit from. + /// If the ordering of the two offsets differs between source and destination, the image will + /// be flipped. + /// + /// The default value is `[[0; 3]; 2]`, which must be overridden. + pub src_offsets: [[u32; 3]; 2], + + /// The subresource of `dst_image` to blit to. + /// + /// The default value is empty, which must be overridden. + pub dst_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `dst_image` defining two corners of the + /// region to blit to. + /// If the ordering of the two offsets differs between source and destination, the image will + /// be flipped. + /// + /// The default value is `[[0; 3]; 2]`, which must be overridden. + pub dst_offsets: [[u32; 3]; 2], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ImageBlit<'_> { + #[inline] + fn default() -> Self { + Self { + src_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + src_offsets: [[0; 3]; 2], + dst_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + dst_offsets: [[0; 3]; 2], + _ne: crate::NE, + } + } +} + +/// Parameters to resolve image data. +#[derive(Clone, Debug)] +pub struct ResolveImageInfo<'a> { + /// The multisampled image to resolve from. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub src_image: Id, + + /// The layout used for `src_image` during the resolve operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub src_image_layout: ImageLayoutType, + + /// The non-multisampled image to resolve into. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub dst_image: Id, + + /// The layout used for `dst_image` during the resolve operation. + /// + /// The default value is [`ImageLayoutType::Optimal`]. + pub dst_image_layout: ImageLayoutType, + + /// The regions of both images to resolve between. + /// + /// The default value is a single region, covering the first mip level, and the smallest of the + /// array layers and extent of the two images. All aspects of each image are selected, or + /// `plane0` if the image is multi-planar. + pub regions: &'a [ImageResolve<'a>], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ResolveImageInfo<'_> { + #[inline] + fn default() -> Self { + ResolveImageInfo { + src_image: Id::INVALID, + src_image_layout: ImageLayoutType::Optimal, + dst_image: Id::INVALID, + dst_image_layout: ImageLayoutType::Optimal, + regions: &[], + _ne: crate::NE, + } + } +} + +/// A region of data to resolve between images. +#[derive(Clone, Debug)] +pub struct ImageResolve<'a> { + /// The subresource of `src_image` to resolve from. + /// + /// The default value is empty, which must be overridden. + pub src_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `src_image` that resolving will start from. + /// + /// The default value is `[0; 3]`. + pub src_offset: [u32; 3], + + /// The subresource of `dst_image` to resolve into. + /// + /// The default value is empty, which must be overridden. + pub dst_subresource: ImageSubresourceLayers, + + /// The offset from the zero coordinate of `dst_image` that resolving will start from. + /// + /// The default value is `[0; 3]`. + pub dst_offset: [u32; 3], + + /// The extent of texels to resolve. + /// + /// The default value is `[0; 3]`, which must be overridden. + pub extent: [u32; 3], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ImageResolve<'_> { + #[inline] + fn default() -> Self { + Self { + src_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + src_offset: [0; 3], + dst_subresource: ImageSubresourceLayers { + aspects: ImageAspects::empty(), + mip_level: 0, + array_layers: 0..0, + }, + dst_offset: [0; 3], + extent: [0; 3], + _ne: crate::NE, + } + } +} + +fn convert_offset(offset: [u32; 3]) -> vk::Offset3D { + vk::Offset3D { + x: offset[0] as i32, + y: offset[1] as i32, + z: offset[2] as i32, + } +} + +fn convert_extent(extent: [u32; 3]) -> vk::Extent3D { + vk::Extent3D { + width: extent[0], + height: extent[1], + depth: extent[2], + } +} diff --git a/vulkano-taskgraph/src/command_buffer/commands/dynamic_state.rs b/vulkano-taskgraph/src/command_buffer/commands/dynamic_state.rs new file mode 100644 index 0000000000..fb9678cbdd --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/dynamic_state.rs @@ -0,0 +1,276 @@ +use crate::command_buffer::RecordingCommandBuffer; +use std::ops::RangeInclusive; +use vulkano::pipeline::graphics::{ + color_blend::LogicOp, + conservative_rasterization::ConservativeRasterizationMode, + depth_stencil::{CompareOp, StencilFaces, StencilOp}, + input_assembly::PrimitiveTopology, + rasterization::{CullMode, FrontFace}, + vertex_input::VertexInputState, + viewport::{Scissor, Viewport}, +}; + +/// # Commands to set dynamic state for pipelines +/// +/// These commands require a queue with a pipeline type that uses the given state. +impl RecordingCommandBuffer<'_> { + pub unsafe fn set_blend_constants_unchecked(&mut self, constants: [f32; 4]) -> &mut Self { + unsafe { self.inner.set_blend_constants_unchecked(constants) }; + + self + } + + pub unsafe fn set_color_write_enable_unchecked(&mut self, enables: &[bool]) -> &mut Self { + unsafe { self.inner.set_color_write_enable_unchecked(enables) }; + + self + } + + pub unsafe fn set_cull_mode_unchecked(&mut self, cull_mode: CullMode) -> &mut Self { + unsafe { self.inner.set_cull_mode_unchecked(cull_mode) }; + + self + } + + pub unsafe fn set_depth_bias_unchecked( + &mut self, + constant_factor: f32, + clamp: f32, + slope_factor: f32, + ) -> &mut Self { + unsafe { + self.inner + .set_depth_bias_unchecked(constant_factor, clamp, slope_factor) + }; + + self + } + + pub unsafe fn set_depth_bias_enable_unchecked(&mut self, enable: bool) -> &mut Self { + unsafe { self.inner.set_depth_bias_enable_unchecked(enable) }; + + self + } + + pub unsafe fn set_depth_bounds_unchecked(&mut self, bounds: RangeInclusive) -> &mut Self { + unsafe { self.inner.set_depth_bounds_unchecked(bounds.clone()) }; + + self + } + + pub unsafe fn set_depth_bounds_test_enable_unchecked(&mut self, enable: bool) -> &mut Self { + unsafe { self.inner.set_depth_bounds_test_enable_unchecked(enable) }; + + self + } + + pub unsafe fn set_depth_compare_op_unchecked(&mut self, compare_op: CompareOp) -> &mut Self { + unsafe { self.inner.set_depth_compare_op_unchecked(compare_op) }; + + self + } + + pub unsafe fn set_depth_test_enable_unchecked(&mut self, enable: bool) -> &mut Self { + unsafe { self.inner.set_depth_test_enable_unchecked(enable) }; + + self + } + + pub unsafe fn set_depth_write_enable_unchecked(&mut self, enable: bool) -> &mut Self { + unsafe { self.inner.set_depth_write_enable_unchecked(enable) }; + + self + } + + pub unsafe fn set_discard_rectangle_unchecked( + &mut self, + first_rectangle: u32, + rectangles: &[Scissor], + ) -> &mut Self { + unsafe { + self.inner + .set_discard_rectangle_unchecked(first_rectangle, rectangles) + }; + + self + } + + pub unsafe fn set_front_face_unchecked(&mut self, face: FrontFace) -> &mut Self { + unsafe { self.inner.set_front_face_unchecked(face) }; + + self + } + + pub unsafe fn set_line_stipple_unchecked(&mut self, factor: u32, pattern: u16) -> &mut Self { + unsafe { self.inner.set_line_stipple_unchecked(factor, pattern) }; + + self + } + + pub unsafe fn set_line_width_unchecked(&mut self, line_width: f32) -> &mut Self { + unsafe { self.inner.set_line_width_unchecked(line_width) }; + + self + } + + pub unsafe fn set_logic_op_unchecked(&mut self, logic_op: LogicOp) -> &mut Self { + unsafe { self.inner.set_logic_op_unchecked(logic_op) }; + + self + } + + pub unsafe fn set_patch_control_points_unchecked(&mut self, num: u32) -> &mut Self { + unsafe { self.inner.set_patch_control_points_unchecked(num) }; + + self + } + + pub unsafe fn set_primitive_restart_enable_unchecked(&mut self, enable: bool) -> &mut Self { + unsafe { self.inner.set_primitive_restart_enable_unchecked(enable) }; + + self + } + + pub unsafe fn set_primitive_topology_unchecked( + &mut self, + topology: PrimitiveTopology, + ) -> &mut Self { + unsafe { self.inner.set_primitive_topology_unchecked(topology) }; + + self + } + + pub unsafe fn set_rasterizer_discard_enable_unchecked(&mut self, enable: bool) -> &mut Self { + unsafe { self.inner.set_rasterizer_discard_enable_unchecked(enable) }; + + self + } + + pub unsafe fn set_scissor_unchecked( + &mut self, + first_scissor: u32, + scissors: &[Scissor], + ) -> &mut Self { + unsafe { self.inner.set_scissor_unchecked(first_scissor, scissors) }; + + self + } + + pub unsafe fn set_scissor_with_count_unchecked(&mut self, scissors: &[Scissor]) -> &mut Self { + unsafe { self.inner.set_scissor_with_count_unchecked(scissors) }; + + self + } + + pub unsafe fn set_stencil_compare_mask_unchecked( + &mut self, + faces: StencilFaces, + compare_mask: u32, + ) -> &mut Self { + unsafe { + self.inner + .set_stencil_compare_mask_unchecked(faces, compare_mask) + }; + + self + } + + pub unsafe fn set_stencil_op_unchecked( + &mut self, + faces: StencilFaces, + fail_op: StencilOp, + pass_op: StencilOp, + depth_fail_op: StencilOp, + compare_op: CompareOp, + ) -> &mut Self { + unsafe { + self.inner + .set_stencil_op_unchecked(faces, fail_op, pass_op, depth_fail_op, compare_op) + }; + + self + } + + pub unsafe fn set_stencil_reference_unchecked( + &mut self, + faces: StencilFaces, + reference: u32, + ) -> &mut Self { + unsafe { self.inner.set_stencil_reference_unchecked(faces, reference) }; + + self + } + + pub unsafe fn set_stencil_test_enable_unchecked(&mut self, enable: bool) -> &mut Self { + unsafe { self.inner.set_stencil_test_enable_unchecked(enable) }; + + self + } + + pub unsafe fn set_stencil_write_mask_unchecked( + &mut self, + faces: StencilFaces, + write_mask: u32, + ) -> &mut Self { + unsafe { + self.inner + .set_stencil_write_mask_unchecked(faces, write_mask) + }; + + self + } + + pub unsafe fn set_vertex_input_unchecked( + &mut self, + vertex_input_state: &VertexInputState, + ) -> &mut Self { + unsafe { self.inner.set_vertex_input_unchecked(vertex_input_state) }; + + self + } + + pub unsafe fn set_viewport_unchecked( + &mut self, + first_viewport: u32, + viewports: &[Viewport], + ) -> &mut Self { + unsafe { self.inner.set_viewport_unchecked(first_viewport, viewports) }; + + self + } + + pub unsafe fn set_viewport_with_count_unchecked( + &mut self, + viewports: &[Viewport], + ) -> &mut Self { + unsafe { self.inner.set_viewport_with_count_unchecked(viewports) }; + + self + } + + pub unsafe fn set_conservative_rasterization_mode_unchecked( + &mut self, + conservative_rasterization_mode: ConservativeRasterizationMode, + ) -> &mut Self { + unsafe { + self.inner + .set_conservative_rasterization_mode_unchecked(conservative_rasterization_mode) + }; + + self + } + + pub unsafe fn set_extra_primitive_overestimation_size_unchecked( + &mut self, + extra_primitive_overestimation_size: f32, + ) -> &mut Self { + unsafe { + self.inner + .set_extra_primitive_overestimation_size_unchecked( + extra_primitive_overestimation_size, + ) + }; + + self + } +} diff --git a/vulkano-taskgraph/src/command_buffer/commands/mod.rs b/vulkano-taskgraph/src/command_buffer/commands/mod.rs new file mode 100644 index 0000000000..080117d52a --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/mod.rs @@ -0,0 +1,6 @@ +pub(super) mod bind_push; +pub(super) mod clear; +pub(super) mod copy; +pub(super) mod dynamic_state; +pub(super) mod pipeline; +pub(super) mod sync; diff --git a/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs b/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs new file mode 100644 index 0000000000..424ff8b39b --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/pipeline.rs @@ -0,0 +1,263 @@ +use crate::{command_buffer::RecordingCommandBuffer, Id}; +use vulkano::{buffer::Buffer, device::DeviceOwned, DeviceSize, Version, VulkanObject}; + +/// # Commands to execute a bound pipeline +/// +/// Dispatch commands require a compute queue, draw commands require a graphics queue. +impl RecordingCommandBuffer<'_> { + pub unsafe fn dispatch_unchecked(&mut self, group_counts: [u32; 3]) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_dispatch)( + self.handle(), + group_counts[0], + group_counts[1], + group_counts[2], + ) + }; + + self + } + + pub unsafe fn dispatch_indirect_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + let fns = self.device().fns(); + unsafe { (fns.v1_0.cmd_dispatch_indirect)(self.handle(), buffer.handle(), offset) }; + + self + } + + pub unsafe fn draw_unchecked( + &mut self, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_draw)( + self.handle(), + vertex_count, + instance_count, + first_vertex, + first_instance, + ) + }; + + self + } + + pub unsafe fn draw_indirect_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + draw_count: u32, + stride: u32, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_draw_indirect)(self.handle(), buffer.handle(), offset, draw_count, stride) + }; + + self + } + + pub unsafe fn draw_indirect_count_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + count_buffer: Id, + count_buffer_offset: DeviceSize, + max_draw_count: u32, + stride: u32, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + let count_buffer = unsafe { self.accesses.buffer_unchecked(count_buffer) }; + + let device = self.device(); + let fns = device.fns(); + let cmd_draw_indirect_count = if device.api_version() >= Version::V1_2 { + fns.v1_2.cmd_draw_indirect_count + } else if device.enabled_extensions().khr_draw_indirect_count { + fns.khr_draw_indirect_count.cmd_draw_indirect_count_khr + } else if device.enabled_extensions().amd_draw_indirect_count { + fns.amd_draw_indirect_count.cmd_draw_indirect_count_amd + } else { + std::process::abort(); + }; + + unsafe { + cmd_draw_indirect_count( + self.handle(), + buffer.handle(), + offset, + count_buffer.handle(), + count_buffer_offset, + max_draw_count, + stride, + ) + }; + + self + } + + pub unsafe fn draw_indexed_unchecked( + &mut self, + index_count: u32, + instance_count: u32, + first_index: u32, + vertex_offset: i32, + first_instance: u32, + ) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_draw_indexed)( + self.handle(), + index_count, + instance_count, + first_index, + vertex_offset, + first_instance, + ) + }; + + self + } + + pub unsafe fn draw_indexed_indirect_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + draw_count: u32, + stride: u32, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_draw_indexed_indirect)( + self.handle(), + buffer.handle(), + offset, + draw_count, + stride, + ) + }; + + self + } + + pub unsafe fn draw_indexed_indirect_count_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + count_buffer: Id, + count_buffer_offset: DeviceSize, + max_draw_count: u32, + stride: u32, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + let count_buffer = unsafe { self.accesses.buffer_unchecked(count_buffer) }; + + let device = self.device(); + let fns = device.fns(); + let cmd_draw_indexed_indirect_count = if device.api_version() >= Version::V1_2 { + fns.v1_2.cmd_draw_indexed_indirect_count + } else if device.enabled_extensions().khr_draw_indirect_count { + fns.khr_draw_indirect_count + .cmd_draw_indexed_indirect_count_khr + } else if device.enabled_extensions().amd_draw_indirect_count { + fns.amd_draw_indirect_count + .cmd_draw_indexed_indirect_count_amd + } else { + std::process::abort(); + }; + + unsafe { + cmd_draw_indexed_indirect_count( + self.handle(), + buffer.handle(), + offset, + count_buffer.handle(), + count_buffer_offset, + max_draw_count, + stride, + ) + }; + + self + } + + pub unsafe fn draw_mesh_tasks_unchecked(&mut self, group_counts: [u32; 3]) -> &mut Self { + let fns = self.device().fns(); + unsafe { + (fns.ext_mesh_shader.cmd_draw_mesh_tasks_ext)( + self.handle(), + group_counts[0], + group_counts[1], + group_counts[2], + ) + }; + + self + } + + pub unsafe fn draw_mesh_tasks_indirect_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + draw_count: u32, + stride: u32, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + let fns = self.device().fns(); + unsafe { + (fns.ext_mesh_shader.cmd_draw_mesh_tasks_indirect_ext)( + self.handle(), + buffer.handle(), + offset, + draw_count, + stride, + ) + }; + + self + } + + pub unsafe fn draw_mesh_tasks_indirect_count_unchecked( + &mut self, + buffer: Id, + offset: DeviceSize, + count_buffer: Id, + count_buffer_offset: DeviceSize, + max_draw_count: u32, + stride: u32, + ) -> &mut Self { + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + let count_buffer = unsafe { self.accesses.buffer_unchecked(count_buffer) }; + + let fns = self.device().fns(); + unsafe { + (fns.ext_mesh_shader.cmd_draw_mesh_tasks_indirect_count_ext)( + self.handle(), + buffer.handle(), + offset, + count_buffer.handle(), + count_buffer_offset, + max_draw_count, + stride, + ) + }; + + self + } +} diff --git a/vulkano-taskgraph/src/command_buffer/commands/sync.rs b/vulkano-taskgraph/src/command_buffer/commands/sync.rs new file mode 100644 index 0000000000..0cbdf1559a --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/commands/sync.rs @@ -0,0 +1,464 @@ +use crate::{command_buffer::RecordingCommandBuffer, Id}; +use ash::vk; +use smallvec::SmallVec; +use std::ops::Range; +use vulkano::{ + buffer::Buffer, + device::DeviceOwned, + image::{Image, ImageAspects, ImageLayout, ImageSubresourceRange}, + sync::{AccessFlags, DependencyFlags, PipelineStages}, + DeviceSize, Version, VulkanObject, +}; + +/// # Commands to synchronize resource accesses +impl RecordingCommandBuffer<'_> { + pub unsafe fn pipeline_barrier_unchecked( + &mut self, + dependency_info: &DependencyInfo<'_>, + ) -> &mut Self { + if dependency_info.is_empty() { + return self; + } + + let &DependencyInfo { + dependency_flags, + memory_barriers, + buffer_memory_barriers, + image_memory_barriers, + _ne: _, + } = dependency_info; + + if self.device().enabled_features().synchronization2 { + let memory_barriers_vk: SmallVec<[_; 2]> = memory_barriers + .iter() + .map(|barrier| { + let &MemoryBarrier { + src_stages, + src_access, + dst_stages, + dst_access, + _ne: _, + } = barrier; + + vk::MemoryBarrier2::default() + .src_stage_mask(src_stages.into()) + .src_access_mask(src_access.into()) + .dst_stage_mask(dst_stages.into()) + .dst_access_mask(dst_access.into()) + }) + .collect(); + + let buffer_memory_barriers_vk: SmallVec<[_; 8]> = buffer_memory_barriers + .iter() + .map(|barrier| { + let &BufferMemoryBarrier { + src_stages, + src_access, + dst_stages, + dst_access, + buffer, + ref range, + _ne: _, + } = barrier; + + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + vk::BufferMemoryBarrier2::default() + .src_stage_mask(src_stages.into()) + .src_access_mask(src_access.into()) + .dst_stage_mask(dst_stages.into()) + .dst_access_mask(dst_access.into()) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .buffer(buffer.handle()) + .offset(range.start) + .size(range.end - range.start) + }) + .collect(); + + let image_memory_barriers_vk: SmallVec<[_; 8]> = image_memory_barriers + .iter() + .map(|barrier| { + let &ImageMemoryBarrier { + src_stages, + src_access, + dst_stages, + dst_access, + old_layout, + new_layout, + image, + ref subresource_range, + _ne: _, + } = barrier; + + let image = unsafe { self.accesses.image_unchecked(image) }; + + vk::ImageMemoryBarrier2::default() + .src_stage_mask(src_stages.into()) + .src_access_mask(src_access.into()) + .dst_stage_mask(dst_stages.into()) + .dst_access_mask(dst_access.into()) + .old_layout(old_layout.into()) + .new_layout(new_layout.into()) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(image.handle()) + .subresource_range(subresource_range.clone().into()) + }) + .collect(); + + let dependency_info_vk = vk::DependencyInfo::default() + .dependency_flags(dependency_flags.into()) + .memory_barriers(&memory_barriers_vk) + .buffer_memory_barriers(&buffer_memory_barriers_vk) + .image_memory_barriers(&image_memory_barriers_vk); + + let fns = self.device().fns(); + let cmd_pipeline_barrier2 = if self.device().api_version() >= Version::V1_3 { + fns.v1_3.cmd_pipeline_barrier2 + } else { + fns.khr_synchronization2.cmd_pipeline_barrier2_khr + }; + + unsafe { cmd_pipeline_barrier2(self.handle(), &dependency_info_vk) }; + } else { + let mut src_stage_mask = vk::PipelineStageFlags::empty(); + let mut dst_stage_mask = vk::PipelineStageFlags::empty(); + + let memory_barriers_vk: SmallVec<[_; 2]> = memory_barriers + .iter() + .map(|barrier| { + let &MemoryBarrier { + src_stages, + src_access, + dst_stages, + dst_access, + _ne: _, + } = barrier; + + src_stage_mask |= src_stages.into(); + dst_stage_mask |= dst_stages.into(); + + vk::MemoryBarrier::default() + .src_access_mask(src_access.into()) + .dst_access_mask(dst_access.into()) + }) + .collect(); + + let buffer_memory_barriers_vk: SmallVec<[_; 8]> = buffer_memory_barriers + .iter() + .map(|barrier| { + let &BufferMemoryBarrier { + src_stages, + src_access, + dst_stages, + dst_access, + buffer, + ref range, + _ne: _, + } = barrier; + + src_stage_mask |= src_stages.into(); + dst_stage_mask |= dst_stages.into(); + + let buffer = unsafe { self.accesses.buffer_unchecked(buffer) }; + + vk::BufferMemoryBarrier::default() + .src_access_mask(src_access.into()) + .dst_access_mask(dst_access.into()) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .buffer(buffer.handle()) + .offset(range.start) + .size(range.end - range.start) + }) + .collect(); + + let image_memory_barriers_vk: SmallVec<[_; 8]> = image_memory_barriers + .iter() + .map(|barrier| { + let &ImageMemoryBarrier { + src_stages, + src_access, + dst_stages, + dst_access, + old_layout, + new_layout, + image, + ref subresource_range, + _ne: _, + } = barrier; + + src_stage_mask |= src_stages.into(); + dst_stage_mask |= dst_stages.into(); + + let image = unsafe { self.accesses.image_unchecked(image) }; + + vk::ImageMemoryBarrier::default() + .src_access_mask(src_access.into()) + .dst_access_mask(dst_access.into()) + .old_layout(old_layout.into()) + .new_layout(new_layout.into()) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .image(image.handle()) + .subresource_range(subresource_range.clone().into()) + }) + .collect(); + + if src_stage_mask.is_empty() { + // "VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT is [...] equivalent to + // VK_PIPELINE_STAGE_2_NONE in the first scope." + src_stage_mask |= vk::PipelineStageFlags::TOP_OF_PIPE; + } + + if dst_stage_mask.is_empty() { + // "VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT is [...] equivalent to + // VK_PIPELINE_STAGE_2_NONE in the second scope." + dst_stage_mask |= vk::PipelineStageFlags::BOTTOM_OF_PIPE; + } + + let fns = self.device().fns(); + unsafe { + (fns.v1_0.cmd_pipeline_barrier)( + self.handle(), + src_stage_mask, + dst_stage_mask, + dependency_flags.into(), + memory_barriers_vk.len() as u32, + memory_barriers_vk.as_ptr(), + buffer_memory_barriers_vk.len() as u32, + buffer_memory_barriers_vk.as_ptr(), + image_memory_barriers_vk.len() as u32, + image_memory_barriers_vk.as_ptr(), + ) + }; + } + + self + } +} + +/// Dependency info for barriers in a pipeline barrier command. +/// +/// A pipeline barrier creates a dependency between commands submitted before the barrier (the +/// source scope) and commands submitted after it (the destination scope). Each `DependencyInfo` +/// consists of multiple individual barriers that concern either a single resource or operate +/// globally. +/// +/// Each barrier has a set of source/destination pipeline stages and source/destination memory +/// access types. The pipeline stages create an *execution dependency*: the `src_stages` of +/// commands submitted before the barrier must be completely finished before any of the +/// `dst_stages` of commands after the barrier are allowed to start. The memory access types create +/// a *memory dependency*: in addition to the execution dependency, any `src_access` +/// performed before the barrier must be made available and visible before any `dst_access` +/// are made after the barrier. +#[derive(Clone, Debug)] +pub struct DependencyInfo<'a> { + /// Flags to modify how the execution and memory dependencies are formed. + /// + /// The default value is empty. + pub dependency_flags: DependencyFlags, + + /// Memory barriers for global operations and accesses, not limited to a single resource. + /// + /// The default value is empty. + pub memory_barriers: &'a [MemoryBarrier<'a>], + + /// Memory barriers for individual buffers. + /// + /// The default value is empty. + pub buffer_memory_barriers: &'a [BufferMemoryBarrier<'a>], + + /// Memory barriers for individual images. + /// + /// The default value is empty. + pub image_memory_barriers: &'a [ImageMemoryBarrier<'a>], + + pub _ne: crate::NonExhaustive<'a>, +} + +impl DependencyInfo<'_> { + /// Returns `true` if `self` doesn't contain any barriers. + #[inline] + pub fn is_empty(&self) -> bool { + self.memory_barriers.is_empty() + && self.buffer_memory_barriers.is_empty() + && self.image_memory_barriers.is_empty() + } +} + +impl Default for DependencyInfo<'_> { + #[inline] + fn default() -> Self { + DependencyInfo { + dependency_flags: DependencyFlags::default(), + memory_barriers: &[], + buffer_memory_barriers: &[], + image_memory_barriers: &[], + _ne: crate::NE, + } + } +} + +/// A memory barrier that is applied globally. +#[derive(Clone, Debug)] +pub struct MemoryBarrier<'a> { + /// The pipeline stages in the source scope to wait for. + /// + /// The default value is [`PipelineStages::empty()`]. + pub src_stages: PipelineStages, + + /// The memory accesses in the source scope to make available and visible. + /// + /// The default value is [`AccessFlags::empty()`]. + pub src_access: AccessFlags, + + /// The pipeline stages in the destination scope that must wait for `src_stages`. + /// + /// The default value is [`PipelineStages::empty()`]. + pub dst_stages: PipelineStages, + + /// The memory accesses in the destination scope that must wait for `src_access` to be made + /// available and visible. + /// + /// The default value is [`AccessFlags::empty()`]. + pub dst_access: AccessFlags, + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for MemoryBarrier<'_> { + #[inline] + fn default() -> Self { + Self { + src_stages: PipelineStages::empty(), + src_access: AccessFlags::empty(), + dst_stages: PipelineStages::empty(), + dst_access: AccessFlags::empty(), + _ne: crate::NE, + } + } +} + +/// A memory barrier that is applied to a single buffer. +#[derive(Clone, Debug)] +pub struct BufferMemoryBarrier<'a> { + /// The pipeline stages in the source scope to wait for. + /// + /// The default value is [`PipelineStages::empty()`]. + pub src_stages: PipelineStages, + + /// The memory accesses in the source scope to make available and visible. + /// + /// The default value is [`AccessFlags::empty()`]. + pub src_access: AccessFlags, + + /// The pipeline stages in the destination scope that must wait for `src_stages`. + /// + /// The default value is [`PipelineStages::empty()`]. + pub dst_stages: PipelineStages, + + /// The memory accesses in the destination scope that must wait for `src_access` to be made + /// available and visible. + /// + /// The default value is [`AccessFlags::empty()`]. + pub dst_access: AccessFlags, + + /// The buffer to apply the barrier to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub buffer: Id, + + /// The byte range of `buffer` to apply the barrier to. + /// + /// The default value is empty, which must be overridden. + pub range: Range, + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for BufferMemoryBarrier<'_> { + #[inline] + fn default() -> Self { + BufferMemoryBarrier { + src_stages: PipelineStages::empty(), + src_access: AccessFlags::empty(), + dst_stages: PipelineStages::empty(), + dst_access: AccessFlags::empty(), + buffer: Id::INVALID, + range: 0..0, + _ne: crate::NE, + } + } +} + +/// A memory barrier that is applied to a single image. +#[derive(Clone, Debug)] +pub struct ImageMemoryBarrier<'a> { + /// The pipeline stages in the source scope to wait for. + /// + /// The default value is [`PipelineStages::empty()`]. + pub src_stages: PipelineStages, + + /// The memory accesses in the source scope to make available and visible. + /// + /// The default value is [`AccessFlags::empty()`]. + pub src_access: AccessFlags, + + /// The pipeline stages in the destination scope that must wait for `src_stages`. + /// + /// The default value is [`PipelineStages::empty()`]. + pub dst_stages: PipelineStages, + + /// The memory accesses in the destination scope that must wait for `src_access` to be made + /// available and visible. + /// + /// The default value is [`AccessFlags::empty()`]. + pub dst_access: AccessFlags, + + /// The layout that the specified `subresource_range` of `image` is expected to be in when the + /// source scope completes. + /// + /// The default value is [`ImageLayout::Undefined`]. + pub old_layout: ImageLayout, + + /// The layout that the specified `subresource_range` of `image` will be transitioned to before + /// the destination scope begins. + /// + /// The default value is [`ImageLayout::Undefined`]. + pub new_layout: ImageLayout, + + /// The image to apply the barrier to. + /// + /// The default value is [`Id::INVALID`], which must be overridden. + pub image: Id, + + /// The subresource range of `image` to apply the barrier to. + /// + /// The default value is empty, which must be overridden. + pub subresource_range: ImageSubresourceRange, + + pub _ne: crate::NonExhaustive<'a>, +} + +impl Default for ImageMemoryBarrier<'_> { + #[inline] + fn default() -> Self { + ImageMemoryBarrier { + src_stages: PipelineStages::empty(), + src_access: AccessFlags::empty(), + dst_stages: PipelineStages::empty(), + dst_access: AccessFlags::empty(), + old_layout: ImageLayout::Undefined, + new_layout: ImageLayout::Undefined, + image: Id::INVALID, + subresource_range: ImageSubresourceRange { + aspects: ImageAspects::empty(), + mip_levels: 0..0, + array_layers: 0..0, + }, + _ne: crate::NE, + } + } +} diff --git a/vulkano-taskgraph/src/command_buffer/mod.rs b/vulkano-taskgraph/src/command_buffer/mod.rs new file mode 100644 index 0000000000..9b9347c731 --- /dev/null +++ b/vulkano-taskgraph/src/command_buffer/mod.rs @@ -0,0 +1,120 @@ +#[allow(unused_imports)] // everything is exported for future-proofing +pub use self::commands::{clear::*, copy::*, dynamic_state::*, pipeline::*, sync::*}; +use crate::{ + graph::{self, ResourceMap}, + Id, +}; +use ash::vk; +use std::sync::Arc; +use vulkano::{ + buffer::Buffer, + command_buffer::sys::{RawCommandBuffer, RawRecordingCommandBuffer}, + device::{Device, DeviceOwned}, + image::Image, + VulkanError, VulkanObject, +}; + +mod commands; + +/// A command buffer in the recording state. +/// +/// Unlike [`RawRecordingCommandBuffer`], this type has knowledge of the current task context and +/// can therefore validate resource accesses. (TODO) +pub struct RecordingCommandBuffer<'a> { + inner: RawRecordingCommandBuffer, + accesses: ResourceAccesses<'a>, +} + +struct ResourceAccesses<'a> { + inner: &'a graph::ResourceAccesses, + resource_map: &'a ResourceMap<'a>, +} + +impl<'a> RecordingCommandBuffer<'a> { + pub(crate) unsafe fn new( + inner: RawRecordingCommandBuffer, + resource_map: &'a ResourceMap<'a>, + ) -> Self { + const EMPTY_ACCESSES: &graph::ResourceAccesses = &graph::ResourceAccesses::new(); + + RecordingCommandBuffer { + inner, + accesses: ResourceAccesses { + inner: EMPTY_ACCESSES, + resource_map, + }, + } + } + + pub(crate) unsafe fn set_accesses(&mut self, accesses: &'a graph::ResourceAccesses) { + self.accesses.inner = accesses; + } + + /// Returns the underlying raw command buffer. + /// + /// While this method is safe, using the command buffer isn't. You must guarantee that any + /// subresources you use while recording commands are either accounted for in the [task's + /// access set], or that those subresources don't require any synchronization (including layout + /// transitions and queue family ownership transfers), or that no other task is accessing the + /// subresources at the same time without appropriate synchronization. + #[inline] + pub fn raw_command_buffer(&mut self) -> &mut RawRecordingCommandBuffer { + &mut self.inner + } + + pub(crate) unsafe fn end(self) -> Result { + unsafe { self.inner.end() } + } +} + +unsafe impl DeviceOwned for RecordingCommandBuffer<'_> { + #[inline] + fn device(&self) -> &Arc { + self.inner.device() + } +} + +unsafe impl VulkanObject for RecordingCommandBuffer<'_> { + type Handle = vk::CommandBuffer; + + #[inline] + fn handle(&self) -> Self::Handle { + self.inner.handle() + } +} + +impl<'a> ResourceAccesses<'a> { + unsafe fn buffer_unchecked(&self, id: Id) -> &'a Arc { + if id.is_virtual() { + // SAFETY: + // * The caller of `Task::execute` must ensure that `self.resource_map` maps the virtual + // IDs of the graph exhaustively. + // * The caller must ensure that `id` is valid. + unsafe { self.resource_map.buffer_unchecked(id) }.buffer() + } else { + let resources = self.resource_map.resources(); + + // SAFETY: + // * `ResourceMap` owns an `epoch::Guard`. + // * The caller must ensure that `id` is valid. + unsafe { resources.buffer_unchecked_unprotected(id) }.buffer() + } + } + + unsafe fn image_unchecked(&self, id: Id) -> &'a Arc { + if id.is_virtual() { + // SAFETY: + // * The caller must ensure that `id` is valid. + // * The caller of `Task::execute` must ensure that `self.resource_map` maps the virtual + // IDs of the graph exhaustively. + unsafe { self.resource_map.image_unchecked(id) }.image() + } else { + let resources = self.resource_map.resources(); + + // SAFETY: + // * The caller must ensure that `id` is valid. + // * `ResourceMap` owns an `epoch::Guard`. + unsafe { resources.image_unchecked_unprotected(id) }.image() + } + } +} diff --git a/vulkano-taskgraph/src/graph/compile.rs b/vulkano-taskgraph/src/graph/compile.rs index 2025e1318d..c943409725 100644 --- a/vulkano-taskgraph/src/graph/compile.rs +++ b/vulkano-taskgraph/src/graph/compile.rs @@ -47,9 +47,9 @@ impl TaskGraph { /// [directed cycles]: https://en.wikipedia.org/wiki/Cycle_(graph_theory)#Directed_circuit_and_directed_cycle pub unsafe fn compile( mut self, - compile_info: CompileInfo, + compile_info: &CompileInfo<'_>, ) -> Result, CompileError> { - let CompileInfo { + let &CompileInfo { queues, present_queue, flight_id, @@ -60,7 +60,7 @@ impl TaskGraph { let device = &self.device().clone(); - for queue in &queues { + for queue in queues { assert_eq!(queue.device(), device); assert_eq!( queues @@ -86,14 +86,14 @@ impl TaskGraph { }; unsafe { self.dependency_levels(&topological_order) }; let queue_family_indices = - match unsafe { self.queue_family_indices(device, &queues, &topological_order) } { + match unsafe { self.queue_family_indices(device, queues, &topological_order) } { Ok(queue_family_indices) => queue_family_indices, Err(kind) => return Err(CompileError::new(self, kind)), }; let mut queues_by_queue_family_index: SmallVec<[_; 8]> = smallvec![None; *queue_family_indices.iter().max().unwrap() as usize + 1]; - for queue in &queues { + for &queue in queues { if let Some(x) = queues_by_queue_family_index.get_mut(queue.queue_family_index() as usize) { @@ -262,7 +262,7 @@ impl TaskGraph { if should_submit { let queue = queues_by_queue_family_index[task_node.queue_family_index as usize] .unwrap(); - state.submit(queue.clone()); + state.submit(queue); prev_submission_end = i + 1; break; } @@ -278,7 +278,7 @@ impl TaskGraph { } state.flush_submit(); - state.submit(state.present_queue.clone().unwrap()); + state.submit(state.present_queue.unwrap()); } let semaphores = match (0..semaphore_count) @@ -304,7 +304,7 @@ impl TaskGraph { image_barriers: state.image_barriers, semaphores: RefCell::new(semaphores), swapchains, - present_queue: state.present_queue, + present_queue: state.present_queue.cloned(), last_accesses: prev_accesses, }) } @@ -447,7 +447,7 @@ impl TaskGraph { unsafe fn queue_family_indices( &mut self, device: &Device, - queues: &[Arc], + queues: &[&Arc], topological_order: &[NodeIndex], ) -> Result, CompileErrorKind> { let queue_family_properties = device.physical_device().queue_family_properties(); @@ -625,7 +625,7 @@ struct CompileState<'a> { submissions: Vec, buffer_barriers: Vec, image_barriers: Vec, - present_queue: Option>, + present_queue: Option<&'a Arc>, initial_buffer_barrier_range: Range, initial_image_barrier_range: Range, has_flushed_submit: bool, @@ -636,7 +636,7 @@ struct CompileState<'a> { } impl<'a> CompileState<'a> { - fn new(prev_accesses: &'a mut [ResourceAccess], present_queue: Option>) -> Self { + fn new(prev_accesses: &'a mut [ResourceAccess], present_queue: Option<&'a Arc>) -> Self { CompileState { prev_accesses, instructions: Vec::new(), @@ -932,7 +932,7 @@ impl<'a> CompileState<'a> { self.should_flush_submit = false; } - fn submit(&mut self, queue: Arc) { + fn submit(&mut self, queue: &Arc) { self.instructions.push(Instruction::Submit); let prev_instruction_range_end = self @@ -941,7 +941,7 @@ impl<'a> CompileState<'a> { .map(|s| s.instruction_range.end) .unwrap_or(0); self.submissions.push(Submission { - queue, + queue: queue.clone(), initial_buffer_barrier_range: self.initial_buffer_barrier_range.clone(), initial_image_barrier_range: self.initial_image_barrier_range.clone(), instruction_range: prev_instruction_range_end..self.instructions.len(), @@ -961,13 +961,13 @@ impl ExecutableTaskGraph { /// /// [compile]: TaskGraph::compile #[derive(Clone, Debug)] -pub struct CompileInfo { +pub struct CompileInfo<'a> { /// The queues to work with. /// /// You must supply at least one queue and all queues must be from unique queue families. /// /// The default value is empty, which must be overridden. - pub queues: Vec>, + pub queues: &'a [&'a Arc], /// The queue to use for swapchain presentation, if any. /// @@ -977,21 +977,21 @@ pub struct CompileInfo { /// The default value is `None`. /// /// [`queues`]: Self::queues - pub present_queue: Option>, + pub present_queue: Option<&'a Arc>, /// The flight which will be executed. /// /// The default value is `Id::INVALID`, which must be overridden. pub flight_id: Id, - pub _ne: vulkano::NonExhaustive, + pub _ne: crate::NonExhaustive<'a>, } -impl Default for CompileInfo { +impl Default for CompileInfo<'_> { #[inline] fn default() -> Self { CompileInfo { - queues: Vec::new(), + queues: &[], present_queue: None, flight_id: Id::INVALID, _ne: crate::NE, @@ -1112,7 +1112,7 @@ mod tests { fn unconnected() { let (resources, queues) = test_queues!(); let compile_info = CompileInfo { - queues, + queues: &queues.iter().collect::>(), ..Default::default() }; @@ -1124,7 +1124,7 @@ mod tests { // ┌───┐ // │ B │ // └───┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1133,7 +1133,7 @@ mod tests { .build(); assert!(matches!( - unsafe { graph.compile(compile_info.clone()) }, + unsafe { graph.compile(&compile_info) }, Err(CompileError { kind: CompileErrorKind::Unconnected, .. @@ -1155,7 +1155,7 @@ mod tests { // │ ┌───┐ // └─►│ D │ // └───┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1172,7 +1172,7 @@ mod tests { graph.add_edge(b, d).unwrap(); assert!(matches!( - unsafe { graph.compile(compile_info.clone()) }, + unsafe { graph.compile(&compile_info) }, Err(CompileError { kind: CompileErrorKind::Unconnected, .. @@ -1190,7 +1190,7 @@ mod tests { // └───┘│ └───┘│ └───┘┌─►│ G │ // │ └──────┘┌►│ │ // └──────────────┘ └───┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1221,7 +1221,7 @@ mod tests { graph.add_edge(f, g).unwrap(); assert!(matches!( - unsafe { graph.compile(compile_info) }, + unsafe { graph.compile(&compile_info) }, Err(CompileError { kind: CompileErrorKind::Unconnected, .. @@ -1234,7 +1234,7 @@ mod tests { fn cycle() { let (resources, queues) = test_queues!(); let compile_info = CompileInfo { - queues, + queues: &queues.iter().collect::>(), ..Default::default() }; @@ -1243,7 +1243,7 @@ mod tests { // ┌►│ A ├─►│ B ├─►│ C ├┐ // │ └───┘ └───┘ └───┘│ // └────────────────────┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1258,7 +1258,7 @@ mod tests { graph.add_edge(c, a).unwrap(); assert!(matches!( - unsafe { graph.compile(compile_info.clone()) }, + unsafe { graph.compile(&compile_info) }, Err(CompileError { kind: CompileErrorKind::Cycle, .. @@ -1275,7 +1275,7 @@ mod tests { // │ └►│ D ├─►│ E ├┴►│ F ├┐ // │ └───┘ └───┘ └───┘│ // └───────────────────────────┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1303,7 +1303,7 @@ mod tests { graph.add_edge(f, a).unwrap(); assert!(matches!( - unsafe { graph.compile(compile_info.clone()) }, + unsafe { graph.compile(&compile_info) }, Err(CompileError { kind: CompileErrorKind::Cycle, .. @@ -1322,7 +1322,7 @@ mod tests { // │ ┌►└───┘ └───┘ └───┘││ // │ └────────────────────┘│ // └───────────────────────────┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1351,7 +1351,7 @@ mod tests { graph.add_edge(f, b).unwrap(); assert!(matches!( - unsafe { graph.compile(compile_info) }, + unsafe { graph.compile(&compile_info) }, Err(CompileError { kind: CompileErrorKind::Cycle, .. @@ -1364,12 +1364,12 @@ mod tests { fn initial_pipeline_barrier() { let (resources, queues) = test_queues!(); let compile_info = CompileInfo { - queues, + queues: &queues.iter().collect::>(), ..Default::default() }; { - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let buffer = graph.add_buffer(&BufferCreateInfo::default()); let image = graph.add_image(&ImageCreateInfo::default()); let node = graph @@ -1382,7 +1382,7 @@ mod tests { ) .build(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, @@ -1429,7 +1429,7 @@ mod tests { } let compile_info = CompileInfo { - queues, + queues: &queues.iter().collect::>(), ..Default::default() }; @@ -1444,7 +1444,7 @@ mod tests { // │└►┌───┐ // └─►│ C │ // └───┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1457,7 +1457,7 @@ mod tests { graph.add_edge(a, c).unwrap(); graph.add_edge(b, c).unwrap(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, @@ -1500,7 +1500,7 @@ mod tests { // │ ┌───┐ // └►│ C │ // └───┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1513,7 +1513,7 @@ mod tests { graph.add_edge(a, b).unwrap(); graph.add_edge(a, c).unwrap(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, @@ -1556,7 +1556,7 @@ mod tests { // │ ┌───┐└►┌───┐│ // └►│ C ├─►│ D ├┘ // └───┘ └───┘ - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let a = graph .create_task_node("A", QueueFamilyType::Graphics, PhantomData) .build(); @@ -1578,7 +1578,7 @@ mod tests { graph.add_edge(c, d).unwrap(); graph.add_edge(d, e).unwrap(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); // TODO: This could be brought down to 3 submissions with task reordering. assert_matches_instructions!( @@ -1645,12 +1645,12 @@ mod tests { } let compile_info = CompileInfo { - queues, + queues: &queues.iter().collect::>(), ..Default::default() }; { - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let buffer1 = graph.add_buffer(&BufferCreateInfo::default()); let buffer2 = graph.add_buffer(&BufferCreateInfo::default()); let image1 = graph.add_image(&ImageCreateInfo::default()); @@ -1687,7 +1687,7 @@ mod tests { .build(); graph.add_edge(compute_node, graphics_node).unwrap(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, @@ -1789,7 +1789,7 @@ mod tests { } { - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let sharing = Sharing::Concurrent( compile_info .queues @@ -1845,7 +1845,7 @@ mod tests { .build(); graph.add_edge(compute_node, graphics_node).unwrap(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, @@ -1900,13 +1900,13 @@ mod tests { queue_flags.contains(QueueFlags::GRAPHICS) }); let compile_info = CompileInfo { - queues: queues.clone(), - present_queue: Some(present_queue.unwrap().clone()), + queues: &queues.iter().collect::>(), + present_queue: Some(present_queue.unwrap()), ..Default::default() }; { - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let swapchain1 = graph.add_swapchain(&SwapchainCreateInfo::default()); let swapchain2 = graph.add_swapchain(&SwapchainCreateInfo::default()); let node = graph @@ -1923,7 +1923,7 @@ mod tests { ) .build(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, @@ -2006,13 +2006,13 @@ mod tests { } let compile_info = CompileInfo { - queues: queues.clone(), - present_queue: Some(present_queue.unwrap().clone()), + queues: &queues.iter().collect::>(), + present_queue: Some(present_queue.unwrap()), ..Default::default() }; { - let mut graph = TaskGraph::<()>::new(resources.clone(), 10, 10); + let mut graph = TaskGraph::<()>::new(&resources, 10, 10); let concurrent_sharing = Sharing::Concurrent( compile_info .queues @@ -2054,7 +2054,7 @@ mod tests { ) .build(); - let graph = unsafe { graph.compile(compile_info.clone()) }.unwrap(); + let graph = unsafe { graph.compile(&compile_info) }.unwrap(); assert_matches_instructions!( graph, diff --git a/vulkano-taskgraph/src/graph/execute.rs b/vulkano-taskgraph/src/graph/execute.rs index 602fd27afe..6fd496811b 100644 --- a/vulkano-taskgraph/src/graph/execute.rs +++ b/vulkano-taskgraph/src/graph/execute.rs @@ -2,6 +2,7 @@ use super::{ BarrierIndex, ExecutableTaskGraph, Instruction, NodeIndex, ResourceAccess, SemaphoreIndex, }; use crate::{ + command_buffer::RecordingCommandBuffer, resource::{ BufferAccess, BufferState, DeathRow, ImageAccess, ImageState, Resources, SwapchainState, }, @@ -619,7 +620,7 @@ struct ExecuteState2<'a, W: ?Sized + 'static> { queue_submit2: vk::PFN_vkQueueSubmit2, per_submits: SmallVec<[PerSubmitInfo2; 4]>, current_per_submit: PerSubmitInfo2, - current_command_buffer: Option, + current_command_buffer: Option>, command_buffers: Vec>, current_buffer_barriers: Vec>, current_image_barriers: Vec>, @@ -654,7 +655,7 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> { } let current_command_buffer = - create_command_buffer(resource_map, &executable.submissions[0].queue)?; + unsafe { create_command_buffer(resource_map, &executable.submissions[0].queue) }?; Ok(ExecuteState2 { executable, @@ -823,6 +824,8 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> { command_buffers: Cell::new(Some(&mut self.command_buffers)), }; + unsafe { current_command_buffer.set_accesses(&task_node.accesses) }; + unsafe { task.execute(current_command_buffer, &mut context, self.world) } .map_err(|error| ExecuteError::Task { node_index, error })?; @@ -1022,10 +1025,9 @@ impl<'a, W: ?Sized + 'static> ExecuteState2<'a, W> { vk::CommandBufferSubmitInfo::default().command_buffer(command_buffer.handle()), ); self.death_row.push(Arc::new(command_buffer)); - self.current_command_buffer = Some(create_command_buffer( - self.resource_map, - &self.current_submission().queue, - )?); + self.current_command_buffer = Some(unsafe { + create_command_buffer(self.resource_map, &self.current_submission().queue) + }?); } Ok(()) @@ -1044,7 +1046,7 @@ struct ExecuteState<'a, W: ?Sized + 'static> { queue_submit: vk::PFN_vkQueueSubmit, per_submits: SmallVec<[PerSubmitInfo; 4]>, current_per_submit: PerSubmitInfo, - current_command_buffer: Option, + current_command_buffer: Option>, command_buffers: Vec>, current_buffer_barriers: Vec>, current_image_barriers: Vec>, @@ -1075,7 +1077,7 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { let queue_submit = fns.v1_0.queue_submit; let current_command_buffer = - create_command_buffer(resource_map, &executable.submissions[0].queue)?; + unsafe { create_command_buffer(resource_map, &executable.submissions[0].queue) }?; Ok(ExecuteState { executable, @@ -1250,6 +1252,8 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { command_buffers: Cell::new(Some(&mut self.command_buffers)), }; + unsafe { current_command_buffer.set_accesses(&task_node.accesses) }; + unsafe { task.execute(current_command_buffer, &mut context, self.world) } .map_err(|error| ExecuteError::Task { node_index, error })?; @@ -1463,22 +1467,21 @@ impl<'a, W: ?Sized + 'static> ExecuteState<'a, W> { .command_buffers .push(command_buffer.handle()); self.death_row.push(Arc::new(command_buffer)); - self.current_command_buffer = Some(create_command_buffer( - self.resource_map, - &self.current_submission().queue, - )?); + self.current_command_buffer = Some(unsafe { + create_command_buffer(self.resource_map, &self.current_submission().queue) + }?); } Ok(()) } } -fn create_command_buffer( - resource_map: &ResourceMap<'_>, +unsafe fn create_command_buffer<'a>( + resource_map: &'a ResourceMap<'a>, queue: &Queue, -) -> Result { +) -> Result, VulkanError> { // SAFETY: The parameters are valid. - unsafe { + let raw = unsafe { RawRecordingCommandBuffer::new_unchecked( resource_map .physical_resources @@ -1495,7 +1498,9 @@ fn create_command_buffer( } // This can't panic because we know that the queue family index is active on the device, // otherwise we wouldn't have a reference to the `Queue`. - .map_err(Validated::unwrap) + .map_err(Validated::unwrap)?; + + Ok(unsafe { RecordingCommandBuffer::new(raw, resource_map) }) } fn convert_stage_mask(mut stage_mask: PipelineStages) -> vk::PipelineStageFlags { diff --git a/vulkano-taskgraph/src/graph/mod.rs b/vulkano-taskgraph/src/graph/mod.rs index d5c707381f..7d1e457c48 100644 --- a/vulkano-taskgraph/src/graph/mod.rs +++ b/vulkano-taskgraph/src/graph/mod.rs @@ -72,7 +72,7 @@ impl TaskGraph { /// maximum number of virtual resources the graph can ever have. #[must_use] pub fn new( - physical_resources: Arc, + physical_resources: &Arc, max_nodes: u32, max_resources: u32, ) -> Self { @@ -82,7 +82,7 @@ impl TaskGraph { }, resources: Resources { inner: SlotMap::new(max_resources), - physical_resources, + physical_resources: physical_resources.clone(), physical_map: HashMap::default(), host_reads: Vec::new(), host_writes: Vec::new(), @@ -565,7 +565,7 @@ struct ResourceAccess { impl TaskNode { fn new(queue_family_type: QueueFamilyType, task: impl Task) -> Self { TaskNode { - accesses: ResourceAccesses { inner: Vec::new() }, + accesses: ResourceAccesses::new(), queue_family_type, queue_family_index: 0, dependency_level_index: 0, @@ -596,6 +596,10 @@ impl TaskNode { } impl ResourceAccesses { + pub(crate) const fn new() -> Self { + ResourceAccesses { inner: Vec::new() } + } + fn get_mut( &mut self, resources: &mut Resources, @@ -1022,7 +1026,7 @@ mod tests { #[test] fn basic_usage1() { let (resources, _) = test_queues!(); - let mut graph = TaskGraph::<()>::new(resources, 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let x = graph .create_task_node("X", QueueFamilyType::Graphics, PhantomData) @@ -1057,7 +1061,7 @@ mod tests { #[test] fn basic_usage2() { let (resources, _) = test_queues!(); - let mut graph = TaskGraph::<()>::new(resources, 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let x = graph .create_task_node("X", QueueFamilyType::Graphics, PhantomData) @@ -1094,7 +1098,7 @@ mod tests { #[test] fn self_referential_node() { let (resources, _) = test_queues!(); - let mut graph = TaskGraph::<()>::new(resources, 10, 0); + let mut graph = TaskGraph::<()>::new(&resources, 10, 0); let x = graph .create_task_node("X", QueueFamilyType::Graphics, PhantomData) diff --git a/vulkano-taskgraph/src/lib.rs b/vulkano-taskgraph/src/lib.rs index 7edf77375c..839dfb35cb 100644 --- a/vulkano-taskgraph/src/lib.rs +++ b/vulkano-taskgraph/src/lib.rs @@ -1,5 +1,6 @@ #![forbid(unsafe_op_in_unsafe_fn)] +use command_buffer::RecordingCommandBuffer; use concurrent_slotmap::SlotId; use graph::{CompileInfo, ExecuteError, ResourceMap, TaskGraph}; use resource::{ @@ -20,29 +21,30 @@ use std::{ }; use vulkano::{ buffer::{Buffer, BufferContents, BufferMemory, Subbuffer}, - command_buffer::sys::{RawCommandBuffer, RawRecordingCommandBuffer}, + command_buffer::sys::RawCommandBuffer, device::Queue, image::Image, swapchain::Swapchain, DeviceSize, ValidationError, }; +pub mod command_buffer; pub mod graph; pub mod resource; /// Creates a [`TaskGraph`] with one task node, compiles it, and executes it. pub unsafe fn execute( - queue: Arc, - resources: Arc, + queue: &Arc, + resources: &Arc, flight_id: Id, - task: impl FnOnce(&mut RawRecordingCommandBuffer, &mut TaskContext<'_>) -> TaskResult, + task: impl FnOnce(&mut RecordingCommandBuffer<'_>, &mut TaskContext<'_>) -> TaskResult, host_buffer_accesses: impl IntoIterator, HostAccessType)>, buffer_accesses: impl IntoIterator, AccessType)>, image_accesses: impl IntoIterator, AccessType, ImageLayoutType)>, ) -> Result<(), ExecuteError> { #[repr(transparent)] struct OnceTask<'a>( - &'a dyn Fn(&mut RawRecordingCommandBuffer, &mut TaskContext<'_>) -> TaskResult, + &'a dyn Fn(&mut RecordingCommandBuffer<'_>, &mut TaskContext<'_>) -> TaskResult, ); // SAFETY: The task is constructed inside this function and never leaves its scope, so there is @@ -58,7 +60,7 @@ pub unsafe fn execute( unsafe fn execute( &self, - cbf: &mut RawRecordingCommandBuffer, + cbf: &mut RecordingCommandBuffer<'_>, tcx: &mut TaskContext<'_>, _: &Self::World, ) -> TaskResult { @@ -67,7 +69,7 @@ pub unsafe fn execute( } let task = Cell::new(Some(task)); - let trampoline = move |cbf: &mut RawRecordingCommandBuffer, tcx: &mut TaskContext<'_>| { + let trampoline = move |cbf: &mut RecordingCommandBuffer<'_>, tcx: &mut TaskContext<'_>| { // `ExecutableTaskGraph::execute` calls each task exactly once, and we only execute the // task graph once. (Cell::take(&task).unwrap())(cbf, tcx) @@ -101,8 +103,8 @@ pub unsafe fn execute( // * The user must ensure that there are no accesses that are incompatible with the queue. // * The user must ensure that there are no accesses incompatible with the device. let task_graph = unsafe { - task_graph.compile(CompileInfo { - queues: vec![queue], + task_graph.compile(&CompileInfo { + queues: &[queue], present_queue: None, flight_id, _ne: crate::NE, @@ -139,7 +141,7 @@ pub trait Task: Any + Send + Sync { /// [sharing mode]: vulkano::sync::Sharing unsafe fn execute( &self, - cbf: &mut RawRecordingCommandBuffer, + cbf: &mut RecordingCommandBuffer<'_>, tcx: &mut TaskContext<'_>, world: &Self::World, ) -> TaskResult; @@ -213,7 +215,7 @@ impl Task for PhantomData W> { unsafe fn execute( &self, - _cbf: &mut RawRecordingCommandBuffer, + _cbf: &mut RecordingCommandBuffer<'_>, _tcx: &mut TaskContext<'_>, _world: &Self::World, ) -> TaskResult { diff --git a/vulkano-taskgraph/src/resource.rs b/vulkano-taskgraph/src/resource.rs index 42a9a6fa79..347e0a1308 100644 --- a/vulkano-taskgraph/src/resource.rs +++ b/vulkano-taskgraph/src/resource.rs @@ -121,9 +121,9 @@ impl Resources { /// /// - Panics if `device` already has a `Resources` collection associated with it. #[must_use] - pub fn new(device: Arc, create_info: ResourcesCreateInfo) -> Arc { + pub fn new(device: &Arc, create_info: &ResourcesCreateInfo<'_>) -> Arc { let mut registered_devices = REGISTERED_DEVICES.lock(); - let device_addr = Arc::as_ptr(&device) as usize; + let device_addr = Arc::as_ptr(device) as usize; assert!( !registered_devices.contains(&device_addr), @@ -141,7 +141,7 @@ impl Resources { let global = epoch::GlobalHandle::new(); Arc::new(Resources { - device, + device: device.clone(), memory_allocator, command_buffer_allocator, locals: ThreadLocal::new(), @@ -1065,7 +1065,7 @@ impl Flight { /// Parameters to create a new [`Resources`] collection. #[derive(Debug)] -pub struct ResourcesCreateInfo { +pub struct ResourcesCreateInfo<'a> { /// The maximum number of [`Buffer`]s that the collection can hold at once. pub max_buffers: u32, @@ -1078,10 +1078,10 @@ pub struct ResourcesCreateInfo { /// The maximum number of [`Flight`]s that the collection can hold at once. pub max_flights: u32, - pub _ne: vulkano::NonExhaustive, + pub _ne: crate::NonExhaustive<'a>, } -impl Default for ResourcesCreateInfo { +impl Default for ResourcesCreateInfo<'_> { #[inline] fn default() -> Self { ResourcesCreateInfo { diff --git a/vulkano/src/pipeline/layout.rs b/vulkano/src/pipeline/layout.rs index f90a3508f1..b94c3e08a6 100644 --- a/vulkano/src/pipeline/layout.rs +++ b/vulkano/src/pipeline/layout.rs @@ -278,8 +278,9 @@ impl PipelineLayout { /// /// The ranges are guaranteed to be sorted deterministically by offset, and /// guaranteed to be disjoint, meaning that there is no overlap between the ranges. + #[doc(hidden)] #[inline] - pub(crate) fn push_constant_ranges_disjoint(&self) -> &[PushConstantRange] { + pub fn push_constant_ranges_disjoint(&self) -> &[PushConstantRange] { &self.push_constant_ranges_disjoint }