phobos/command_buffer/
incomplete.rs

1//! Extra utilities for command buffers not tied to a domain
2
3use std::collections::hash_map::Entry;
4use std::collections::HashMap;
5use std::marker::PhantomData;
6use std::sync::{Arc, MutexGuard};
7
8use anyhow::{anyhow, ensure, Result};
9use ash::vk;
10
11use crate::{
12    Allocator, BufferView, DebugMessenger, DescriptorCache, DescriptorSet, Device, ImageView,
13    IncompleteCmdBuffer, PhysicalResourceBindings, PipelineCache, PipelineStage, Sampler,
14    VirtualResource,
15};
16use crate::command_buffer::{CommandBuffer, IncompleteCommandBuffer};
17use crate::command_buffer::state::{RenderingAttachmentInfo, RenderingInfo};
18use crate::core::queue::Queue;
19use crate::descriptor::builder::DescriptorSetBuilder;
20use crate::pipeline::create_info::PipelineRenderingInfo;
21use crate::query_pool::{QueryPool, ScopedQuery, TimestampQuery};
22use crate::raytracing::acceleration_structure::AccelerationStructure;
23use crate::sync::domain::ExecutionDomain;
24
25impl<'q, D: ExecutionDomain, A: Allocator> IncompleteCmdBuffer<'q, A>
26for IncompleteCommandBuffer<'q, D, A>
27{
28    type Domain = D;
29
30    /// Create a new command buffer ready for recording.
31    /// This will hold the lock on the queue until [`IncompleteCommandBuffer::finish()`] is called.
32    fn new(
33        device: Device,
34        queue_lock: MutexGuard<'q, Queue>,
35        handle: vk::CommandBuffer,
36        flags: vk::CommandBufferUsageFlags,
37        pipelines: PipelineCache<A>,
38        descriptors: DescriptorCache,
39    ) -> Result<Self> {
40        unsafe {
41            let begin_info = vk::CommandBufferBeginInfo {
42                s_type: vk::StructureType::COMMAND_BUFFER_BEGIN_INFO,
43                p_next: std::ptr::null(),
44                flags,
45                p_inheritance_info: std::ptr::null(),
46            };
47            // SAFETY:
48            // * A valid VkDevice was passed in
49            // * The command buffer passed in may not be NULL
50            // * The begin_info structure is valid.
51            device.begin_command_buffer(handle, &begin_info)?;
52        };
53        Ok(IncompleteCommandBuffer {
54            device,
55            handle,
56            timestamp_valid_bits: queue_lock.family_properties().timestamp_valid_bits,
57            queue_lock,
58            current_pipeline_layout: vk::PipelineLayout::null(),
59            current_set_layouts: vec![],
60            current_bindpoint: vk::PipelineBindPoint::default(),
61            current_rendering_state: None,
62            current_render_area: Default::default(),
63            current_descriptor_sets: None,
64            descriptor_state_needs_update: false,
65            current_sbt_regions: None,
66            descriptor_cache: descriptors,
67            pipeline_cache: pipelines,
68            _domain: PhantomData,
69        })
70    }
71
72    /// Finish recording this command buffer. After calling this, no more commands can be
73    /// recorded to this object and it should be submitted. This also releases the lock on the queue, so
74    /// call this as soon as you are done recording to minimize contention of that lock.
75    /// # Example
76    /// ```
77    /// # use phobos::*;
78    /// # use anyhow::Result;
79    /// # use phobos::sync::domain::{ExecutionDomain, Graphics};
80    /// fn finish_command_buffer<D: ExecutionDomain>(cmd: IncompleteCommandBuffer<D>) -> Result<CommandBuffer<D>> {
81    ///     // Releases the lock on a queue associated with the domain `D`, allowing other command
82    ///     // buffers to start recording on this domain.
83    ///     let cmd = cmd.finish()?;
84    ///     Ok(cmd)
85    /// }
86    /// ```
87    fn finish(self) -> Result<CommandBuffer<D>> {
88        // SAFETY:
89        // * `self` is valid, so `device` and `self.handle` are valid.
90        // * `self` is valid, so this command buffer is in the recording state (see `new()`).
91        unsafe { self.device.end_command_buffer(self.handle)? }
92        Ok(CommandBuffer {
93            handle: self.handle,
94            _domain: PhantomData,
95        })
96    }
97}
98
99impl<D: ExecutionDomain, A: Allocator> IncompleteCommandBuffer<'_, D, A> {
100    /// Bind a descriptor set to the command buffer.
101    /// # Errors
102    /// - Fails if no pipeline was bound.
103    pub(super) fn bind_descriptor_set(&self, index: u32, set: &DescriptorSet) -> Result<()> {
104        ensure!(
105            self.current_pipeline_layout != vk::PipelineLayout::null(),
106            "cannot bind descriptor set at index {index} without binding a pipeline first."
107        );
108        unsafe {
109            // SAFETY:
110            // * self is valid, so self.handle is valid.
111            // * We just verified using the ensure statement above that a pipeline is bound.
112            // * We assume index is a valid descriptor set index, otherwise we get a validation layer error
113            // * Caller passed in a valid descriptor set object.
114            self.device.cmd_bind_descriptor_sets(
115                self.handle,
116                self.current_bindpoint,
117                self.current_pipeline_layout,
118                index,
119                std::slice::from_ref(&set.handle),
120                &[],
121            );
122        }
123        Ok(())
124    }
125
126    /// Modify the descriptor set state at a given set binding.
127    /// # Errors
128    /// * Fails if the supplied callback fails.
129    pub(super) fn modify_descriptor_set(
130        &mut self,
131        set: u32,
132        f: impl FnOnce(&mut DescriptorSetBuilder) -> Result<()>,
133    ) -> Result<()> {
134        if self.current_descriptor_sets.is_none() {
135            self.current_descriptor_sets = Some(HashMap::new());
136        }
137
138        match self.current_descriptor_sets.as_mut().unwrap().entry(set) {
139            Entry::Occupied(mut entry) => {
140                f(entry.get_mut())?;
141            }
142            Entry::Vacant(entry) => {
143                let mut builder = DescriptorSetBuilder::new();
144                f(&mut builder)?;
145                entry.insert(builder);
146            }
147        };
148        self.descriptor_state_needs_update = true;
149        Ok(())
150    }
151
152    /// If there are unwritten descriptor sets, update the entire descriptor set state by binding a new set.
153    /// # Errors
154    /// * Fails if the descriptor set cache lookup fails.
155    /// * Fails if binding the descriptor set fails.
156    pub(super) fn ensure_descriptor_state(mut self) -> Result<Self> {
157        // No need to do anything
158        if !self.descriptor_state_needs_update {
159            return Ok(self);
160        }
161
162        let cache = self.descriptor_cache.clone();
163        for (index, builder) in self.current_descriptor_sets.take().unwrap() {
164            let mut info = builder.build();
165            info.layout = *self.current_set_layouts.get(index as usize).unwrap();
166            cache.with_descriptor_set(info, |set| {
167                self.bind_descriptor_set(index, set)?;
168                Ok(())
169            })?;
170        }
171
172        // We updated all our descriptor sets, were good now.
173        self.descriptor_state_needs_update = false;
174        Ok(self)
175    }
176
177    /// Binds the given pipeline to the given bindpoint.
178    /// # Errors
179    /// None
180    pub(super) fn bind_pipeline_impl(
181        &mut self,
182        handle: vk::Pipeline,
183        layout: vk::PipelineLayout,
184        set_layouts: Vec<vk::DescriptorSetLayout>,
185        bind_point: vk::PipelineBindPoint,
186    ) -> Result<()> {
187        unsafe {
188            // SAFETY:
189            // * `self` is valid, so `self.device` and `self.handle` are valid vulkan objects.
190            // * `pipeline.handle` is a valid entry from the pipeline cache, so it is a valid compute pipeline.
191            self.device
192                .cmd_bind_pipeline(self.handle, bind_point, handle);
193        }
194        self.current_bindpoint = bind_point;
195        self.current_pipeline_layout = layout;
196        self.current_set_layouts = set_layouts.clone();
197        Ok(())
198    }
199
200    /// Clear descriptor set state. Calling this will reset the current descriptor state to nothing being bound.
201    /// It does not explicitly unbind descriptor sets, but the next `draw()` or `dispatch()` call will
202    /// reflect this change. This function is not extremely useful at the moment.
203    /// # Example
204    /// ```
205    /// # use phobos::sync::domain::ExecutionDomain;
206    /// # use phobos::{BufferView, IncompleteCommandBuffer};
207    /// # use anyhow::Result;
208    /// fn use_descriptor_forget<'q, D: ExecutionDomain>(cmd: IncompleteCommandBuffer<'q, D>, buffer: &BufferView, other_buffer: &BufferView) -> Result<IncompleteCommandBuffer<'q, D>> {
209    ///     cmd.bind_uniform_buffer(0, 0, buffer)?
210    ///         // Forget the previous binding
211    ///        .forget_descriptor_state()
212    ///         // And overwrite it with a new one.
213    ///        .bind_uniform_buffer(0, 0, other_buffer)
214    /// }
215    /// ```
216    pub fn forget_descriptor_state(mut self) -> Self {
217        self.current_descriptor_sets = None;
218        self.descriptor_state_needs_update = true;
219        self
220    }
221
222    /// Binds a new descriptor with descriptor type [`vk::DescriptorType::COMBINED_IMAGE_SAMPLER`]. The image bound to this is
223    /// the image obtained by resolving the input resource from the given resource bindings. The sampler bound to this
224    /// is the one given. This binding is not actually flushed to the command buffer until the next draw or dispatch call.
225    ///
226    /// Expects the image to be in [`vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL`]
227    /// # Errors
228    /// * Fails if the virtual resource has no physical binding associated to it.
229    /// # Example
230    /// ```
231    /// # use anyhow::Result;
232    /// # use phobos::sync::domain::ExecutionDomain;
233    /// # use phobos::*;
234    /// fn use_resolve_and_bind<'q, D: ExecutionDomain>(cmd: IncompleteCommandBuffer<'q, D>, image: &ImageView, sampler: &Sampler) -> Result<IncompleteCommandBuffer<'q, D>> {
235    ///     let resource = image!("image");
236    ///     let mut bindings = PhysicalResourceBindings::new();
237    ///     bindings.bind_image("image", image);
238    ///
239    ///     cmd.resolve_and_bind_sampled_image(0, 0, &resource, sampler, &bindings)
240    /// }
241    /// ```
242    pub fn resolve_and_bind_sampled_image(
243        mut self,
244        set: u32,
245        binding: u32,
246        resource: &VirtualResource,
247        sampler: &Sampler,
248        bindings: &PhysicalResourceBindings,
249    ) -> Result<Self> {
250        self.modify_descriptor_set(set, |builder| {
251            builder.resolve_and_bind_sampled_image(binding, resource, sampler, bindings)
252        })?;
253        Ok(self)
254    }
255
256    /// Binds a new descriptor with type [`vk::DescriptorType::COMBINED_IMAGE_SAMPLER`].
257    /// This binding is not actually flushed to the command buffer until the next draw or dispatch call.
258    ///
259    /// Expects the image to be in [`vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL`]
260    /// # Errors
261    /// None
262    /// # Example
263    /// ```
264    /// # use anyhow::Result;
265    /// # use phobos::sync::domain::ExecutionDomain;
266    /// # use phobos::*;
267    /// fn use_bind_sampled_image<'q, D: ExecutionDomain + GfxSupport>(cmd: IncompleteCommandBuffer<'q, D>, image: &ImageView, sampler: &Sampler) -> Result<IncompleteCommandBuffer<'q, D>> {
268    ///     cmd.bind_sampled_image(0, 0, image, sampler)?
269    ///         // This drawcall will flush the descriptor state and bind proper descriptor sets.
270    ///        .draw(6, 1, 0, 0)
271    /// }
272    /// ```
273    pub fn bind_sampled_image(
274        mut self,
275        set: u32,
276        binding: u32,
277        image: &ImageView,
278        sampler: &Sampler,
279    ) -> Result<Self> {
280        self.modify_descriptor_set(set, |builder| {
281            builder.bind_sampled_image(binding, image, sampler);
282            Ok(())
283        })?;
284        Ok(self)
285    }
286
287    /// Binds a new descriptor with type [`vk::DescriptorType::UNIFORM_BUFFER`].
288    /// This binding is not actually flushed to the command buffer until the next draw or dispatch call.
289    /// # Errors
290    /// None
291    /// # Example
292    /// ```
293    /// # use anyhow::Result;
294    /// # use phobos::sync::domain::ExecutionDomain;
295    /// # use phobos::*;
296    /// fn use_bind_uniform_buffer<'q, D: ExecutionDomain + GfxSupport>(cmd: IncompleteCommandBuffer<'q, D>, buffer: &BufferView) -> Result<IncompleteCommandBuffer<'q, D>> {
297    ///     cmd.bind_uniform_buffer(0, 0, buffer)?
298    ///         // This drawcall will flush the descriptor state and bind proper descriptor sets.
299    ///        .draw(6, 1, 0, 0)
300    /// }
301    /// ```
302    pub fn bind_uniform_buffer(
303        mut self,
304        set: u32,
305        binding: u32,
306        buffer: &BufferView,
307    ) -> Result<Self> {
308        self.modify_descriptor_set(set, |builder| {
309            builder.bind_uniform_buffer(binding, buffer);
310            Ok(())
311        })?;
312        Ok(self)
313    }
314
315    /// Binds a new descriptor with type [`vk::DescriptorType::STORAGE_BUFFER`].
316    /// This binding is not actually flushed to the command buffer until the next draw or dispatch call.
317    /// # Errors
318    /// None
319    /// # Example
320    /// ```
321    /// # use anyhow::Result;
322    /// # use phobos::sync::domain::ExecutionDomain;
323    /// # use phobos::*;
324    /// fn use_bind_storage_buffer<'q, D: ExecutionDomain + GfxSupport>(cmd: IncompleteCommandBuffer<'q, D>, buffer: &BufferView) -> Result<IncompleteCommandBuffer<'q, D>> {
325    ///     cmd.bind_storage_buffer(0, 0, buffer)?
326    ///         // This drawcall will flush the descriptor state and bind proper descriptor sets.
327    ///        .draw(6, 1, 0, 0)
328    /// }
329    /// ```
330    pub fn bind_storage_buffer(
331        mut self,
332        set: u32,
333        binding: u32,
334        buffer: &BufferView,
335    ) -> Result<Self> {
336        self.modify_descriptor_set(set, |builder| {
337            builder.bind_storage_buffer(binding, buffer);
338            Ok(())
339        })?;
340        Ok(self)
341    }
342
343    /// Binds a new descriptor with type [`vk::DescriptorType::STORAGE_IMAGE`].
344    /// This binding is not actually flushed to the command buffer until the next draw or dispatch call.
345    ///
346    /// Expects the image to be in [`vk::ImageLayout::GENERAL`]
347    /// # Errors
348    /// None
349    /// # Example
350    /// ```
351    /// # use anyhow::Result;
352    /// # use phobos::sync::domain::ExecutionDomain;
353    /// # use phobos::*;
354    /// fn use_bind_storage_image<'q, D: ExecutionDomain + GfxSupport>(cmd: IncompleteCommandBuffer<'q, D>, image: &ImageView) -> Result<IncompleteCommandBuffer<'q, D>> {
355    ///     cmd.bind_storage_image(0, 0, image)?
356    ///         // This drawcall will flush the descriptor state and bind proper descriptor sets.
357    ///        .draw(6, 1, 0, 0)
358    /// }
359    /// ```
360    pub fn bind_storage_image(mut self, set: u32, binding: u32, image: &ImageView) -> Result<Self> {
361        self.modify_descriptor_set(set, |builder| {
362            builder.bind_storage_image(binding, image);
363            Ok(())
364        })?;
365        Ok(self)
366    }
367
368    /// Binds a new descriptor with descriptor type [`vk::DescriptorType::STORAGE_IMAGE`]. The image bound to this is
369    /// the image obtained by resolving the input resource from the given resource bindings.
370    /// This binding is not actually flushed to the command buffer until the next draw or dispatch call.
371    ///
372    /// Expects the image to be in [`vk::ImageLayout::GENERAL`]
373    /// # Errors
374    /// * Fails if the virtual resource has no physical binding associated to it.
375    /// # Example
376    /// ```
377    /// # use anyhow::Result;
378    /// # use phobos::sync::domain::ExecutionDomain;
379    /// # use phobos::*;
380    /// fn use_resolve_and_bind<'q, D: ExecutionDomain>(cmd: IncompleteCommandBuffer<'q, D>, image: &ImageView) -> Result<IncompleteCommandBuffer<'q, D>> {
381    ///     let resource = VirtualResource::image("image");
382    ///     let mut bindings = PhysicalResourceBindings::new();
383    ///     bindings.bind_image("image", image);
384    ///
385    ///     cmd.resolve_and_bind_storage_image(0, 0, &resource, &bindings)
386    /// }
387    /// ```
388    pub fn resolve_and_bind_storage_image(
389        mut self,
390        set: u32,
391        binding: u32,
392        resource: &VirtualResource,
393        bindings: &PhysicalResourceBindings,
394    ) -> Result<Self> {
395        self.modify_descriptor_set(set, |builder| {
396            builder.resolve_and_bind_storage_image(binding, resource, bindings)
397        })?;
398        Ok(self)
399    }
400
401    /// Binds a new descriptor with descriptor type [`vk::DescriptorType::ACCELERATION_STRUCTURE_KHR`]. The
402    /// `VK_KHR_acceleration_structure` extension must be enabled for this (use [`AppBuilder::raytracing()`](crate::AppBuilder::raytracing() to enable).
403    /// # Example
404    /// ```
405    /// # use anyhow::Result;
406    /// # use phobos::sync::domain::ExecutionDomain;
407    /// # use phobos::*;
408    /// fn use_bind_acceleration_structure<'q, D: ExecutionDomain + GfxSupport>(cmd: IncompleteCommandBuffer<'q, D>, accel: &AccelerationStructure) -> Result<IncompleteCommandBuffer<'q, D>> {
409    ///     cmd.use_bind_acceleration_structure(0, 0, accel)?
410    ///         // This call will flush the descriptor state and bind proper descriptor sets.
411    ///        .trace_rays(1920, 1080, 1)
412    /// }
413    /// ```
414    pub fn bind_acceleration_structure(
415        mut self,
416        set: u32,
417        binding: u32,
418        accel: &AccelerationStructure,
419    ) -> Result<Self> {
420        self.modify_descriptor_set(set, |builder| {
421            builder.bind_acceleration_structure(binding, accel);
422            Ok(())
423        })?;
424        Ok(self)
425    }
426
427    /// Transitions an image layout manually. For attachment layouts and other
428    /// resources used in the pass graph, this can be done automatically.
429    pub fn transition_image(
430        self,
431        image: &ImageView,
432        src_stage: PipelineStage,
433        dst_stage: PipelineStage,
434        from: vk::ImageLayout,
435        to: vk::ImageLayout,
436        src_access: vk::AccessFlags2,
437        dst_access: vk::AccessFlags2,
438    ) -> Self {
439        let barrier = vk::ImageMemoryBarrier2 {
440            s_type: vk::StructureType::IMAGE_MEMORY_BARRIER_2,
441            p_next: std::ptr::null(),
442            src_stage_mask: src_stage,
443            src_access_mask: src_access,
444            dst_stage_mask: dst_stage,
445            dst_access_mask: dst_access,
446            old_layout: from,
447            new_layout: to,
448            src_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
449            dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED,
450            // SAFETY: A valid image view object has a valid `VkImage` handle.
451            image: unsafe { image.image() },
452            subresource_range: image.subresource_range(),
453        };
454        let dependency = vk::DependencyInfo {
455            s_type: vk::StructureType::DEPENDENCY_INFO,
456            p_next: std::ptr::null(),
457            dependency_flags: vk::DependencyFlags::BY_REGION,
458            memory_barrier_count: 0,
459            p_memory_barriers: std::ptr::null(),
460            buffer_memory_barrier_count: 0,
461            p_buffer_memory_barriers: std::ptr::null(),
462            image_memory_barrier_count: 1,
463            p_image_memory_barriers: &barrier,
464        };
465        self.pipeline_barrier(&dependency)
466    }
467
468    /// Insert a global memory barrier. If you want to create a barrier for a buffer, prefer using this as every driver
469    /// implements buffer barriers as global memory barriers anyway.
470    /// Uses [`vkCmdPipelineBarrier2`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdPipelineBarrier2KHR.html) directly.
471    pub fn memory_barrier(
472        self,
473        src_stage: PipelineStage,
474        src_access: vk::AccessFlags2,
475        dst_stage: PipelineStage,
476        dst_access: vk::AccessFlags2,
477    ) -> Self {
478        let barrier = vk::MemoryBarrier2 {
479            s_type: vk::StructureType::MEMORY_BARRIER_2,
480            p_next: std::ptr::null(),
481            src_stage_mask: src_stage,
482            src_access_mask: src_access,
483            dst_stage_mask: dst_stage,
484            dst_access_mask: dst_access,
485        };
486
487        let dependency = vk::DependencyInfo {
488            s_type: vk::StructureType::DEPENDENCY_INFO,
489            p_next: std::ptr::null(),
490            dependency_flags: vk::DependencyFlags::BY_REGION,
491            memory_barrier_count: 1,
492            p_memory_barriers: &barrier,
493            buffer_memory_barrier_count: 0,
494            p_buffer_memory_barriers: std::ptr::null(),
495            image_memory_barrier_count: 0,
496            p_image_memory_barriers: std::ptr::null(),
497        };
498
499        self.pipeline_barrier(&dependency)
500    }
501
502    /// The direct equivalent of a raw [`vkCmdPipelineBarrier2`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdPipelineBarrier2KHR.html) call.
503    /// Before calling this, make sure there is not an automatic way to insert this barrier, for example
504    /// using the pass graph or using [`IncompleteCommandBuffer::transition_image()`].
505    pub fn pipeline_barrier(self, dependency: &vk::DependencyInfo) -> Self {
506        unsafe {
507            self.device.cmd_pipeline_barrier2(self.handle, dependency);
508        }
509        self
510    }
511
512    /// Upload a single value of push constants. These are small packets of data stored inside the command buffer, so their state is tracked while recording and executing.
513    /// Direct translation of [`vkCmdPushConstants`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdPushConstants.html).
514    /// # Example
515    /// ```
516    /// # use phobos::*;
517    /// # use phobos::sync::domain::ExecutionDomain;
518    /// fn use_push_constant<D: ExecutionDomain>(cmd: IncompleteCommandBuffer<D>) -> IncompleteCommandBuffer<D> {
519    ///     // Assumes a pipeline is bound, and that this pipeline has a vertex shader with the specified push constant range.
520    ///     let data: f32 = 1.0;
521    ///     cmd.push_constant(vk::ShaderStageFlags::VERTEX, 0, &data)
522    /// }
523    pub fn push_constant<T: Copy + Sized>(
524        self,
525        stage: vk::ShaderStageFlags,
526        offset: u32,
527        data: &T,
528    ) -> Self {
529        self.push_constants(stage, offset, std::slice::from_ref(data))
530    }
531
532    /// Upload push constants. These are small packets of data stored inside the command buffer, so their state is tracked while recording and executing.
533    /// Direct translation of [`vkCmdPushConstants`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdPushConstants.html).
534    /// # Example
535    /// ```
536    /// # use phobos::*;
537    /// # use phobos::sync::domain::ExecutionDomain;
538    /// fn use_push_constants<D: ExecutionDomain>(cmd: IncompleteCommandBuffer<D>) -> IncompleteCommandBuffer<D> {
539    ///     // Assumes a pipeline is bound, and that this pipeline has a vertex shader with the specified push constant range.
540    ///     let data: [f32; 2] = [64.0, 32.0];
541    ///     cmd.push_constants(vk::ShaderStageFlags::VERTEX, 0, &data)
542    /// }
543    /// ```
544    pub fn push_constants<T: Copy + Sized>(
545        self,
546        stage: vk::ShaderStageFlags,
547        offset: u32,
548        data: &[T],
549    ) -> Self {
550        // TODO: Validate push constant ranges with current pipeline layout to prevent crashes.
551        unsafe {
552            // SAFETY: every data structure can be aligned to a byte slice.
553            let (_, data, _) = data.align_to::<u8>();
554            // SAFETY: self is valid, everything else is up to validation layers.
555            self.device.cmd_push_constants(
556                self.handle,
557                self.current_pipeline_layout,
558                stage,
559                offset,
560                data,
561            );
562        }
563        self
564    }
565
566    /// Begin a scoped query. Not all query types are scoped, so the query type must implement
567    /// [`ScopedQuery`].
568    pub fn begin_query<Q: ScopedQuery>(self, query_pool: &QueryPool<Q>, index: u32) -> Self {
569        unsafe {
570            self.device.cmd_begin_query(
571                self.handle,
572                query_pool.handle(),
573                index,
574                vk::QueryControlFlags::default(),
575            );
576        }
577        self
578    }
579
580    /// End a scoped query. This query must be started with [`Self::begin_query()`] first.
581    pub fn end_query<Q: ScopedQuery>(self, query_pool: &QueryPool<Q>, index: u32) -> Self {
582        unsafe {
583            self.device
584                .cmd_end_query(self.handle, query_pool.handle(), index);
585        }
586        self
587    }
588
589    /// Write a timestamp to the next entry in a query pool.
590    /// # Errors
591    /// * Fails if the query pool is out of entries.
592    pub fn write_timestamp(
593        self,
594        query_pool: &mut QueryPool<TimestampQuery>,
595        stage: PipelineStage,
596    ) -> Result<Self> {
597        let index = query_pool
598            .next()
599            .ok_or_else(|| anyhow!("Query pool capacity exceeded"))?;
600        query_pool.write_timestamp(self.timestamp_valid_bits, self.handle, stage, index);
601        Ok(self)
602    }
603
604    /// Begins a dynamic renderpass. This must be called before binding any pipelines.
605    pub(crate) fn begin_rendering(mut self, info: &RenderingInfo) -> Self {
606        let map_attachment = |attachment: &RenderingAttachmentInfo| vk::RenderingAttachmentInfo {
607            s_type: vk::StructureType::RENDERING_ATTACHMENT_INFO,
608            p_next: std::ptr::null(),
609            // SAFETY: A valid RenderingAttachmentInfo always stores a valid image view
610            image_view: unsafe { attachment.image_view.handle() },
611            image_layout: attachment.image_layout,
612            resolve_mode: attachment
613                .resolve_mode
614                .unwrap_or(vk::ResolveModeFlagsKHR::NONE),
615            resolve_image_view: match &attachment.resolve_image_view {
616                // SAFETY: A valid RenderingAttachmentInfo always stores a valid image view
617                Some(view) => unsafe { view.handle() },
618                None => vk::ImageView::null(),
619            },
620            resolve_image_layout: attachment
621                .resolve_image_layout
622                .unwrap_or(vk::ImageLayout::UNDEFINED),
623            load_op: attachment.load_op,
624            store_op: attachment.store_op,
625            clear_value: attachment.clear_value,
626        };
627
628        let color_attachments = info
629            .color_attachments
630            .iter()
631            .map(map_attachment)
632            .collect::<Vec<_>>();
633        let depth_attachment = info.depth_attachment.as_ref().map(map_attachment);
634        let stencil_attachment = info.stencil_attachment.as_ref().map(map_attachment);
635        let vk_info = vk::RenderingInfo {
636            s_type: vk::StructureType::RENDERING_INFO,
637            p_next: std::ptr::null(),
638            flags: info.flags,
639            render_area: info.render_area,
640            layer_count: info.layer_count,
641            view_mask: info.view_mask,
642            color_attachment_count: color_attachments.len() as u32,
643            p_color_attachments: color_attachments.as_ptr(),
644            p_depth_attachment: match &depth_attachment {
645                Some(attachment) => attachment,
646                None => std::ptr::null(),
647            },
648            p_stencil_attachment: match &stencil_attachment {
649                Some(attachment) => attachment,
650                None => std::ptr::null(),
651            },
652        };
653
654        unsafe {
655            // SAFETY: self is valid, vk_info is valid.
656            self.device.cmd_begin_rendering(self.handle, &vk_info);
657        }
658
659        self.current_rendering_state = Some(PipelineRenderingInfo {
660            view_mask: info.view_mask,
661            color_formats: info
662                .color_attachments
663                .iter()
664                .map(|attachment| attachment.image_view.format())
665                .collect(),
666            depth_format: info
667                .depth_attachment
668                .as_ref()
669                .map(|attachment| attachment.image_view.format()),
670            stencil_format: info
671                .stencil_attachment
672                .as_ref()
673                .map(|attachment| attachment.image_view.format()),
674        });
675        self.current_render_area = info.render_area;
676
677        self
678    }
679
680    /// Ends a dynamic renderpass.
681    pub(crate) fn end_rendering(mut self) -> Self {
682        unsafe {
683            // Safety: self is valid, the caller must ensure begin_rendering() was called first.
684            self.device.cmd_end_rendering(self.handle);
685        }
686        self.current_rendering_state = None;
687        self.current_render_area = vk::Rect2D::default();
688
689        self
690    }
691
692    /// Start a label region.
693    #[cfg(feature = "debug-markers")]
694    pub(crate) fn begin_label(
695        self,
696        label: vk::DebugUtilsLabelEXT,
697        debug: &Arc<DebugMessenger>,
698    ) -> Self {
699        unsafe {
700            debug.cmd_begin_debug_utils_label(self.handle, &label);
701        }
702        self
703    }
704
705    /// End a label region.
706    #[cfg(feature = "debug-markers")]
707    pub(crate) fn end_label(self, debug: &Arc<DebugMessenger>) -> Self {
708        unsafe {
709            debug.cmd_end_debug_utils_label(self.handle);
710        }
711        self
712    }
713
714    /// Get unsafe access to the underlying `VkCommandBuffer` handle.
715    /// # Safety
716    /// Any vulkan calls that mutate the command buffer's state may put the system in an undefined state.
717    pub unsafe fn handle(&self) -> vk::CommandBuffer {
718        self.handle
719    }
720}