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    /// Deprecated stream equivalent of explicit-region blitting.
765    #[doc(hidden)]
766    #[deprecated(note = "use Command::blit_image for explicit regions")]
767    pub fn blit_image_region(
768        &mut self,
769        src: impl Into<AnyImageNode>,
770        dst: impl Into<AnyImageNode>,
771        filter: vk::Filter,
772        regions: impl AsRef<[vk::ImageBlit]> + 'static + Send,
773    ) -> &mut Self {
774        self.graph
775            .begin_cmd()
776            .debug_name("blit image")
777            .blit_image(src, dst, filter, regions)
778            .end_cmd();
779        self
780    }
781
782    /// Stream equivalent of [`Graph::clear_color_image`].
783    pub fn clear_color_image(
784        &mut self,
785        image: impl Into<AnyImageNode>,
786        color: impl Into<ClearColorValue>,
787    ) -> &mut Self {
788        self.graph.clear_color_image(image, color);
789        self
790    }
791
792    /// Stream equivalent of [`Graph::clear_depth_stencil_image`].
793    pub fn clear_depth_stencil_image(
794        &mut self,
795        image: impl Into<AnyImageNode>,
796        depth: f32,
797        stencil: u32,
798    ) -> &mut Self {
799        self.graph.clear_depth_stencil_image(image, depth, stencil);
800        self
801    }
802
803    /// Stream equivalent of [`Graph::copy_buffer`].
804    pub fn copy_buffer(
805        &mut self,
806        src: impl Into<AnyBufferNode>,
807        dst: impl Into<AnyBufferNode>,
808    ) -> &mut Self {
809        self.graph.copy_buffer(src, dst);
810        self
811    }
812
813    /// Deprecated stream equivalent of explicit-region buffer copies.
814    #[doc(hidden)]
815    #[deprecated(note = "use Command::copy_buffer for explicit regions")]
816    pub fn copy_buffer_region(
817        &mut self,
818        src: impl Into<AnyBufferNode>,
819        dst: impl Into<AnyBufferNode>,
820        regions: impl AsRef<[vk::BufferCopy]> + 'static + Send,
821    ) -> &mut Self {
822        self.graph
823            .begin_cmd()
824            .debug_name("copy buffer")
825            .copy_buffer(src, dst, regions)
826            .end_cmd();
827        self
828    }
829
830    /// Stream equivalent of [`Graph::copy_buffer_to_image`].
831    pub fn copy_buffer_to_image(
832        &mut self,
833        src: impl Into<AnyBufferNode>,
834        dst: impl Into<AnyImageNode>,
835    ) -> &mut Self {
836        self.graph.copy_buffer_to_image(src, dst);
837        self
838    }
839
840    /// Deprecated stream equivalent of explicit-region buffer-to-image copies.
841    #[doc(hidden)]
842    #[deprecated(note = "use Command::copy_buffer_to_image for explicit regions")]
843    pub fn copy_buffer_to_image_region(
844        &mut self,
845        src: impl Into<AnyBufferNode>,
846        dst: impl Into<AnyImageNode>,
847        regions: impl AsRef<[vk::BufferImageCopy]> + 'static + Send,
848    ) -> &mut Self {
849        self.graph
850            .begin_cmd()
851            .debug_name("copy buffer to image")
852            .copy_buffer_to_image(src, dst, regions)
853            .end_cmd();
854        self
855    }
856
857    /// Stream equivalent of [`Graph::copy_image`].
858    pub fn copy_image(
859        &mut self,
860        src: impl Into<AnyImageNode>,
861        dst: impl Into<AnyImageNode>,
862    ) -> &mut Self {
863        self.graph.copy_image(src, dst);
864        self
865    }
866
867    /// Deprecated stream equivalent of explicit-region image copies.
868    #[doc(hidden)]
869    #[deprecated(note = "use Command::copy_image for explicit regions")]
870    pub fn copy_image_region(
871        &mut self,
872        src: impl Into<AnyImageNode>,
873        dst: impl Into<AnyImageNode>,
874        regions: impl AsRef<[vk::ImageCopy]> + 'static + Send,
875    ) -> &mut Self {
876        self.graph
877            .begin_cmd()
878            .debug_name("copy image")
879            .copy_image(src, dst, regions)
880            .end_cmd();
881        self
882    }
883
884    /// Stream equivalent of [`Graph::copy_image_to_buffer`].
885    pub fn copy_image_to_buffer(
886        &mut self,
887        src: impl Into<AnyImageNode>,
888        dst: impl Into<AnyBufferNode>,
889    ) -> &mut Self {
890        self.graph.copy_image_to_buffer(src, dst);
891        self
892    }
893
894    /// Deprecated stream equivalent of explicit-region image-to-buffer copies.
895    #[doc(hidden)]
896    #[deprecated(note = "use Command::copy_image_to_buffer for explicit regions")]
897    pub fn copy_image_to_buffer_region(
898        &mut self,
899        src: impl Into<AnyImageNode>,
900        dst: impl Into<AnyBufferNode>,
901        regions: impl AsRef<[vk::BufferImageCopy]> + 'static + Send,
902    ) -> &mut Self {
903        self.graph
904            .begin_cmd()
905            .debug_name("copy image to buffer")
906            .copy_image_to_buffer(src, dst, regions)
907            .end_cmd();
908        self
909    }
910
911    /// Stream equivalent of [`Graph::fill_buffer`].
912    pub fn fill_buffer(
913        &mut self,
914        buffer: impl Into<AnyBufferNode>,
915        region: Range<vk::DeviceSize>,
916        data: u32,
917    ) -> &mut Self {
918        self.graph.fill_buffer(buffer, region, data);
919        self
920    }
921
922    /// Stream equivalent of [`Graph::update_buffer`].
923    pub fn update_buffer(
924        &mut self,
925        buffer: impl Into<AnyBufferNode>,
926        offset: vk::DeviceSize,
927        data: impl AsRef<[u8]> + 'static + Send,
928    ) -> &mut Self {
929        self.graph.update_buffer(buffer, offset, data);
930        self
931    }
932
933    fn push_arg(&mut self, data: StreamArgData) -> usize {
934        let index = self.args.len();
935        self.args.push(data);
936        index
937    }
938
939    fn bind_arg_resource(&mut self, data: StreamArgData) -> usize {
940        let resource = match data {
941            StreamArgData::AccelerationStructure(info) => {
942                AnyResource::AccelerationStructureArg(info)
943            }
944            StreamArgData::Buffer(info) => AnyResource::BufferArg(info),
945            StreamArgData::Image(info) => AnyResource::ImageArg(info),
946        };
947
948        self.graph.bind_stream_arg_resource(resource)
949    }
950}
951
952impl CommandStream<()> {
953    /// Finalizes a reusable command stream without preparing optimizations.
954    ///
955    /// The returned draft can be inserted as an unprepared stream with [`CommandStreamDraft::into_stream`]
956    /// or prepared later with [`CommandStreamDraft::prepare`].
957    pub fn finalize<A>(build: impl FnOnce(&mut CommandStreamMut) -> A) -> CommandStreamDraft<A> {
958        let mut stream = CommandStreamMut {
959            arg_nodes: Vec::new(),
960            args: Vec::new(),
961            graph: Graph::new(),
962            #[cfg(feature = "checked")]
963            stream_id: CommandStreamId::next(),
964        };
965        let args = build(&mut stream);
966
967        #[cfg(feature = "checked")]
968        let graph_id = stream.graph.graph_id();
969
970        let submission = stream.graph.finalize();
971        submission.assert_reusable_commands();
972
973        CommandStreamDraft {
974            args,
975            inner: CommandStreamInner {
976                arg_nodes: stream.arg_nodes.into_boxed_slice(),
977                args: stream.args.into_boxed_slice(),
978                prepared: false,
979                submission: Mutex::new(submission),
980
981                #[cfg(feature = "checked")]
982                stream_id: stream.stream_id,
983
984                #[cfg(feature = "checked")]
985                graph_id,
986            },
987        }
988    }
989
990    /// Finalizes and prepares a reusable command stream.
991    ///
992    /// Prepared streams do more work up front so repeated insertions can reuse prepared scheduling
993    /// and static recording resources.
994    pub fn prepare<P, A>(
995        pool: &mut P,
996        build: impl FnOnce(&mut CommandStreamMut) -> A,
997    ) -> Result<CommandStream<A>, DriverError>
998    where
999        P: SubmissionPool,
1000    {
1001        Self::finalize(build).prepare(pool)
1002    }
1003}
1004
1005impl<A> CommandStreamDraft<A> {
1006    /// Converts this draft into a command stream without preparing optimizations.
1007    ///
1008    /// Unprepared streams avoid preparation cost until insertion, but they do not cache the prepared
1009    /// schedule or static recording resources.
1010    pub fn into_stream(self) -> CommandStream<A> {
1011        CommandStream {
1012            args: self.args,
1013            inner: Arc::new(self.inner),
1014        }
1015    }
1016
1017    /// Prepares this stream by optimizing its finalized graph and leasing static recording
1018    /// resources for the prepared schedule.
1019    ///
1020    /// This is most useful when the same stream is inserted many times with different arguments.
1021    pub fn prepare<P>(mut self, pool: &mut P) -> Result<CommandStream<A>, DriverError>
1022    where
1023        P: SubmissionPool,
1024    {
1025        self.inner
1026            .submission
1027            .get_mut()
1028            .expect("poisoned command stream submission")
1029            .prepare_command_stream(pool)?;
1030        self.inner.prepared = true;
1031
1032        Ok(self.into_stream())
1033    }
1034}
1035
1036/// An in-progress invocation of a [`CommandStream`] into a [`Graph`].
1037///
1038/// Bind every declared stream argument before calling [`CommandStreamRun::finish`].
1039///
1040/// ```no_run
1041/// # use ash::vk;
1042/// # use vk_graph::{Graph, driver::image::ImageInfo, node::ImageNode, stream::CommandStream};
1043/// # let image: ImageNode = todo!();
1044/// let stream = CommandStream::finalize(|stream| {
1045///     stream.arg(ImageInfo::image_2d(
1046///         32,
1047///         32,
1048///         vk::Format::R8G8B8A8_UNORM,
1049///         vk::ImageUsageFlags::TRANSFER_DST,
1050///     ))
1051/// })
1052/// .into_stream();
1053///
1054/// let mut graph = Graph::new();
1055/// graph
1056///     .insert_cmd_stream(&stream)
1057///     .with_arg(stream.args, image)
1058///     .finish();
1059/// ```
1060pub struct CommandStreamRun<'a, A> {
1061    pub(crate) bindings: Vec<Option<usize>>,
1062    pub(crate) graph: &'a mut Graph,
1063    pub(crate) stream: &'a CommandStream<A>,
1064}
1065
1066impl<'a, A> CommandStreamRun<'a, A> {
1067    /// Sets a stream argument to a graph node for this invocation.
1068    pub fn with_arg<T, N>(mut self, arg: StreamArg<T>, node: N) -> Self
1069    where
1070        N: StreamArgBindable<T>,
1071    {
1072        #[cfg(feature = "checked")]
1073        assert!(
1074            arg.stream_id == self.stream.inner.stream_id,
1075            "argument belongs to a different command stream"
1076        );
1077        node.assert_parent_node();
1078        self.graph.assert_node_owner(&node);
1079        self.bindings[arg.arg_index] = Some(node.index());
1080        self
1081    }
1082
1083    /// Finishes this stream invocation and returns to the parent graph.
1084    pub fn finish(self) -> &'a mut Graph {
1085        #[cfg(feature = "checked")]
1086        assert!(
1087            self.bindings.iter().all(Option::is_some),
1088            "missing command stream argument"
1089        );
1090
1091        self.graph
1092            .append_command_stream(self.stream, &self.bindings);
1093        self.graph
1094    }
1095}
1096
1097impl Graph {
1098    /// Inserts a command stream into this graph.
1099    ///
1100    /// Prepared streams reduce repeated preparation work, but insertion still has argument binding,
1101    /// dependency reconciliation, scheduling, and recording costs.
1102    pub fn insert_cmd_stream<'a, A>(
1103        &'a mut self,
1104        stream: &'a CommandStream<A>,
1105    ) -> CommandStreamRun<'a, A> {
1106        CommandStreamRun {
1107            bindings: vec![None; stream.inner.args.len()],
1108            graph: self,
1109            stream,
1110        }
1111    }
1112}
1113
1114impl Graph {
1115    pub(crate) fn append_command_stream<A>(
1116        &mut self,
1117        stream: &CommandStream<A>,
1118        bindings: &[Option<usize>],
1119    ) {
1120        if stream.inner.prepared {
1121            self.append_prepared_command_stream(stream, bindings);
1122        } else {
1123            self.append_unprepared_command_stream(stream, bindings);
1124        }
1125    }
1126
1127    fn append_unprepared_command_stream<A>(
1128        &mut self,
1129        stream: &CommandStream<A>,
1130        bindings: &[Option<usize>],
1131    ) {
1132        let submission = stream
1133            .inner
1134            .submission
1135            .lock()
1136            .expect("poisoned command stream submission");
1137        let stream_graph = submission.graph();
1138        let mut arg_by_node = HashMap::new();
1139
1140        for (arg_idx, &node_idx) in stream.inner.arg_nodes.iter().enumerate() {
1141            arg_by_node.insert(node_idx, arg_idx);
1142        }
1143
1144        let mut node_map = Vec::with_capacity(stream_graph.resources.len());
1145        for (node_idx, resource) in stream_graph.resources.iter().enumerate() {
1146            if let Some(&arg_idx) = arg_by_node.get(&node_idx) {
1147                node_map.push(bindings[arg_idx].expect("missing command stream argument"));
1148            } else {
1149                node_map.push(self.resources.bind(resource.clone()));
1150            }
1151        }
1152
1153        for cmd in &stream_graph.cmds {
1154            let mut cmd = cmd.clone();
1155            cmd.remap_nodes(&node_map);
1156
1157            #[cfg(feature = "checked")]
1158            for exec in &mut cmd.execs {
1159                exec.stream_graph_id = Some(stream.inner.graph_id);
1160            }
1161
1162            self.cmds.push(cmd);
1163        }
1164    }
1165
1166    fn append_prepared_command_stream<A>(
1167        &mut self,
1168        stream: &CommandStream<A>,
1169        bindings: &[Option<usize>],
1170    ) {
1171        let stream_scope_id = next_stream_scope_id();
1172        let submission = stream
1173            .inner
1174            .submission
1175            .lock()
1176            .expect("poisoned command stream submission");
1177        let stream_graph = submission.graph();
1178        let mut arg_by_node = HashMap::new();
1179
1180        for (arg_idx, &node_idx) in stream.inner.arg_nodes.iter().enumerate() {
1181            arg_by_node.insert(node_idx, arg_idx);
1182        }
1183
1184        let mut cmd = self.begin_cmd().debug_name("command stream");
1185        cmd.set_stream_scope_id(stream_scope_id);
1186
1187        for stream_cmd in &stream_graph.cmds {
1188            for (node_idx, accesses) in stream_cmd
1189                .execs
1190                .iter()
1191                .flat_map(|exec| exec.accesses.iter())
1192            {
1193                let Some(&arg_idx) = arg_by_node.get(&node_idx) else {
1194                    continue;
1195                };
1196                let parent_node_idx = bindings[arg_idx].expect("missing command stream argument");
1197
1198                for access in accesses {
1199                    cmd.push_subresource_access_index(
1200                        parent_node_idx,
1201                        access.subresource,
1202                        access.access,
1203                    );
1204                }
1205            }
1206        }
1207
1208        drop(submission);
1209
1210        let stream = Arc::clone(&stream.inner);
1211        let bindings = bindings.to_vec();
1212        cmd.record_stream(move |cmd| {
1213            let submission = stream
1214                .submission
1215                .lock()
1216                .expect("poisoned command stream submission");
1217            let stream_graph = submission.graph();
1218            let mut arg_by_node = HashMap::new();
1219
1220            for (arg_idx, &node_idx) in stream.arg_nodes.iter().enumerate() {
1221                arg_by_node.insert(node_idx, arg_idx);
1222            }
1223
1224            let resources = stream_graph
1225                .resources
1226                .iter()
1227                .enumerate()
1228                .map(|(node_idx, resource)| {
1229                    if let Some(&arg_idx) = arg_by_node.get(&node_idx) {
1230                        cmd.clone_resource_at(
1231                            bindings[arg_idx].expect("missing command stream argument"),
1232                        )
1233                    } else {
1234                        resource.clone()
1235                    }
1236                })
1237                .collect();
1238            drop(submission);
1239
1240            stream
1241                .submission
1242                .lock()
1243                .expect("poisoned command stream submission")
1244                .record_prepared_command_stream(&cmd, ResourceMap::from_resources(resources))
1245                .expect("unable to record command stream");
1246        });
1247    }
1248}
1249
1250/// Information that can declare a typed [`CommandStream`] argument.
1251#[allow(private_bounds)]
1252#[doc(hidden)]
1253pub trait StreamArgInfo: stream_private::StreamArgInfoSealed {
1254    /// The typed argument handle returned for this info.
1255    type Arg;
1256
1257    #[doc(hidden)]
1258    fn bind_stream_arg(self, stream: &mut CommandStreamMut) -> Self::Arg;
1259}
1260
1261/// A graph node that can be supplied for a [`StreamArg`].
1262#[allow(private_bounds)]
1263#[doc(hidden)]
1264pub trait StreamArgBindable<T>: stream_private::StreamArgBindableSealed<T> + Node {
1265    #[doc(hidden)]
1266    fn assert_parent_node(&self);
1267}
1268
1269/// A graph node that can be borrowed while building a [`CommandStream`].
1270#[allow(private_bounds)]
1271#[doc(hidden)]
1272pub trait StreamResourceNode: stream_private::StreamResourceNodeSealed + Node {}
1273
1274mod stream_private {
1275    pub trait StreamArgInfoSealed {}
1276
1277    pub trait StreamArgBindableSealed<T> {}
1278
1279    pub trait StreamResourceNodeSealed {}
1280
1281    pub trait StreamPipelineSealed {}
1282}
1283
1284macro_rules! stream_arg_info {
1285    ($info:ty, $builder:ty, $variant:ident, $arg:ty) => {
1286        impl stream_private::StreamArgInfoSealed for $info {}
1287
1288        impl StreamArgInfo for $info {
1289            type Arg = $arg;
1290
1291            fn bind_stream_arg(self, stream: &mut CommandStreamMut) -> Self::Arg {
1292                let data = StreamArgData::$variant(self);
1293                let arg_index = stream.push_arg(data);
1294                let node_index = stream.bind_arg_resource(data);
1295                stream.arg_nodes.push(node_index);
1296                StreamArg::new(
1297                    arg_index,
1298                    node_index,
1299                    #[cfg(feature = "checked")]
1300                    stream.stream_id,
1301                    #[cfg(feature = "checked")]
1302                    stream.graph.graph_id(),
1303                )
1304            }
1305        }
1306
1307        impl stream_private::StreamArgInfoSealed for $builder {}
1308
1309        impl StreamArgInfo for $builder {
1310            type Arg = $arg;
1311
1312            fn bind_stream_arg(self, stream: &mut CommandStreamMut) -> Self::Arg {
1313                self.build().bind_stream_arg(stream)
1314            }
1315        }
1316    };
1317}
1318
1319stream_arg_info!(
1320    AccelerationStructureInfo,
1321    AccelerationStructureInfoBuilder,
1322    AccelerationStructure,
1323    AccelerationStructureArg
1324);
1325stream_arg_info!(BufferInfo, BufferInfoBuilder, Buffer, BufferArg);
1326stream_arg_info!(ImageInfo, ImageInfoBuilder, Image, ImageArg);
1327
1328macro_rules! stream_arg_bindable {
1329    ($resource:ty => $($node:ty),+ $(,)?) => {
1330        $(
1331            impl stream_private::StreamArgBindableSealed<$resource> for $node {}
1332
1333            impl StreamArgBindable<$resource> for $node {
1334                fn assert_parent_node(&self) {}
1335            }
1336        )+
1337    };
1338}
1339
1340stream_arg_bindable!(
1341    AccelerationStructure => AccelerationStructureNode,
1342    AccelerationStructureLeaseNode,
1343);
1344stream_arg_bindable!(Buffer => BufferNode, BufferLeaseNode);
1345stream_arg_bindable!(Image => ImageNode, ImageLeaseNode, SwapchainImageNode);
1346
1347impl stream_private::StreamArgBindableSealed<AccelerationStructure>
1348    for AnyAccelerationStructureNode
1349{
1350}
1351
1352impl StreamArgBindable<AccelerationStructure> for AnyAccelerationStructureNode {
1353    fn assert_parent_node(&self) {
1354        assert!(
1355            !matches!(self, Self::Arg(_)),
1356            "stream argument cannot be supplied as a parent graph node"
1357        );
1358    }
1359}
1360
1361impl stream_private::StreamArgBindableSealed<Buffer> for AnyBufferNode {}
1362
1363impl StreamArgBindable<Buffer> for AnyBufferNode {
1364    fn assert_parent_node(&self) {
1365        assert!(
1366            !matches!(self, Self::Arg(_)),
1367            "stream argument cannot be supplied as a parent graph node"
1368        );
1369    }
1370}
1371
1372impl stream_private::StreamArgBindableSealed<Image> for AnyImageNode {}
1373
1374impl StreamArgBindable<Image> for AnyImageNode {
1375    fn assert_parent_node(&self) {
1376        assert!(
1377            !matches!(self, Self::Arg(_)),
1378            "stream argument cannot be supplied as a parent graph node"
1379        );
1380    }
1381}
1382
1383macro_rules! stream_resource_node {
1384    ($($node:ty),+ $(,)?) => {
1385        $(
1386            impl stream_private::StreamResourceNodeSealed for $node {}
1387            impl StreamResourceNode for $node {}
1388        )+
1389    };
1390}
1391
1392stream_resource_node!(
1393    AccelerationStructureNode,
1394    AccelerationStructureLeaseNode,
1395    BufferNode,
1396    BufferLeaseNode,
1397    ImageNode,
1398    ImageLeaseNode,
1399    SwapchainImageNode,
1400);
1401
1402#[cfg(test)]
1403mod tests {
1404    use super::*;
1405    use crate::{
1406        driver::{
1407            buffer::BufferInfo,
1408            descriptor_set::{DescriptorPool, DescriptorPoolInfo},
1409            render_pass::{RenderPass, RenderPassInfo},
1410        },
1411        pool::Pool,
1412    };
1413
1414    struct NoopPool;
1415
1416    impl Pool<DescriptorPoolInfo, DescriptorPool> for NoopPool {
1417        fn resource(
1418            &mut self,
1419            _: DescriptorPoolInfo,
1420        ) -> Result<crate::pool::Lease<DescriptorPool>, DriverError> {
1421            unreachable!()
1422        }
1423    }
1424
1425    impl Pool<RenderPassInfo, RenderPass> for NoopPool {
1426        fn resource(
1427            &mut self,
1428            _: RenderPassInfo,
1429        ) -> Result<crate::pool::Lease<RenderPass>, DriverError> {
1430            unreachable!()
1431        }
1432    }
1433
1434    #[test]
1435    fn empty_stream_can_be_inserted() {
1436        let stream = CommandStream::finalize(|_| {}).into_stream();
1437        let mut graph = Graph::new();
1438
1439        graph.insert_cmd_stream(&stream).finish();
1440    }
1441
1442    #[test]
1443    fn reusable_callback_can_prepare_stream() {
1444        let stream = CommandStream::finalize(|stream| {
1445            stream.begin_cmd().record_cmd(|_| {});
1446        })
1447        .into_stream();
1448        let mut graph = Graph::new();
1449
1450        graph.insert_cmd_stream(&stream).finish();
1451        assert_eq!(graph.cmds.len(), 1);
1452    }
1453
1454    #[test]
1455    fn graph_copy_wrapper_can_prepare_stream() {
1456        let _stream = CommandStream::finalize(|stream| {
1457            let src = stream.arg(BufferInfo::device_mem(
1458                4,
1459                vk::BufferUsageFlags::TRANSFER_SRC,
1460            ));
1461            let dst = stream.arg(BufferInfo::device_mem(
1462                4,
1463                vk::BufferUsageFlags::TRANSFER_DST,
1464            ));
1465
1466            stream.graph.copy_buffer(src, dst);
1467        })
1468        .into_stream();
1469    }
1470
1471    #[test]
1472    fn reusable_callback_can_prepare_optimized_stream() {
1473        let mut pool = NoopPool;
1474        let stream = CommandStream::prepare(&mut pool, |stream| {
1475            stream.begin_cmd().record_cmd(|_| {});
1476        })
1477        .expect("prepare stream");
1478        let mut graph = Graph::new();
1479
1480        graph.insert_cmd_stream(&stream).finish();
1481        assert_eq!(graph.cmds.len(), 1);
1482    }
1483
1484    #[test]
1485    fn unprepared_stream_expands_commands() {
1486        let stream = CommandStream::finalize(|stream| {
1487            stream.begin_cmd().record_cmd(|_| {});
1488            stream.begin_cmd().record_cmd(|_| {});
1489        })
1490        .into_stream();
1491        let mut graph = Graph::new();
1492
1493        graph.insert_cmd_stream(&stream).finish();
1494        assert_eq!(graph.cmds.len(), 2);
1495    }
1496
1497    #[test]
1498    fn prepared_stream_is_opaque_by_default() {
1499        let mut pool = NoopPool;
1500        let stream = CommandStream::prepare(&mut pool, |stream| {
1501            stream.begin_cmd().record_cmd(|_| {});
1502            stream.begin_cmd().record_cmd(|_| {});
1503        })
1504        .expect("prepare stream");
1505        let mut graph = Graph::new();
1506
1507        graph.insert_cmd_stream(&stream).finish();
1508        assert_eq!(graph.cmds.len(), 1);
1509    }
1510
1511    #[test]
1512    fn image_arg_can_use_info_based_helpers() {
1513        let stream = CommandStream::finalize(|stream| {
1514            let output = stream.arg(ImageInfo::image_2d(
1515                1,
1516                1,
1517                vk::Format::R8G8B8A8_UNORM,
1518                vk::ImageUsageFlags::TRANSFER_DST,
1519            ));
1520
1521            stream.clear_color_image(output, [0.0, 0.0, 0.0, 0.0]);
1522
1523            output
1524        })
1525        .into_stream();
1526
1527        assert_eq!(stream.inner.args.len(), 1);
1528    }
1529
1530    #[test]
1531    #[should_panic(expected = "missing command stream argument")]
1532    fn missing_arg_panics_at_finish() {
1533        let stream = CommandStream::finalize(|stream| {
1534            stream.arg(ImageInfo::image_2d(
1535                1,
1536                1,
1537                vk::Format::R8G8B8A8_UNORM,
1538                vk::ImageUsageFlags::SAMPLED,
1539            ));
1540        })
1541        .into_stream();
1542        let mut graph = Graph::new();
1543
1544        graph.insert_cmd_stream(&stream).finish();
1545    }
1546
1547    #[test]
1548    #[cfg(feature = "checked")]
1549    #[should_panic(expected = "argument belongs to a different command stream")]
1550    fn wrong_stream_arg_panics_at_with_arg() {
1551        let stream_a = CommandStream::finalize(|stream| {
1552            stream.arg(ImageInfo::image_2d(
1553                1,
1554                1,
1555                vk::Format::R8G8B8A8_UNORM,
1556                vk::ImageUsageFlags::SAMPLED,
1557            ))
1558        })
1559        .into_stream();
1560        let stream_b = CommandStream::finalize(|stream| {
1561            stream.arg(ImageInfo::image_2d(
1562                1,
1563                1,
1564                vk::Format::R8G8B8A8_UNORM,
1565                vk::ImageUsageFlags::SAMPLED,
1566            ))
1567        })
1568        .into_stream();
1569        let mut graph = Graph::new();
1570
1571        graph
1572            .insert_cmd_stream(&stream_a)
1573            .with_arg(stream_b.args, AnyImageNode::from(stream_b.args))
1574            .finish();
1575    }
1576
1577    #[test]
1578    #[should_panic(expected = "stream argument cannot be supplied as a parent graph node")]
1579    fn stream_arg_cannot_bind_as_parent_graph_node() {
1580        let stream = CommandStream::finalize(|stream| {
1581            stream.arg(ImageInfo::image_2d(
1582                1,
1583                1,
1584                vk::Format::R8G8B8A8_UNORM,
1585                vk::ImageUsageFlags::SAMPLED,
1586            ))
1587        })
1588        .into_stream();
1589        let mut graph = Graph::new();
1590
1591        graph
1592            .insert_cmd_stream(&stream)
1593            .with_arg(stream.args, AnyImageNode::from(stream.args))
1594            .finish();
1595    }
1596
1597    #[test]
1598    #[should_panic(expected = "command stream contains a one-shot callback")]
1599    fn one_shot_callback_cannot_prepare_stream() {
1600        let _ = CommandStream::finalize(|stream| {
1601            stream.graph.begin_cmd().record_cmd(|_| {});
1602        });
1603    }
1604}