use std::fmt;
use std::marker::PhantomData;
use half::f16;
use crate::lens::Element;
use crate::{
BufferIdentity, Colorimetry, CpuAccess, DType, Error, IntegerType, PixelFormat, Quantization,
Region, Result, TensorDyn, TensorMapTrait, TensorMemory, TensorTrait, ViewOrigin,
};
#[repr(transparent)]
pub struct Tensor<T: Element> {
inner: TensorDyn,
_marker: PhantomData<fn() -> T>,
}
impl<T: Element> Tensor<T> {
fn from_inner(inner: TensorDyn) -> Self {
Tensor {
inner,
_marker: PhantomData,
}
}
pub(crate) fn into_inner(self) -> TensorDyn {
self.inner
}
pub fn new(shape: &[usize], memory: Option<TensorMemory>, name: Option<&str>) -> Result<Self> {
TensorDyn::new(shape, T::DTYPE, memory, name).map(Self::from_inner)
}
#[cfg(unix)]
pub fn from_fd(fd: std::os::fd::OwnedFd, shape: &[usize], name: Option<&str>) -> Result<Self> {
TensorDyn::from_fd(fd, shape, T::DTYPE, name).map(Self::from_inner)
}
pub fn from_slice(values: &[T], shape: &[usize]) -> Result<Self>
where
T: Copy,
{
let expected: usize = shape.iter().product();
if values.len() != expected {
return Err(Error::InvalidShape(format!(
"from_slice: values.len()={} but shape product={expected} (shape={shape:?})",
values.len()
)));
}
let t = Self::new(shape, Some(TensorMemory::Mem), None)?;
{
let mut m = TensorTrait::map(&t)?;
m.as_mut_slice().copy_from_slice(values);
}
Ok(t)
}
#[cfg(feature = "ndarray")]
pub fn from_arrayview3(view: ndarray::ArrayView3<'_, T>) -> Result<Self>
where
T: Copy,
{
let (h, w, c) = view.dim();
let t = Self::new(&[h, w, c], Some(TensorMemory::Mem), None)?;
{
let mut m = TensorTrait::map(&t)?;
let dst = m.as_mut_slice();
if let Some(src) = view.as_slice() {
dst.copy_from_slice(src);
} else {
for (d, &s) in dst.iter_mut().zip(view.iter()) {
*d = s;
}
}
}
Ok(t)
}
pub fn dtype(&self) -> DType {
self.inner.dtype()
}
pub fn is_multiplane(&self) -> bool {
self.inner.multiplane_chroma.is_some()
}
pub fn chroma(&self) -> Option<&Self> {
self.inner
.multiplane_chroma
.as_deref()
.and_then(|td| td.as_typed::<T>())
}
pub fn chroma_mut(&mut self) -> Option<&mut Self> {
self.inner
.multiplane_chroma
.as_deref_mut()
.and_then(|td| td.as_typed_mut::<T>())
}
pub fn from_planes(luma: Self, chroma: Self, format: PixelFormat) -> Result<Self> {
let c_format = std::ffi::CString::new(format.as_str()).map_err(|e| {
Error::InvalidArgument(format!("pixel format string contains a NUL: {e}"))
})?;
#[cfg(unix)]
let chroma_shadow = match chroma.inner.shadow_multiplane_chroma() {
Ok(shadow) => Some(shadow),
Err(e) => {
log::warn!(
"Tensor::from_planes: could not preserve a chroma shadow ({e}) -- \
is_multiplane()/chroma() will report no chroma for this tensor, matching \
the behavior before task 17's fix; see TensorDyn::multiplane_chroma's doc \
comment"
);
None
}
};
let luma_raw = TensorDyn::from(luma).into_raw();
let chroma_raw = TensorDyn::from(chroma).into_raw();
let handle = unsafe {
edgefirst_tensor_ffi::ef_tensor_from_planes(luma_raw, chroma_raw, c_format.as_ptr())
};
match std::ptr::NonNull::new(handle) {
Some(h) => {
#[allow(unused_mut)]
let mut inner = unsafe { TensorDyn::from_raw(h.as_ptr()) };
#[cfg(unix)]
{
inner.multiplane_chroma = chroma_shadow.map(Box::new);
}
Ok(Self::from_inner(inner))
}
None => Err(Error::InvalidArgument(format!(
"ef_tensor_from_planes failed: {} -- see this method's doc comment for the \
known non-consuming-rejection case",
crate::tensor_dyn::ffi_last_error()
))),
}
}
pub fn format(&self) -> Option<PixelFormat> {
self.inner.format()
}
pub fn width(&self) -> Option<usize> {
self.inner.width()
}
pub fn height(&self) -> Option<usize> {
self.inner.height()
}
pub fn aliases(&self, other: &Self) -> bool {
self.inner.aliases(&other.inner)
}
pub fn colorimetry(&self) -> Option<Colorimetry> {
self.inner.colorimetry()
}
pub fn set_colorimetry(&mut self, c: Option<Colorimetry>) {
self.inner.set_colorimetry(c)
}
pub fn effective_row_stride(&self) -> Option<usize> {
self.inner.effective_row_stride()
}
pub fn row_stride(&self) -> Option<usize> {
self.inner.row_stride()
}
pub fn view_origin(&self) -> Option<ViewOrigin> {
self.inner.view_origin()
}
pub fn plane_offset(&self) -> Option<usize> {
self.inner.plane_offset()
}
pub fn set_row_stride(&mut self, stride: usize) -> Result<()> {
self.inner.set_row_stride(stride)
}
pub fn set_row_stride_unchecked(&mut self, stride: usize) {
self.inner.set_row_stride_unchecked(stride)
}
pub fn set_plane_offset(&mut self, offset: usize) {
self.inner.set_plane_offset(offset)
}
#[cfg(target_os = "linux")]
pub fn dmabuf(&self) -> Result<std::os::fd::BorrowedFd<'_>> {
if self.inner.memory() != TensorMemory::DmaBuf {
return Err(Error::NotImplemented(format!(
"dmabuf requires DMA-backed tensor, got {:?}",
self.inner.memory()
)));
}
let plane = self
.inner
.plane0()
.ok_or_else(|| Error::NotImplemented("dmabuf: this tensor has no plane 0".into()))?;
if plane.handle < 0 {
return Err(Error::NotImplemented(
"dmabuf: this tensor has no native fd (not DMA-backed)".into(),
));
}
Ok(unsafe { std::os::fd::BorrowedFd::borrow_raw(plane.handle as i32) })
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub fn iosurface_ref(&self) -> Option<*mut std::ffi::c_void> {
self.inner.iosurface_ref()
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub unsafe fn from_iosurface(
surface_ref: *mut std::ffi::c_void,
shape: &[usize],
name: Option<&str>,
) -> Result<Self> {
unsafe { TensorDyn::from_iosurface(surface_ref, shape, T::DTYPE, name) }
.map(Self::from_inner)
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub fn iosurface_physical_dims(&self) -> Option<(usize, usize)> {
self.inner.iosurface_physical_dims()
}
pub fn cuda(&self) -> Option<&crate::cuda::CudaHandle> {
self.inner.cuda.as_deref()
}
pub fn cuda_map(&self) -> Option<crate::cuda::CudaMap<'_>> {
self.cuda()?.map()
}
pub fn set_cuda_handle(&mut self, h: crate::cuda::CudaHandle) {
self.inner.cuda = Some(Box::new(h));
}
pub fn from_pbo(pbo: crate::PboTensor<T>) -> Result<Self> {
let mut inner = TensorDyn::new(&pbo.shape, T::DTYPE, Some(TensorMemory::Mem), None)?;
inner.pbo = Some(Box::new(pbo));
Ok(Self::from_inner(inner))
}
pub fn as_pbo(&self) -> Option<&crate::PboTensor<T>> {
let any = self.inner.pbo.as_ref()?;
if let Some(p) = any.downcast_ref::<crate::PboTensor<T>>() {
return Some(p);
}
macro_rules! reinterpret_arm {
($t:ty) => {{
const {
assert!(
std::mem::size_of::<crate::PboTensor<$t>>()
== std::mem::size_of::<crate::PboTensor<T>>(),
"PboTensor is no longer layout-identical across element types; \
Tensor::as_pbo's reinterpretation is unsound -- see its doc comment"
);
assert!(
std::mem::align_of::<crate::PboTensor<$t>>()
== std::mem::align_of::<crate::PboTensor<T>>(),
"PboTensor's alignment now varies by element type; \
Tensor::as_pbo's reinterpretation is unsound -- see its doc comment"
);
}
any.downcast_ref::<crate::PboTensor<$t>>().map(|p| {
unsafe { &*(p as *const crate::PboTensor<$t> as *const crate::PboTensor<T>) }
})
}};
}
None.or_else(|| reinterpret_arm!(u8))
.or_else(|| reinterpret_arm!(i8))
.or_else(|| reinterpret_arm!(u16))
.or_else(|| reinterpret_arm!(i16))
.or_else(|| reinterpret_arm!(half::f16))
.or_else(|| reinterpret_arm!(u32))
.or_else(|| reinterpret_arm!(i32))
.or_else(|| reinterpret_arm!(f32))
.or_else(|| reinterpret_arm!(u64))
.or_else(|| reinterpret_arm!(i64))
.or_else(|| reinterpret_arm!(f64))
}
pub fn image(
width: usize,
height: usize,
format: PixelFormat,
memory: Option<TensorMemory>,
access: CpuAccess,
) -> Result<Self> {
TensorDyn::image(width, height, format, T::DTYPE, memory, access).map(Self::from_inner)
}
pub fn image_desc(desc: &crate::ImageDesc) -> Result<Self> {
TensorDyn::image_desc(desc).map(Self::from_inner)
}
pub fn image_with_stride(
width: usize,
height: usize,
format: PixelFormat,
row_stride_bytes: usize,
memory: Option<TensorMemory>,
access: CpuAccess,
) -> Result<Self> {
TensorDyn::image_with_stride(
width,
height,
format,
T::DTYPE,
row_stride_bytes,
memory,
access,
)
.map(Self::from_inner)
}
pub fn set_format(&mut self, format: PixelFormat) -> Result<()> {
self.inner.set_format(format)
}
pub fn configure_image(
&mut self,
width: usize,
height: usize,
format: PixelFormat,
) -> Result<()> {
self.inner.configure_image(width, height, format)
}
pub fn pin_host<'a>(&self, access: CpuAccess) -> Result<crate::pin::HostPin<'a>> {
self.inner.pin_host(access)
}
pub fn view(&self, region: Region) -> Result<Self> {
self.inner.view(region).map(Self::from_inner)
}
}
impl<T: Element + IntegerType> Tensor<T> {
pub fn quantization(&self) -> Option<&Quantization> {
self.inner.quantization()
}
pub fn set_quantization(&mut self, q: Quantization) -> Result<()> {
self.inner.set_quantization(q)
}
pub fn with_quantization(mut self, q: Quantization) -> Result<Self> {
self.set_quantization(q)?;
Ok(self)
}
pub fn clear_quantization(&mut self) {
self.inner.clear_quantization()
}
}
macro_rules! downcast_methods {
($ty:ty, $as_name:ident, $as_mut_name:ident, $into_name:ident) => {
pub fn $as_name(&self) -> Option<&Tensor<$ty>> {
self.as_typed::<$ty>()
}
pub fn $as_mut_name(&mut self) -> Option<&mut Tensor<$ty>> {
self.as_typed_mut::<$ty>()
}
#[allow(clippy::result_large_err)]
pub fn $into_name(self) -> std::result::Result<Tensor<$ty>, Self> {
if self.dtype() == <$ty as Element>::DTYPE {
Ok(Tensor::from_inner(self))
} else {
Err(self)
}
}
};
}
impl TensorDyn {
downcast_methods!(u8, as_u8, as_u8_mut, into_u8);
downcast_methods!(i8, as_i8, as_i8_mut, into_i8);
downcast_methods!(u16, as_u16, as_u16_mut, into_u16);
downcast_methods!(i16, as_i16, as_i16_mut, into_i16);
downcast_methods!(u32, as_u32, as_u32_mut, into_u32);
downcast_methods!(i32, as_i32, as_i32_mut, into_i32);
downcast_methods!(u64, as_u64, as_u64_mut, into_u64);
downcast_methods!(i64, as_i64, as_i64_mut, into_i64);
downcast_methods!(f16, as_f16, as_f16_mut, into_f16);
downcast_methods!(f32, as_f32, as_f32_mut, into_f32);
downcast_methods!(f64, as_f64, as_f64_mut, into_f64);
}
impl<T: Element> From<Tensor<T>> for TensorDyn {
fn from(t: Tensor<T>) -> TensorDyn {
let mut inner = t.inner;
if let Err(e) = inner.set_dtype(T::DTYPE) {
log::error!(
"erasing a Tensor<{}> whose handle reports {:?}: {e}. The typed lens and its \
handle disagree on element width, which no layout-identical transmute can \
produce; the erased tensor keeps the handle's dtype.",
std::any::type_name::<T>(),
inner.dtype()
);
}
inner
}
}
impl<T: Element> fmt::Debug for Tensor<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.inner, f)
}
}
impl<T: Element> TensorTrait<T> for Tensor<T> {
fn new(shape: &[usize], name: Option<&str>) -> Result<Self>
where
Self: Sized,
{
Self::new(shape, None, name)
}
#[cfg(unix)]
fn from_fd(fd: std::os::fd::OwnedFd, shape: &[usize], name: Option<&str>) -> Result<Self>
where
Self: Sized,
{
Self::from_fd(fd, shape, name)
}
#[cfg(unix)]
fn clone_fd(&self) -> Result<std::os::fd::OwnedFd> {
self.inner.clone_fd()
}
fn memory(&self) -> TensorMemory {
self.inner.memory()
}
fn name(&self) -> String {
self.inner.name()
}
fn shape(&self) -> &[usize] {
self.inner.shape()
}
fn reshape(&mut self, shape: &[usize]) -> Result<()> {
self.inner.reshape(shape)
}
fn set_logical_shape(&mut self, shape: &[usize]) -> Result<()> {
self.inner.set_logical_shape(shape)
}
fn capacity_bytes(&self) -> usize {
self.inner.capacity_bytes()
}
fn map_with<'a>(&self, access: CpuAccess) -> Result<crate::view::HostView<'a, T>>
where
T: 'a,
{
let pin = self.inner.map_pin(access)?;
let byte_len = pin.len();
Ok(crate::view::HostView::new(
pin,
self.inner.shape().to_vec(),
Some(byte_len),
access,
))
}
fn buffer_identity(&self) -> &BufferIdentity {
self.inner.buffer_identity()
}
}