Skip to main content

vyre_driver_wgpu/engine/
graph.rs

1//! GPU-resident dispatch graph execution (Innovation I.14).
2//!
3//! A graph records many dependent pipeline dispatches into one command buffer
4//! and submits it once. This gives callers one CPU-to-GPU launch while the GPU
5//! executes the ordered kernel sequence already resident in the command queue.
6
7use crate::buffer::GpuBufferHandle;
8use crate::pipeline::compound::CompoundResource;
9use crate::pipeline::WgpuPipeline;
10use smallvec::SmallVec;
11use vyre_driver::{BackendError, DispatchConfig};
12
13/// A GPU-resident or host-side resource.
14#[derive(Clone)]
15pub enum GpuResource {
16    /// Host-side byte slice.
17    Borrowed(Vec<u8>),
18    /// GPU-resident buffer handle.
19    Resident(GpuBufferHandle),
20}
21
22impl From<Vec<u8>> for GpuResource {
23    fn from(bytes: Vec<u8>) -> Self {
24        Self::Borrowed(bytes)
25    }
26}
27
28impl From<GpuBufferHandle> for GpuResource {
29    fn from(handle: GpuBufferHandle) -> Self {
30        Self::Resident(handle)
31    }
32}
33
34/// Ordered graph of compiled wgpu pipeline dispatches.
35#[derive(Default)]
36pub struct GpuDispatchGraph {
37    ops: SmallVec<[GraphOp; 8]>,
38}
39
40#[derive(Clone)]
41struct GraphOp {
42    pipeline: WgpuPipeline,
43    input: GpuResource,
44}
45
46impl GpuDispatchGraph {
47    /// Create an empty graph.
48    #[must_use]
49    pub fn new() -> Self {
50        Self {
51            ops: SmallVec::new(),
52        }
53    }
54
55    /// Append one pipeline dispatch to the graph.
56    pub fn push(&mut self, pipeline: WgpuPipeline, input: impl Into<GpuResource>) {
57        self.ops.push(GraphOp {
58            pipeline,
59            input: input.into(),
60        });
61    }
62
63    /// Number of dispatch nodes in the graph.
64    #[must_use]
65    pub fn len(&self) -> usize {
66        self.ops.len()
67    }
68
69    /// Return true when the graph has no dispatch nodes.
70    #[must_use]
71    pub fn is_empty(&self) -> bool {
72        self.ops.is_empty()
73    }
74
75    /// Execute all graph nodes with one queue submission.
76    ///
77    /// # Errors
78    ///
79    /// Returns a backend error if any pipeline binding, dispatch, or readback
80    /// fails. Empty graphs return an empty output vector.
81    pub fn dispatch(&self, config: &DispatchConfig) -> Result<Vec<Vec<Vec<u8>>>, BackendError> {
82        if self.ops.is_empty() {
83            return Ok(Vec::new());
84        }
85
86        // V7-PERF-021: Zero-copy graph execution (I.14).
87        // Convert GpuResources to substrate-neutral Resources for the pipeline engine.
88        // Resident handles are identified by their process-stable id.
89        let mut internal_requests: SmallVec<[(&WgpuPipeline, CompoundResource<'_>); 8]> =
90            SmallVec::with_capacity(self.ops.len());
91        for op in &self.ops {
92            let res = match &op.input {
93                GpuResource::Borrowed(bytes) => CompoundResource::Borrowed(bytes),
94                GpuResource::Resident(handle) => CompoundResource::Resident(handle.id()),
95            };
96            internal_requests.push((&op.pipeline, res));
97        }
98
99        WgpuPipeline::dispatch_compound_borrowed(&internal_requests, config)
100    }
101}
102
103/// CPU-side launch accounting for graph dispatch.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct LaunchAccounting {
106    /// Number of queue submissions required by sequential per-op dispatch.
107    pub sequential_submissions: usize,
108    /// Number of queue submissions required by graph dispatch.
109    pub graph_submissions: usize,
110}
111
112impl LaunchAccounting {
113    /// Return the integer launch-count reduction from graph recording.
114    #[must_use]
115    pub fn reduction_factor(self) -> usize {
116        self.sequential_submissions / self.graph_submissions.max(1)
117    }
118}
119
120/// Compute launch-count accounting for an ordered graph with `op_count` nodes.
121#[must_use]
122pub fn launch_accounting(op_count: usize) -> LaunchAccounting {
123    LaunchAccounting {
124        sequential_submissions: op_count,
125        graph_submissions: usize::from(op_count > 0),
126    }
127}