use cudarc::driver::{CudaSlice, CudaStream};
use crate::{
CudaBatchDestination, CudaDestination, CudaError, CudaResidentBatch, DenseCudaBatchLayout,
ResidentCudaImage,
};
pub struct CudaSubmission {
pending: Option<PendingCuda>,
report: jxr_core::DecodeReport,
}
struct PendingCuda {
encoded: crate::encode::EncodedCudaSubmission,
}
impl core::fmt::Debug for CudaSubmission {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("CudaSubmission")
.field("submitted", &self.pending.is_some())
.field("report", &self.report)
.finish_non_exhaustive()
}
}
impl CudaSubmission {
pub(crate) fn submitted(
encoded: crate::encode::EncodedCudaSubmission,
report: jxr_core::DecodeReport,
) -> Self {
Self {
pending: Some(PendingCuda { encoded }),
report,
}
}
#[must_use]
pub fn is_complete(&self) -> bool {
self.pending
.as_ref()
.is_none_or(|pending| pending.encoded.completion.is_complete())
}
pub fn enqueue_consumer_wait(&self, stream: &CudaStream) -> Result<(), CudaError> {
let pending = self
.pending
.as_ref()
.ok_or(CudaError::InvalidSubmissionState {
expected: "submitted",
actual: "completed",
})?;
if pending.encoded.stream.context() != stream.context() {
return Err(CudaError::InvalidDestination {
reason: "consumer stream belongs to a different CUDA context",
});
}
stream.wait(&pending.encoded.completion)?;
Ok(())
}
pub fn output_layout(&self) -> Result<&jxr_core::SurfaceLayout, CudaError> {
self.pending
.as_ref()
.map(|pending| &pending.encoded.layout)
.ok_or(CudaError::InvalidSubmissionState {
expected: "submitted",
actual: "completed",
})
}
pub unsafe fn pending_device_buffer(&self) -> Result<&CudaSlice<u8>, CudaError> {
self.pending
.as_ref()
.and_then(|pending| pending.encoded.output.as_ref())
.ok_or(CudaError::InvalidSubmissionState {
expected: "submitted owned output",
actual: "completed or caller-owned output",
})
}
pub fn wait(mut self) -> Result<ResidentCudaImage, CudaError> {
let mut pending = self
.pending
.take()
.ok_or(CudaError::InvalidSubmissionState {
expected: "submitted",
actual: "completed",
})?;
complete(&mut pending)?;
let output = pending
.encoded
.output
.take()
.ok_or(CudaError::InvalidSubmissionState {
expected: "owned output",
actual: "caller-owned output",
})?;
Ok(ResidentCudaImage::from_buffer(
output,
pending.encoded.layout.clone(),
self.report.clone(),
))
}
pub(crate) fn wait_completion(mut self) -> Result<(), CudaError> {
let mut pending = self
.pending
.take()
.ok_or(CudaError::InvalidSubmissionState {
expected: "submitted",
actual: "completed",
})?;
complete(&mut pending)
}
}
impl Drop for CudaSubmission {
fn drop(&mut self) {
if let Some(mut pending) = self.pending.take() {
let _ = pending.encoded.completion.synchronize();
let _ = recycle_scratch(&mut pending);
}
}
}
fn complete(pending: &mut PendingCuda) -> Result<(), CudaError> {
pending.encoded.completion.synchronize()?;
let mut status = [0_u32; 1];
pending
.encoded
.stream
.memcpy_dtoh(&pending.encoded.status, &mut status)?;
pending.encoded.stream.synchronize()?;
recycle_scratch(pending)?;
if status[0] != 0 {
return Err(CudaError::KernelArithmetic { status: status[0] });
}
Ok(())
}
fn recycle_scratch(pending: &mut PendingCuda) -> Result<(), CudaError> {
for buffer in pending.encoded.scratch.drain(..) {
pending.encoded.runtime.buffer_pool.recycle(buffer)?;
}
Ok(())
}
#[derive(Debug)]
pub struct CudaBatchSubmission {
submissions: Vec<CudaSubmission>,
}
impl CudaBatchSubmission {
pub(crate) fn new(submissions: Vec<CudaSubmission>) -> Self {
Self { submissions }
}
#[must_use]
pub fn len(&self) -> usize {
self.submissions.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.submissions.is_empty()
}
pub fn wait(self) -> Result<Vec<ResidentCudaImage>, CudaError> {
self.submissions
.into_iter()
.map(CudaSubmission::wait)
.collect()
}
}
#[derive(Debug)]
pub struct CudaResidentBatchSubmission {
submissions: Vec<CudaSubmission>,
output: CudaSlice<u8>,
layout: DenseCudaBatchLayout,
reports: Vec<jxr_core::DecodeReport>,
}
impl CudaResidentBatchSubmission {
pub(crate) fn new(
submissions: Vec<CudaSubmission>,
output: CudaSlice<u8>,
layout: DenseCudaBatchLayout,
reports: Vec<jxr_core::DecodeReport>,
) -> Self {
Self {
submissions,
output,
layout,
reports,
}
}
#[must_use]
pub const fn layout(&self) -> &DenseCudaBatchLayout {
&self.layout
}
pub fn wait(self) -> Result<CudaResidentBatch, CudaError> {
for submission in self.submissions {
submission.wait_completion()?;
}
Ok(CudaResidentBatch::from_buffer(
self.output,
self.layout,
self.reports,
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CudaDestinationCompletion {
pub layout: jxr_core::SurfaceLayout,
pub report: jxr_core::DecodeReport,
}
#[derive(Debug)]
pub struct CudaDestinationSubmission {
submission: CudaSubmission,
destination: CudaDestination,
report: jxr_core::DecodeReport,
}
impl CudaDestinationSubmission {
pub(crate) fn new(
submission: CudaSubmission,
destination: CudaDestination,
report: jxr_core::DecodeReport,
) -> Self {
Self {
submission,
destination,
report,
}
}
pub fn wait(self) -> Result<(CudaDestinationCompletion, CudaDestination), CudaError> {
self.submission.wait_completion()?;
let completion = CudaDestinationCompletion {
layout: self.destination.layout().clone(),
report: self.report,
};
Ok((completion, self.destination))
}
}
#[derive(Debug)]
pub struct CudaBatchDestinationCompletion {
destination: CudaBatchDestination,
reports: Vec<jxr_core::DecodeReport>,
}
impl CudaBatchDestinationCompletion {
#[must_use]
pub const fn layout(&self) -> &DenseCudaBatchLayout {
self.destination.layout()
}
#[must_use]
pub fn reports(&self) -> &[jxr_core::DecodeReport] {
&self.reports
}
#[must_use]
pub fn into_parts(self) -> (CudaBatchDestination, Vec<jxr_core::DecodeReport>) {
(self.destination, self.reports)
}
}
#[derive(Debug)]
pub struct CudaBatchDestinationSubmission {
submissions: Vec<CudaSubmission>,
destination: CudaBatchDestination,
reports: Vec<jxr_core::DecodeReport>,
}
impl CudaBatchDestinationSubmission {
pub(crate) fn new(
submissions: Vec<CudaSubmission>,
destination: CudaBatchDestination,
reports: Vec<jxr_core::DecodeReport>,
) -> Self {
Self {
submissions,
destination,
reports,
}
}
pub fn wait(self) -> Result<CudaBatchDestinationCompletion, CudaError> {
for submission in self.submissions {
submission.wait_completion()?;
}
Ok(CudaBatchDestinationCompletion {
destination: self.destination,
reports: self.reports,
})
}
}