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}