// Copyright (c) 2022 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

use crate::{
    command_buffer::{
        synced::{Command, SyncCommandBufferBuilder},
        sys::UnsafeCommandBufferBuilder,
    },
    image::ImageLayout,
    sync::{
        BufferMemoryBarrier, DependencyInfo, Event, ImageMemoryBarrier, MemoryBarrier,
        PipelineStages,
    },
    Version, VulkanObject,
};
use smallvec::SmallVec;
use std::{ptr, sync::Arc};

impl SyncCommandBufferBuilder {
    /// Calls `vkCmdSetEvent` on the builder.
    #[inline]
    pub unsafe fn set_event(&mut self, event: Arc<Event>, dependency_info: DependencyInfo) {
        struct Cmd {
            event: Arc<Event>,
            dependency_info: DependencyInfo,
        }

        impl Command for Cmd {
            fn name(&self) -> &'static str {
                "set_event"
            }

            unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
                out.set_event(&self.event, &self.dependency_info);
            }
        }

        self.commands.push(Box::new(Cmd {
            event,
            dependency_info,
        }));
    }

    /// Calls `vkCmdWaitEvents` on the builder.
    #[inline]
    pub unsafe fn wait_events(
        &mut self,
        events: impl IntoIterator<Item = (Arc<Event>, DependencyInfo)>,
    ) {
        struct Cmd {
            events: SmallVec<[(Arc<Event>, DependencyInfo); 4]>,
        }

        impl Command for Cmd {
            fn name(&self) -> &'static str {
                "wait_events"
            }

            unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
                out.wait_events(
                    self.events
                        .iter()
                        .map(|&(ref event, ref dependency_info)| (event.as_ref(), dependency_info)),
                );
            }
        }

        self.commands.push(Box::new(Cmd {
            events: events.into_iter().collect(),
        }));
    }

    /// Calls `vkCmdResetEvent` on the builder.
    #[inline]
    pub unsafe fn reset_event(&mut self, event: Arc<Event>, stages: PipelineStages) {
        struct Cmd {
            event: Arc<Event>,
            stages: PipelineStages,
        }

        impl Command for Cmd {
            fn name(&self) -> &'static str {
                "reset_event"
            }

            unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
                out.reset_event(&self.event, self.stages);
            }
        }

        self.commands.push(Box::new(Cmd { event, stages }));
    }
}

impl UnsafeCommandBufferBuilder {
    #[inline]
    pub unsafe fn pipeline_barrier(&mut self, dependency_info: &DependencyInfo) {
        if dependency_info.is_empty() {
            return;
        }

        let DependencyInfo {
            memory_barriers,
            buffer_memory_barriers,
            image_memory_barriers,
            _ne: _,
        } = dependency_info;

        let dependency_flags = ash::vk::DependencyFlags::BY_REGION;

        if self.device.enabled_features().synchronization2 {
            let memory_barriers_vk: SmallVec<[_; 2]> = memory_barriers
                .into_iter()
                .map(|barrier| {
                    let &MemoryBarrier {
                        src_stages,
                        src_access,
                        dst_stages,
                        dst_access,
                        _ne: _,
                    } = barrier;

                    debug_assert!(src_stages.supported_access().contains(&src_access));
                    debug_assert!(dst_stages.supported_access().contains(&dst_access));

                    ash::vk::MemoryBarrier2 {
                        src_stage_mask: src_stages.into(),
                        src_access_mask: src_access.into(),
                        dst_stage_mask: dst_stages.into(),
                        dst_access_mask: dst_access.into(),
                        ..Default::default()
                    }
                })
                .collect();

            let buffer_memory_barriers_vk: SmallVec<[_; 8]> = buffer_memory_barriers
                .into_iter()
                .map(|barrier| {
                    let &BufferMemoryBarrier {
                        src_stages,
                        src_access,
                        dst_stages,
                        dst_access,
                        queue_family_transfer,
                        ref buffer,
                        ref range,
                        _ne: _,
                    } = barrier;

                    debug_assert!(src_stages.supported_access().contains(&src_access));
                    debug_assert!(dst_stages.supported_access().contains(&dst_access));
                    debug_assert!(!range.is_empty());
                    debug_assert!(range.end <= buffer.size());

                    ash::vk::BufferMemoryBarrier2 {
                        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: queue_family_transfer
                            .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                transfer.source_index
                            }),
                        dst_queue_family_index: queue_family_transfer
                            .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                transfer.destination_index
                            }),
                        buffer: buffer.handle(),
                        offset: range.start,
                        size: range.end - range.start,
                        ..Default::default()
                    }
                })
                .collect();

            let image_memory_barriers_vk: SmallVec<[_; 8]> = image_memory_barriers
                .into_iter()
                .map(|barrier| {
                    let &ImageMemoryBarrier {
                        src_stages,
                        src_access,
                        dst_stages,
                        dst_access,
                        old_layout,
                        new_layout,
                        queue_family_transfer,
                        ref image,
                        ref subresource_range,
                        _ne: _,
                    } = barrier;

                    debug_assert!(src_stages.supported_access().contains(&src_access));
                    debug_assert!(dst_stages.supported_access().contains(&dst_access));
                    debug_assert!(!matches!(
                        new_layout,
                        ImageLayout::Undefined | ImageLayout::Preinitialized
                    ));
                    debug_assert!(image
                        .format()
                        .unwrap()
                        .aspects()
                        .contains(&subresource_range.aspects));
                    debug_assert!(!subresource_range.mip_levels.is_empty());
                    debug_assert!(subresource_range.mip_levels.end <= image.mip_levels());
                    debug_assert!(!subresource_range.array_layers.is_empty());
                    debug_assert!(
                        subresource_range.array_layers.end <= image.dimensions().array_layers()
                    );

                    ash::vk::ImageMemoryBarrier2 {
                        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: queue_family_transfer
                            .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                transfer.source_index
                            }),
                        dst_queue_family_index: queue_family_transfer
                            .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                transfer.destination_index
                            }),
                        image: image.handle(),
                        subresource_range: subresource_range.clone().into(),
                        ..Default::default()
                    }
                })
                .collect();

            let dependency_info_vk = ash::vk::DependencyInfo {
                dependency_flags,
                memory_barrier_count: memory_barriers_vk.len() as u32,
                p_memory_barriers: memory_barriers_vk.as_ptr(),
                buffer_memory_barrier_count: buffer_memory_barriers_vk.len() as u32,
                p_buffer_memory_barriers: buffer_memory_barriers_vk.as_ptr(),
                image_memory_barrier_count: image_memory_barriers_vk.len() as u32,
                p_image_memory_barriers: image_memory_barriers_vk.as_ptr(),
                ..Default::default()
            };

            let fns = self.device.fns();

            if self.device.api_version() >= Version::V1_3 {
                (fns.v1_3.cmd_pipeline_barrier2)(self.handle, &dependency_info_vk);
            } else {
                debug_assert!(self.device.enabled_extensions().khr_synchronization2);
                (fns.khr_synchronization2.cmd_pipeline_barrier2_khr)(
                    self.handle,
                    &dependency_info_vk,
                );
            }
        } else {
            let mut src_stage_mask = ash::vk::PipelineStageFlags::empty();
            let mut dst_stage_mask = ash::vk::PipelineStageFlags::empty();

            let memory_barriers_vk: SmallVec<[_; 2]> = memory_barriers
                .into_iter()
                .map(|barrier| {
                    let &MemoryBarrier {
                        src_stages,
                        src_access,
                        dst_stages,
                        dst_access,
                        _ne: _,
                    } = barrier;

                    debug_assert!(src_stages.supported_access().contains(&src_access));
                    debug_assert!(dst_stages.supported_access().contains(&dst_access));

                    src_stage_mask |= src_stages.into();
                    dst_stage_mask |= dst_stages.into();

                    ash::vk::MemoryBarrier {
                        src_access_mask: src_access.into(),
                        dst_access_mask: dst_access.into(),
                        ..Default::default()
                    }
                })
                .collect();

            let buffer_memory_barriers_vk: SmallVec<[_; 8]> = buffer_memory_barriers
                .into_iter()
                .map(|barrier| {
                    let &BufferMemoryBarrier {
                        src_stages,
                        src_access,
                        dst_stages,
                        dst_access,
                        queue_family_transfer,
                        ref buffer,
                        ref range,
                        _ne: _,
                    } = barrier;

                    debug_assert!(src_stages.supported_access().contains(&src_access));
                    debug_assert!(dst_stages.supported_access().contains(&dst_access));
                    debug_assert!(!range.is_empty());
                    debug_assert!(range.end <= buffer.size());

                    src_stage_mask |= src_stages.into();
                    dst_stage_mask |= dst_stages.into();

                    ash::vk::BufferMemoryBarrier {
                        src_access_mask: src_access.into(),
                        dst_access_mask: dst_access.into(),
                        src_queue_family_index: queue_family_transfer
                            .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                transfer.source_index
                            }),
                        dst_queue_family_index: queue_family_transfer
                            .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                transfer.destination_index
                            }),
                        buffer: buffer.handle(),
                        offset: range.start,
                        size: range.end - range.start,
                        ..Default::default()
                    }
                })
                .collect();

            let image_memory_barriers_vk: SmallVec<[_; 8]> = image_memory_barriers
                .into_iter()
                .map(|barrier| {
                    let &ImageMemoryBarrier {
                        src_stages,
                        src_access,
                        dst_stages,
                        dst_access,
                        old_layout,
                        new_layout,
                        queue_family_transfer,
                        ref image,
                        ref subresource_range,
                        _ne: _,
                    } = barrier;

                    debug_assert!(src_stages.supported_access().contains(&src_access));
                    debug_assert!(dst_stages.supported_access().contains(&dst_access));
                    debug_assert!(!matches!(
                        new_layout,
                        ImageLayout::Undefined | ImageLayout::Preinitialized
                    ));
                    debug_assert!(image
                        .format()
                        .unwrap()
                        .aspects()
                        .contains(&subresource_range.aspects));
                    debug_assert!(!subresource_range.mip_levels.is_empty());
                    debug_assert!(subresource_range.mip_levels.end <= image.mip_levels());
                    debug_assert!(!subresource_range.array_layers.is_empty());
                    debug_assert!(
                        subresource_range.array_layers.end <= image.dimensions().array_layers()
                    );

                    src_stage_mask |= src_stages.into();
                    dst_stage_mask |= dst_stages.into();

                    ash::vk::ImageMemoryBarrier {
                        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: queue_family_transfer
                            .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                transfer.source_index
                            }),
                        dst_queue_family_index: queue_family_transfer
                            .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                transfer.destination_index
                            }),
                        image: image.handle(),
                        subresource_range: subresource_range.clone().into(),
                        ..Default::default()
                    }
                })
                .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 |= ash::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 |= ash::vk::PipelineStageFlags::BOTTOM_OF_PIPE;
            }

            let fns = self.device.fns();
            (fns.v1_0.cmd_pipeline_barrier)(
                self.handle,
                src_stage_mask,
                dst_stage_mask,
                dependency_flags,
                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(),
            );
        }
    }

    /// Calls `vkCmdSetEvent` on the builder.
    #[inline]
    pub unsafe fn set_event(&mut self, event: &Event, dependency_info: &DependencyInfo) {
        let DependencyInfo {
            memory_barriers,
            buffer_memory_barriers,
            image_memory_barriers,
            _ne: _,
        } = dependency_info;

        let dependency_flags = ash::vk::DependencyFlags::BY_REGION;

        let fns = self.device.fns();

        if self.device.enabled_features().synchronization2 {
            let memory_barriers_vk: SmallVec<[_; 2]> = memory_barriers
                .into_iter()
                .map(|barrier| {
                    let &MemoryBarrier {
                        src_stages,
                        src_access,
                        dst_stages,
                        dst_access,
                        _ne: _,
                    } = barrier;

                    ash::vk::MemoryBarrier2 {
                        src_stage_mask: src_stages.into(),
                        src_access_mask: src_access.into(),
                        dst_stage_mask: dst_stages.into(),
                        dst_access_mask: dst_access.into(),
                        ..Default::default()
                    }
                })
                .collect();

            let buffer_memory_barriers_vk: SmallVec<[_; 8]> = buffer_memory_barriers
                .into_iter()
                .map(|barrier| {
                    let &BufferMemoryBarrier {
                        src_stages,
                        src_access,
                        dst_stages,
                        dst_access,
                        queue_family_transfer,
                        ref buffer,
                        ref range,
                        _ne: _,
                    } = barrier;

                    ash::vk::BufferMemoryBarrier2 {
                        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: queue_family_transfer
                            .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                transfer.source_index
                            }),
                        dst_queue_family_index: queue_family_transfer
                            .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                transfer.destination_index
                            }),
                        buffer: buffer.handle(),
                        offset: range.start,
                        size: range.end - range.start,
                        ..Default::default()
                    }
                })
                .collect();

            let image_memory_barriers_vk: SmallVec<[_; 8]> = image_memory_barriers
                .into_iter()
                .map(|barrier| {
                    let &ImageMemoryBarrier {
                        src_stages,
                        src_access,
                        dst_stages,
                        dst_access,
                        old_layout,
                        new_layout,
                        queue_family_transfer,
                        ref image,
                        ref subresource_range,
                        _ne: _,
                    } = barrier;

                    ash::vk::ImageMemoryBarrier2 {
                        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: queue_family_transfer
                            .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                transfer.source_index
                            }),
                        dst_queue_family_index: queue_family_transfer
                            .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                transfer.destination_index
                            }),
                        image: image.handle(),
                        subresource_range: subresource_range.clone().into(),
                        ..Default::default()
                    }
                })
                .collect();

            let dependency_info_vk = ash::vk::DependencyInfo {
                dependency_flags,
                memory_barrier_count: memory_barriers_vk.len() as u32,
                p_memory_barriers: memory_barriers_vk.as_ptr(),
                buffer_memory_barrier_count: buffer_memory_barriers_vk.len() as u32,
                p_buffer_memory_barriers: buffer_memory_barriers_vk.as_ptr(),
                image_memory_barrier_count: image_memory_barriers_vk.len() as u32,
                p_image_memory_barriers: image_memory_barriers_vk.as_ptr(),
                ..Default::default()
            };

            if self.device.api_version() >= Version::V1_3 {
                (fns.v1_3.cmd_set_event2)(self.handle, event.handle(), &dependency_info_vk);
            } else {
                debug_assert!(self.device.enabled_extensions().khr_synchronization2);
                (fns.khr_synchronization2.cmd_set_event2_khr)(
                    self.handle,
                    event.handle(),
                    &dependency_info_vk,
                );
            }
        } else {
            // The original function only takes a source stage mask; the rest of the info is
            // provided with `wait_events` instead. Therefore, we condense the source stages
            // here and ignore the rest.

            let mut stage_mask = ash::vk::PipelineStageFlags::empty();

            for barrier in memory_barriers {
                stage_mask |= barrier.src_stages.into();
            }

            for barrier in buffer_memory_barriers {
                stage_mask |= barrier.src_stages.into();
            }

            for barrier in image_memory_barriers {
                stage_mask |= barrier.src_stages.into();
            }

            if stage_mask.is_empty() {
                // "VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT is [...] equivalent to
                // VK_PIPELINE_STAGE_2_NONE in the first scope."
                stage_mask |= ash::vk::PipelineStageFlags::TOP_OF_PIPE;
            }

            (fns.v1_0.cmd_set_event)(self.handle, event.handle(), stage_mask);
        }
    }

    /// Calls `vkCmdWaitEvents` on the builder.
    pub unsafe fn wait_events<'a>(
        &mut self,
        events: impl IntoIterator<Item = (&'a Event, &'a DependencyInfo)>,
    ) {
        let fns = self.device.fns();

        if self.device.enabled_features().synchronization2 {
            struct PerDependencyInfo {
                memory_barriers_vk: SmallVec<[ash::vk::MemoryBarrier2; 2]>,
                buffer_memory_barriers_vk: SmallVec<[ash::vk::BufferMemoryBarrier2; 8]>,
                image_memory_barriers_vk: SmallVec<[ash::vk::ImageMemoryBarrier2; 8]>,
            }

            let mut events_vk: SmallVec<[_; 4]> = SmallVec::new();
            let mut dependency_infos_vk: SmallVec<[_; 4]> = SmallVec::new();
            let mut per_dependency_info_vk: SmallVec<[_; 4]> = SmallVec::new();

            for (event, dependency_info) in events {
                let DependencyInfo {
                    memory_barriers,
                    buffer_memory_barriers,
                    image_memory_barriers,
                    _ne: _,
                } = dependency_info;

                let dependency_flags = ash::vk::DependencyFlags::BY_REGION;

                let memory_barriers_vk: SmallVec<[_; 2]> = memory_barriers
                    .into_iter()
                    .map(|barrier| {
                        let &MemoryBarrier {
                            src_stages,
                            src_access,
                            dst_stages,
                            dst_access,
                            _ne: _,
                        } = barrier;

                        ash::vk::MemoryBarrier2 {
                            src_stage_mask: src_stages.into(),
                            src_access_mask: src_access.into(),
                            dst_stage_mask: dst_stages.into(),
                            dst_access_mask: dst_access.into(),
                            ..Default::default()
                        }
                    })
                    .collect();

                let buffer_memory_barriers_vk: SmallVec<[_; 8]> = buffer_memory_barriers
                    .into_iter()
                    .map(|barrier| {
                        let &BufferMemoryBarrier {
                            src_stages,
                            src_access,
                            dst_stages,
                            dst_access,
                            queue_family_transfer,
                            ref buffer,
                            ref range,
                            _ne: _,
                        } = barrier;

                        ash::vk::BufferMemoryBarrier2 {
                            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: queue_family_transfer
                                .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                    transfer.source_index
                                }),
                            dst_queue_family_index: queue_family_transfer
                                .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                    transfer.destination_index
                                }),
                            buffer: buffer.handle(),
                            offset: range.start,
                            size: range.end - range.start,
                            ..Default::default()
                        }
                    })
                    .collect();

                let image_memory_barriers_vk: SmallVec<[_; 8]> = image_memory_barriers
                    .into_iter()
                    .map(|barrier| {
                        let &ImageMemoryBarrier {
                            src_stages,
                            src_access,
                            dst_stages,
                            dst_access,
                            old_layout,
                            new_layout,
                            queue_family_transfer,
                            ref image,
                            ref subresource_range,
                            _ne: _,
                        } = barrier;

                        ash::vk::ImageMemoryBarrier2 {
                            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: queue_family_transfer
                                .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                    transfer.source_index
                                }),
                            dst_queue_family_index: queue_family_transfer
                                .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                    transfer.destination_index
                                }),
                            image: image.handle(),
                            subresource_range: subresource_range.clone().into(),
                            ..Default::default()
                        }
                    })
                    .collect();

                events_vk.push(event.handle());
                dependency_infos_vk.push(ash::vk::DependencyInfo {
                    dependency_flags,
                    memory_barrier_count: 0,
                    p_memory_barriers: ptr::null(),
                    buffer_memory_barrier_count: 0,
                    p_buffer_memory_barriers: ptr::null(),
                    image_memory_barrier_count: 0,
                    p_image_memory_barriers: ptr::null(),
                    ..Default::default()
                });
                per_dependency_info_vk.push(PerDependencyInfo {
                    memory_barriers_vk,
                    buffer_memory_barriers_vk,
                    image_memory_barriers_vk,
                });
            }

            for (
                dependency_info_vk,
                PerDependencyInfo {
                    memory_barriers_vk,
                    buffer_memory_barriers_vk,
                    image_memory_barriers_vk,
                },
            ) in (dependency_infos_vk.iter_mut()).zip(per_dependency_info_vk.iter_mut())
            {
                *dependency_info_vk = ash::vk::DependencyInfo {
                    memory_barrier_count: memory_barriers_vk.len() as u32,
                    p_memory_barriers: memory_barriers_vk.as_ptr(),
                    buffer_memory_barrier_count: buffer_memory_barriers_vk.len() as u32,
                    p_buffer_memory_barriers: buffer_memory_barriers_vk.as_ptr(),
                    image_memory_barrier_count: image_memory_barriers_vk.len() as u32,
                    p_image_memory_barriers: image_memory_barriers_vk.as_ptr(),
                    ..*dependency_info_vk
                }
            }

            if self.device.api_version() >= Version::V1_3 {
                (fns.v1_3.cmd_wait_events2)(
                    self.handle,
                    events_vk.len() as u32,
                    events_vk.as_ptr(),
                    dependency_infos_vk.as_ptr(),
                );
            } else {
                debug_assert!(self.device.enabled_extensions().khr_synchronization2);
                (fns.khr_synchronization2.cmd_wait_events2_khr)(
                    self.handle,
                    events_vk.len() as u32,
                    events_vk.as_ptr(),
                    dependency_infos_vk.as_ptr(),
                );
            }
        } else {
            // With the original function, you can only specify a single dependency info for all
            // events at once, rather than separately for each event. Therefore, to achieve the
            // same behaviour as the "2" function, we split it up into multiple Vulkan API calls,
            // one per event.

            for (event, dependency_info) in events {
                let events_vk = [event.handle()];

                let DependencyInfo {
                    memory_barriers,
                    buffer_memory_barriers,
                    image_memory_barriers,
                    _ne: _,
                } = dependency_info;

                let mut src_stage_mask = ash::vk::PipelineStageFlags::empty();
                let mut dst_stage_mask = ash::vk::PipelineStageFlags::empty();

                let memory_barriers_vk: SmallVec<[_; 2]> = memory_barriers
                    .into_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();

                        ash::vk::MemoryBarrier {
                            src_access_mask: src_access.into(),
                            dst_access_mask: dst_access.into(),
                            ..Default::default()
                        }
                    })
                    .collect();

                let buffer_memory_barriers_vk: SmallVec<[_; 8]> = buffer_memory_barriers
                    .into_iter()
                    .map(|barrier| {
                        let &BufferMemoryBarrier {
                            src_stages,
                            src_access,
                            dst_stages,
                            dst_access,
                            queue_family_transfer,
                            ref buffer,
                            ref range,
                            _ne: _,
                        } = barrier;

                        src_stage_mask |= src_stages.into();
                        dst_stage_mask |= dst_stages.into();

                        ash::vk::BufferMemoryBarrier {
                            src_access_mask: src_access.into(),
                            dst_access_mask: dst_access.into(),
                            src_queue_family_index: queue_family_transfer
                                .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                    transfer.source_index
                                }),
                            dst_queue_family_index: queue_family_transfer
                                .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                    transfer.destination_index
                                }),
                            buffer: buffer.handle(),
                            offset: range.start,
                            size: range.end - range.start,
                            ..Default::default()
                        }
                    })
                    .collect();

                let image_memory_barriers_vk: SmallVec<[_; 8]> = image_memory_barriers
                    .into_iter()
                    .map(|barrier| {
                        let &ImageMemoryBarrier {
                            src_stages,
                            src_access,
                            dst_stages,
                            dst_access,
                            old_layout,
                            new_layout,
                            queue_family_transfer,
                            ref image,
                            ref subresource_range,
                            _ne: _,
                        } = barrier;

                        src_stage_mask |= src_stages.into();
                        dst_stage_mask |= dst_stages.into();

                        ash::vk::ImageMemoryBarrier {
                            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: queue_family_transfer
                                .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                    transfer.source_index
                                }),
                            dst_queue_family_index: queue_family_transfer
                                .map_or(ash::vk::QUEUE_FAMILY_IGNORED, |transfer| {
                                    transfer.destination_index
                                }),
                            image: image.handle(),
                            subresource_range: subresource_range.clone().into(),
                            ..Default::default()
                        }
                    })
                    .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 |= ash::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 |= ash::vk::PipelineStageFlags::BOTTOM_OF_PIPE;
                }

                (fns.v1_0.cmd_wait_events)(
                    self.handle,
                    1,
                    events_vk.as_ptr(),
                    src_stage_mask,
                    dst_stage_mask,
                    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(),
                );
            }
        }
    }

    /// Calls `vkCmdResetEvent` on the builder.
    #[inline]
    pub unsafe fn reset_event(&mut self, event: &Event, stages: PipelineStages) {
        debug_assert!(!stages.host);
        debug_assert_ne!(stages, PipelineStages::empty());

        let fns = self.device.fns();

        if self.device.enabled_features().synchronization2 {
            if self.device.api_version() >= Version::V1_3 {
                (fns.v1_3.cmd_reset_event2)(self.handle, event.handle(), stages.into());
            } else {
                debug_assert!(self.device.enabled_extensions().khr_synchronization2);
                (fns.khr_synchronization2.cmd_reset_event2_khr)(
                    self.handle,
                    event.handle(),
                    stages.into(),
                );
            }
        } else {
            (fns.v1_0.cmd_reset_event)(self.handle, event.handle(), stages.into());
        }
    }

    // TODO: wait_event
}
