Skip to main content

vk_graph/
stream.rs

1//! Reusable command streams.
2//!
3//! A [`CommandStream`] is a prepared graph-like command sequence that can be inserted into a
4//! per-frame [`Graph`] with typed arguments.
5//!
6//! Streams are useful when part of a frame is structurally the same across many frames but still
7//! needs per-frame resources such as the current swapchain image. Declare those resources as stream
8//! arguments, record reusable commands once, and bind concrete graph nodes when inserting the stream.
9//!
10//! ```no_run
11//! # use ash::vk;
12//! # use vk_graph::{Graph, node::{BufferNode, ImageNode}, pool::hash::HashPool};
13//! # use vk_graph::cmd::{LoadOp, StoreOp};
14//! # use vk_graph::driver::buffer::BufferInfo;
15//! # use vk_graph::driver::graphics::GraphicsPipeline;
16//! # use vk_graph::driver::image::ImageInfo;
17//! # use vk_graph::stream::CommandStream;
18//! # use vk_sync::AccessType;
19//! # let mut pool: HashPool = todo!();
20//! # let pipeline: GraphicsPipeline = todo!();
21//! # let swapchain_image: ImageNode = todo!();
22//! # let vertex_buffer: BufferNode = todo!();
23//! let stream = CommandStream::prepare(&mut pool, |stream| {
24//!     let output = stream.arg(ImageInfo::image_2d(
25//!         1280,
26//!         720,
27//!         vk::Format::R8G8B8A8_UNORM,
28//!         vk::ImageUsageFlags::COLOR_ATTACHMENT,
29//!     ));
30//!     let vertices = stream.arg(BufferInfo::device_mem(
31//!         4096,
32//!         vk::BufferUsageFlags::VERTEX_BUFFER,
33//!     ));
34//!
35//!     stream
36//!         .begin_cmd()
37//!         .debug_name("reusable overlay")
38//!         .bind_pipeline(&pipeline)
39//!         .color_attachment_image(0, output, LoadOp::Load, StoreOp::Store)
40//!         .resource_access(vertices, AccessType::VertexBuffer)
41//!         .record_cmd(move |cmd| {
42//!             cmd.bind_vertex_buffer(0, vertices, 0).draw(3, 1, 0, 0);
43//!         });
44//!
45//!     (output, vertices)
46//! })?;
47//!
48//! let mut graph = Graph::new();
49//! graph
50//!     .insert_cmd_stream(&stream)
51//!     .with_arg(stream.args.0, swapchain_image)
52//!     .with_arg(stream.args.1, vertex_buffer)
53//!     .finish();
54//! # Ok::<(), vk_graph::driver::DriverError>(())
55//! ```
56
57use crate::private::NodeSealed;
58use {
59    crate::{
60        AnyResource, Graph, Node, Resource, ResourceMap,
61        cmd::{
62            AttachmentIndex, ClearColorValue, Command, CommandRef, ComputeCommandRef,
63            GraphicsCommandRef, LoadOp, PipelineCommand, RayTracingCommandRef, StoreOp,
64            Subresource, SubresourceRange,
65        },
66        driver::{
67            DriverError,
68            accel_struct::{
69                AccelerationStructure, AccelerationStructureInfo, AccelerationStructureInfoBuilder,
70            },
71            buffer::{Buffer, BufferInfo, BufferInfoBuilder},
72            compute::ComputePipeline,
73            graphics::{DepthStencilInfo, GraphicsPipeline},
74            image::{Image, ImageInfo, ImageInfoBuilder, ImageViewInfo},
75            ray_tracing::RayTracingPipeline,
76        },
77        node::{
78            AccelerationStructureLeaseNode, AccelerationStructureNode,
79            AnyAccelerationStructureNode, AnyBufferNode, AnyImageNode, BufferLeaseNode, BufferNode,
80            ImageLeaseNode, ImageNode, SwapchainImageNode,
81        },
82        pool::SubmissionPool,
83        submission::Submission,
84    },
85    ash::vk,
86    std::{
87        collections::HashMap,
88        marker::PhantomData,
89        ops::Range,
90        sync::{Arc, Mutex},
91    },
92};
93
94#[cfg(feature = "checked")]
95use crate::GraphId;
96
97use std::sync::atomic::{AtomicU64, Ordering};
98
99fn next_stream_scope_id() -> u64 {
100    static NEXT_ID: AtomicU64 = AtomicU64::new(1);
101
102    NEXT_ID.fetch_add(1, Ordering::Relaxed)
103}
104
105#[cfg(feature = "checked")]
106#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
107pub(crate) struct CommandStreamId(u64);
108
109#[cfg(feature = "checked")]
110impl CommandStreamId {
111    fn next() -> Self {
112        Self(next_stream_scope_id())
113    }
114}
115
116/// A typed external argument for a [`CommandStream`].
117///
118/// `StreamArg` values are created with [`CommandStreamMut::arg`] while building a stream and are
119/// later bound to parent-graph nodes with [`CommandStreamRun::with_arg`].
120///
121/// ```no_run
122/// # use ash::vk;
123/// # use vk_graph::{Graph, driver::image::ImageInfo, node::ImageNode, stream::CommandStream};
124/// # let swapchain_image: ImageNode = todo!();
125/// let stream = CommandStream::finalize(|stream| {
126///     stream.arg(ImageInfo::image_2d(
127///         640,
128///         480,
129///         vk::Format::R8G8B8A8_UNORM,
130///         vk::ImageUsageFlags::COLOR_ATTACHMENT,
131///     ))
132/// })
133/// .into_stream();
134///
135/// let mut graph = Graph::new();
136/// graph
137///     .insert_cmd_stream(&stream)
138///     .with_arg(stream.args, swapchain_image)
139///     .finish();
140/// ```
141#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
142pub struct StreamArg<T> {
143    pub(crate) arg_index: usize,
144    pub(crate) index: usize,
145
146    #[cfg(feature = "checked")]
147    pub(crate) stream_id: CommandStreamId,
148
149    #[cfg(feature = "checked")]
150    pub(crate) graph_id: GraphId,
151
152    __: PhantomData<fn() -> T>,
153}
154
155impl<T> Clone for StreamArg<T> {
156    fn clone(&self) -> Self {
157        *self
158    }
159}
160
161impl<T> Copy for StreamArg<T> {}
162
163impl<T> StreamArg<T> {
164    pub(crate) fn new(
165        arg_index: usize,
166        index: usize,
167        #[cfg(feature = "checked")] stream_id: CommandStreamId,
168        #[cfg(feature = "checked")] graph_id: GraphId,
169    ) -> Self {
170        Self {
171            arg_index,
172            index,
173            #[cfg(feature = "checked")]
174            stream_id,
175            #[cfg(feature = "checked")]
176            graph_id,
177            __: PhantomData,
178        }
179    }
180}
181
182impl NodeSealed for StreamArg<AccelerationStructure> {
183    fn borrow(self, resources: &[AnyResource]) -> &<Self as Node>::Resource {
184        resources[self.index].expect_accel_struct()
185    }
186
187    fn borrow_at(self, resources: &[AnyResource], index: usize) -> &<Self as Node>::Resource {
188        resources[index].expect_accel_struct()
189    }
190
191    #[cfg(feature = "checked")]
192    fn assert_owner(&self, _graph_id: GraphId) {
193        #[cfg(feature = "checked")]
194        assert!(
195            self.graph_id == _graph_id,
196            "node belongs to a different graph"
197        );
198    }
199}
200
201impl Node for StreamArg<AccelerationStructure> {
202    type Resource = AccelerationStructure;
203    type SyncInfo = crate::driver::accel_struct::AccelerationStructureSyncInfo;
204
205    fn index(&self) -> usize {
206        self.index
207    }
208}
209
210impl NodeSealed for StreamArg<Buffer> {
211    fn borrow(self, resources: &[AnyResource]) -> &<Self as Node>::Resource {
212        resources[self.index].expect_buffer()
213    }
214
215    fn borrow_at(self, resources: &[AnyResource], index: usize) -> &<Self as Node>::Resource {
216        resources[index].expect_buffer()
217    }
218
219    #[cfg(feature = "checked")]
220    fn assert_owner(&self, _graph_id: GraphId) {
221        #[cfg(feature = "checked")]
222        assert!(
223            self.graph_id == _graph_id,
224            "node belongs to a different graph"
225        );
226    }
227}
228
229impl Node for StreamArg<Buffer> {
230    type Resource = Buffer;
231    type SyncInfo = crate::driver::buffer::BufferSyncInfo;
232
233    fn index(&self) -> usize {
234        self.index
235    }
236}
237
238impl NodeSealed for StreamArg<Image> {
239    fn borrow(self, resources: &[AnyResource]) -> &<Self as Node>::Resource {
240        resources[self.index].expect_image()
241    }
242
243    fn borrow_at(self, resources: &[AnyResource], index: usize) -> &<Self as Node>::Resource {
244        resources[index].expect_image()
245    }
246
247    #[cfg(feature = "checked")]
248    fn assert_owner(&self, _graph_id: GraphId) {
249        #[cfg(feature = "checked")]
250        assert!(
251            self.graph_id == _graph_id,
252            "node belongs to a different graph"
253        );
254    }
255}
256
257impl Node for StreamArg<Image> {
258    type Resource = Image;
259    type SyncInfo = crate::driver::image::ImageSyncInfo;
260
261    fn index(&self) -> usize {
262        self.index
263    }
264}
265
266/// A stream argument for an acceleration structure.
267///
268/// ```no_run
269/// # use vk_graph::driver::accel_struct::AccelerationStructureInfo;
270/// # use vk_graph::stream::{AccelerationStructureArg, CommandStream};
271/// # let info: AccelerationStructureInfo = todo!();
272/// let stream = CommandStream::finalize(|stream| -> AccelerationStructureArg {
273///     stream.arg(info)
274/// })
275/// .into_stream();
276/// ```
277pub type AccelerationStructureArg = StreamArg<AccelerationStructure>;
278
279/// A stream argument for a buffer.
280///
281/// ```no_run
282/// # use ash::vk;
283/// # use vk_graph::driver::buffer::BufferInfo;
284/// # use vk_graph::stream::{BufferArg, CommandStream};
285/// let stream = CommandStream::finalize(|stream| -> BufferArg {
286///     stream.arg(BufferInfo::device_mem(
287///         4096,
288///         vk::BufferUsageFlags::STORAGE_BUFFER,
289///     ))
290/// })
291/// .into_stream();
292/// ```
293pub type BufferArg = StreamArg<Buffer>;
294
295/// A stream argument for an image.
296///
297/// ```no_run
298/// # use ash::vk;
299/// # use vk_graph::driver::image::ImageInfo;
300/// # use vk_graph::stream::{CommandStream, ImageArg};
301/// let stream = CommandStream::finalize(|stream| -> ImageArg {
302///     stream.arg(ImageInfo::image_2d(
303///         128,
304///         128,
305///         vk::Format::R8G8B8A8_UNORM,
306///         vk::ImageUsageFlags::SAMPLED,
307///     ))
308/// })
309/// .into_stream();
310/// ```
311pub type ImageArg = StreamArg<Image>;
312
313#[derive(Clone, Copy, Debug)]
314pub(crate) enum StreamArgData {
315    AccelerationStructure(AccelerationStructureInfo),
316    Buffer(BufferInfo),
317    Image(ImageInfo),
318}
319
320#[derive(Debug)]
321pub(crate) struct CommandStreamInner {
322    pub(crate) arg_nodes: Box<[usize]>,
323    pub(crate) args: Box<[StreamArgData]>,
324    pub(crate) prepared: bool,
325    pub(crate) submission: Mutex<Submission>,
326
327    #[cfg(feature = "checked")]
328    pub(crate) stream_id: CommandStreamId,
329
330    #[cfg(feature = "checked")]
331    pub(crate) graph_id: GraphId,
332}
333
334/// A reusable command stream.
335///
336/// Prepared streams reduce repeated CPU-side graph construction and preparation work by caching an
337/// optimized schedule and static recording resources. Unprepared streams keep finalization cheaper
338/// up front, but each insertion still has to reconcile arguments, dependencies, scheduling, and
339/// recording with the parent graph.
340///
341/// Inserting or concatenating many tiny streams is not free. Profile release builds before designing
342/// around heavy stream composition.
343///
344/// ```no_run
345/// # use vk_graph::{Graph, pool::hash::HashPool, stream::CommandStream};
346/// # let mut pool: HashPool = todo!();
347/// let stream = CommandStream::prepare(&mut pool, |stream| {
348///     stream.begin_cmd().debug_name("cached commands").record_cmd(|_| {});
349/// })?;
350///
351/// let mut graph = Graph::new();
352/// graph.insert_cmd_stream(&stream).finish();
353/// # Ok::<(), vk_graph::driver::DriverError>(())
354/// ```
355#[derive(Clone, Debug)]
356pub struct CommandStream<A = ()> {
357    /// Typed handles returned by the preparation callback.
358    pub args: A,
359    pub(crate) inner: Arc<CommandStreamInner>,
360}
361
362/// A finalized command stream definition that can be prepared later.
363///
364/// Drafts are useful when construction should happen separately from preparation. Convert a draft
365/// with [`CommandStreamDraft::into_stream`] for unprepared insertion or
366/// [`CommandStreamDraft::prepare`] to cache preparation work.
367///
368/// ```no_run
369/// # use vk_graph::{Graph, pool::hash::HashPool, stream::CommandStream};
370/// # let mut pool: HashPool = todo!();
371/// let draft = CommandStream::finalize(|stream| {
372///     stream.begin_cmd().record_cmd(|_| {});
373/// });
374///
375/// let prepared = draft.prepare(&mut pool)?;
376/// let mut graph = Graph::new();
377/// graph.insert_cmd_stream(&prepared).finish();
378/// # Ok::<(), vk_graph::driver::DriverError>(())
379/// ```
380#[derive(Debug)]
381pub struct CommandStreamDraft<A = ()> {
382    /// Typed handles returned by the finalization callback.
383    pub args: A,
384    inner: CommandStreamInner,
385}
386
387/// A mutable graph-like command stream being prepared.
388///
389/// `CommandStreamMut` is passed to [`CommandStream::finalize`] and [`CommandStream::prepare`]
390/// callbacks. It provides graph-like methods plus [`CommandStreamMut::arg`] for typed stream
391/// inputs.
392///
393/// ```no_run
394/// # use ash::vk;
395/// # use vk_graph::{driver::buffer::BufferInfo, stream::CommandStream};
396/// let stream = CommandStream::finalize(|stream| {
397///     let staging = stream.arg(BufferInfo::host_mem(
398///         1024,
399///         vk::BufferUsageFlags::TRANSFER_SRC,
400///     ));
401///     stream.begin_cmd().resource_access(staging, vk_sync::AccessType::TransferRead);
402///     staging
403/// })
404/// .into_stream();
405/// ```
406pub struct CommandStreamMut {
407    pub(crate) arg_nodes: Vec<usize>,
408    pub(crate) args: Vec<StreamArgData>,
409    pub(crate) graph: Graph,
410    #[cfg(feature = "checked")]
411    pub(crate) stream_id: CommandStreamId,
412}
413
414/// A command being recorded into a [`CommandStreamMut`].
415///
416/// ```no_run
417/// # use vk_graph::stream::CommandStream;
418/// let stream = CommandStream::finalize(|stream| {
419///     stream
420///         .begin_cmd()
421///         .debug_name("stream command")
422///         .record_cmd(|cmd| {
423///             let _ = cmd;
424///         });
425/// })
426/// .into_stream();
427/// ```
428pub struct StreamCommand<'a> {
429    inner: Command<'a>,
430}
431
432/// A stream command with a bound pipeline.
433///
434/// ```no_run
435/// # use vk_graph::stream::CommandStream;
436/// # use vk_graph::driver::compute::ComputePipeline;
437/// # let pipeline: ComputePipeline = todo!();
438/// let stream = CommandStream::finalize(|stream| {
439///     stream
440///         .begin_cmd()
441///         .bind_pipeline(&pipeline)
442///         .record_cmd(|cmd| {
443///             cmd.dispatch(1, 1, 1);
444///         });
445/// })
446/// .into_stream();
447/// ```
448pub struct StreamPipelineCommand<'a, T> {
449    inner: PipelineCommand<'a, T>,
450}
451
452/// A pipeline that can be bound to a stream command.
453#[doc(hidden)]
454pub trait StreamPipeline<'a>: stream_private::StreamPipelineSealed {
455    /// The stream command type returned after binding.
456    type Command;
457
458    /// Stream equivalent of [`Pipeline::bind_cmd`].
459    fn bind_stream_cmd(self, cmd: StreamCommand<'a>) -> Self::Command;
460}
461
462macro_rules! stream_pipeline {
463    ($pipeline:ty) => {
464        impl<'a> StreamPipeline<'a> for $pipeline {
465            type Command = StreamPipelineCommand<'a, $pipeline>;
466
467            fn bind_stream_cmd(self, cmd: StreamCommand<'a>) -> Self::Command {
468                StreamPipelineCommand {
469                    inner: cmd.inner.bind_pipeline(self),
470                }
471            }
472        }
473
474        impl stream_private::StreamPipelineSealed for $pipeline {}
475
476        impl<'a> StreamPipeline<'a> for &'a $pipeline {
477            type Command = StreamPipelineCommand<'a, $pipeline>;
478
479            fn bind_stream_cmd(self, cmd: StreamCommand<'a>) -> Self::Command {
480                StreamPipelineCommand {
481                    inner: cmd.inner.bind_pipeline(self),
482                }
483            }
484        }
485
486        impl<'a> stream_private::StreamPipelineSealed for &'a $pipeline {}
487    };
488}
489
490stream_pipeline!(ComputePipeline);
491stream_pipeline!(GraphicsPipeline);
492stream_pipeline!(RayTracingPipeline);
493
494#[allow(private_bounds)]
495impl<'a> StreamCommand<'a> {
496    /// Stream equivalent of [`Command::bind_resource`].
497    pub fn bind_resource<R>(&mut self, resource: R) -> R::Node
498    where
499        R: Resource,
500    {
501        self.inner.bind_resource(resource)
502    }
503
504    /// Stream equivalent of [`Command::bind_pipeline`].
505    pub fn bind_pipeline<P>(self, pipeline: P) -> P::Command
506    where
507        P: StreamPipeline<'a>,
508    {
509        pipeline.bind_stream_cmd(self)
510    }
511
512    /// Stream equivalent of [`Command::debug_name`].
513    pub fn debug_name(mut self, name: impl Into<String>) -> Self {
514        self.inner.set_debug_name(name);
515        self
516    }
517
518    /// Stream equivalent of [`Command::record_cmd`].
519    ///
520    /// Unlike graph commands, stream callbacks must be reusable and therefore implement
521    /// `Fn + Send + Sync + 'static`.
522    pub fn record_cmd(
523        mut self,
524        func: impl for<'r> Fn(CommandRef<'r>) + Send + Sync + 'static,
525    ) -> Self {
526        self.record_cmd_mut(func);
527        self
528    }
529
530    /// Mutable-borrow stream equivalent of [`Command::record_cmd`].
531    ///
532    /// Unlike graph commands, stream callbacks must be reusable and therefore implement
533    /// `Fn + Send + Sync + 'static`.
534    pub fn record_cmd_mut(
535        &mut self,
536        func: impl for<'r> Fn(CommandRef<'r>) + Send + Sync + 'static,
537    ) {
538        self.inner.record_stream_mut(func);
539    }
540
541    /// Stream equivalent of [`Command::resource_access`].
542    pub fn resource_access<N>(mut self, resource_node: N, access: vk_sync::AccessType) -> Self
543    where
544        N: Node + Subresource,
545        SubresourceRange: From<N::Range>,
546    {
547        self.inner.set_resource_access(resource_node, access);
548        self
549    }
550
551    /// Mutable-borrow stream equivalent of [`Command::resource_access`].
552    pub fn set_resource_access<N>(&mut self, resource_node: N, access: vk_sync::AccessType)
553    where
554        N: Node + Subresource,
555        SubresourceRange: From<N::Range>,
556    {
557        self.inner.set_resource_access(resource_node, access);
558    }
559}
560
561#[allow(private_bounds)]
562impl<'a, T> StreamPipelineCommand<'a, T> {
563    /// Stream equivalent of [`PipelineCommand::bind_resource`].
564    pub fn bind_resource<R>(&mut self, resource: R) -> R::Node
565    where
566        R: Resource,
567    {
568        self.inner.bind_resource(resource)
569    }
570
571    /// Stream equivalent of [`PipelineCommand::resource_access`].
572    pub fn resource_access<N>(mut self, resource_node: N, access: vk_sync::AccessType) -> Self
573    where
574        N: Node + Subresource,
575        SubresourceRange: From<N::Range>,
576    {
577        self.inner.set_resource_access(resource_node, access);
578        self
579    }
580
581    /// Mutable-borrow stream equivalent of [`PipelineCommand::resource_access`].
582    pub fn set_resource_access<N>(
583        &mut self,
584        resource_node: N,
585        access: vk_sync::AccessType,
586    ) -> &mut Self
587    where
588        N: Node + Subresource,
589        SubresourceRange: From<N::Range>,
590    {
591        self.inner.set_resource_access(resource_node, access);
592        self
593    }
594}
595
596impl StreamPipelineCommand<'_, ComputePipeline> {
597    /// Stream equivalent of [`PipelineCommand::<ComputePipeline>::record_cmd`].
598    ///
599    /// Unlike graph commands, stream callbacks must be reusable and therefore implement
600    /// `Fn + Send + Sync + 'static`.
601    pub fn record_cmd(
602        mut self,
603        func: impl for<'r> Fn(ComputeCommandRef<'r>) + Send + Sync + 'static,
604    ) -> Self {
605        self.record_cmd_mut(func);
606        self
607    }
608
609    /// Mutable-borrow stream equivalent of [`PipelineCommand::<ComputePipeline>::record_cmd`].
610    ///
611    /// Unlike graph commands, stream callbacks must be reusable and therefore implement
612    /// `Fn + Send + Sync + 'static`.
613    pub fn record_cmd_mut(
614        &mut self,
615        func: impl for<'r> Fn(ComputeCommandRef<'r>) + Send + Sync + 'static,
616    ) {
617        self.inner.record_stream_mut(func);
618    }
619}
620
621impl StreamPipelineCommand<'_, GraphicsPipeline> {
622    /// Stream equivalent of [`PipelineCommand::<GraphicsPipeline>::depth_stencil`].
623    pub fn depth_stencil(mut self, depth_stencil: impl Into<DepthStencilInfo>) -> Self {
624        self.inner.set_depth_stencil(depth_stencil);
625        self
626    }
627
628    /// Stream equivalent of [`PipelineCommand::<GraphicsPipeline>::color_attachment_image`].
629    pub fn color_attachment_image(
630        mut self,
631        color_attachment: AttachmentIndex,
632        image: impl Into<AnyImageNode>,
633        load: LoadOp<ClearColorValue>,
634        store: StoreOp,
635    ) -> Self {
636        self.inner
637            .set_color_attachment_image(color_attachment, image, load, store);
638        self
639    }
640
641    /// Stream equivalent of [`PipelineCommand::<GraphicsPipeline>::color_attachment_image_view`].
642    pub fn color_attachment_image_view(
643        mut self,
644        color_attachment: AttachmentIndex,
645        image: impl Into<AnyImageNode>,
646        image_view_info: impl Into<ImageViewInfo>,
647        load: LoadOp<ClearColorValue>,
648        store: StoreOp,
649    ) -> Self {
650        self.inner.set_color_attachment_image_view(
651            color_attachment,
652            image,
653            image_view_info,
654            load,
655            store,
656        );
657        self
658    }
659
660    /// Stream equivalent of [`PipelineCommand::<GraphicsPipeline>::depth_stencil_attachment_image`].
661    pub fn depth_stencil_attachment_image(
662        mut self,
663        image: impl Into<AnyImageNode>,
664        load: LoadOp<vk::ClearDepthStencilValue>,
665        store: StoreOp,
666    ) -> Self {
667        self.inner
668            .set_depth_stencil_attachment_image(image, load, store);
669        self
670    }
671
672    /// Stream equivalent of [`PipelineCommand::<GraphicsPipeline>::record_cmd`].
673    ///
674    /// Unlike graph commands, stream callbacks must be reusable and therefore implement
675    /// `Fn + Send + Sync + 'static`.
676    pub fn record_cmd(
677        mut self,
678        func: impl for<'r> Fn(GraphicsCommandRef<'r>) + Send + Sync + 'static,
679    ) -> Self {
680        self.record_cmd_mut(func);
681        self
682    }
683
684    /// Mutable-borrow stream equivalent of [`PipelineCommand::<GraphicsPipeline>::record_cmd`].
685    ///
686    /// Unlike graph commands, stream callbacks must be reusable and therefore implement
687    /// `Fn + Send + Sync + 'static`.
688    pub fn record_cmd_mut(
689        &mut self,
690        func: impl for<'r> Fn(GraphicsCommandRef<'r>) + Send + Sync + 'static,
691    ) {
692        self.inner.record_stream_mut(func);
693    }
694}
695
696impl StreamPipelineCommand<'_, RayTracingPipeline> {
697    /// Stream equivalent of [`PipelineCommand::<RayTracingPipeline>::record_cmd`].
698    ///
699    /// Unlike graph commands, stream callbacks must be reusable and therefore implement
700    /// `Fn + Send + Sync + 'static`.
701    pub fn record_cmd(
702        mut self,
703        func: impl for<'r> Fn(RayTracingCommandRef<'r>) + Send + Sync + 'static,
704    ) -> Self {
705        self.record_cmd_mut(func);
706        self
707    }
708
709    /// Mutable-borrow stream equivalent of [`PipelineCommand::<RayTracingPipeline>::record_cmd`].
710    ///
711    /// Unlike graph commands, stream callbacks must be reusable and therefore implement
712    /// `Fn + Send + Sync + 'static`.
713    pub fn record_cmd_mut(
714        &mut self,
715        func: impl for<'r> Fn(RayTracingCommandRef<'r>) + Send + Sync + 'static,
716    ) {
717        self.inner.record_stream_mut(func);
718    }
719}
720
721impl CommandStreamMut {
722    /// Declares a typed argument required by this command stream.
723    pub fn arg<I>(&mut self, info: I) -> I::Arg
724    where
725        I: StreamArgInfo,
726    {
727        info.bind_stream_arg(self)
728    }
729
730    /// Stream equivalent of [`Graph::begin_cmd`].
731    pub fn begin_cmd(&mut self) -> StreamCommand<'_> {
732        StreamCommand {
733            inner: self.graph.begin_cmd(),
734        }
735    }
736
737    /// Stream equivalent of [`Graph::bind_resource`].
738    pub fn bind_resource<R>(&mut self, resource: R) -> R::Node
739    where
740        R: Resource,
741    {
742        self.graph.bind_resource(resource)
743    }
744
745    /// Stream equivalent of [`Graph::resource`].
746    pub fn resource<N>(&self, resource_node: N) -> &N::Resource
747    where
748        N: StreamResourceNode,
749    {
750        self.graph.resource(resource_node)
751    }
752
753    /// Stream equivalent of [`Graph::blit_image`].
754    pub fn blit_image(
755        &mut self,
756        src: impl Into<AnyImageNode>,
757        dst: impl Into<AnyImageNode>,
758        filter: vk::Filter,
759    ) -> &mut Self {
760        self.graph.blit_image(src, dst, filter);
761        self
762    }
763
764    /// Stream equivalent of [`Graph::blit_image_region`].
765    pub fn blit_image_region(
766        &mut self,
767        src: impl Into<AnyImageNode>,
768        dst: impl Into<AnyImageNode>,
769        filter: vk::Filter,
770        regions: impl AsRef<[vk::ImageBlit]> + 'static + Send,
771    ) -> &mut Self {
772        self.graph.blit_image_region(src, dst, filter, regions);
773        self
774    }
775
776    /// Stream equivalent of [`Graph::clear_color_image`].
777    pub fn clear_color_image(
778        &mut self,
779        image: impl Into<AnyImageNode>,
780        color: impl Into<ClearColorValue>,
781    ) -> &mut Self {
782        self.graph.clear_color_image(image, color);
783        self
784    }
785
786    /// Stream equivalent of [`Graph::clear_depth_stencil_image`].
787    pub fn clear_depth_stencil_image(
788        &mut self,
789        image: impl Into<AnyImageNode>,
790        depth: f32,
791        stencil: u32,
792    ) -> &mut Self {
793        self.graph.clear_depth_stencil_image(image, depth, stencil);
794        self
795    }
796
797    /// Stream equivalent of [`Graph::copy_buffer`].
798    pub fn copy_buffer(
799        &mut self,
800        src: impl Into<AnyBufferNode>,
801        dst: impl Into<AnyBufferNode>,
802    ) -> &mut Self {
803        self.graph.copy_buffer(src, dst);
804        self
805    }
806
807    /// Stream equivalent of [`Graph::copy_buffer_region`].
808    pub fn copy_buffer_region(
809        &mut self,
810        src: impl Into<AnyBufferNode>,
811        dst: impl Into<AnyBufferNode>,
812        regions: impl AsRef<[vk::BufferCopy]> + 'static + Send,
813    ) -> &mut Self {
814        self.graph.copy_buffer_region(src, dst, regions);
815        self
816    }
817
818    /// Stream equivalent of [`Graph::copy_buffer_to_image`].
819    pub fn copy_buffer_to_image(
820        &mut self,
821        src: impl Into<AnyBufferNode>,
822        dst: impl Into<AnyImageNode>,
823    ) -> &mut Self {
824        self.graph.copy_buffer_to_image(src, dst);
825        self
826    }
827
828    /// Stream equivalent of [`Graph::copy_buffer_to_image_region`].
829    pub fn copy_buffer_to_image_region(
830        &mut self,
831        src: impl Into<AnyBufferNode>,
832        dst: impl Into<AnyImageNode>,
833        regions: impl AsRef<[vk::BufferImageCopy]> + 'static + Send,
834    ) -> &mut Self {
835        self.graph.copy_buffer_to_image_region(src, dst, regions);
836        self
837    }
838
839    /// Stream equivalent of [`Graph::copy_image`].
840    pub fn copy_image(
841        &mut self,
842        src: impl Into<AnyImageNode>,
843        dst: impl Into<AnyImageNode>,
844    ) -> &mut Self {
845        self.graph.copy_image(src, dst);
846        self
847    }
848
849    /// Stream equivalent of [`Graph::copy_image_region`].
850    pub fn copy_image_region(
851        &mut self,
852        src: impl Into<AnyImageNode>,
853        dst: impl Into<AnyImageNode>,
854        regions: impl AsRef<[vk::ImageCopy]> + 'static + Send,
855    ) -> &mut Self {
856        self.graph.copy_image_region(src, dst, regions);
857        self
858    }
859
860    /// Stream equivalent of [`Graph::copy_image_to_buffer`].
861    pub fn copy_image_to_buffer(
862        &mut self,
863        src: impl Into<AnyImageNode>,
864        dst: impl Into<AnyBufferNode>,
865    ) -> &mut Self {
866        self.graph.copy_image_to_buffer(src, dst);
867        self
868    }
869
870    /// Stream equivalent of [`Graph::copy_image_to_buffer_region`].
871    pub fn copy_image_to_buffer_region(
872        &mut self,
873        src: impl Into<AnyImageNode>,
874        dst: impl Into<AnyBufferNode>,
875        regions: impl AsRef<[vk::BufferImageCopy]> + 'static + Send,
876    ) -> &mut Self {
877        self.graph.copy_image_to_buffer_region(src, dst, regions);
878        self
879    }
880
881    /// Stream equivalent of [`Graph::fill_buffer`].
882    pub fn fill_buffer(
883        &mut self,
884        buffer: impl Into<AnyBufferNode>,
885        region: Range<vk::DeviceSize>,
886        data: u32,
887    ) -> &mut Self {
888        self.graph.fill_buffer(buffer, region, data);
889        self
890    }
891
892    /// Stream equivalent of [`Graph::update_buffer`].
893    pub fn update_buffer(
894        &mut self,
895        buffer: impl Into<AnyBufferNode>,
896        offset: vk::DeviceSize,
897        data: impl AsRef<[u8]> + 'static + Send,
898    ) -> &mut Self {
899        self.graph.update_buffer(buffer, offset, data);
900        self
901    }
902
903    fn push_arg(&mut self, data: StreamArgData) -> usize {
904        let index = self.args.len();
905        self.args.push(data);
906        index
907    }
908
909    fn bind_arg_resource(&mut self, data: StreamArgData) -> usize {
910        let resource = match data {
911            StreamArgData::AccelerationStructure(info) => {
912                AnyResource::AccelerationStructureArg(info)
913            }
914            StreamArgData::Buffer(info) => AnyResource::BufferArg(info),
915            StreamArgData::Image(info) => AnyResource::ImageArg(info),
916        };
917
918        self.graph.bind_stream_arg_resource(resource)
919    }
920}
921
922impl CommandStream<()> {
923    /// Finalizes a reusable command stream without preparing optimizations.
924    ///
925    /// The returned draft can be inserted as an unprepared stream with [`CommandStreamDraft::into_stream`]
926    /// or prepared later with [`CommandStreamDraft::prepare`].
927    pub fn finalize<A>(build: impl FnOnce(&mut CommandStreamMut) -> A) -> CommandStreamDraft<A> {
928        let mut stream = CommandStreamMut {
929            arg_nodes: Vec::new(),
930            args: Vec::new(),
931            graph: Graph::new(),
932            #[cfg(feature = "checked")]
933            stream_id: CommandStreamId::next(),
934        };
935        let args = build(&mut stream);
936
937        #[cfg(feature = "checked")]
938        let graph_id = stream.graph.graph_id();
939
940        let submission = stream.graph.finalize();
941        submission.assert_reusable_commands();
942
943        CommandStreamDraft {
944            args,
945            inner: CommandStreamInner {
946                arg_nodes: stream.arg_nodes.into_boxed_slice(),
947                args: stream.args.into_boxed_slice(),
948                prepared: false,
949                submission: Mutex::new(submission),
950
951                #[cfg(feature = "checked")]
952                stream_id: stream.stream_id,
953
954                #[cfg(feature = "checked")]
955                graph_id,
956            },
957        }
958    }
959
960    /// Finalizes and prepares a reusable command stream.
961    ///
962    /// Prepared streams do more work up front so repeated insertions can reuse prepared scheduling
963    /// and static recording resources.
964    pub fn prepare<P, A>(
965        pool: &mut P,
966        build: impl FnOnce(&mut CommandStreamMut) -> A,
967    ) -> Result<CommandStream<A>, DriverError>
968    where
969        P: SubmissionPool,
970    {
971        Self::finalize(build).prepare(pool)
972    }
973}
974
975impl<A> CommandStreamDraft<A> {
976    /// Converts this draft into a command stream without preparing optimizations.
977    ///
978    /// Unprepared streams avoid preparation cost until insertion, but they do not cache the prepared
979    /// schedule or static recording resources.
980    pub fn into_stream(self) -> CommandStream<A> {
981        CommandStream {
982            args: self.args,
983            inner: Arc::new(self.inner),
984        }
985    }
986
987    /// Prepares this stream by optimizing its finalized graph and leasing static recording
988    /// resources for the prepared schedule.
989    ///
990    /// This is most useful when the same stream is inserted many times with different arguments.
991    pub fn prepare<P>(mut self, pool: &mut P) -> Result<CommandStream<A>, DriverError>
992    where
993        P: SubmissionPool,
994    {
995        self.inner
996            .submission
997            .get_mut()
998            .expect("poisoned command stream submission")
999            .prepare_command_stream(pool)?;
1000        self.inner.prepared = true;
1001
1002        Ok(self.into_stream())
1003    }
1004}
1005
1006/// An in-progress invocation of a [`CommandStream`] into a [`Graph`].
1007///
1008/// Bind every declared stream argument before calling [`CommandStreamRun::finish`].
1009///
1010/// ```no_run
1011/// # use ash::vk;
1012/// # use vk_graph::{Graph, driver::image::ImageInfo, node::ImageNode, stream::CommandStream};
1013/// # let image: ImageNode = todo!();
1014/// let stream = CommandStream::finalize(|stream| {
1015///     stream.arg(ImageInfo::image_2d(
1016///         32,
1017///         32,
1018///         vk::Format::R8G8B8A8_UNORM,
1019///         vk::ImageUsageFlags::TRANSFER_DST,
1020///     ))
1021/// })
1022/// .into_stream();
1023///
1024/// let mut graph = Graph::new();
1025/// graph
1026///     .insert_cmd_stream(&stream)
1027///     .with_arg(stream.args, image)
1028///     .finish();
1029/// ```
1030pub struct CommandStreamRun<'a, A> {
1031    pub(crate) bindings: Vec<Option<usize>>,
1032    pub(crate) graph: &'a mut Graph,
1033    pub(crate) stream: &'a CommandStream<A>,
1034}
1035
1036impl<'a, A> CommandStreamRun<'a, A> {
1037    /// Sets a stream argument to a graph node for this invocation.
1038    pub fn with_arg<T, N>(mut self, arg: StreamArg<T>, node: N) -> Self
1039    where
1040        N: StreamArgBindable<T>,
1041    {
1042        #[cfg(feature = "checked")]
1043        assert!(
1044            arg.stream_id == self.stream.inner.stream_id,
1045            "argument belongs to a different command stream"
1046        );
1047        node.assert_parent_node();
1048        self.graph.assert_node_owner(&node);
1049        self.bindings[arg.arg_index] = Some(node.index());
1050        self
1051    }
1052
1053    /// Finishes this stream invocation and returns to the parent graph.
1054    pub fn finish(self) -> &'a mut Graph {
1055        #[cfg(feature = "checked")]
1056        assert!(
1057            self.bindings.iter().all(Option::is_some),
1058            "missing command stream argument"
1059        );
1060
1061        self.graph
1062            .append_command_stream(self.stream, &self.bindings);
1063        self.graph
1064    }
1065}
1066
1067impl Graph {
1068    /// Inserts a command stream into this graph.
1069    ///
1070    /// Prepared streams reduce repeated preparation work, but insertion still has argument binding,
1071    /// dependency reconciliation, scheduling, and recording costs.
1072    pub fn insert_cmd_stream<'a, A>(
1073        &'a mut self,
1074        stream: &'a CommandStream<A>,
1075    ) -> CommandStreamRun<'a, A> {
1076        CommandStreamRun {
1077            bindings: vec![None; stream.inner.args.len()],
1078            graph: self,
1079            stream,
1080        }
1081    }
1082}
1083
1084impl Graph {
1085    pub(crate) fn append_command_stream<A>(
1086        &mut self,
1087        stream: &CommandStream<A>,
1088        bindings: &[Option<usize>],
1089    ) {
1090        if stream.inner.prepared {
1091            self.append_prepared_command_stream(stream, bindings);
1092        } else {
1093            self.append_unprepared_command_stream(stream, bindings);
1094        }
1095    }
1096
1097    fn append_unprepared_command_stream<A>(
1098        &mut self,
1099        stream: &CommandStream<A>,
1100        bindings: &[Option<usize>],
1101    ) {
1102        let submission = stream
1103            .inner
1104            .submission
1105            .lock()
1106            .expect("poisoned command stream submission");
1107        let stream_graph = submission.graph();
1108        let mut arg_by_node = HashMap::new();
1109
1110        for (arg_idx, &node_idx) in stream.inner.arg_nodes.iter().enumerate() {
1111            arg_by_node.insert(node_idx, arg_idx);
1112        }
1113
1114        let mut node_map = Vec::with_capacity(stream_graph.resources.len());
1115        for (node_idx, resource) in stream_graph.resources.iter().enumerate() {
1116            if let Some(&arg_idx) = arg_by_node.get(&node_idx) {
1117                node_map.push(bindings[arg_idx].expect("missing command stream argument"));
1118            } else {
1119                node_map.push(self.resources.bind(resource.clone()));
1120            }
1121        }
1122
1123        for cmd in &stream_graph.cmds {
1124            let mut cmd = cmd.clone();
1125            cmd.remap_nodes(&node_map);
1126
1127            #[cfg(feature = "checked")]
1128            for exec in &mut cmd.execs {
1129                exec.stream_graph_id = Some(stream.inner.graph_id);
1130            }
1131
1132            self.cmds.push(cmd);
1133        }
1134    }
1135
1136    fn append_prepared_command_stream<A>(
1137        &mut self,
1138        stream: &CommandStream<A>,
1139        bindings: &[Option<usize>],
1140    ) {
1141        let stream_scope_id = next_stream_scope_id();
1142        let submission = stream
1143            .inner
1144            .submission
1145            .lock()
1146            .expect("poisoned command stream submission");
1147        let stream_graph = submission.graph();
1148        let mut arg_by_node = HashMap::new();
1149
1150        for (arg_idx, &node_idx) in stream.inner.arg_nodes.iter().enumerate() {
1151            arg_by_node.insert(node_idx, arg_idx);
1152        }
1153
1154        let mut cmd = self.begin_cmd().debug_name("command stream");
1155        cmd.set_stream_scope_id(stream_scope_id);
1156
1157        for stream_cmd in &stream_graph.cmds {
1158            for (node_idx, accesses) in stream_cmd
1159                .execs
1160                .iter()
1161                .flat_map(|exec| exec.accesses.iter())
1162            {
1163                let Some(&arg_idx) = arg_by_node.get(&node_idx) else {
1164                    continue;
1165                };
1166                let parent_node_idx = bindings[arg_idx].expect("missing command stream argument");
1167
1168                for access in accesses {
1169                    cmd.push_subresource_access_index(
1170                        parent_node_idx,
1171                        access.subresource,
1172                        access.access,
1173                    );
1174                }
1175            }
1176        }
1177
1178        drop(submission);
1179
1180        let stream = Arc::clone(&stream.inner);
1181        let bindings = bindings.to_vec();
1182        cmd.record_stream(move |cmd| {
1183            let submission = stream
1184                .submission
1185                .lock()
1186                .expect("poisoned command stream submission");
1187            let stream_graph = submission.graph();
1188            let mut arg_by_node = HashMap::new();
1189
1190            for (arg_idx, &node_idx) in stream.arg_nodes.iter().enumerate() {
1191                arg_by_node.insert(node_idx, arg_idx);
1192            }
1193
1194            let resources = stream_graph
1195                .resources
1196                .iter()
1197                .enumerate()
1198                .map(|(node_idx, resource)| {
1199                    if let Some(&arg_idx) = arg_by_node.get(&node_idx) {
1200                        cmd.clone_resource_at(
1201                            bindings[arg_idx].expect("missing command stream argument"),
1202                        )
1203                    } else {
1204                        resource.clone()
1205                    }
1206                })
1207                .collect();
1208            drop(submission);
1209
1210            stream
1211                .submission
1212                .lock()
1213                .expect("poisoned command stream submission")
1214                .record_prepared_command_stream(&cmd, ResourceMap::from_resources(resources))
1215                .expect("unable to record command stream");
1216        });
1217    }
1218}
1219
1220/// Information that can declare a typed [`CommandStream`] argument.
1221#[allow(private_bounds)]
1222#[doc(hidden)]
1223pub trait StreamArgInfo: stream_private::StreamArgInfoSealed {
1224    /// The typed argument handle returned for this info.
1225    type Arg;
1226
1227    #[doc(hidden)]
1228    fn bind_stream_arg(self, stream: &mut CommandStreamMut) -> Self::Arg;
1229}
1230
1231/// A graph node that can be supplied for a [`StreamArg`].
1232#[allow(private_bounds)]
1233#[doc(hidden)]
1234pub trait StreamArgBindable<T>: stream_private::StreamArgBindableSealed<T> + Node {
1235    #[doc(hidden)]
1236    fn assert_parent_node(&self);
1237}
1238
1239/// A graph node that can be borrowed while building a [`CommandStream`].
1240#[allow(private_bounds)]
1241#[doc(hidden)]
1242pub trait StreamResourceNode: stream_private::StreamResourceNodeSealed + Node {}
1243
1244mod stream_private {
1245    pub trait StreamArgInfoSealed {}
1246
1247    pub trait StreamArgBindableSealed<T> {}
1248
1249    pub trait StreamResourceNodeSealed {}
1250
1251    pub trait StreamPipelineSealed {}
1252}
1253
1254macro_rules! stream_arg_info {
1255    ($info:ty, $builder:ty, $variant:ident, $arg:ty) => {
1256        impl stream_private::StreamArgInfoSealed for $info {}
1257
1258        impl StreamArgInfo for $info {
1259            type Arg = $arg;
1260
1261            fn bind_stream_arg(self, stream: &mut CommandStreamMut) -> Self::Arg {
1262                let data = StreamArgData::$variant(self);
1263                let arg_index = stream.push_arg(data);
1264                let node_index = stream.bind_arg_resource(data);
1265                stream.arg_nodes.push(node_index);
1266                StreamArg::new(
1267                    arg_index,
1268                    node_index,
1269                    #[cfg(feature = "checked")]
1270                    stream.stream_id,
1271                    #[cfg(feature = "checked")]
1272                    stream.graph.graph_id(),
1273                )
1274            }
1275        }
1276
1277        impl stream_private::StreamArgInfoSealed for $builder {}
1278
1279        impl StreamArgInfo for $builder {
1280            type Arg = $arg;
1281
1282            fn bind_stream_arg(self, stream: &mut CommandStreamMut) -> Self::Arg {
1283                self.build().bind_stream_arg(stream)
1284            }
1285        }
1286    };
1287}
1288
1289stream_arg_info!(
1290    AccelerationStructureInfo,
1291    AccelerationStructureInfoBuilder,
1292    AccelerationStructure,
1293    AccelerationStructureArg
1294);
1295stream_arg_info!(BufferInfo, BufferInfoBuilder, Buffer, BufferArg);
1296stream_arg_info!(ImageInfo, ImageInfoBuilder, Image, ImageArg);
1297
1298macro_rules! stream_arg_bindable {
1299    ($resource:ty => $($node:ty),+ $(,)?) => {
1300        $(
1301            impl stream_private::StreamArgBindableSealed<$resource> for $node {}
1302
1303            impl StreamArgBindable<$resource> for $node {
1304                fn assert_parent_node(&self) {}
1305            }
1306        )+
1307    };
1308}
1309
1310stream_arg_bindable!(
1311    AccelerationStructure => AccelerationStructureNode,
1312    AccelerationStructureLeaseNode,
1313);
1314stream_arg_bindable!(Buffer => BufferNode, BufferLeaseNode);
1315stream_arg_bindable!(Image => ImageNode, ImageLeaseNode, SwapchainImageNode);
1316
1317impl stream_private::StreamArgBindableSealed<AccelerationStructure>
1318    for AnyAccelerationStructureNode
1319{
1320}
1321
1322impl StreamArgBindable<AccelerationStructure> for AnyAccelerationStructureNode {
1323    fn assert_parent_node(&self) {
1324        assert!(
1325            !matches!(self, Self::Arg(_)),
1326            "stream argument cannot be supplied as a parent graph node"
1327        );
1328    }
1329}
1330
1331impl stream_private::StreamArgBindableSealed<Buffer> for AnyBufferNode {}
1332
1333impl StreamArgBindable<Buffer> for AnyBufferNode {
1334    fn assert_parent_node(&self) {
1335        assert!(
1336            !matches!(self, Self::Arg(_)),
1337            "stream argument cannot be supplied as a parent graph node"
1338        );
1339    }
1340}
1341
1342impl stream_private::StreamArgBindableSealed<Image> for AnyImageNode {}
1343
1344impl StreamArgBindable<Image> for AnyImageNode {
1345    fn assert_parent_node(&self) {
1346        assert!(
1347            !matches!(self, Self::Arg(_)),
1348            "stream argument cannot be supplied as a parent graph node"
1349        );
1350    }
1351}
1352
1353macro_rules! stream_resource_node {
1354    ($($node:ty),+ $(,)?) => {
1355        $(
1356            impl stream_private::StreamResourceNodeSealed for $node {}
1357            impl StreamResourceNode for $node {}
1358        )+
1359    };
1360}
1361
1362stream_resource_node!(
1363    AccelerationStructureNode,
1364    AccelerationStructureLeaseNode,
1365    BufferNode,
1366    BufferLeaseNode,
1367    ImageNode,
1368    ImageLeaseNode,
1369    SwapchainImageNode,
1370);
1371
1372#[cfg(test)]
1373mod tests {
1374    use super::*;
1375    use crate::{
1376        driver::{
1377            descriptor_set::{DescriptorPool, DescriptorPoolInfo},
1378            render_pass::{RenderPass, RenderPassInfo},
1379        },
1380        pool::Pool,
1381    };
1382
1383    struct NoopPool;
1384
1385    impl Pool<DescriptorPoolInfo, DescriptorPool> for NoopPool {
1386        fn resource(
1387            &mut self,
1388            _: DescriptorPoolInfo,
1389        ) -> Result<crate::pool::Lease<DescriptorPool>, DriverError> {
1390            unreachable!()
1391        }
1392    }
1393
1394    impl Pool<RenderPassInfo, RenderPass> for NoopPool {
1395        fn resource(
1396            &mut self,
1397            _: RenderPassInfo,
1398        ) -> Result<crate::pool::Lease<RenderPass>, DriverError> {
1399            unreachable!()
1400        }
1401    }
1402
1403    #[test]
1404    fn empty_stream_can_be_inserted() {
1405        let stream = CommandStream::finalize(|_| {}).into_stream();
1406        let mut graph = Graph::new();
1407
1408        graph.insert_cmd_stream(&stream).finish();
1409    }
1410
1411    #[test]
1412    fn reusable_callback_can_prepare_stream() {
1413        let stream = CommandStream::finalize(|stream| {
1414            stream.begin_cmd().record_cmd(|_| {});
1415        })
1416        .into_stream();
1417        let mut graph = Graph::new();
1418
1419        graph.insert_cmd_stream(&stream).finish();
1420        assert_eq!(graph.cmds.len(), 1);
1421    }
1422
1423    #[test]
1424    fn reusable_callback_can_prepare_optimized_stream() {
1425        let mut pool = NoopPool;
1426        let stream = CommandStream::prepare(&mut pool, |stream| {
1427            stream.begin_cmd().record_cmd(|_| {});
1428        })
1429        .expect("prepare stream");
1430        let mut graph = Graph::new();
1431
1432        graph.insert_cmd_stream(&stream).finish();
1433        assert_eq!(graph.cmds.len(), 1);
1434    }
1435
1436    #[test]
1437    fn unprepared_stream_expands_commands() {
1438        let stream = CommandStream::finalize(|stream| {
1439            stream.begin_cmd().record_cmd(|_| {});
1440            stream.begin_cmd().record_cmd(|_| {});
1441        })
1442        .into_stream();
1443        let mut graph = Graph::new();
1444
1445        graph.insert_cmd_stream(&stream).finish();
1446        assert_eq!(graph.cmds.len(), 2);
1447    }
1448
1449    #[test]
1450    fn prepared_stream_is_opaque_by_default() {
1451        let mut pool = NoopPool;
1452        let stream = CommandStream::prepare(&mut pool, |stream| {
1453            stream.begin_cmd().record_cmd(|_| {});
1454            stream.begin_cmd().record_cmd(|_| {});
1455        })
1456        .expect("prepare stream");
1457        let mut graph = Graph::new();
1458
1459        graph.insert_cmd_stream(&stream).finish();
1460        assert_eq!(graph.cmds.len(), 1);
1461    }
1462
1463    #[test]
1464    fn image_arg_can_use_info_based_helpers() {
1465        let stream = CommandStream::finalize(|stream| {
1466            let output = stream.arg(ImageInfo::image_2d(
1467                1,
1468                1,
1469                vk::Format::R8G8B8A8_UNORM,
1470                vk::ImageUsageFlags::TRANSFER_DST,
1471            ));
1472
1473            stream.clear_color_image(output, [0.0, 0.0, 0.0, 0.0]);
1474
1475            output
1476        })
1477        .into_stream();
1478
1479        assert_eq!(stream.inner.args.len(), 1);
1480    }
1481
1482    #[test]
1483    #[should_panic(expected = "missing command stream argument")]
1484    fn missing_arg_panics_at_finish() {
1485        let stream = CommandStream::finalize(|stream| {
1486            stream.arg(ImageInfo::image_2d(
1487                1,
1488                1,
1489                vk::Format::R8G8B8A8_UNORM,
1490                vk::ImageUsageFlags::SAMPLED,
1491            ));
1492        })
1493        .into_stream();
1494        let mut graph = Graph::new();
1495
1496        graph.insert_cmd_stream(&stream).finish();
1497    }
1498
1499    #[test]
1500    #[cfg(feature = "checked")]
1501    #[should_panic(expected = "argument belongs to a different command stream")]
1502    fn wrong_stream_arg_panics_at_with_arg() {
1503        let stream_a = CommandStream::finalize(|stream| {
1504            stream.arg(ImageInfo::image_2d(
1505                1,
1506                1,
1507                vk::Format::R8G8B8A8_UNORM,
1508                vk::ImageUsageFlags::SAMPLED,
1509            ))
1510        })
1511        .into_stream();
1512        let stream_b = CommandStream::finalize(|stream| {
1513            stream.arg(ImageInfo::image_2d(
1514                1,
1515                1,
1516                vk::Format::R8G8B8A8_UNORM,
1517                vk::ImageUsageFlags::SAMPLED,
1518            ))
1519        })
1520        .into_stream();
1521        let mut graph = Graph::new();
1522
1523        graph
1524            .insert_cmd_stream(&stream_a)
1525            .with_arg(stream_b.args, AnyImageNode::from(stream_b.args))
1526            .finish();
1527    }
1528
1529    #[test]
1530    #[should_panic(expected = "stream argument cannot be supplied as a parent graph node")]
1531    fn stream_arg_cannot_bind_as_parent_graph_node() {
1532        let stream = CommandStream::finalize(|stream| {
1533            stream.arg(ImageInfo::image_2d(
1534                1,
1535                1,
1536                vk::Format::R8G8B8A8_UNORM,
1537                vk::ImageUsageFlags::SAMPLED,
1538            ))
1539        })
1540        .into_stream();
1541        let mut graph = Graph::new();
1542
1543        graph
1544            .insert_cmd_stream(&stream)
1545            .with_arg(stream.args, AnyImageNode::from(stream.args))
1546            .finish();
1547    }
1548
1549    #[test]
1550    #[should_panic(expected = "command stream contains a one-shot callback")]
1551    fn one_shot_callback_cannot_prepare_stream() {
1552        let _ = CommandStream::finalize(|stream| {
1553            stream.graph.begin_cmd().record_cmd(|_| {});
1554        });
1555    }
1556}