use core::ffi::{c_void, CStr};
use core::ptr::addr_of_mut;
use pyo3::exceptions::PyBufferError;
use pyo3::ffi;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};
use std::sync::Arc;
use g2g_core::memory::{CudaKeepAlive, OwnedCudaBuffer};
use g2g_core::RawVideoFormat;
const CAI_VERSION: u32 = 3;
const PLANE_READ_ONLY: bool = true;
const DLPACK_MAJOR: u32 = 1;
const DLPACK_MINOR: u32 = 0;
const DLPACK_DEVICE_CUDA: i32 = 2;
const DLPACK_CODE_UINT: u8 = 1;
const DLPACK_FLAG_READ_ONLY: u64 = 1;
const CAPSULE_LEGACY: &CStr = c"dltensor";
const CAPSULE_VERSIONED: &CStr = c"dltensor_versioned";
const MAX_RANK: usize = 3;
#[repr(C)]
#[derive(Debug)]
struct DlPackVersion {
major: u32,
minor: u32,
}
#[repr(C)]
#[derive(Debug)]
struct DlDevice {
device_type: i32,
device_id: i32,
}
#[repr(C)]
#[derive(Debug)]
struct DlDataType {
code: u8,
bits: u8,
lanes: u16,
}
#[repr(C)]
#[derive(Debug)]
struct DlTensor {
data: *mut c_void,
device: DlDevice,
ndim: i32,
dtype: DlDataType,
shape: *mut i64,
strides: *mut i64,
byte_offset: u64,
}
#[repr(C)]
#[derive(Debug)]
struct DlManagedTensor {
dl_tensor: DlTensor,
manager_ctx: *mut c_void,
deleter: Option<unsafe extern "C" fn(*mut DlManagedTensor)>,
}
#[repr(C)]
#[derive(Debug)]
struct DlManagedTensorVersioned {
version: DlPackVersion,
manager_ctx: *mut c_void,
deleter: Option<unsafe extern "C" fn(*mut DlManagedTensorVersioned)>,
flags: u64,
dl_tensor: DlTensor,
}
const _: () = assert!(core::mem::size_of::<DlTensor>() == 48);
const _: () = assert!(core::mem::size_of::<DlManagedTensor>() == 64);
const _: () = assert!(core::mem::size_of::<DlManagedTensorVersioned>() == 80);
#[derive(Debug)]
struct Exported<T> {
managed: T,
shape: [i64; MAX_RANK],
strides: [i64; MAX_RANK],
#[allow(dead_code, reason = "held only to keep the exporting plane alive")]
plane: Py<CudaPlane>,
}
#[pyclass(frozen, module = "g2g")]
#[derive(Debug)]
pub(crate) struct CudaPlane {
device_ptr: u64,
shape: Vec<usize>,
strides: Vec<usize>,
sample_bytes: usize,
context: u64,
device_ordinal: i32,
}
impl CudaPlane {
fn typestr(&self) -> &'static str {
if self.sample_bytes == 1 {
"|u1"
} else {
"<u2"
}
}
fn dl_tensor(&self) -> Option<(DlTensor, [i64; MAX_RANK], [i64; MAX_RANK])> {
let mut shape = [0i64; MAX_RANK];
let mut strides = [0i64; MAX_RANK];
for (slot, value) in shape.iter_mut().zip(&self.shape) {
*slot = *value as i64;
}
for (slot, bytes) in strides.iter_mut().zip(&self.strides) {
if bytes % self.sample_bytes != 0 {
return None;
}
*slot = (bytes / self.sample_bytes) as i64;
}
let tensor = DlTensor {
data: self.device_ptr as *mut c_void,
device: DlDevice {
device_type: DLPACK_DEVICE_CUDA,
device_id: self.device_ordinal,
},
ndim: self.shape.len() as i32,
dtype: DlDataType {
code: DLPACK_CODE_UINT,
bits: (self.sample_bytes * 8) as u8,
lanes: 1,
},
shape: core::ptr::null_mut(),
strides: core::ptr::null_mut(),
byte_offset: 0,
};
Some((tensor, shape, strides))
}
}
unsafe extern "C" fn drop_legacy(managed: *mut DlManagedTensor) {
unsafe {
let context = (*managed).manager_ctx as *mut Exported<DlManagedTensor>;
drop(Box::from_raw(context));
}
}
unsafe extern "C" fn drop_versioned(managed: *mut DlManagedTensorVersioned) {
unsafe {
let context = (*managed).manager_ctx as *mut Exported<DlManagedTensorVersioned>;
drop(Box::from_raw(context));
}
}
unsafe extern "C" fn drop_unconsumed_legacy(capsule: *mut ffi::PyObject) {
unsafe {
if ffi::PyCapsule_IsValid(capsule, CAPSULE_LEGACY.as_ptr()) == 1 {
let managed =
ffi::PyCapsule_GetPointer(capsule, CAPSULE_LEGACY.as_ptr()) as *mut DlManagedTensor;
if let Some(deleter) = managed.as_ref().and_then(|m| m.deleter) {
deleter(managed);
}
}
}
}
unsafe extern "C" fn drop_unconsumed_versioned(capsule: *mut ffi::PyObject) {
unsafe {
if ffi::PyCapsule_IsValid(capsule, CAPSULE_VERSIONED.as_ptr()) == 1 {
let managed = ffi::PyCapsule_GetPointer(capsule, CAPSULE_VERSIONED.as_ptr())
as *mut DlManagedTensorVersioned;
if let Some(deleter) = managed.as_ref().and_then(|m| m.deleter) {
deleter(managed);
}
}
}
}
unsafe fn capsule<'py, T>(
py: Python<'py>,
raw: *mut Exported<T>,
managed: *mut c_void,
name: &CStr,
destructor: unsafe extern "C" fn(*mut ffi::PyObject),
) -> PyResult<Bound<'py, PyAny>> {
let object = unsafe { ffi::PyCapsule_New(managed, name.as_ptr(), Some(destructor)) };
match unsafe { Bound::from_owned_ptr_or_opt(py, object) } {
Some(capsule) => Ok(capsule),
None => {
drop(unsafe { Box::from_raw(raw) });
Err(PyErr::fetch(py))
}
}
}
fn export_legacy<'py>(
py: Python<'py>,
plane: Py<CudaPlane>,
tensor: DlTensor,
shape: [i64; MAX_RANK],
strides: [i64; MAX_RANK],
) -> PyResult<Bound<'py, PyAny>> {
let raw = Box::into_raw(Box::new(Exported {
managed: DlManagedTensor {
dl_tensor: tensor,
manager_ctx: core::ptr::null_mut(),
deleter: Some(drop_legacy),
},
shape,
strides,
plane,
}));
unsafe {
(*raw).managed.dl_tensor.shape = addr_of_mut!((*raw).shape) as *mut i64;
(*raw).managed.dl_tensor.strides = addr_of_mut!((*raw).strides) as *mut i64;
(*raw).managed.manager_ctx = raw as *mut c_void;
let managed = addr_of_mut!((*raw).managed) as *mut c_void;
capsule(py, raw, managed, CAPSULE_LEGACY, drop_unconsumed_legacy)
}
}
fn export_versioned<'py>(
py: Python<'py>,
plane: Py<CudaPlane>,
tensor: DlTensor,
shape: [i64; MAX_RANK],
strides: [i64; MAX_RANK],
) -> PyResult<Bound<'py, PyAny>> {
let raw = Box::into_raw(Box::new(Exported {
managed: DlManagedTensorVersioned {
version: DlPackVersion {
major: DLPACK_MAJOR,
minor: DLPACK_MINOR,
},
manager_ctx: core::ptr::null_mut(),
deleter: Some(drop_versioned),
flags: DLPACK_FLAG_READ_ONLY,
dl_tensor: tensor,
},
shape,
strides,
plane,
}));
unsafe {
(*raw).managed.dl_tensor.shape = addr_of_mut!((*raw).shape) as *mut i64;
(*raw).managed.dl_tensor.strides = addr_of_mut!((*raw).strides) as *mut i64;
(*raw).managed.manager_ctx = raw as *mut c_void;
let managed = addr_of_mut!((*raw).managed) as *mut c_void;
capsule(
py,
raw,
managed,
CAPSULE_VERSIONED,
drop_unconsumed_versioned,
)
}
}
#[pymethods]
impl CudaPlane {
#[getter(__cuda_array_interface__)]
fn cuda_array_interface<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
let dict = PyDict::new(py);
dict.set_item("shape", PyTuple::new(py, &self.shape)?)?;
dict.set_item("typestr", self.typestr())?;
dict.set_item("data", (self.device_ptr, PLANE_READ_ONLY))?;
dict.set_item("strides", PyTuple::new(py, &self.strides)?)?;
dict.set_item("version", CAI_VERSION)?;
dict.set_item("stream", py.None())?;
Ok(dict)
}
#[getter]
fn cuda_context(&self) -> u64 {
self.context
}
#[pyo3(signature = (stream=None, max_version=None, dl_device=None, copy=None))]
fn __dlpack__<'py>(
slf: &Bound<'py, Self>,
stream: Option<Bound<'py, PyAny>>,
max_version: Option<(u32, u32)>,
dl_device: Option<(i32, i32)>,
copy: Option<bool>,
) -> PyResult<Bound<'py, PyAny>> {
let _ = stream;
if copy == Some(true) {
return Err(PyBufferError::new_err(
"g2g.CudaPlane cannot copy the producer's device memory",
));
}
let plane = slf.get();
if let Some(device) = dl_device {
if device != (DLPACK_DEVICE_CUDA, plane.device_ordinal) {
let ordinal = plane.device_ordinal;
return Err(PyBufferError::new_err(format!(
"g2g.CudaPlane lives on CUDA device {ordinal}, not {device:?}"
)));
}
}
let Some((tensor, shape, strides)) = plane.dl_tensor() else {
return Err(PyBufferError::new_err(
"row pitch is not a whole number of samples, so DLPack element strides cannot describe this plane",
));
};
let py = slf.py();
let handle = slf.clone().unbind();
match max_version {
Some((major, _)) if major >= DLPACK_MAJOR => {
export_versioned(py, handle, tensor, shape, strides)
}
_ => export_legacy(py, handle, tensor, shape, strides),
}
}
fn __dlpack_device__(&self) -> (i32, i32) {
(DLPACK_DEVICE_CUDA, self.device_ordinal)
}
}
pub(crate) fn nv12_planes(
fmt: RawVideoFormat,
buf: &OwnedCudaBuffer,
) -> Option<(CudaPlane, CudaPlane)> {
if !matches!(fmt, RawVideoFormat::Nv12 | RawVideoFormat::P010) {
return None;
}
let sample = fmt.bytes_per_sample();
let (width, height) = (buf.width as usize, buf.height as usize);
let luma = CudaPlane {
device_ptr: buf.luma_ptr,
shape: vec![height, width],
strides: vec![buf.luma_pitch as usize, sample],
sample_bytes: sample,
context: buf.context,
device_ordinal: buf.device_ordinal,
};
let chroma = CudaPlane {
device_ptr: buf.chroma_ptr,
shape: vec![height.div_ceil(2), width.div_ceil(2), 2],
strides: vec![buf.chroma_pitch as usize, 2 * sample, sample],
sample_bytes: sample,
context: buf.context,
device_ordinal: buf.device_ordinal,
};
Some((luma, chroma))
}
#[derive(Debug)]
struct PyOwnedSurface {
#[allow(dead_code, reason = "held only to keep the device memory alive")]
planes: [Py<PyAny>; 2],
}
impl CudaKeepAlive for PyOwnedSurface {}
struct ProducedPlane {
device_ptr: u64,
pitch: u32,
}
fn read_produced_plane(
object: &Bound<'_, PyAny>,
expected_shape: &[usize],
sample_bytes: usize,
) -> PyResult<ProducedPlane> {
let cai = object.getattr("__cuda_array_interface__")?;
let shape: Vec<usize> = cai.get_item("shape")?.extract()?;
if shape != expected_shape {
return Err(PyBufferError::new_err(format!(
"produced plane has shape {shape:?}, expected {expected_shape:?}"
)));
}
let typestr: String = cai.get_item("typestr")?.extract()?;
let expected_typestr = if sample_bytes == 1 { "|u1" } else { "<u2" };
if typestr != expected_typestr {
return Err(PyBufferError::new_err(format!(
"produced plane has samples of type {typestr}, expected {expected_typestr}"
)));
}
let (device_ptr, _read_only): (u64, bool) = cai.get_item("data")?.extract()?;
if device_ptr == 0 {
return Err(PyBufferError::new_err("produced plane has a null pointer"));
}
let packed_row: usize = shape[1..].iter().product::<usize>() * sample_bytes;
let reported: Option<Vec<usize>> = cai.get_item("strides")?.extract()?;
let pitch = match reported {
None => packed_row,
Some(reported) if reported.len() == shape.len() => {
let mut expected = reported.clone();
for axis in 1..shape.len() {
expected[axis] = shape[axis + 1..].iter().product::<usize>() * sample_bytes;
}
if reported != expected {
return Err(PyBufferError::new_err(format!(
"produced plane must be packed within each row, got strides {reported:?}"
)));
}
reported[0]
}
Some(reported) => {
return Err(PyBufferError::new_err(format!(
"produced plane has {} strides for {} axes",
reported.len(),
shape.len()
)))
}
};
if pitch < packed_row || u32::try_from(pitch).is_err() {
return Err(PyBufferError::new_err(format!(
"produced plane has an unusable row pitch of {pitch} bytes"
)));
}
Ok(ProducedPlane {
device_ptr,
pitch: pitch as u32,
})
}
pub(crate) fn produced_cuda_buffer(
luma: &Bound<'_, PyAny>,
chroma: &Bound<'_, PyAny>,
fmt: RawVideoFormat,
width: u32,
height: u32,
context: u64,
device_ordinal: i32,
) -> PyResult<OwnedCudaBuffer> {
if !matches!(fmt, RawVideoFormat::Nv12 | RawVideoFormat::P010) {
return Err(PyBufferError::new_err(
"a GPU-resident frame must be semi-planar (NV12 or P010)",
));
}
let sample = fmt.bytes_per_sample();
let (w, h) = (width as usize, height as usize);
let y = read_produced_plane(luma, &[h, w], sample)?;
let uv = read_produced_plane(chroma, &[h.div_ceil(2), w.div_ceil(2), 2], sample)?;
Ok(OwnedCudaBuffer::new(
y.device_ptr,
uv.device_ptr,
y.pitch,
uv.pitch,
width,
height,
context,
device_ordinal,
Arc::new(PyOwnedSurface {
planes: [luma.clone().unbind(), chroma.clone().unbind()],
}),
))
}
#[cfg(test)]
mod tests {
use super::*;
use g2g_core::memory::CudaKeepAlive;
use std::sync::Arc;
#[derive(Debug)]
struct NoOwner;
impl CudaKeepAlive for NoOwner {}
fn owner() -> Arc<dyn CudaKeepAlive> {
Arc::new(NoOwner)
}
const FAKE_LUMA: u64 = 0xdead_0000;
const FAKE_CHROMA: u64 = 0xdead_8000;
const FAKE_DEVICE: i32 = 1;
fn pitched(fmt: RawVideoFormat) -> (CudaPlane, CudaPlane) {
let buf = OwnedCudaBuffer::new(
FAKE_LUMA,
FAKE_CHROMA,
2048,
2048,
1920,
1080,
0x1234,
FAKE_DEVICE,
owner(),
);
nv12_planes(fmt, &buf).expect("NV12 / P010 are semi-planar")
}
struct Described {
shape: Vec<usize>,
strides: Vec<usize>,
typestr: String,
data: (u64, bool),
version: u32,
}
fn describe(plane: &CudaPlane) -> Described {
Python::attach(|py| {
let dict = plane.cuda_array_interface(py).unwrap();
let get = |key: &str| dict.get_item(key).unwrap().expect("CAI key present");
assert!(get("stream").is_none(), "no stream synchronization claimed");
Described {
shape: get("shape").extract().unwrap(),
strides: get("strides").extract().unwrap(),
typestr: get("typestr").extract().unwrap(),
data: get("data").extract().unwrap(),
version: get("version").extract().unwrap(),
}
})
}
#[test]
fn nv12_planes_describe_the_pitched_layout() {
let (luma, chroma) = pitched(RawVideoFormat::Nv12);
let y = describe(&luma);
assert_eq!(y.shape, vec![1080, 1920]);
assert_eq!(
y.strides,
vec![2048, 1],
"row stride is the pitch, not width"
);
assert_eq!(y.typestr, "|u1");
assert_eq!(y.data, (FAKE_LUMA, true));
assert_eq!(y.version, 3);
let uv = describe(&chroma);
assert_eq!(uv.shape, vec![540, 960, 2], "interleaved UV pairs");
assert_eq!(uv.strides, vec![2048, 2, 1]);
assert_eq!(uv.typestr, "|u1");
assert_eq!(uv.data, (FAKE_CHROMA, true));
}
#[test]
fn p010_planes_are_16_bit_samples() {
let (luma, chroma) = pitched(RawVideoFormat::P010);
let y = describe(&luma);
assert_eq!(y.shape, vec![1080, 1920]);
assert_eq!(y.strides, vec![2048, 2]);
assert_eq!(y.typestr, "<u2");
let uv = describe(&chroma);
assert_eq!(uv.shape, vec![540, 960, 2]);
assert_eq!(uv.strides, vec![2048, 4, 2]);
}
#[test]
fn odd_dimensions_round_the_chroma_plane_up() {
let buf = OwnedCudaBuffer::new(FAKE_LUMA, FAKE_CHROMA, 64, 64, 33, 17, 0, 0, owner());
let (_, chroma) = nv12_planes(RawVideoFormat::Nv12, &buf).unwrap();
assert_eq!(describe(&chroma).shape, vec![9, 17, 2]);
}
#[test]
fn non_semi_planar_format_has_no_planes() {
let buf = OwnedCudaBuffer::new(
FAKE_LUMA,
FAKE_CHROMA,
2048,
2048,
1920,
1080,
0,
0,
owner(),
);
assert!(nv12_planes(RawVideoFormat::Rgba8, &buf).is_none());
assert!(nv12_planes(RawVideoFormat::I420, &buf).is_none());
}
#[test]
fn context_reaches_python() {
let (luma, _) = pitched(RawVideoFormat::Nv12);
assert_eq!(luma.cuda_context(), 0x1234);
}
#[test]
fn dlpack_reports_the_producers_device_ordinal() {
let (luma, chroma) = pitched(RawVideoFormat::Nv12);
for plane in [&luma, &chroma] {
assert_eq!(
plane.__dlpack_device__(),
(DLPACK_DEVICE_CUDA, FAKE_DEVICE),
"kDLCUDA on the device the producer allocated on"
);
let (tensor, _, _) = plane.dl_tensor().expect("pitch is a whole sample count");
assert_eq!(tensor.device.device_id, FAKE_DEVICE);
}
}
}