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 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.is_multiplane()
}
pub fn chroma(&self) -> Option<&Self> {
self.inner.chroma()?.as_typed::<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}"))
})?;
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) => {
Ok(Self::from_inner(unsafe { TensorDyn::from_raw(h.as_ptr()) }))
}
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()
}
#[cfg(target_os = "windows")]
pub fn d3d11_texture(&self) -> Option<*mut std::ffi::c_void> {
self.inner.d3d11_texture()
}
#[cfg(target_os = "windows")]
pub fn d3d11_layout(&self) -> Option<crate::d3d11_layout::D3d11ImageLayout> {
self.inner.d3d11_layout()
}
#[cfg(target_os = "windows")]
pub fn d3d11_shared_handle(&self) -> Result<std::os::windows::io::OwnedHandle> {
self.inner.d3d11_shared_handle()
}
#[cfg(target_os = "windows")]
pub fn gpu_completion(&self) -> Result<Option<crate::d3d11::GpuCompletion>> {
self.inner.gpu_completion()
}
pub fn gpu_write_value(&self) -> u64 {
self.inner.gpu_write_value()
}
#[cfg(target_os = "windows")]
pub fn set_gpu_write(&self, value: u64) -> Result<()> {
self.inner.set_gpu_write(value)
}
#[cfg(target_os = "windows")]
pub unsafe fn from_d3d11_texture(
texture: *mut std::ffi::c_void,
width: usize,
height: usize,
format: PixelFormat,
access: CpuAccess,
name: Option<&str>,
) -> Result<Self> {
unsafe {
TensorDyn::from_d3d11_texture(texture, width, height, format, T::DTYPE, access, name)
}
.map(Self::from_inner)
}
#[cfg(target_os = "windows")]
pub unsafe fn from_d3d11_shared_handle(
handle: std::os::windows::io::RawHandle,
width: usize,
height: usize,
format: PixelFormat,
access: CpuAccess,
completion: Option<(std::os::windows::io::RawHandle, u64)>,
name: Option<&str>,
) -> Result<Self> {
unsafe {
TensorDyn::from_d3d11_shared_handle(
handle,
width,
height,
format,
T::DTYPE,
access,
completion,
name,
)
}
.map(Self::from_inner)
}
#[deprecated(
since = "0.32.0",
note = "On the dynamic backend use is_cuda_attached() or cuda_map() — the CudaHandle is not borrowable through this lens."
)]
pub fn cuda(&self) -> Option<&crate::cuda::CudaHandle> {
#[allow(deprecated)]
{
self.inner.cuda()
}
}
pub fn is_cuda_attached(&self) -> bool {
self.inner.is_cuda_attached()
}
pub fn cuda_map(&self) -> Option<crate::cuda::CudaMap<'_>> {
self.inner.cuda_map()
}
pub fn cuda_map_mut(&self) -> Option<crate::cuda::CudaMap<'_>> {
self.inner.cuda_map_mut()
}
pub fn set_cuda_handle(&mut self, h: crate::cuda::CudaHandle) {
self.inner.set_cuda_handle(h);
}
pub fn from_pbo(pbo: crate::PboTensor<T>) -> Result<Self> {
let parts = pbo.into_client_parts()?;
let inner = unsafe {
TensorDyn::from_client_pbo(
parts.state,
parts.map_fn,
parts.unmap_fn,
parts.buffer_id,
parts.size,
&parts.shape,
T::DTYPE,
)
}?;
drop(parts);
Ok(Self::from_inner(inner))
}
pub fn pbo_id(&self) -> Option<u32> {
self.inner.pbo_id()
}
pub fn pbo_is_mapped(&self) -> Option<bool> {
self.inner.pbo_is_mapped()
}
pub fn pbo_vtable_ptr(&self) -> Option<*const std::ffi::c_void> {
self.inner.pbo_vtable_ptr()
}
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 try_map_with<'a>(&self, access: CpuAccess) -> Result<crate::view::HostView<'a, T>>
where
T: 'a,
{
let pin = self.inner.map_pin_with(access, true)?;
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()
}
}