Skip to main content

vk_graph/cmd/
mod.rs

1//! Strongly-typed [`Graph`] commands.
2//!
3//! ## Lifecycle
4//!
5//! Commands follow a builder-style chain:
6//!
7//! 1. [`Graph::begin_cmd`] opens a [`Command`].
8//! 2. Declare resource accesses with [`Command::resource_access`] or bind a shader pipeline
9//!    with [`Command::bind_pipeline`], returning a [`PipelineCommand`].
10//! 3. With a pipeline, declare shader bindings with [`PipelineCommand::shader_resource_access`].
11//! 4. Record work with [`record_cmd`](Command::record_cmd) — available on both
12//!    [`Command`] and [`PipelineCommand`].
13//! 5. The command auto-closes when dropped or when [`Graph::finalize`] is called.
14//!
15//! A single command can call `record_cmd` multiple times — each call creates a separate
16//! "execution" within that command. Executions within a command stay in the specified
17//! order, but the graph system may re-order entire commands or merge them during
18//! submission for optimal scheduling.
19
20mod cmd_ref;
21mod compute;
22mod graphics;
23mod pipeline;
24mod ray_tracing;
25
26pub use {
27    self::{
28        cmd_ref::{
29            BuildAccelerationStructureIndirectInfo, BuildAccelerationStructureInfo, CommandRef,
30            UpdateAccelerationStructureIndirectInfo, UpdateAccelerationStructureInfo,
31        },
32        compute::ComputeCommandRef,
33        graphics::{ClearColorValue, GraphicsCommandRef},
34        pipeline::{Pipeline, PipelineCommand},
35        ray_tracing::RayTracingCommandRef,
36    },
37    super::{LoadOp, StoreOp},
38};
39
40use {
41    super::{
42        AccelerationStructureLeaseNode, AccelerationStructureNode, AnyAccelerationStructureNode,
43        AnyBufferNode, AnyImageNode, AnyResource, BufferLeaseNode, BufferNode, CommandData,
44        CommandFunction, Execution, Graph, ImageLeaseNode, ImageNode, Node, Resource,
45        SwapchainImageNode,
46    },
47    crate::{
48        NodeIndex,
49        driver::{buffer::BufferSubresourceRange, image::ImageViewInfo},
50        stream::{AccelerationStructureArg, BufferArg, ImageArg},
51    },
52    ash::vk,
53    std::{ops::Range, sync::Arc},
54    vk_sync::AccessType,
55};
56
57/// Alias for the index of a framebuffer attachment.
58pub(crate) type AttachmentIndex = u32;
59
60/// Alias for the binding index of a shader descriptor.
61pub(crate) type BindingIndex = u32;
62
63/// Alias for the binding offset of a shader descriptor array element.
64pub(crate) type BindingOffset = u32;
65
66/// Alias for the descriptor set index of a shader descriptor.
67pub(crate) type DescriptorSetIndex = u32;
68
69/// A general-purpose Vulkan command which may contain acceleration structure operations, transfers,
70/// or shader pipelines.
71///
72/// There are four main uses of a [`Command`]:
73///
74/// 1. Bind resources ([`Self::bind_resource`])
75/// 1. Declare resource accesses ([`Self::resource_access`])
76/// 1. Record general-purpose command buffers or acceleration structure operations
77///    ([`Self::record_cmd`])
78/// 1. Bind shader pipelines ([`Self::bind_pipeline`])
79///
80/// When bound, a shader pipeline consumes the `Command` and returns a [`PipelineCommand`] which
81/// provides command recording functions specific to each pipeline type.
82pub struct Command<'a> {
83    pub(super) cmd_idx: usize,
84    pub(super) exec_idx: usize,
85    pub(super) graph: &'a mut Graph,
86}
87
88#[allow(private_bounds)]
89impl<'a> Command<'a> {
90    pub(super) fn new(graph: &'a mut Graph) -> Self {
91        let cmd_idx = graph.cmds.len();
92        graph.cmds.push(CommandData {
93            execs: vec![Default::default()], // We start off with a default execution!
94            #[cfg(debug_assertions)]
95            name: None,
96            stream_scope_id: None,
97        });
98
99        Self {
100            cmd_idx,
101            exec_idx: 0,
102            graph,
103        }
104    }
105
106    fn cmd(&self) -> &CommandData {
107        &self.graph.cmds[self.cmd_idx]
108    }
109
110    fn cmd_mut(&mut self) -> &mut CommandData {
111        &mut self.graph.cmds[self.cmd_idx]
112    }
113
114    /// Binds a Vulkan buffer, image, or acceleration structure resource to the graph associated
115    /// with this command.
116    ///
117    /// Bound nodes may be used in commands for pipeline and shader operations.
118    pub fn bind_resource<R>(&mut self, resource: R) -> R::Node
119    where
120        R: Resource,
121    {
122        self.graph.bind_resource(resource)
123    }
124
125    /// Binds a shader pipeline to the current command, allowing for strongly typed access to the
126    /// related functions.
127    ///
128    /// | `P` | `P::Command` |
129    /// | --- | --- |
130    /// | [`ComputePipeline`](crate::driver::compute::ComputePipeline) | [`PipelineCommand<'_, ComputePipeline>`] |
131    /// | [`GraphicsPipeline`](crate::driver::graphics::GraphicsPipeline) | [`PipelineCommand<'_, GraphicsPipeline>`] |
132    /// | [`RayTracingPipeline`](crate::driver::ray_tracing::RayTracingPipeline) | [`PipelineCommand<'_, RayTracingPipeline>`] |
133    pub fn bind_pipeline<P>(self, pipeline: P) -> P::Command
134    where
135        P: Pipeline<'a>,
136    {
137        pipeline.bind_cmd(self)
138    }
139
140    /// Sets a debugging name, but only in debug builds.
141    pub fn debug_name(mut self, name: impl Into<String>) -> Self {
142        self.set_debug_name(name);
143        self
144    }
145
146    /// Finalize the recording of this command and return to the `Graph` where you may record
147    /// additional commands.
148    pub fn end_cmd(self) -> &'a mut Graph {
149        // If nothing was done in this command we can just ignore it.
150        if self.exec_idx == 0 {
151            self.graph.cmds.pop();
152        }
153
154        self.graph
155    }
156
157    fn push_exec(&mut self, func: impl FnOnce(CommandRef) + Send + 'static) {
158        let cmd = self.cmd_mut();
159        let exec = {
160            let last_exec = cmd.expect_last_exec_mut();
161            last_exec.func = Some(CommandFunction::Once(Box::new(func)));
162
163            Execution {
164                pipeline: last_exec.pipeline.clone(),
165                ..Default::default()
166            }
167        };
168
169        cmd.execs.push(exec);
170        self.exec_idx += 1;
171    }
172
173    pub(crate) fn push_reusable_exec(
174        &mut self,
175        func: impl for<'r> Fn(CommandRef<'r>) + Send + Sync + 'static,
176    ) {
177        let cmd = self.cmd_mut();
178        let exec = {
179            let last_exec = cmd.expect_last_exec_mut();
180            last_exec.func = Some(CommandFunction::Reusable(Arc::new(func)));
181
182            Execution {
183                pipeline: last_exec.pipeline.clone(),
184                ..Default::default()
185            }
186        };
187
188        cmd.execs.push(exec);
189        self.exec_idx += 1;
190    }
191
192    fn push_subresource_access(
193        &mut self,
194        resource_node: impl Node,
195        subresource: SubresourceRange,
196        access: AccessType,
197    ) {
198        self.graph.assert_node_owner(&resource_node);
199
200        let node_idx = resource_node.index();
201
202        self.push_subresource_access_index(node_idx, subresource, access);
203    }
204
205    pub(crate) fn push_subresource_access_index(
206        &mut self,
207        node_idx: NodeIndex,
208        subresource: SubresourceRange,
209        access: AccessType,
210    ) {
211        debug_assert!(self.graph.resources.get(node_idx).is_some());
212
213        self.cmd_mut().expect_last_exec_mut().accesses.push(
214            node_idx,
215            SubresourceAccess {
216                access,
217                subresource,
218            },
219        );
220    }
221
222    /// Begin recording general-purpose work for this graph command.
223    ///
224    /// This is the entry point for building and updating an
225    /// [`AccelerationStructure`](crate::driver::accel_struct::AccelerationStructure) instance.
226    ///
227    /// The provided closure allows you to run any Vulkan code, or interoperate with other Vulkan
228    /// code and interfaces.
229    pub fn record_cmd(mut self, func: impl FnOnce(CommandRef<'_>) + Send + 'static) -> Self {
230        self.record_cmd_mut(func);
231        self
232    }
233
234    /// Mutable-borrow form of [`Self::record_cmd`].
235    pub fn record_cmd_mut(&mut self, func: impl FnOnce(CommandRef<'_>) + Send + 'static) {
236        self.push_exec(move |cmd| {
237            func(cmd);
238        });
239    }
240
241    pub(crate) fn record_stream(
242        mut self,
243        func: impl for<'r> Fn(CommandRef<'r>) + Send + Sync + 'static,
244    ) -> Self {
245        self.record_stream_mut(func);
246        self
247    }
248
249    pub(crate) fn record_stream_mut(
250        &mut self,
251        func: impl for<'r> Fn(CommandRef<'r>) + Send + Sync + 'static,
252    ) {
253        self.push_reusable_exec(func);
254    }
255
256    /// Returns a borrow of the original Vulkan resource (buffer, image or acceleration structure)
257    /// which the given bound resource node represents.
258    pub fn resource<N>(&self, resource_node: N) -> &N::Resource
259    where
260        N: Node,
261    {
262        self.graph.resource(resource_node)
263    }
264
265    /// Informs the command that recorded work will read or write `resource_node`
266    /// using `access`.
267    ///
268    /// An access function must be called for `resource_node` before it is used within a recording
269    /// function.
270    pub fn resource_access<N>(mut self, resource_node: N, access: AccessType) -> Self
271    where
272        N: Node + Subresource,
273        SubresourceRange: From<N::Range>,
274    {
275        self.set_resource_access(resource_node, access);
276        self
277    }
278
279    /// Mutable-borrow form of [`Self::debug_name`].
280    pub fn set_debug_name(&mut self, name: impl Into<String>) -> &mut Self {
281        #[cfg(debug_assertions)]
282        {
283            self.cmd_mut().name = Some(name.into());
284        }
285
286        #[cfg(not(debug_assertions))]
287        {
288            let _ = name;
289        }
290
291        self
292    }
293
294    /// Mutable-borrow form of [`Self::resource_access`].
295    pub fn set_resource_access<N>(&mut self, resource_node: N, access: AccessType)
296    where
297        N: Node + Subresource,
298        SubresourceRange: From<N::Range>,
299    {
300        let whole_resource = resource_node.range(&self.graph.resources);
301        let subresource = SubresourceRange::from(whole_resource);
302
303        self.push_subresource_access(resource_node, subresource, access);
304    }
305
306    pub(crate) fn set_stream_scope_id(&mut self, stream_scope_id: u64) {
307        self.cmd_mut().stream_scope_id = Some(stream_scope_id);
308    }
309
310    /// Mutable-borrow form of [`Self::subresource_access`].
311    pub fn set_subresource_access<N>(
312        &mut self,
313        resource_node: N,
314        subresource: impl Into<N::Range>,
315        access: AccessType,
316    ) where
317        N: Node + Subresource,
318        SubresourceRange: From<N::Range>,
319    {
320        let subresource = subresource.into();
321        let subresource = SubresourceRange::from(subresource);
322
323        self.push_subresource_access(resource_node, subresource, access);
324    }
325
326    /// Informs the command that recorded work will read or write the `subresource` of
327    /// `resource_node` using `access`.
328    ///
329    /// An access function must be called for `resource_node` before it is used within a recording
330    /// function.
331    pub fn subresource_access<N>(
332        mut self,
333        resource_node: N,
334        subresource: impl Into<N::Range>,
335        access: AccessType,
336    ) -> Self
337    where
338        N: Node + Subresource,
339        SubresourceRange: From<N::Range>,
340    {
341        self.set_subresource_access(resource_node, subresource, access);
342        self
343    }
344}
345
346/// Describes the SPIR-V binding index, and optionally a specific descriptor set
347/// and array index.
348///
349/// Generally you might pass a function a descriptor using a simple integer:
350///
351/// ```rust
352/// # fn my_func(_: usize, _: ()) {}
353/// # let image = ();
354/// let descriptor = 42;
355/// my_func(descriptor, image);
356/// ```
357///
358/// But also:
359///
360/// - `(0, 42)` for descriptor set `0` and binding index `42`
361/// - `(42, [8])` for the same binding, but the 8th element
362/// - `(0, 42, [8])` same as the previous example
363#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
364pub struct Binding {
365    /// The value of the descriptor binding decoration applied to the variable.
366    pub binding: u32,
367
368    /// An array-element offset applied to this descriptor.
369    pub offset: u32,
370
371    /// An optional descriptor set index value.
372    pub set: u32,
373}
374
375impl Binding {
376    pub(super) fn into_tuple(self) -> (DescriptorSetIndex, BindingIndex, BindingOffset) {
377        (self.set, self.binding, self.offset)
378    }
379
380    pub(super) fn set(self) -> DescriptorSetIndex {
381        let (res, _, _) = self.into_tuple();
382        res
383    }
384}
385
386impl From<BindingIndex> for Binding {
387    fn from(binding: BindingIndex) -> Self {
388        Self {
389            binding,
390            offset: 0,
391            set: 0,
392        }
393    }
394}
395
396impl From<(DescriptorSetIndex, BindingIndex)> for Binding {
397    fn from((set, binding): (DescriptorSetIndex, BindingIndex)) -> Self {
398        Self {
399            binding,
400            offset: 0,
401            set,
402        }
403    }
404}
405
406impl From<(BindingIndex, [BindingOffset; 1])> for Binding {
407    fn from((binding, [offset]): (BindingIndex, [BindingOffset; 1])) -> Self {
408        Self {
409            binding,
410            offset,
411            set: 0,
412        }
413    }
414}
415
416impl From<(DescriptorSetIndex, BindingIndex, [BindingOffset; 1])> for Binding {
417    fn from(
418        (set, binding, [offset]): (DescriptorSetIndex, BindingIndex, [BindingOffset; 1]),
419    ) -> Self {
420        Self {
421            binding,
422            offset,
423            set,
424        }
425    }
426}
427
428/// Allows for a resource to be reinterpreted as differently formatted data.
429#[allow(private_bounds)]
430pub trait Subresource: private::SubresourceSealed {
431    /// The information about the subresource when bound directly to shader descriptors.
432    type Info;
433
434    /// The information about the subresource when used indirectly by any part of a graph.
435    type Range;
436}
437
438macro_rules! view_accel_struct {
439    ($name:ty) => {
440        impl Subresource for $name {
441            type Info = Self::Range;
442            type Range = ();
443        }
444
445        impl private::SubresourceSealed for $name {
446            fn info(&self, _: &[AnyResource]) -> <Self as Subresource>::Info
447            where
448                Self: Node + Subresource,
449            {
450            }
451
452            fn range(&self, resources: &[AnyResource]) -> <Self as Subresource>::Range
453            where
454                Self: Node + Subresource,
455            {
456                resources[self.index()].expect_accel_struct_info();
457            }
458        }
459    };
460}
461
462view_accel_struct!(AnyAccelerationStructureNode);
463view_accel_struct!(AccelerationStructureArg);
464view_accel_struct!(AccelerationStructureLeaseNode);
465view_accel_struct!(AccelerationStructureNode);
466
467macro_rules! view_buffer {
468    ($name:ty) => {
469        impl Subresource for $name {
470            type Info = Self::Range;
471            type Range = BufferSubresourceRange;
472        }
473
474        impl private::SubresourceSealed for $name {
475            fn info(&self, resources: &[AnyResource]) -> <Self as Subresource>::Info
476            where
477                Self: Node + Subresource,
478            {
479                self.range(resources)
480            }
481
482            fn range(&self, resources: &[AnyResource]) -> <Self as Subresource>::Range
483            where
484                Self: Node + Subresource,
485            {
486                let idx = self.index();
487
488                resources[idx].expect_buffer_info().into()
489            }
490        }
491    };
492}
493
494view_buffer!(AnyBufferNode);
495view_buffer!(BufferArg);
496view_buffer!(BufferLeaseNode);
497view_buffer!(BufferNode);
498
499macro_rules! view_image {
500    ($name:ty) => {
501        impl Subresource for $name {
502            type Info = ImageViewInfo;
503            type Range = vk::ImageSubresourceRange;
504        }
505
506        impl private::SubresourceSealed for $name {
507            fn info(&self, resources: &[AnyResource]) -> <Self as Subresource>::Info
508            where
509                Self: Node + Subresource,
510            {
511                let idx = self.index();
512
513                resources[idx].expect_image_info().into()
514            }
515
516            fn range(&self, resources: &[AnyResource]) -> <Self as Subresource>::Range
517            where
518                Self: Node + Subresource,
519            {
520                self.info(resources).into()
521            }
522        }
523    };
524}
525
526view_image!(AnyImageNode);
527view_image!(ImageArg);
528view_image!(ImageLeaseNode);
529view_image!(ImageNode);
530view_image!(SwapchainImageNode);
531
532#[derive(Clone, Copy, Debug)]
533pub(crate) enum SubresourceRange {
534    /// Acceleration structures are bound whole.
535    AccelerationStructure,
536
537    /// Images may be partially bound.
538    Image(vk::ImageSubresourceRange),
539
540    /// Buffers may be partially bound.
541    Buffer(BufferSubresourceRange),
542}
543
544impl SubresourceRange {
545    pub(super) fn as_image(&self) -> Option<&vk::ImageSubresourceRange> {
546        if let Self::Image(subresource) = self {
547            Some(subresource)
548        } else {
549            None
550        }
551    }
552
553    pub(super) fn expect_image(&self) -> &vk::ImageSubresourceRange {
554        self.as_image().expect("missing image subresource")
555    }
556}
557
558impl From<BufferSubresourceRange> for SubresourceRange {
559    fn from(subresource: BufferSubresourceRange) -> Self {
560        Self::Buffer(subresource)
561    }
562}
563
564impl From<()> for SubresourceRange {
565    fn from(_: ()) -> Self {
566        Self::AccelerationStructure
567    }
568}
569
570impl From<ImageViewInfo> for SubresourceRange {
571    fn from(subresource: ImageViewInfo) -> Self {
572        Self::Image(subresource.into())
573    }
574}
575
576impl From<vk::ImageSubresourceRange> for SubresourceRange {
577    fn from(subresource: vk::ImageSubresourceRange) -> Self {
578        Self::Image(subresource)
579    }
580}
581
582#[derive(Clone, Copy, Debug)]
583pub(super) struct SubresourceAccess {
584    pub access: AccessType,
585    pub subresource: SubresourceRange,
586}
587
588/// Describes the interpretation of a resource.
589#[derive(Clone, Copy, Debug, Eq, PartialEq)]
590pub(crate) enum ViewInfo {
591    /// Acceleration structures are always whole resources.
592    AccelerationStructure,
593
594    /// Images may be interpreted as differently formatted images.
595    Image(ImageViewInfo),
596
597    /// Buffers may be interpreted as subregions of the same buffer.
598    Buffer(BufferSubresourceRange),
599}
600
601impl ViewInfo {
602    pub(crate) fn as_buffer(&self) -> Option<&BufferSubresourceRange> {
603        match self {
604            Self::Buffer(info) => Some(info),
605            _ => None,
606        }
607    }
608
609    pub(crate) fn as_image(&self) -> Option<&ImageViewInfo> {
610        match self {
611            Self::Image(info) => Some(info),
612            _ => None,
613        }
614    }
615
616    pub(crate) fn expect_buffer(&self) -> &BufferSubresourceRange {
617        self.as_buffer().expect("missing buffer view info")
618    }
619
620    pub(crate) fn expect_image(&self) -> &ImageViewInfo {
621        self.as_image().expect("missing image view info")
622    }
623}
624
625impl From<()> for ViewInfo {
626    fn from(_: ()) -> Self {
627        Self::AccelerationStructure
628    }
629}
630
631impl From<BufferSubresourceRange> for ViewInfo {
632    fn from(info: BufferSubresourceRange) -> Self {
633        Self::Buffer(info)
634    }
635}
636
637impl From<ImageViewInfo> for ViewInfo {
638    fn from(info: ImageViewInfo) -> Self {
639        Self::Image(info)
640    }
641}
642
643impl From<Range<vk::DeviceSize>> for ViewInfo {
644    fn from(range: Range<vk::DeviceSize>) -> Self {
645        Self::Buffer(BufferSubresourceRange {
646            start: range.start,
647            end: range.end,
648        })
649    }
650}
651
652mod private {
653    use crate::{AnyResource, Node};
654
655    pub(crate) trait SubresourceSealed: Sized {
656        fn info(&self, resources: &[AnyResource]) -> <Self as super::Subresource>::Info
657        where
658            Self: Node + super::Subresource;
659
660        fn range(&self, resources: &[AnyResource]) -> <Self as super::Subresource>::Range
661        where
662            Self: Node + super::Subresource;
663    }
664}