use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode, cuGraphGetNodes};
use cudarc::driver::{CudaContext, CudaGraph};
use crate::{Error, Gpu};
pub struct Graph(CudaGraph);
unsafe impl Send for Graph {}
impl std::fmt::Debug for Graph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Graph")
}
}
impl Graph {
pub fn launch(&self) -> Result<(), Error> {
self.0.launch()?;
Ok(())
}
pub fn as_cuda(&self) -> &CudaGraph {
&self.0
}
}
impl Gpu {
pub unsafe fn capture<E: From<Error>>(
&self,
enqueue: impl FnOnce() -> Result<(), E>,
) -> Result<Option<Graph>, E> {
let stream = self.stream();
stream.synchronize().map_err(Error::from)?;
let _tracking = unsafe { EventTrackingPause::new(self.context()) };
stream
.begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_THREAD_LOCAL)
.map_err(Error::from)?;
let enqueued = enqueue();
let graph = stream.end_capture(CUgraphInstantiate_flags(0));
enqueued?;
let Some(graph) = graph.map_err(Error::from)? else {
return Ok(None);
};
Ok((node_count(&graph)? > 0).then_some(Graph(graph)))
}
pub unsafe fn capture_nonempty<E: From<Error>>(
&self,
enqueue: impl FnOnce() -> Result<(), E>,
) -> Result<Graph, E> {
Ok(unsafe { self.capture(enqueue) }?.ok_or(Error::EmptyCapture)?)
}
}
fn node_count(graph: &CudaGraph) -> Result<usize, Error> {
let mut count = 0;
unsafe { cuGraphGetNodes(graph.cu_graph(), std::ptr::null_mut(), &mut count) }.result()?;
Ok(count)
}
pub(crate) struct EventTrackingPause<'a> {
context: &'a CudaContext,
was_tracking: bool,
}
impl<'a> EventTrackingPause<'a> {
pub(crate) unsafe fn new(context: &'a CudaContext) -> Self {
let was_tracking = context.is_event_tracking();
unsafe { context.disable_event_tracking() };
Self {
context,
was_tracking,
}
}
}
impl Drop for EventTrackingPause<'_> {
fn drop(&mut self) {
if self.was_tracking {
unsafe { self.context.enable_event_tracking() };
}
}
}