pub struct Tensor<T>{ /* private fields */ }Expand description
Multi-backend tensor with optional image format metadata.
When format is Some, this tensor represents an image. Width, height,
and channels are derived from shape + format. When format is None,
this is a raw tensor (identical to the pre-refactoring behavior).
This is the static backend’s Tensor<T> – it owns its storage. See
tensor_dyn/dynamic_tensor.rs for dynamic’s Tensor<T>, a typed lens
with no storage of its own.
Implementations§
Source§impl<T> Tensor<T>
impl<T> Tensor<T>
Sourcepub fn from_slice(values: &[T], shape: &[usize]) -> Result<Self>where
T: Copy,
pub fn from_slice(values: &[T], shape: &[usize]) -> Result<Self>where
T: Copy,
Construct a tensor from a row-major element slice + shape. Allocates a
new buffer (TensorMemory::Mem) and memcpys the contents; caller
retains ownership of the input slice.
§Errors
Error::InvalidShapeifvalues.len() != shape.iter().product().- Propagates any allocation error from
Self::new.
Sourcepub unsafe fn from_foreign(
ptr: *mut T,
shape: &[usize],
owner: Option<ForeignOwner>,
name: Option<&str>,
) -> Result<Self>
pub unsafe fn from_foreign( ptr: *mut T, shape: &[usize], owner: Option<ForeignOwner>, name: Option<&str>, ) -> Result<Self>
Wrap externally-owned memory as a tensor without copying. The tensor
borrows [ptr, ptr + shape.product() * size_of::<T>()) as
TensorMemory::Mem; owner, when Some, co-owns the source so it
outlives the tensor (and all derived views/maps). See ForeignOwner.
The canonical use is CUDA zero-copy: allocate host-coherent memory
(cudaHostAlloc), wrap the host pointer here, and bind the matching
device pointer to the inference engine — reads and writes hit the same
physical buffer with no host copy. The identical primitive backs the
Python Tensor.from_numpy zero-copy borrow (owner = the NumPy object).
§Safety
ptr must be non-null, aligned to align_of::<T>(), and valid for
shape.product() elements of T for as long as the returned tensor —
and every view/map sharing its backing — is alive. Pass an owner that
co-owns the source to uphold that contract.
§Errors
Error::InvalidSize if shape is empty.
Sourcepub unsafe fn from_foreign_with_capacity(
ptr: *mut T,
shape: &[usize],
capacity_bytes: usize,
owner: Option<ForeignOwner>,
name: Option<&str>,
) -> Result<Self>
pub unsafe fn from_foreign_with_capacity( ptr: *mut T, shape: &[usize], capacity_bytes: usize, owner: Option<ForeignOwner>, name: Option<&str>, ) -> Result<Self>
Self::from_foreign, additionally recording that the allocation
behind ptr holds at least capacity_bytes bytes – headroom beyond
the tight shape.product() * size_of::<T>() footprint that a later
configure_image / set_logical_shape can
grow into without erroring.
This is the consumer half of the cross-package capsule protocol’s
crate::protocol::TensorDesc::capacity: a producer’s pool tensor
(allocated for its largest expected image, holding a smaller one
today) reports its real allocation size so the imported alias
inherits the same headroom instead of being clamped to today’s
logical shape. capacity_bytes below the tight footprint is clamped
up to it, never down – the shape-implied floor always holds.
§Safety
Same as Self::from_foreign, except ptr must be valid for
capacity_bytes bytes (or the tight shape footprint, whichever is
larger), not just the shape-implied footprint.
§Errors
Error::InvalidSize if shape is empty.
Sourcepub fn pin_host<'a>(&self, access: CpuAccess) -> Result<HostPin<'a>>where
T: 'a,
pub fn pin_host<'a>(&self, access: CpuAccess) -> Result<HostPin<'a>>where
T: 'a,
Pin a stable host address for this tensor’s data.
The returned HostPin carries no borrow of the tensor, so a
caller can pin once at init and still pass &mut self to
ImageProcessor::convert every frame. That borrow conflict is the
blocker reported in #134.
The address stays valid until the pin is dropped, and survives
re-layout: configure_image and set_logical_shape only rewrite the
logical shape after a capacity check, and every backend’s pin owns its
own mapping, so nothing can move underneath it.
A pin is therefore a snapshot, not a tracker: HostPin::len
reports the extent at pin time and does not follow a later re-layout.
That is the safe direction — the window always stays inside the
allocation — but it means a caller who re-layouts should re-pin if it
wants the new extent.
The address is not a coherency guarantee: on non-coherent backends
bracket CPU access with
sync_for_cpu and
sync_for_device, which are no-ops where the
backend does not require them.
§Cost
On Mem this is an Arc refcount bump — the backing allocation is
never resized, so its base pointer is already stable. Other backends
establish a mapping once and reuse it.
§Errors
Error::InvalidArgumentwhenaccessisCpuAccess::None: a pinned mapping is CPU access by definition, so this is an error rather than a warn-and-stage.Error::NotImplementedfor the two backends that cannot pin, rather than merely not having been wired yet: a PBO (glMapBufferRangeestablishes the address and the visibility together, and there is noGL_MAP_PERSISTENT_BITin GLES) and an AHardwareBuffer (AHardwareBuffer_lockis what yields the address at all, so there is no address outside the lock). Both namemap()/map_with()as the alternative.Mem,Shm, DMA-BUF, IOSurface and the Windows D3D11 texture all pin — the texture through a persistent host buffer the storage owns, whichsync_for_cpuandsync_for_devicemove bytes between and the texture, rather than through the staging mapping (a pin outlives every map guard, so holding the staging texture mapped would freeze it).
Sourcepub fn sync_for_cpu(&self, access: CpuAccess) -> Result<()>
pub fn sync_for_cpu(&self, access: CpuAccess) -> Result<()>
Make CPU reads/writes of this tensor coherent — the CPU is about to access the buffer.
Corresponds to DMA_BUF_SYNC_START, and the name matches the Linux
kernel DMA API for the same reason: for_cpu says who is about to
touch the memory, which is what the caller actually knows.
Pair with sync_for_device.
§Which backends owe maintenance
| backend | behaviour |
|---|---|
Mem, Shm | no-op — plain CPU memory, coherent by construction |
Dma (Linux DMA-BUF) | one DMA_BUF_IOCTL_SYNC, direction-tagged |
Dma (macOS/iOS IOSurface) | IOSurfaceLock / IOSurfaceUnlock, options matched to access |
Dma (Android AHardwareBuffer) | Error::NotImplemented — no sync independent of the lock |
Dma (Windows D3D11 texture) | refreshes the pinned host buffer from the texture through the staging copy, or uploads it back; a no-op until something pins |
Pbo | Error::NotImplemented — no coherency window independent of the map |
Only the first row is a no-op, so “bracket unconditionally and pay
nothing” holds for Mem/Shm alone. The two that report
NotImplemented do so because their platform fuses addressing and
coherency into one call — AHardwareBuffer_lock and
glMapBufferRange each perform the maintenance as they hand out the
address, so a separate bracket would have to nest inside a map guard
that already owns the pair. Both name map()/map_with() as the
alternative. They previously fell through a wildcard and answered
Ok(()), which is worse than an error because the symptom is
stale-but-plausible pixels rather than a failure. The match is
exhaustive so a new backend cannot inherit that silence — it must state
its coherency story to compile.
§Cost
Mem/Shm: nothing. Dma on Linux: one ioctl. IOSurface: one
lock/unlock pair. Windows D3D11 texture: one CopyResource plus a
staging map per direction, and nothing at all on a tensor no one has
pinned.
§Errors
Error::InvalidArgument when access is CpuAccess::None, which
describes no access to make coherent.
Sourcepub fn sync_for_device(&self, access: CpuAccess) -> Result<()>
pub fn sync_for_device(&self, access: CpuAccess) -> Result<()>
Release the buffer back to the device — the CPU is done accessing it.
Corresponds to DMA_BUF_SYNC_END. See
sync_for_cpu for the pairing and cost.
Sourcepub fn from_arrayview3(view: ArrayView3<'_, T>) -> Result<Self>where
T: Copy,
pub fn from_arrayview3(view: ArrayView3<'_, T>) -> Result<Self>where
T: Copy,
Construct a tensor from a 3-D ndarray view. Respects strides — one copy in all cases; contiguous views take a memcpy fast path.
Only available when the ndarray feature is enabled.
Sourcepub fn new(
shape: &[usize],
memory: Option<TensorMemory>,
name: Option<&str>,
) -> Result<Self>
pub fn new( shape: &[usize], memory: Option<TensorMemory>, name: Option<&str>, ) -> Result<Self>
Create a new tensor with the given shape, memory type, and optional name. If no name is given, a random name will be generated. If no memory type is given, the best available memory type will be chosen based on the platform and environment variables.
On Linux platforms, the order of preference is: Dma -> Shm -> Mem. On other Unix platforms (macOS), the order is: Shm -> Mem. On non-Unix platforms, only Mem is available.
§Environment Variables
EDGEFIRST_TENSOR_FORCE_MEM: If set to a non-zero and non-false value, forces the use of regular system memory allocation (TensorMemory::Mem) regardless of platform capabilities.
§Example
use edgefirst_tensor::{Error, Tensor, TensorMemory, TensorTrait};
let tensor = Tensor::<f32>::new(&[2, 3, 4], Some(TensorMemory::Mem), Some("test_tensor"))?;
assert_eq!(tensor.memory(), TensorMemory::Mem);
assert_eq!(tensor.name(), "test_tensor");Sourcepub fn image_desc(desc: &ImageDesc) -> Result<Self>where
T: 'static,
pub fn image_desc(desc: &ImageDesc) -> Result<Self>where
T: 'static,
Create an image tensor with the given format.
Allocate an image tensor from a declarative request — the
full-featured constructor behind Self::image and friends.
Adds the Compression request to the classic parameters:
- a request with any CPU access other than
CpuAccess::NoneisError::InvalidArgument(CPU mapping pins the layout linear); Compression::Schemefails withError::NotImplementedon platforms without vendor tile compression, and withError::InvalidArgumentwhen the device’s native scheme or the format eligibility doesn’t match;Compression::Anynever fails for compression reasons: it records the scheme when the allocation is eligible and otherwise resolves linear, incrementingcompression_fallback_count.
The recorded outcome is readable via Tensor::compression.
Sourcepub fn image(
width: usize,
height: usize,
format: PixelFormat,
memory: Option<TensorMemory>,
access: CpuAccess,
) -> Result<Self>where
T: 'static,
pub fn image(
width: usize,
height: usize,
format: PixelFormat,
memory: Option<TensorMemory>,
access: CpuAccess,
) -> Result<Self>where
T: 'static,
Allocate an image tensor of width × height in format.
This is the common-case image constructor. The shape follows from the
format’s layout — packed [H, W, C], planar [C, H, W], semi-planar
[H + chroma_rows, W] — and the format is recorded on the tensor, so
the image crate and the codec can work from it without out-of-band
parameters.
memory requests a backend, or None to auto-select. Auto-select for
images is DMA → Mem: unlike Tensor::new it does not try SHM,
which offers an image nothing that Mem doesn’t (it is not
GPU-importable, and Mem always succeeds). Ask for
TensorMemory::Shm explicitly if you need it.
access declares what the CPU intends to do with the buffer and is
required; see CpuAccess. Hardware access — GPU, NPU, ISP, codec —
is always implied and never declared. Prefer the narrowest declaration
that fits: None keeps the allocation eligible for Android vendor tile
compression, and anything else pins the layout linear.
DMA-backed images always get a 64-byte-aligned row stride, in every
layout, because Mali and Vivante reject an EGLImage import at an
unaligned pitch. Read the real pitch back with
Tensor::effective_row_stride rather than assuming width × bpp.
For a compression request or any other less common knob, build an
ImageDesc and call Tensor::image_desc.
§Errors
Error::InvalidArgumentifwidth×heightis not a valid size forformat(for example odd dimensions where the format’s chroma subsampling forbids them), or, on macOS with an explicitTensorMemory::DmaBuf, if the format cannot be expressed as an image-formatted IOSurface at this width. The message names the aligned width to use.- Whatever the chosen backend returns if the allocation itself fails —
for an explicit
memoryrequest there is no fallback.
§Example
use edgefirst_tensor::{CpuAccess, PixelFormat, Tensor, TensorMemory};
// A camera frame the GPU converts and the CPU never touches.
let frame = Tensor::<u8>::image(1920, 1080, PixelFormat::Nv12,
Some(TensorMemory::Mem), CpuAccess::None)?;
assert_eq!(frame.format(), Some(PixelFormat::Nv12));Sourcepub fn image_with_stride(
width: usize,
height: usize,
format: PixelFormat,
row_stride_bytes: usize,
memory: Option<TensorMemory>,
access: CpuAccess,
) -> Result<Self>
pub fn image_with_stride( width: usize, height: usize, format: PixelFormat, row_stride_bytes: usize, memory: Option<TensorMemory>, access: CpuAccess, ) -> Result<Self>
Create a DMA-backed image tensor with an explicit row stride that
may exceed the natural width * channels * sizeof(T) pitch.
Used for image tensors that need GPU pitch alignment padding: the
underlying DMA-BUF is sized to row_stride * height bytes, but
the tensor’s logical shape stays at [height, width, channels].
width() / height() / shape() continue to report the
user-requested values; the padding is visible only via
row_stride() / effective_row_stride() and is automatically
propagated to the GL backend’s EGLImage import so Mali Valhall
accepts the buffer.
§Supported formats
Currently only packed pixel layouts (RGBA8, BGRA8, RGB888,
Grey, etc.) are supported — the formats the GL backend uses as
render destinations. Semi-planar formats (NV12, NV16) come from
external allocators (camera capture, video decoders) and are
imported via TensorDyn::from_fd + set_row_stride, which
already supports padded strides.
§Supported memory
Currently only TensorMemory::DmaBuf is supported. PBO and Mem
storage don’t go through EGLImage import so they don’t need
pitch alignment; if you pass any other memory type this returns
NotImplemented. None (auto-select) is treated as Dma.
§Errors
InvalidArgumentifrow_stride_bytes < width * channels * sizeof(T)(the requested stride would not fit a single row)NotImplementedfor non-packed formats or non-DMA memoryIoErrorif the DMA-heap allocation fails (propagated fromDmaTensor::new_with_byte_size)
Sourcepub fn set_format(&mut self, format: PixelFormat) -> Result<()>
pub fn set_format(&mut self, format: PixelFormat) -> Result<()>
Attach format metadata to an existing tensor.
§Arguments
format- The pixel format to attach
§Returns
Ok(()) on success, with the format stored as metadata on the tensor.
§Errors
Returns Error::InvalidShape if the tensor shape is incompatible with
the format’s layout (packed expects [H, W, C], planar expects
[C, H, W], semi-planar expects [H*k, W] with format-specific
height constraints).
Sourcepub fn configure_image(
&mut self,
width: usize,
height: usize,
format: PixelFormat,
) -> Result<()>
pub fn configure_image( &mut self, width: usize, height: usize, format: PixelFormat, ) -> Result<()>
Set this tensor’s logical dimensions and pixel format to a decoded
image, reusing the existing allocation. The shape is derived from the
format layout; fails with Error::InsufficientCapacity if the
allocation cannot hold width×height in format, or
Error::InvalidArgument if the dimensions are invalid for the format.
For NV12/NV16/NV24 the buffer width is rounded up to even (a chroma-plane
interleaving requirement); the true odd width is reported by the decoder
in ImageInfo and trimmed by a convert() crop. See
PixelFormat::allocation_shape.
When the backing has a fixed physical row pitch (an IOSurface’s
64-aligned bytesPerRow) that exceeds the new format’s natural row
stride — i.e. a reused max-sized pool tensor reconfigured to a smaller
image — the physical pitch is preserved as the tensor’s row_stride.
This keeps the physical grid (allocation stride/surface) fixed while
the logical ROI (this image’s W×H) changes, so the decode writes rows
at the surface’s real stride and the GPU samples them at the same stride.
Exact-sized buffers (pitch == natural) stay tightly packed unchanged.
Sourcepub fn image_with_capacity(
width: usize,
height: usize,
format: PixelFormat,
memory: Option<TensorMemory>,
access: CpuAccess,
) -> Result<Self>where
T: 'static,
pub fn image_with_capacity(
width: usize,
height: usize,
format: PixelFormat,
memory: Option<TensorMemory>,
access: CpuAccess,
) -> Result<Self>where
T: 'static,
Allocate an image tensor sized to hold up to width×height in
format, reusable for any smaller image via configure_image.
Sourcepub fn format(&self) -> Option<PixelFormat>
pub fn format(&self) -> Option<PixelFormat>
Pixel format (None if not an image).
Sourcepub fn height(&self) -> Option<usize>
pub fn height(&self) -> Option<usize>
Image height (None if not an image).
For semi-planar formats the combined-plane shape row count is divided
by the format’s luma-to-total ratio to recover logical height. This
returns the exact logical height (including odd heights) only because
the logical dimensions are tracked separately from the physical shape —
configure_image stores the actual (width, height) in the format’s
allocation_shape, which round-trips losslessly via these accessors.
Sourcepub fn from_planes(
luma: Tensor<T>,
chroma: Tensor<T>,
format: PixelFormat,
) -> Result<Self>
pub fn from_planes( luma: Tensor<T>, chroma: Tensor<T>, format: PixelFormat, ) -> Result<Self>
Create from separate Y and UV planes (multiplane NV12/NV16).
Sourcepub fn is_multiplane(&self) -> bool
pub fn is_multiplane(&self) -> bool
Whether this tensor uses separate plane allocations.
Sourcepub fn chroma(&self) -> Option<&Tensor<T>>
pub fn chroma(&self) -> Option<&Tensor<T>>
Access the chroma plane for multiplane semi-planar images.
Sourcepub fn chroma_mut(&mut self) -> Option<&mut Tensor<T>>
pub fn chroma_mut(&mut self) -> Option<&mut Tensor<T>>
Mutable access to the chroma plane for multiplane semi-planar images.
Sourcepub fn row_stride(&self) -> Option<usize>
pub fn row_stride(&self) -> Option<usize>
Row stride in bytes (None = tightly packed).
Sourcepub fn effective_row_stride(&self) -> Option<usize>
pub fn effective_row_stride(&self) -> Option<usize>
Effective row stride in bytes: the stored stride if set, otherwise the
minimum stride computed from the format, width, and element size.
Returns None only when no format is set and no explicit stride was
stored via set_row_stride.
GREY note: effective_row_stride() for a GREY tensor returns the
tight width bytes (no padding), which is what normalize_to_numpy and
the CPU convert path expect. The codec’s internal native_row_stride
(64-byte-aligned) is used only during decoding and is not propagated to
the tensor’s stored stride, so callers reading via
effective_row_stride() always see the tight value for GREY.
Sourcepub fn copy_to_flat(&self, dst: &mut [u8]) -> Result<()>
pub fn copy_to_flat(&self, dst: &mut [u8]) -> Result<()>
Copy the tensor’s logical bytes into dst, compacting away any
recorded row-stride padding.
The flatness helper for NPU handoff: consumers that need a FLAT
layout (e.g. [1, C, H, W] for NNAPI/LiteRT when the runtime cannot
take a padded pitch) call this when row_stride
is Some — on a tight tensor it degenerates to one memcpy, so it is
safe to call unconditionally. dst.len() must equal the tight byte
footprint (shape product × element size). Zero-copy consumers
should prefer the buffer handle + Tensor::effective_row_stride
(Self::effective_row_stride) and skip this copy entirely.
Sourcepub fn set_row_stride(&mut self, stride: usize) -> Result<()>
pub fn set_row_stride(&mut self, stride: usize) -> Result<()>
Set the row stride in bytes for externally allocated buffers with row padding (e.g. V4L2 or GStreamer allocators).
The stride is propagated to the EGL DMA-BUF import attributes so
the GPU interprets the padded buffer layout correctly. Must be
called after set_format and before the tensor
is first passed to ImageProcessor::convert. The stored stride
is cleared automatically if the pixel format is later changed.
No stride-vs-buffer-size validation is performed because the backing allocation size is not reliably known: external DMA-BUFs may be over-allocated by the allocator, and internal tensors store a logical (unpadded) shape. An incorrect stride will be caught by the EGL driver at import time.
§Arguments
stride- Row stride in bytes. Must be >= the minimum stride for the format (width * channels * sizeof(T) for packed, width * sizeof(T) for planar/semi-planar).
§Errors
InvalidArgumentif no pixel format is set on this tensorInvalidArgumentifstrideis less than the minimum for the format and width
Sourcepub fn set_row_stride_unchecked(&mut self, stride: usize)
pub fn set_row_stride_unchecked(&mut self, stride: usize)
Set the row stride without format validation.
Use this for raw sub-tensors (e.g. chroma planes) that don’t carry format metadata. The caller is responsible for ensuring the stride is valid.
Sourcepub fn with_row_stride(self, stride: usize) -> Result<Self>
pub fn with_row_stride(self, stride: usize) -> Result<Self>
Builder-style variant of set_row_stride,
consuming and returning self.
§Errors
Same conditions as set_row_stride.
Sourcepub fn plane_offset(&self) -> Option<usize>
pub fn plane_offset(&self) -> Option<usize>
Byte offset within the DMA-BUF where image data starts (None = 0).
Sourcepub fn view_origin(&self) -> Option<ViewOrigin>
pub fn view_origin(&self) -> Option<ViewOrigin>
The parent-image snapshot if this tensor is a view/
batch sub-region; None for a whole tensor. The GL
backend keys its import on the parent geometry and renders this view as a
glViewport/glScissor ROI at (x, y, width, height). See ViewOrigin.
Sourcepub fn set_plane_offset(&mut self, offset: usize)
pub fn set_plane_offset(&mut self, offset: usize)
Set the byte offset within the DMA-BUF where image data starts.
Propagated to EGL_DMA_BUF_PLANE0_OFFSET_EXT on GPU import.
Unlike set_row_stride, no format is required
since the offset is format-independent.
Sourcepub fn cpu_access(&self) -> CpuAccess
pub fn cpu_access(&self) -> CpuAccess
Colorimetry metadata (None = undefined; never auto-filled).
The CPU access declared for this tensor at allocation (see
CpuAccess). Views share their parent’s declaration.
Sourcepub fn compression(&self) -> Option<CompressionScheme>
pub fn compression(&self) -> Option<CompressionScheme>
The vendor tile-compression scheme recorded at allocation, or
None for a linear layout. Some means the pixels live in a
proprietary tile order: the row-stride accessors describe no
meaningful linear layout and CPU maps are best-effort (see
Compression). Only Android hardware-only allocations that
requested compression record a scheme.
pub fn colorimetry(&self) -> Option<Colorimetry>
Sourcepub fn set_colorimetry(&mut self, c: Option<Colorimetry>)
pub fn set_colorimetry(&mut self, c: Option<Colorimetry>)
Attach/clear colorimetry metadata.
Sourcepub fn with_colorimetry(self, c: Colorimetry) -> Self
pub fn with_colorimetry(self, c: Colorimetry) -> Self
Builder-style colorimetry attach.
Sourcepub fn batch(&self, n: usize) -> Result<Tensor<T>>
pub fn batch(&self, n: usize) -> Result<Tensor<T>>
Borrow batch element n of a batched tensor as a zero-copy view.
A batched tensor prepends N as the leading dimension over the
per-element image layout ([N, H, W, C] packed or [N, C, H, W]
planar) — N is over the whole per-element block regardless of
HWC/CHW. batch(n) returns element n: the contiguous per-element
region at byte offset n * element_size, sharing the parent’s
BufferIdentity and inheriting its format / row stride / colorimetry.
batch(0) on a tensor with N == 1 is equivalent to the whole tensor.
§Errors
Error::BatchIndexOutOfBoundsifn >= N.Error::InvalidShapeif the tensor is not batched (a formatted tensor whose rank lacks the leadingN, or an empty shape).
Sourcepub fn view(&self, region: Region) -> Result<Tensor<T>>
pub fn view(&self, region: Region) -> Result<Tensor<T>>
Borrow a rectangular spatial sub-region of an image tensor as a zero-copy view — the destination/source crop primitive.
region is in pixels of the image’s leading frame. The returned view
shares the parent’s BufferIdentity and addresses the sub-rectangle by
offset + the parent’s row pitch (so each row lands at the correct
columns). convert(src, &mut dst.view(rect), …) renders into that
sub-rectangle of dst; a letterbox fit then clears the view and renders
the aspect-preserved content into its inner region. view/batch/the
whole tensor are the one coherent destination model — there is no
separate dst_rect.
§Known limitation: bottom-right multi-row views
A multi-row view is mapped as parent_pitch × rows bytes from its own
offset, so the window it asks for overruns the allocation by
region.x * bpp whenever the view is both offset horizontally and
reaches the parent’s last row. Such a view constructs fine but fails at
map time with Error::InsufficientCapacity —
loudly, never as an out-of-bounds access. For a tiled destination this is
the bottom row of tiles at every column but the first. The single-row
case is already special-cased below (it keeps its own tight stride); the
general fix is to clamp the last row’s exposure to the view’s own row
bytes, which is not yet implemented.
§Errors
Error::RegionOutOfBoundsifregionexceeds the image bounds.Error::InvalidOperationif the tensor is not a packed-format image (planar/semi-planar spatial sub-rects are not a single strided window; usebatchfor batched planar tensors).
Sourcepub fn as_pbo(&self) -> Option<&PboTensor<T>>
pub fn as_pbo(&self) -> Option<&PboTensor<T>>
Downcast to PBO tensor reference (for GL backends).
Sourcepub fn pbo_id(&self) -> Option<u32>
pub fn pbo_id(&self) -> Option<u32>
The GL buffer ID backing this tensor (for GL backends / the
cross-package protocol’s handle). None when the tensor is not
PBO-backed.
Sourcepub fn pbo_vtable_ptr(&self) -> Option<*const c_void>
pub fn pbo_vtable_ptr(&self) -> Option<*const c_void>
The C-ABI PboOpsVtable address backing this tensor (for the
cross-package protocol’s ptr under kind::PBO; see
crate::protocol::TensorDesc::ptr’s doc comment). None when the
tensor is not PBO-backed.
Sourcepub fn pbo_keepalive(&self) -> Option<Arc<dyn Send + Sync>>
pub fn pbo_keepalive(&self) -> Option<Arc<dyn Send + Sync>>
A type-erased keepalive that must stay alive for at least as long as
Self::pbo_vtable_ptr’s address is used – see
PboTensor::pbo_keepalive’s own doc comment. None when the
tensor is not PBO-backed.
Sourcepub fn as_dma(&self) -> Option<&DmaTensor<T>>
pub fn as_dma(&self) -> Option<&DmaTensor<T>>
Downcast to DMA tensor reference (for EGL import, G2D).
Sourcepub fn dmabuf(&self) -> Result<BorrowedFd<'_>>
pub fn dmabuf(&self) -> Result<BorrowedFd<'_>>
Sourcepub fn from_pbo(pbo: PboTensor<T>) -> Result<Self>
pub fn from_pbo(pbo: PboTensor<T>) -> Result<Self>
Construct a Tensor from a PBO tensor (for GL backends that allocate PBOs).
Infallible in this backend (Ok always) – kept as Result to match
dynamic’s own Tensor::from_pbo (dynamic_tensor.rs), which
genuinely can fail: it must allocate a real ef_tensor_* handle
alongside pbo to carry shape/dtype/format metadata (this backend
has nowhere else to put it), an allocation this backend never
needed since it just wraps pbo into TensorStorage::Pbo directly.
One shared, fallible signature lets edgefirst-image’s own call
sites (gl/threaded.rs) stay backend-agnostic.
Sourcepub fn cuda(&self) -> Option<&CudaHandle>
pub fn cuda(&self) -> Option<&CudaHandle>
The CUDA registration for this tensor, if any (set at creation on CUDA devices).
Sourcepub fn set_cuda_handle(&mut self, h: CudaHandle)
pub fn set_cuda_handle(&mut self, h: CudaHandle)
Attach a CUDA handle (called by ImageProcessor::create_image after registering a PBO).
Sourcepub fn cuda_map(&self) -> Option<CudaMap<'_>>
pub fn cuda_map(&self) -> Option<CudaMap<'_>>
Fast-fail CUDA map: None (no GL routing) when no handle; else map (PBO routes to the GL worker).
Returns a scoped CudaMap guard holding the raw CUDA device pointer
for the duration of the mapping. For GL-buffer-backed tensors the unmap is deferred until the
guard drops, freeing the PBO for the next convert() call. When no CUDA handle is attached
(the common case for plain Mem/DMA tensors without CUDA registration), returns None
immediately — no GL routing, no allocation.
§Example — zero-copy CUDA input with host fallback
use edgefirst_tensor::{Tensor, TensorMemory, TensorTrait};
// Try the zero-copy CUDA device pointer first.
if let Some(cuda) = t.cuda_map() {
feed_tensorrt(cuda.device_ptr(), cuda.len());
// `cuda` (a CudaMap guard) unmaps when it goes out of scope, freeing
// the GPU buffer for the next convert().
} else {
// Fall back to the host mapping when no CUDA handle is attached.
let _host = t.map().expect("host map fallback must succeed");
// `_host` is a TensorMap<f32> that derefs to &[f32].
}§D3D11 texture tensors: the mapping is tight rows
On Windows the mapping is a linear copy of the texture laid out at
width * bytes_per_texel, and len() is that times the row count –
not row_stride, which reports the D3D11 staging
pitch a CPU map sees and is the larger number on a padded backing. A
consumer that strides the device pointer by row_stride() reads past
the end of the mapping.
Semi-planar formats (NV12, NV16, NV24) are the exception: their texture
is allocated as wide as its own row pitch, so the tight row is the
pitch and the mapping equals row_stride() x combined_height bytes.
Striding the device pointer by row_stride() is exactly right there.
Sourcepub fn cuda_map_mut(&self) -> Option<CudaMap<'_>>
pub fn cuda_map_mut(&self) -> Option<CudaMap<'_>>
Writable counterpart of cuda_map. See
CudaHandle::map_mut for which
backings distinguish the two.
The D3D11 arm is the one that does: the guard’s release copies the
buffer back into the texture. Its layout is cuda_map’s
– tight rows of width * bytes_per_texel, not row_stride() – and
writing it at the wrong pitch scrambles the image rather than failing.
Sourcepub fn try_init_dma_cuda(&mut self)
pub fn try_init_dma_cuda(&mut self)
Attempt to attach a CUDA ExternalMemory handle for DMA-backed tensors.
On a CUDA-capable host, imports the DMA-BUF fd via
cudaImportExternalMemory(OpaqueFd) and maps it to a device pointer.
Sets self.cuda to a persistent ExternalMem handle on success. No-op
if CUDA is unavailable, the tensor is not DMA-backed, or a handle is
already set. Import failure is silently ignored — the tensor remains
usable without a CUDA handle.
§RUNTIME-UNVALIDATED
No test platform has both /dev/dma_heap and a CUDA device. ABI is
layout-asserted vs. CUDA 12.6 driver_types.h; the mechanism is proven
by gpu-probe O5 on Orin. Best-effort: tensor creation never fails here.
Source§impl<T> Tensor<T>
The recorded GPU write value, on every platform: the descriptor path and
the Python bindings forward it without a cfg of their own, and only
Windows can record one.
impl<T> Tensor<T>
The recorded GPU write value, on every platform: the descriptor path and
the Python bindings forward it without a cfg of their own, and only
Windows can record one.
Sourcepub fn gpu_write_value(&self) -> u64
pub fn gpu_write_value(&self) -> u64
The fence value of the newest GPU write recorded on this tensor, or 0
when there is none: the value of gpu_completion without the
duplicated fence handle, for a consumer that already holds the process
fence. 0 for a tensor that is not a D3D11 texture, and on every
platform other than Windows.
Source§impl<T> Tensor<T>
impl<T> Tensor<T>
Sourcepub fn quantization(&self) -> Option<&Quantization>
pub fn quantization(&self) -> Option<&Quantization>
Quantization metadata for this tensor, if set.
Sourcepub fn set_quantization(&mut self, q: Quantization) -> Result<()>
pub fn set_quantization(&mut self, q: Quantization) -> Result<()>
Attach quantization metadata to this tensor. Validates against the
tensor’s shape — returns Error::QuantizationInvalid on any
inconsistency (mismatched scale/zp lengths, out-of-range axis, etc.).
Sourcepub fn with_quantization(self, q: Quantization) -> Result<Self>
pub fn with_quantization(self, q: Quantization) -> Result<Self>
Builder-style variant of Self::set_quantization. Consumes self
and returns Result<Self> — on success yields the tensor with the
attached quantization; on validation failure returns
Error::QuantizationInvalid and drops self (the tensor is not
returned in the error arm).
Sourcepub fn clear_quantization(&mut self)
pub fn clear_quantization(&mut self)
Clear any quantization metadata on this tensor.
Trait Implementations§
Source§impl<T> TensorTrait<T> for Tensor<T>
Available on crate feature static only.
impl<T> TensorTrait<T> for Tensor<T>
static only.Source§fn capacity_bytes(&self) -> usize
fn capacity_bytes(&self) -> usize
Bytes available in the underlying allocation, from this tensor’s
window start – may exceed TensorTrait::size when the backing
carries spare room beyond the current logical shape (a pitch-aligned
image, or a pool tensor sized for a larger image than it holds
today). See crate::protocol::TensorDesc::capacity, the
cross-package descriptor field this backs.
Source§fn set_logical_shape(&mut self, shape: &[usize]) -> Result<()>
fn set_logical_shape(&mut self, shape: &[usize]) -> Result<()>
Set the logical shape to any shape whose bytes fit the allocation,
without reshape’s equal-count constraint.
Overridden rather than inherited, which it was not for most of this
crate’s life. The trait’s default body is self.reshape(shape), so
a caller reaching this through Tensor<T> silently got the strict
rule under a name that promises the opposite – on both backends,
which is why G13 cannot see it.
The capacity-aware implementations were never dead: every storage
type has one, and Tensor::configure_image reaches them by
calling self.storage.set_logical_shape(..) directly. That is the
pool-reuse path – a decode into an oversized reusable buffer
reconfiguring it to a smaller image without reallocating – and it
worked precisely because it bypassed this method. This forwards the
same way, so the two agree.
Source§fn new(shape: &[usize], name: Option<&str>) -> Result<Self>where
Self: Sized,
fn new(shape: &[usize], name: Option<&str>) -> Result<Self>where
Self: Sized,
Source§fn from_fd(fd: OwnedFd, shape: &[usize], name: Option<&str>) -> Result<Self>where
Self: Sized,
fn from_fd(fd: OwnedFd, shape: &[usize], name: Option<&str>) -> Result<Self>where
Self: Sized,
Source§fn memory(&self) -> TensorMemory
fn memory(&self) -> TensorMemory
Source§fn reshape(&mut self, shape: &[usize]) -> Result<()>
fn reshape(&mut self, shape: &[usize]) -> Result<()>
Source§fn map_with<'a>(&self, access: CpuAccess) -> Result<HostView<'a, T>>where
T: 'a,
fn map_with<'a>(&self, access: CpuAccess) -> Result<HostView<'a, T>>where
T: 'a,
Source§fn buffer_identity(&self) -> &BufferIdentity
fn buffer_identity(&self) -> &BufferIdentity
Source§fn map<'a>(&self) -> Result<HostView<'a, T>>where
T: 'a,
fn map<'a>(&self) -> Result<HostView<'a, T>>where
T: 'a,
map_with(CpuAccess::ReadWrite) — the historical map()
behavior).Source§fn map_read<'a>(&self) -> Result<HostView<'a, T>>where
T: 'a,
fn map_read<'a>(&self) -> Result<HostView<'a, T>>where
T: 'a,
as_mut_slice; on macOS this takes the read-only IOSurface lock
(skips the unlock flush), on Linux the dma-buf read-direction
sync.Source§fn map_write<'a>(&self) -> Result<HostView<'a, T>>where
T: 'a,
fn map_write<'a>(&self) -> Result<HostView<'a, T>>where
T: 'a,
Source§fn map_mut<'a>(&self) -> Result<HostView<'a, T>>where
T: 'a,
fn map_mut<'a>(&self) -> Result<HostView<'a, T>>where
T: 'a,
map with the
intent spelled out).Auto Trait Implementations§
impl<T> !RefUnwindSafe for Tensor<T>
impl<T> !UnwindSafe for Tensor<T>
impl<T> Freeze for Tensor<T>
impl<T> Send for Tensor<T>
impl<T> Sync for Tensor<T>
impl<T> Unpin for Tensor<T>
impl<T> UnsafeUnpin for Tensor<T>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more