pub struct ImageProcessor {
pub cpu: Option<CPUProcessor>,
pub g2d: Option<G2DProcessor>,
pub opengl: Option<GLProcessorThreaded>,
/* private fields */
}Expand description
Image converter that uses available hardware acceleration or CPU as a fallback.
Fields§
§cpu: Option<CPUProcessor>CPU-based image converter as a fallback. This is only None if the EDGEFIRST_DISABLE_CPU environment variable is set.
g2d: Option<G2DProcessor>G2D-based image converter for Linux systems. This is only available if the EDGEFIRST_DISABLE_G2D environment variable is not set and libg2d.so is available.
opengl: Option<GLProcessorThreaded>OpenGL-based image converter for Linux systems. This is only available if the EDGEFIRST_DISABLE_GL environment variable is not set and OpenGL ES is available.
Implementations§
Source§impl ImageProcessor
impl ImageProcessor
Sourcepub fn alloc_tile_batch(
&self,
n: usize,
cfg: &TilingConfig,
format: PixelFormat,
dtype: DType,
memory: Option<TensorMemory>,
access: CpuAccess,
) -> Result<TensorDyn>
pub fn alloc_tile_batch( &self, n: usize, cfg: &TilingConfig, format: PixelFormat, dtype: DType, memory: Option<TensorMemory>, access: CpuAccess, ) -> Result<TensorDyn>
Allocate the tall packed batch destination [tile_w, n*tile_h] — a
single GL-importable parent that stacks n tiles vertically. Uses
ImageProcessor::create_image so DMA pitch alignment and
Dma>Pbo>Mem selection apply. Caller-owned for pool reuse.
access declares planned CPU involvement (see
edgefirst_tensor::CpuAccess); use None for NPU-bound tile
batches and ReadWrite when the batch will be CPU-mapped. As for
create_image, what this processor’s own convert may do on the CPU
is added; tiles are written through views, so any predicted CPU
write is added as ReadWrite.
§Errors
Returns an error if cfg is invalid (overlap not in [0,1), or zero
tile size) or if the underlying allocation fails.
Sourcepub fn plan_tiles(
&self,
src_w: usize,
src_h: usize,
cfg: &TilingConfig,
) -> Result<Vec<TilePlacement>>
pub fn plan_tiles( &self, src_w: usize, src_h: usize, cfg: &TilingConfig, ) -> Result<Vec<TilePlacement>>
Compute the tile grid and per-tile TilePlacement metadata for a
src_w×src_h frame without touching the GPU. A pipelined runtime
calls this once per frame to size pools and drive its tile stream.
The realized tile count can exceed the naive ceil(frame / tile): the
grid treats cfg.overlap_ratio as a minimum and redistributes the
surplus evenly, so allocate from len() rather than estimating.
The returned placements are consumed by
edgefirst_decoder::tiling to lift per-tile detections back to
full-frame coordinates.
§Errors
Returns an error if cfg is invalid (overlap not in [0,1), or zero
tile size).
Sourcepub fn tile_into(
&mut self,
src: &TensorDyn,
dst_batched: &mut TensorDyn,
cfg: &TilingConfig,
) -> Result<Vec<TilePlacement>>
pub fn tile_into( &mut self, src: &TensorDyn, dst_batched: &mut TensorDyn, cfg: &TilingConfig, ) -> Result<Vec<TilePlacement>>
Render every tile of src into dst_batched (a tall packed parent from
Self::alloc_tile_batch), one deferred convert per tile, then a single
flush. Returns the per-tile TilePlacement in tile-index order.
The source crop is selected via Crop::with_source (a sampled
sub-rect of the whole-frame src) — not src.view() — so all tiles
share one source import. Each destination tile is a view row-band of
the shared parent.
§Examples
let mut processor = ImageProcessor::new()?;
let cfg = TilingConfig::new(640, 640).with_overlap(0.2);
// Pure geometry — size the batch before touching the GPU.
let planned = processor.plan_tiles(3840, 2160, &cfg)?;
let mut batch = processor.alloc_tile_batch(
planned.len(), &cfg, PixelFormat::Rgba, DType::U8, None, CpuAccess::None,
)?;
// One import, one flush, N tiles.
let placements = processor.tile_into(src, &mut batch, &cfg)?;
assert_eq!(placements.len(), planned.len());§Backend note
The GL band lowering covers single-pass geometry only. A zero-copy
(DMA) dst_batched in packed Rgb or a planar layout reinterprets the
render-target shape, which the band path does not yet compute, so GL
declines and each tile falls back to CPU. Rgba, Bgra, and Grey
batches stay on the batched GPU path.
§Errors
Returns an error if cfg is invalid, if src/dst_batched are not
images, if dst_batched is shorter than count * tile_h, or if a tile
convert fails.
Sourcepub fn tile_one(
&mut self,
src: &TensorDyn,
dst_slot: &mut TensorDyn,
placement: &TilePlacement,
cfg: &TilingConfig,
) -> Result<()>
pub fn tile_one( &mut self, src: &TensorDyn, dst_slot: &mut TensorDyn, placement: &TilePlacement, cfg: &TilingConfig, ) -> Result<()>
Render exactly one tile of src into dst_slot (a single model-input
sized destination, e.g. a slot of a caller-owned ring). Deferred — the
caller flushes on its own cadence so tiles overlap with inference. Holds
no frame-wide state; all geometry rides in placement (from
Self::plan_tiles).
A deferred tile is not CPU/CUDA-readable until ImageProcessorTrait::flush
(mirrors ImageProcessorTrait::convert_deferred).
§Errors
Returns an error if cfg is invalid or the tile convert fails.
Source§impl ImageProcessor
impl ImageProcessor
Sourcepub fn new() -> Result<Self>
pub fn new() -> Result<Self>
Creates a new ImageProcessor instance, initializing available
hardware converters based on the system capabilities and environment
variables.
§Examples
let image = std::fs::read("zidane.jpg")?;
// The codec emits the source's native format (a colour JPEG decodes to
// NV12) and configures the destination tensor during the decode.
let info = peek_info(&image).expect("peek");
let mut src = Tensor::<u8>::image(info.width, info.height, info.format,
Some(TensorMemory::Mem), edgefirst_tensor::CpuAccess::ReadWrite)?;
let mut decoder = ImageDecoder::new();
src.load_image(&mut decoder, &image).expect("decode");
let mut converter = ImageProcessor::new()?;
let mut dst =
converter.create_image(640, 480, PixelFormat::Rgb, DType::U8, None, CpuAccess::ReadWrite)?;
converter.convert(&src.into(), &mut dst, None)?;Sourcepub fn convert_fallback_count(&self) -> u64
pub fn convert_fallback_count(&self) -> u64
Number of converts where the auto chain’s GL backend declined and the
frame fell through toward G2D/CPU — each one silently lost any
zero-copy guarantee. A pipeline that expects to stay on the GPU
asserts this stays flat across its steady-state loop; pair with
GLProcessorThreaded::convert_stats to see how the frames that DID
reach GL were fed. Always 0 under a forced backend (no chain).
Sourcepub fn compression_fallback_count(&self) -> u64
pub fn compression_fallback_count(&self) -> u64
Number of Compression::Any
requests since process start that resolved to a linear layout
(process-wide mirror of
edgefirst_tensor::compression_fallback_count, surfaced here
beside convert_fallback_count
so pipeline telemetry reads from one place). A pipeline that
expects compressed convert destinations asserts this stays flat
after warmup.
Sourcepub fn convert_with_fence(
&mut self,
src: &TensorDyn,
dst: &mut TensorDyn,
options: Option<ConvertOptions>,
) -> Result<Option<CompletionFence>>
pub fn convert_with_fence( &mut self, src: &TensorDyn, dst: &mut TensorDyn, options: Option<ConvertOptions>, ) -> Result<Option<CompletionFence>>
Convert and return a native fence that signals when the GPU work
completes, instead of blocking the CPU — the GL→NPU handoff
(EGL_ANDROID_native_fence_sync on Android). On Windows the handle
is a manual-reset event set when the GPU work completes; wait on it
with WaitForSingleObject.
Ok(Some(fence)): the destination buffer is still in flight; hand
the fence to the consumer (e.g.
ANeuralNetworksExecution_startComputeWithDependencies on Android,
or WaitForSingleObject on Windows) or poll it before reading.
Ok(None): the convert completed with the normal blocking contract
(no native fence on this platform, or a non-GL backend handled the
frame) — the destination is already safe. Semantics are otherwise
identical to convert, including
the GL→G2D→CPU fallback chain.
On Windows, for a texture destination, the event and the
destination’s own Tensor::gpu_completion name the same fence value:
one flush and one signal cover the convert, and the value recorded on
dst is the one this event waits for, so a consumer may use either.
A destination that is not a texture records nothing, and the event
then carries a value of its own that only it can be waited on for.
Sourcepub fn supported_render_dtypes(&self) -> RenderDtypeSupport
pub fn supported_render_dtypes(&self) -> RenderDtypeSupport
Report which float dtypes the GPU can render to.
Probes GL_EXT_color_buffer_half_float and
GL_EXT_color_buffer_float once at ImageProcessor::new() time
and caches the result. Call this once at startup to decide whether
to request F16 or F32 destination tensors; create_image uses
the result internally to auto-select float PBO when supported.
Returns RenderDtypeSupport { f32: false, f16: false } when no
OpenGL backend is active or the opengl feature is disabled.
Sourcepub fn with_config(config: ImageProcessorConfig) -> Result<Self>
pub fn with_config(config: ImageProcessorConfig) -> Result<Self>
Creates a new ImageProcessor with the given configuration.
When ImageProcessorConfig::backend is set to a specific backend,
environment variables are ignored and the processor initializes the
requested backend plus CPU as a fallback.
When Auto, the existing EDGEFIRST_FORCE_BACKEND and
EDGEFIRST_DISABLE_* environment variables apply.
Sourcepub fn set_colorimetry_mode(&mut self, mode: ColorimetryMode) -> Result<()>
pub fn set_colorimetry_mode(&mut self, mode: ColorimetryMode) -> Result<()>
Sets the colorimetry/performance trade-off (see ColorimetryMode)
on the OpenGL backend. No-op if OpenGL is not available. The
EDGEFIRST_COLORIMETRY environment variable takes precedence — when
it is set, this call logs and keeps the env-selected mode.
Sourcepub fn set_interpolation(&mut self, mode: Interpolation) -> Result<()>
pub fn set_interpolation(&mut self, mode: Interpolation) -> Result<()>
Sets how convert() resamples when it resizes (see Interpolation)
on every backend this processor holds. The EDGEFIRST_INTERPOLATION
environment variable takes precedence: when it is set, this call logs
and keeps the env-selected mode.
Sourcepub fn set_int8_interpolation_mode(
&mut self,
mode: Int8InterpolationMode,
) -> Result<()>
pub fn set_int8_interpolation_mode( &mut self, mode: Int8InterpolationMode, ) -> Result<()>
Sets the interpolation mode for int8 proto textures on the OpenGL backend. No-op if OpenGL is not available.
Sourcepub fn create_image_desc(&self, desc: &ImageDesc) -> Result<TensorDyn>
pub fn create_image_desc(&self, desc: &ImageDesc) -> Result<TensorDyn>
Allocate an image tensor from a declarative
ImageDesc request — the
full-featured variant of create_image.
Without a compression or contiguous request this is exactly
create_image (the processor’s memory negotiation applies). With
one, the allocation rides the tensor desc path un-negotiated: the
layout decision belongs to the platform allocator, and the request’s
guards and fallback counting live there (see
Tensor::image_desc). A
contiguous request is a CMA DMA-BUF at the same 64-byte GPU pitch
create_image uses, or an error; it never falls back to a PBO or
host memory. Its CPU access gets the same widening as create_image;
a compression request’s CpuAccess::None does not.
Sourcepub fn create_image(
&self,
width: usize,
height: usize,
format: PixelFormat,
dtype: DType,
memory: Option<TensorMemory>,
access: CpuAccess,
) -> Result<TensorDyn>
pub fn create_image( &self, width: usize, height: usize, format: PixelFormat, dtype: DType, memory: Option<TensorMemory>, access: CpuAccess, ) -> Result<TensorDyn>
Create a TensorDyn image with the best available memory backend.
Priority: DMA-buf → float PBO (F16/F32) → u8/i8 PBO → system memory.
Use this method instead of TensorDyn::image() when the tensor will
be used with ImageProcessor::convert(). It selects the optimal
memory backing (including PBO for GPU zero-copy) which direct
allocation cannot achieve.
This method is on ImageProcessor rather than ImageProcessorTrait
because optimal allocation requires knowledge of the active compute
backends (e.g. the GL context handle for PBO allocation). Individual
backend implementations (CPUProcessor, etc.) do not have this
cross-backend visibility.
Float dtype behaviour: when dtype is F16 or F32 and
supported_render_dtypes reports the GPU supports that type,
memory: None auto-selects a float PBO (Linux) or IOSurface (macOS
F16 only). If GPU float support is absent the allocation falls through
to TensorMemory::Mem; convert then uses the CPU path.
Passing memory: Some(TensorMemory::DmaBuf) with dtype: F32 always
returns Error::NotSupported — no 32-bit-float DRM fourcc exists.
§Arguments
width- Image width in pixelsheight- Image height in pixelsformat- Pixel formatdtype- Element data type (e.g.DType::U8,DType::F16,DType::F32)memory- Optional memory type override; whenNone, the best available backend is selected automatically.access- Declares the CPU access you plan on (ReadWriteis the natural choice). UseCpuAccess::Nonefor a destination that only hardware reads (an NPU input). Hardware access is always implied. What this processor’s own convert may do to the image as a destination on the CPU is added to the declaration so that convert succeeds:Writewhen it may fill the whole destination on the CPU (the CPU backend, host memory, a format or float dtype the GPU path does not render),ReadWritewhen a pass reads back what was written or writes only part of the buffer (anI8destination through the CPU or G2D, aBgradestination through the CPU, a G2DRgbletterbox border). The returned tensor’scpu_accessmay therefore be wider thanaccess. Any other CPU map beyond the declaration fails withCpuAccessDenied. The widening covers the destination role of convert only: a tensor later used as a convert source needsReaddeclared by the caller, and a CPU mask draw blends into its destination and needsReadWrite, so declare it when the CPU may draw. A destination written through views (a batch) needsReadWritefor a CPU convert, whichalloc_tile_batchpredicts. A destination left without CPU access (Noneon a GPU-rendered format) is written only by the GPU, so a convert into it needs the GPU to accept the source as well; when it declines, the CPU fallback is refused withError::CpuFallbackDeniedcarrying the GPU’s reason. A PBO destination is alwaysReadWrite: the GL readback maps it on the CPU itself, and a narrower declaration would buy no cheaper mapping.
§Returns
A TensorDyn backed by the highest-performance memory type
available on this system.
§Examples
let processor = ImageProcessor::new()?;
// Model input the NPU reads directly — no CPU mapping planned.
let input = processor.create_image(
640, 640, PixelFormat::Rgb, DType::U8, None, CpuAccess::None,
)?;§Pitch alignment for DMA-backed allocations
DMA-BUF imports into the GL backend (Mali Valhall on i.MX 95
specifically) require every row pitch to be a multiple of
GPU_DMA_BUF_PITCH_ALIGNMENT_BYTES (currently 64). When this
method lands on TensorMemory::DmaBuf, the underlying allocation is
silently padded so the row stride satisfies that requirement.
The user-requested width is preserved — tensor.width()
returns the same value you passed in. The padding is carried by
TensorDyn::row_stride / effective_row_stride(), which the
GL backend reads when importing the buffer as an EGLImage.
Callers that compute byte offsets from the tensor must use the
stride, not width × bytes_per_pixel; the CPU mapping spans the
full stride × height bytes.
Pre-aligned widths (640, 1280, 1920, 3008, 3840 …) allocate
exactly width × bpp × height bytes with no padding. PBO and
Mem fallbacks never pad — they don’t go through EGLImage import.
See also align_width_for_gpu_pitch for an advisory helper
that external callers (GStreamer plugins, video pipelines) can
use to size their own DMA-BUFs for GL compatibility.
§Errors
Returns an error if all allocation strategies fail.
Sourcepub fn import_image(
&self,
image: PlaneDescriptor,
chroma: Option<PlaneDescriptor>,
width: usize,
height: usize,
format: PixelFormat,
dtype: DType,
colorimetry: Option<Colorimetry>,
) -> Result<TensorDyn>
pub fn import_image( &self, image: PlaneDescriptor, chroma: Option<PlaneDescriptor>, width: usize, height: usize, format: PixelFormat, dtype: DType, colorimetry: Option<Colorimetry>, ) -> Result<TensorDyn>
Import an external DMA-BUF image.
Each [PlaneDescriptor] owns an already-duped fd; this method
consumes the descriptors and takes ownership of those fds (whether
the call succeeds or fails).
The caller must ensure the DMA-BUF allocation is large enough for the specified width, height, format, and any stride/offset on the plane descriptors. No buffer-size validation is performed; an undersized buffer may cause GPU faults or EGL import failure.
§Arguments
image- Plane descriptor for the primary (or only) planechroma- Optional plane descriptor for the UV chroma plane (required for multiplane NV12)width- Image width in pixelsheight- Image height in pixelsformat- Pixel format of the bufferdtype- Element data type (e.g.DType::U8)
§Returns
A TensorDyn configured as an image.
§Errors
Error::NotSupportedifchromaisSomefor a non-semi-planar format, or multiplane NV16 (not yet supported), or the fd is not DMA-backedError::InvalidShapeif NV12 height is odd
§Platform
Linux only.
§Examples
use edgefirst_tensor::PlaneDescriptor;
// Single-plane RGBA
let pd = PlaneDescriptor::new(fd.as_fd())?;
let src = proc.import_image(pd, None, 1920, 1080, PixelFormat::Rgba, DType::U8, None)?;
// Multi-plane NV12 with stride
let y_pd = PlaneDescriptor::new(y_fd.as_fd())?.with_stride(2048);
let uv_pd = PlaneDescriptor::new(uv_fd.as_fd())?.with_stride(2048);
let src = proc.import_image(y_pd, Some(uv_pd), 1920, 1080,
PixelFormat::Nv12, DType::U8, None)?;Sourcepub fn materialize_masks(
&mut self,
detect: &[DetectBox],
proto_data: &ProtoData,
letterbox: Option<[f32; 4]>,
resolution: MaskResolution,
) -> Result<Vec<Segmentation>>
pub fn materialize_masks( &mut self, detect: &[DetectBox], proto_data: &ProtoData, letterbox: Option<[f32; 4]>, resolution: MaskResolution, ) -> Result<Vec<Segmentation>>
Materialize per-instance segmentation masks from raw prototype data.
Computes mask_coeff @ protos with sigmoid activation for each detection,
producing compact masks at prototype resolution (e.g., 160×160 crops).
Mask values are continuous sigmoid confidence outputs quantized to u8
(0 = background, 255 = full confidence), NOT binary thresholded.
The returned Vec<Segmentation> can be:
- Inspected or exported for analytics, IoU computation, etc.
- Passed directly to
ImageProcessorTrait::draw_decoded_masksfor GPU-interpolated rendering.
§Performance Note
Calling materialize_masks + draw_decoded_masks separately prevents
the HAL from using its internal fused optimization path. For render-only
use cases, prefer ImageProcessorTrait::draw_proto_masks which selects
the fastest path automatically (currently 1.6×–27× faster on tested
platforms). Use this method when you need access to the intermediate masks.
§Errors
Returns Error::NoConverter if the CPU backend is not available.
Trait Implementations§
Source§impl Debug for ImageProcessor
impl Debug for ImageProcessor
Source§impl ImageProcessorTrait for ImageProcessor
impl ImageProcessorTrait for ImageProcessor
Source§fn convert(
&mut self,
src: &TensorDyn,
dst: &mut TensorDyn,
options: Option<ConvertOptions>,
) -> Result<()>
fn convert( &mut self, src: &TensorDyn, dst: &mut TensorDyn, options: Option<ConvertOptions>, ) -> Result<()>
Converts the source image to the destination image format and size. The image is cropped first, then flipped, then rotated
Prefer hardware accelerators when available, falling back to CPU if necessary.
Source§fn convert_deferred(
&mut self,
src: &TensorDyn,
dst: &mut TensorDyn,
options: Option<ConvertOptions>,
) -> Result<()>
fn convert_deferred( &mut self, src: &TensorDyn, dst: &mut TensorDyn, options: Option<ConvertOptions>, ) -> Result<()>
Source§fn flush(&mut self) -> Result<()>
fn flush(&mut self) -> Result<()>
convert_deferred
since the last flush, issuing a single GPU synchronization. Read moreSource§fn draw_decoded_masks(
&mut self,
dst: &mut TensorDyn,
detect: &[DetectBox],
segmentation: &[Segmentation],
overlay: MaskOverlay<'_>,
) -> Result<()>
fn draw_decoded_masks( &mut self, dst: &mut TensorDyn, detect: &[DetectBox], segmentation: &[Segmentation], overlay: MaskOverlay<'_>, ) -> Result<()>
dst. Read moreSource§fn draw_proto_masks(
&mut self,
dst: &mut TensorDyn,
detect: &[DetectBox],
proto_data: &ProtoData,
overlay: MaskOverlay<'_>,
) -> Result<()>
fn draw_proto_masks( &mut self, dst: &mut TensorDyn, detect: &[DetectBox], proto_data: &ProtoData, overlay: MaskOverlay<'_>, ) -> Result<()>
impl Send for ImageProcessor
impl Sync for ImageProcessor
Auto Trait Implementations§
impl !Freeze for ImageProcessor
impl !RefUnwindSafe for ImageProcessor
impl !UnwindSafe for ImageProcessor
impl Unpin for ImageProcessor
impl UnsafeUnpin for ImageProcessor
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