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/// Distinct stream arguments must bind to distinct parent-graph nodes.
1040///
1041/// ```no_run
1042/// # use ash::vk;
1043/// # use vk_graph::{Graph, driver::image::ImageInfo, node::ImageNode, stream::CommandStream};
1044/// # let image: ImageNode = todo!();
1045/// let stream = CommandStream::finalize(|stream| {
1046///     stream.arg(ImageInfo::image_2d(
1047///         32,
1048///         32,
1049///         vk::Format::R8G8B8A8_UNORM,
1050///         vk::ImageUsageFlags::TRANSFER_DST,
1051///     ))
1052/// })
1053/// .into_stream();
1054///
1055/// let mut graph = Graph::new();
1056/// graph
1057///     .insert_cmd_stream(&stream)
1058///     .with_arg(stream.args, image)
1059///     .finish();
1060/// ```
1061pub struct CommandStreamRun<'a, A> {
1062    pub(crate) bindings: Vec<Option<usize>>,
1063    pub(crate) graph: &'a mut Graph,
1064    pub(crate) stream: &'a CommandStream<A>,
1065}
1066
1067impl<'a, A> CommandStreamRun<'a, A> {
1068    /// Sets a stream argument to a graph node for this invocation.
1069    ///
1070    /// The same argument may be rebound, but distinct arguments cannot bind to the same parent
1071    /// node. Stream scheduling and resource ownership tracking use node identity.
1072    ///
1073    /// # Panics
1074    ///
1075    /// Panics if another argument is already bound to `node`.
1076    pub fn with_arg<T, N>(mut self, arg: StreamArg<T>, node: N) -> Self
1077    where
1078        N: StreamArgBindable<T>,
1079    {
1080        #[cfg(feature = "checked")]
1081        assert!(
1082            arg.stream_id == self.stream.inner.stream_id,
1083            "argument belongs to a different command stream"
1084        );
1085        node.assert_parent_node();
1086        self.graph.assert_node_owner(&node);
1087        let node_idx = node.index();
1088        assert!(
1089            self.bindings
1090                .iter()
1091                .enumerate()
1092                .all(|(arg_idx, binding)| arg_idx == arg.arg_index || *binding != Some(node_idx)),
1093            "distinct command stream arguments cannot bind to the same parent graph node"
1094        );
1095        self.bindings[arg.arg_index] = Some(node_idx);
1096        self
1097    }
1098
1099    /// Finishes this stream invocation and returns to the parent graph.
1100    pub fn finish(self) -> &'a mut Graph {
1101        #[cfg(feature = "checked")]
1102        assert!(
1103            self.bindings.iter().all(Option::is_some),
1104            "missing command stream argument"
1105        );
1106
1107        self.graph
1108            .append_command_stream(self.stream, &self.bindings);
1109        self.graph
1110    }
1111}
1112
1113impl Graph {
1114    /// Inserts a command stream into this graph.
1115    ///
1116    /// Prepared streams reduce repeated preparation work, but insertion still has argument binding,
1117    /// dependency reconciliation, scheduling, and recording costs.
1118    pub fn insert_cmd_stream<'a, A>(
1119        &'a mut self,
1120        stream: &'a CommandStream<A>,
1121    ) -> CommandStreamRun<'a, A> {
1122        CommandStreamRun {
1123            bindings: vec![None; stream.inner.args.len()],
1124            graph: self,
1125            stream,
1126        }
1127    }
1128}
1129
1130impl Graph {
1131    pub(crate) fn append_command_stream<A>(
1132        &mut self,
1133        stream: &CommandStream<A>,
1134        bindings: &[Option<usize>],
1135    ) {
1136        if stream.inner.prepared {
1137            self.append_prepared_command_stream(stream, bindings);
1138        } else {
1139            self.append_unprepared_command_stream(stream, bindings);
1140        }
1141    }
1142
1143    fn append_unprepared_command_stream<A>(
1144        &mut self,
1145        stream: &CommandStream<A>,
1146        bindings: &[Option<usize>],
1147    ) {
1148        let submission = stream
1149            .inner
1150            .submission
1151            .lock()
1152            .expect("poisoned command stream submission");
1153        let stream_graph = submission.graph();
1154        let mut arg_by_node = HashMap::new();
1155
1156        for (arg_idx, &node_idx) in stream.inner.arg_nodes.iter().enumerate() {
1157            arg_by_node.insert(node_idx, arg_idx);
1158        }
1159
1160        let mut node_map = Vec::with_capacity(stream_graph.resources.len());
1161        for (node_idx, resource) in stream_graph.resources.iter().enumerate() {
1162            if let Some(&arg_idx) = arg_by_node.get(&node_idx) {
1163                node_map.push(bindings[arg_idx].expect("missing command stream argument"));
1164            } else {
1165                node_map.push(self.resources.bind(resource.clone()));
1166            }
1167        }
1168
1169        for cmd in &stream_graph.cmds {
1170            let mut cmd = cmd.clone();
1171            cmd.remap_nodes(&node_map);
1172
1173            #[cfg(feature = "checked")]
1174            for exec in &mut cmd.execs {
1175                exec.stream_graph_id = Some(stream.inner.graph_id);
1176            }
1177
1178            self.cmds.push(cmd);
1179        }
1180    }
1181
1182    fn append_prepared_command_stream<A>(
1183        &mut self,
1184        stream: &CommandStream<A>,
1185        bindings: &[Option<usize>],
1186    ) {
1187        let stream_scope_id = next_stream_scope_id();
1188        let submission = stream
1189            .inner
1190            .submission
1191            .lock()
1192            .expect("poisoned command stream submission");
1193        let stream_graph = submission.graph();
1194        let mut arg_by_node = HashMap::new();
1195
1196        for (arg_idx, &node_idx) in stream.inner.arg_nodes.iter().enumerate() {
1197            arg_by_node.insert(node_idx, arg_idx);
1198        }
1199
1200        let mut cmd = self.begin_cmd().debug_name("command stream");
1201        cmd.set_stream_scope_id(stream_scope_id);
1202
1203        for stream_cmd in &stream_graph.cmds {
1204            for (node_idx, accesses) in stream_cmd
1205                .execs
1206                .iter()
1207                .flat_map(|exec| exec.accesses.iter())
1208            {
1209                let Some(&arg_idx) = arg_by_node.get(&node_idx) else {
1210                    continue;
1211                };
1212                let parent_node_idx = bindings[arg_idx].expect("missing command stream argument");
1213
1214                for access in accesses {
1215                    cmd.push_subresource_access_index(
1216                        parent_node_idx,
1217                        access.subresource,
1218                        access.access,
1219                    );
1220                }
1221            }
1222        }
1223
1224        drop(submission);
1225
1226        let stream = Arc::clone(&stream.inner);
1227        let bindings = bindings.to_vec();
1228        cmd.record_stream(move |cmd| {
1229            let submission = stream
1230                .submission
1231                .lock()
1232                .expect("poisoned command stream submission");
1233            let stream_graph = submission.graph();
1234            let mut arg_by_node = HashMap::new();
1235
1236            for (arg_idx, &node_idx) in stream.arg_nodes.iter().enumerate() {
1237                arg_by_node.insert(node_idx, arg_idx);
1238            }
1239
1240            let resources = stream_graph
1241                .resources
1242                .iter()
1243                .enumerate()
1244                .map(|(node_idx, resource)| {
1245                    if let Some(&arg_idx) = arg_by_node.get(&node_idx) {
1246                        cmd.clone_resource_at(
1247                            bindings[arg_idx].expect("missing command stream argument"),
1248                        )
1249                    } else {
1250                        resource.clone()
1251                    }
1252                })
1253                .collect();
1254            drop(submission);
1255
1256            stream
1257                .submission
1258                .lock()
1259                .expect("poisoned command stream submission")
1260                .record_prepared_command_stream(&cmd, ResourceMap::from_resources(resources))
1261                .expect("unable to record command stream");
1262        });
1263    }
1264}
1265
1266/// Information that can declare a typed [`CommandStream`] argument.
1267#[allow(private_bounds)]
1268#[doc(hidden)]
1269pub trait StreamArgInfo: stream_private::StreamArgInfoSealed {
1270    /// The typed argument handle returned for this info.
1271    type Arg;
1272
1273    #[doc(hidden)]
1274    fn bind_stream_arg(self, stream: &mut CommandStreamMut) -> Self::Arg;
1275}
1276
1277/// A graph node that can be supplied for a [`StreamArg`].
1278#[allow(private_bounds)]
1279#[doc(hidden)]
1280pub trait StreamArgBindable<T>: stream_private::StreamArgBindableSealed<T> + Node {
1281    #[doc(hidden)]
1282    fn assert_parent_node(&self);
1283}
1284
1285/// A graph node that can be borrowed while building a [`CommandStream`].
1286#[allow(private_bounds)]
1287#[doc(hidden)]
1288pub trait StreamResourceNode: stream_private::StreamResourceNodeSealed + Node {}
1289
1290mod stream_private {
1291    pub trait StreamArgInfoSealed {}
1292
1293    pub trait StreamArgBindableSealed<T> {}
1294
1295    pub trait StreamResourceNodeSealed {}
1296
1297    pub trait StreamPipelineSealed {}
1298}
1299
1300macro_rules! stream_arg_info {
1301    ($info:ty, $builder:ty, $variant:ident, $arg:ty) => {
1302        impl stream_private::StreamArgInfoSealed for $info {}
1303
1304        impl StreamArgInfo for $info {
1305            type Arg = $arg;
1306
1307            fn bind_stream_arg(self, stream: &mut CommandStreamMut) -> Self::Arg {
1308                let data = StreamArgData::$variant(self);
1309                let arg_index = stream.push_arg(data);
1310                let node_index = stream.bind_arg_resource(data);
1311                stream.arg_nodes.push(node_index);
1312                StreamArg::new(
1313                    arg_index,
1314                    node_index,
1315                    #[cfg(feature = "checked")]
1316                    stream.stream_id,
1317                    #[cfg(feature = "checked")]
1318                    stream.graph.graph_id(),
1319                )
1320            }
1321        }
1322
1323        impl stream_private::StreamArgInfoSealed for $builder {}
1324
1325        impl StreamArgInfo for $builder {
1326            type Arg = $arg;
1327
1328            fn bind_stream_arg(self, stream: &mut CommandStreamMut) -> Self::Arg {
1329                self.build().bind_stream_arg(stream)
1330            }
1331        }
1332    };
1333}
1334
1335stream_arg_info!(
1336    AccelerationStructureInfo,
1337    AccelerationStructureInfoBuilder,
1338    AccelerationStructure,
1339    AccelerationStructureArg
1340);
1341stream_arg_info!(BufferInfo, BufferInfoBuilder, Buffer, BufferArg);
1342stream_arg_info!(ImageInfo, ImageInfoBuilder, Image, ImageArg);
1343
1344macro_rules! stream_arg_bindable {
1345    ($resource:ty => $($node:ty),+ $(,)?) => {
1346        $(
1347            impl stream_private::StreamArgBindableSealed<$resource> for $node {}
1348
1349            impl StreamArgBindable<$resource> for $node {
1350                fn assert_parent_node(&self) {}
1351            }
1352        )+
1353    };
1354}
1355
1356stream_arg_bindable!(
1357    AccelerationStructure => AccelerationStructureNode,
1358    AccelerationStructureLeaseNode,
1359);
1360stream_arg_bindable!(Buffer => BufferNode, BufferLeaseNode);
1361stream_arg_bindable!(Image => ImageNode, ImageLeaseNode, SwapchainImageNode);
1362
1363impl stream_private::StreamArgBindableSealed<AccelerationStructure>
1364    for AnyAccelerationStructureNode
1365{
1366}
1367
1368impl StreamArgBindable<AccelerationStructure> for AnyAccelerationStructureNode {
1369    fn assert_parent_node(&self) {
1370        assert!(
1371            !matches!(self, Self::Arg(_)),
1372            "stream argument cannot be supplied as a parent graph node"
1373        );
1374    }
1375}
1376
1377impl stream_private::StreamArgBindableSealed<Buffer> for AnyBufferNode {}
1378
1379impl StreamArgBindable<Buffer> for AnyBufferNode {
1380    fn assert_parent_node(&self) {
1381        assert!(
1382            !matches!(self, Self::Arg(_)),
1383            "stream argument cannot be supplied as a parent graph node"
1384        );
1385    }
1386}
1387
1388impl stream_private::StreamArgBindableSealed<Image> for AnyImageNode {}
1389
1390impl StreamArgBindable<Image> for AnyImageNode {
1391    fn assert_parent_node(&self) {
1392        assert!(
1393            !matches!(self, Self::Arg(_)),
1394            "stream argument cannot be supplied as a parent graph node"
1395        );
1396    }
1397}
1398
1399macro_rules! stream_resource_node {
1400    ($($node:ty),+ $(,)?) => {
1401        $(
1402            impl stream_private::StreamResourceNodeSealed for $node {}
1403            impl StreamResourceNode for $node {}
1404        )+
1405    };
1406}
1407
1408stream_resource_node!(
1409    AccelerationStructureNode,
1410    AccelerationStructureLeaseNode,
1411    BufferNode,
1412    BufferLeaseNode,
1413    ImageNode,
1414    ImageLeaseNode,
1415    SwapchainImageNode,
1416);
1417
1418#[cfg(test)]
1419mod test {
1420    use super::*;
1421    use crate::{
1422        driver::{
1423            buffer::BufferInfo,
1424            descriptor_set::{DescriptorPool, DescriptorPoolInfo},
1425            render_pass::{RenderPass, RenderPassInfo},
1426        },
1427        pool::Pool,
1428    };
1429
1430    struct NoopPool;
1431
1432    impl Pool<DescriptorPoolInfo, DescriptorPool> for NoopPool {
1433        fn resource(
1434            &mut self,
1435            _: DescriptorPoolInfo,
1436        ) -> Result<crate::pool::Lease<DescriptorPool>, DriverError> {
1437            unreachable!()
1438        }
1439    }
1440
1441    impl Pool<RenderPassInfo, RenderPass> for NoopPool {
1442        fn resource(
1443            &mut self,
1444            _: RenderPassInfo,
1445        ) -> Result<crate::pool::Lease<RenderPass>, DriverError> {
1446            unreachable!()
1447        }
1448    }
1449
1450    fn bind_test_buffer(graph: &mut Graph) -> BufferNode {
1451        let index = graph.bind_stream_arg_resource(AnyResource::BufferArg(BufferInfo::device_mem(
1452            4,
1453            vk::BufferUsageFlags::TRANSFER_SRC,
1454        )));
1455        BufferNode::new(
1456            index,
1457            #[cfg(feature = "checked")]
1458            graph.graph_id(),
1459        )
1460    }
1461
1462    fn two_buffer_arg_stream() -> CommandStreamDraft<(BufferArg, BufferArg)> {
1463        CommandStream::finalize(|stream| {
1464            let first = stream.arg(BufferInfo::device_mem(
1465                4,
1466                vk::BufferUsageFlags::TRANSFER_SRC,
1467            ));
1468            let second = stream.arg(BufferInfo::device_mem(
1469                4,
1470                vk::BufferUsageFlags::TRANSFER_DST,
1471            ));
1472
1473            stream
1474                .begin_cmd()
1475                .resource_access(first, vk_sync::AccessType::TransferRead)
1476                .record_cmd(|_| {});
1477            stream
1478                .begin_cmd()
1479                .resource_access(second, vk_sync::AccessType::TransferWrite)
1480                .record_cmd(|_| {});
1481            stream
1482                .begin_cmd()
1483                .resource_access(first, vk_sync::AccessType::TransferRead)
1484                .record_cmd(|_| {});
1485
1486            (first, second)
1487        })
1488    }
1489
1490    #[test]
1491    fn empty_stream_can_be_inserted() {
1492        let stream = CommandStream::finalize(|_| {}).into_stream();
1493        let mut graph = Graph::new();
1494
1495        graph.insert_cmd_stream(&stream).finish();
1496    }
1497
1498    #[test]
1499    fn reusable_callback_can_prepare_stream() {
1500        let stream = CommandStream::finalize(|stream| {
1501            stream.begin_cmd().record_cmd(|_| {});
1502        })
1503        .into_stream();
1504        let mut graph = Graph::new();
1505
1506        graph.insert_cmd_stream(&stream).finish();
1507        assert_eq!(graph.cmds.len(), 1);
1508    }
1509
1510    #[test]
1511    fn graph_copy_wrapper_can_prepare_stream() {
1512        let _stream = CommandStream::finalize(|stream| {
1513            let src = stream.arg(BufferInfo::device_mem(
1514                4,
1515                vk::BufferUsageFlags::TRANSFER_SRC,
1516            ));
1517            let dst = stream.arg(BufferInfo::device_mem(
1518                4,
1519                vk::BufferUsageFlags::TRANSFER_DST,
1520            ));
1521
1522            stream.graph.copy_buffer(src, dst);
1523        })
1524        .into_stream();
1525    }
1526
1527    #[test]
1528    fn reusable_callback_can_prepare_optimized_stream() {
1529        let mut pool = NoopPool;
1530        let stream = CommandStream::prepare(&mut pool, |stream| {
1531            stream.begin_cmd().record_cmd(|_| {});
1532        })
1533        .expect("prepare stream");
1534        let mut graph = Graph::new();
1535
1536        graph.insert_cmd_stream(&stream).finish();
1537        assert_eq!(graph.cmds.len(), 1);
1538    }
1539
1540    #[test]
1541    fn unprepared_stream_expands_commands() {
1542        let stream = CommandStream::finalize(|stream| {
1543            stream.begin_cmd().record_cmd(|_| {});
1544            stream.begin_cmd().record_cmd(|_| {});
1545        })
1546        .into_stream();
1547        let mut graph = Graph::new();
1548
1549        graph.insert_cmd_stream(&stream).finish();
1550        assert_eq!(graph.cmds.len(), 2);
1551    }
1552
1553    #[test]
1554    fn prepared_stream_is_opaque_by_default() {
1555        let mut pool = NoopPool;
1556        let stream = CommandStream::prepare(&mut pool, |stream| {
1557            stream.begin_cmd().record_cmd(|_| {});
1558            stream.begin_cmd().record_cmd(|_| {});
1559        })
1560        .expect("prepare stream");
1561        let mut graph = Graph::new();
1562
1563        graph.insert_cmd_stream(&stream).finish();
1564        assert_eq!(graph.cmds.len(), 1);
1565    }
1566
1567    #[test]
1568    #[should_panic(
1569        expected = "distinct command stream arguments cannot bind to the same parent graph node"
1570    )]
1571    fn unprepared_stream_rejects_aliased_arguments() {
1572        let stream = two_buffer_arg_stream().into_stream();
1573        let mut graph = Graph::new();
1574        let buffer = bind_test_buffer(&mut graph);
1575
1576        graph
1577            .insert_cmd_stream(&stream)
1578            .with_arg(stream.args.0, buffer)
1579            .with_arg(stream.args.1, buffer)
1580            .finish();
1581    }
1582
1583    #[test]
1584    #[should_panic(
1585        expected = "distinct command stream arguments cannot bind to the same parent graph node"
1586    )]
1587    fn prepared_stream_rejects_aliased_arguments() {
1588        let mut pool = NoopPool;
1589        let stream = two_buffer_arg_stream()
1590            .prepare(&mut pool)
1591            .expect("prepare stream");
1592        let mut graph = Graph::new();
1593        let buffer = bind_test_buffer(&mut graph);
1594
1595        graph
1596            .insert_cmd_stream(&stream)
1597            .with_arg(stream.args.0, buffer)
1598            .with_arg(stream.args.1, buffer)
1599            .finish();
1600    }
1601
1602    #[test]
1603    fn same_argument_can_be_rebound() {
1604        let stream = two_buffer_arg_stream().into_stream();
1605        let mut graph = Graph::new();
1606        let first = bind_test_buffer(&mut graph);
1607        let second = bind_test_buffer(&mut graph);
1608
1609        graph
1610            .insert_cmd_stream(&stream)
1611            .with_arg(stream.args.0, first)
1612            .with_arg(stream.args.0, second)
1613            .with_arg(stream.args.1, first)
1614            .finish();
1615    }
1616
1617    #[test]
1618    fn image_arg_can_use_info_based_helpers() {
1619        let stream = CommandStream::finalize(|stream| {
1620            let output = stream.arg(ImageInfo::image_2d(
1621                1,
1622                1,
1623                vk::Format::R8G8B8A8_UNORM,
1624                vk::ImageUsageFlags::TRANSFER_DST,
1625            ));
1626
1627            stream.clear_color_image(output, [0.0, 0.0, 0.0, 0.0]);
1628
1629            output
1630        })
1631        .into_stream();
1632
1633        assert_eq!(stream.inner.args.len(), 1);
1634    }
1635
1636    #[test]
1637    #[should_panic(expected = "missing command stream argument")]
1638    fn missing_arg_panics_at_finish() {
1639        let stream = CommandStream::finalize(|stream| {
1640            stream.arg(ImageInfo::image_2d(
1641                1,
1642                1,
1643                vk::Format::R8G8B8A8_UNORM,
1644                vk::ImageUsageFlags::SAMPLED,
1645            ));
1646        })
1647        .into_stream();
1648        let mut graph = Graph::new();
1649
1650        graph.insert_cmd_stream(&stream).finish();
1651    }
1652
1653    #[test]
1654    #[cfg(feature = "checked")]
1655    #[should_panic(expected = "argument belongs to a different command stream")]
1656    fn wrong_stream_arg_panics_at_with_arg() {
1657        let stream_a = CommandStream::finalize(|stream| {
1658            stream.arg(ImageInfo::image_2d(
1659                1,
1660                1,
1661                vk::Format::R8G8B8A8_UNORM,
1662                vk::ImageUsageFlags::SAMPLED,
1663            ))
1664        })
1665        .into_stream();
1666        let stream_b = CommandStream::finalize(|stream| {
1667            stream.arg(ImageInfo::image_2d(
1668                1,
1669                1,
1670                vk::Format::R8G8B8A8_UNORM,
1671                vk::ImageUsageFlags::SAMPLED,
1672            ))
1673        })
1674        .into_stream();
1675        let mut graph = Graph::new();
1676
1677        graph
1678            .insert_cmd_stream(&stream_a)
1679            .with_arg(stream_b.args, AnyImageNode::from(stream_b.args))
1680            .finish();
1681    }
1682
1683    #[test]
1684    #[should_panic(expected = "stream argument cannot be supplied as a parent graph node")]
1685    fn stream_arg_cannot_bind_as_parent_graph_node() {
1686        let stream = CommandStream::finalize(|stream| {
1687            stream.arg(ImageInfo::image_2d(
1688                1,
1689                1,
1690                vk::Format::R8G8B8A8_UNORM,
1691                vk::ImageUsageFlags::SAMPLED,
1692            ))
1693        })
1694        .into_stream();
1695        let mut graph = Graph::new();
1696
1697        graph
1698            .insert_cmd_stream(&stream)
1699            .with_arg(stream.args, AnyImageNode::from(stream.args))
1700            .finish();
1701    }
1702
1703    #[test]
1704    #[should_panic(expected = "command stream contains a one-shot callback")]
1705    fn one_shot_callback_cannot_prepare_stream() {
1706        let _ = CommandStream::finalize(|stream| {
1707            stream.graph.begin_cmd().record_cmd(|_| {});
1708        });
1709    }
1710}