You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
Python: __tensor_backend__ on edgefirst.tensor, .codec, .image and .decoder. Reports the edgefirst-tensor backend the extension was built against: "dynamic" for every wheel, which shares the libedgefirst_tensor that edgefirst-tensor bundles.
Normalization and quantized model inputs in convert (edgefirst-image, EDGEAI-2330).ConvertOptions carries a per-channel Normalization, (x - mean) / std on 0–255 values, with the edgefirst.json presets (Normalization::unsigned, the default; signed; raw; imagenet) or explicit coefficients. Float destinations receive the normalized values; a u8 or i8 destination with a non-default normalization is quantized with its own Quantization in the same pass, as clamp(floor(y / scale + 0.5) + zero_point). Applied by the CPU path (NEON on aarch64) and by the GL float shaders and int8 programs; G2D declines such converts. The GL int8 programs link a second, mapped build on the first mapped convert, so a convert without normalization or quantization runs the same int8 shaders as before. Error::InvalidOptions reports a normalization on an integer destination without quantization.
Letterbox placement: LetterboxPlacement::{Center, TopLeft}; MaskOverlay::with_convert_options maps the result back for either.
Output-domain pad: ConvertOptions::pad_output writes the pad as given (quantized for integer destinations) instead of mapping the pixel pad.
ConvertOptions::with_preprocessing (feature decode) reads a model's input.preprocessing.
GL renders F32 PlanarRgb and F16 Rgb into PBO destinations, so all four F32/F16 × NCHW/NHWC layouts have a GL path.
Python: ConvertOptions, LetterboxPlacement and NormalizationPreset, and the keyword-only convert arguments options, letterbox_placement, pad_output and normalization. C: ef_convert_options with ef_convert_options_new/_free/_set_*, ef_letterbox_placement and ef_normalization.
GL PlanarRgba destinations (edgefirst-image). u8 and i8 PlanarRgba render on the GPU like PlanarRgb, as four stacked single-channel planes, zero-copy into a DMA-BUF or read back from heap and PBO memory; before, they always ran on the CPU. An integer alpha plane stays the raw byte, as on the CPU.
TensorMemory::D3d11Texture (code 6). Names a Windows D3D11 texture specifically, as IoSurface does for IOSurface. No backend allocates or reports it; Windows still allocates and reports DmaBuf, and requesting it fails with NotImplemented. C: EF_STORAGE_KIND_D3D11_TEXTURE. Python: TensorMemory.D3D11_TEXTURE.
CpuAccess::code(), from_code(), as_str() and from_str_code().CpuAccess is declared through the shared vocabulary like TensorMemory, so its codes (None=0, Read=1, Write=2, ReadWrite=3) and strings (none, read, write, readwrite) have one declaration. The C ef_cpu_access enumerators are checked against it at build time.
code(), from_code() and all() on ColorSpace, ColorTransfer, ColorEncoding and ColorRange. The four colorimetry axes are declared through the shared vocabulary, so each axis's code is the byte Colorimetry::pack writes and ef_tensor_colorimetry returns. The codes and the schema strings are unchanged.
C: ef_compression_request. Names the values ef_tensor_image_desc_set_compression takes and ef_image_desc_view.compression reports: EF_COMPRESSION_REQUEST_NONE (0), _ANY (1) and _SCHEME (2). The values are unchanged.
Semantic segmentation decoder (edgefirst-decoder, EDGEAI-2164). New semantic module: per-pixel class scores (segmentation, [C, h, w] in any axis order named by dshape; f32, f16, u8, i8, u16, i16) or a class map from an in-graph argmax (masks; u8, u16, i32, u32, i64) decode into a caller-supplied [H, W] class map of u8 (up to 256 classes) or u16, at any resolution.
Each output pixel maps through the letterbox region of the model input (SemanticOptions::with_letterbox, normalized [xmin, ymin, xmax, ymax], any placement), so the padding is cropped and the class map covers the original image.
DecodeMode::Accurate bilinearly upsamples every class (PyTorch align_corners semantics) and takes the argmax; pixels whose low-resolution neighbours agree skip the interpolation, and boundary pixels interpolate only the classes that can win, so the result is exact. DecodeMode::Fast takes the argmax at the model's resolution and upsamples the class map with pixel-centre nearest sampling. The schema's decode_mode is the default; SemanticOptions::with_mode overrides it. Ties go to the lowest class, as torch.argmax.
On SegFormer-B1 ADE20K val (2,000 images), accurate is within 0.01 mIoU points of the mmseg two-step reference at all three input shapes, and fast costs 0.12–0.13 points.
Per-tensor quantized scores decode on the stored integers; per-channel quantization along the class axis is dequantized first. Single-channel outputs are thresholded (threshold, a probability under activation_applied: sigmoid, else a logit) into classes 0 and 1.
An optional [H, W] f32 or u8 confidence map receives the winning class's probability, computed at the model's resolution and upsampled.
activation_required is applied after resampling, as reference pipelines do: it leaves the class map unchanged and selects the confidence (softmax maximum, or the winner's sigmoid). Declaring both activation_applied and activation_required, softmax on a single channel, or tanh is an error.
Inputs are mapped read-only and may carry a padded row pitch; dshape must agree with shape in rank, sizes and distinct axis names, as for other outputs.
Decoder::decode_semantic and Decoder::semantic serve schemas with a decoder: semantic output, programmatic configurations with a DecoderType::Semantic output (Python Decoder.new_from_outputs, C ef_decoder_params_add_output), and ModelPack segmentation outputs (fast by default, keeping the output's activation_applied); decode returns NotSupported for a semantic model. ModelType::Semantic marks such a decoder. Tensors bind by name, then shape, then schema position.
DecodeMode::code/from_code: Accurate 0, Fast 1.
Python: Decoder.decode_semantic(model_output, dst, confidence=None, *, mode=None, letterbox=None), Decoder.semantic_classes, Decoder.semantic_mode and DecodeMode. C: ef_decoder_decode_semantic, ef_decoder_semantic_classes, ef_semantic_options with ef_semantic_options_new/_free/_set_mode/_set_letterbox, and ef_decode_mode.
Semantic segmentation metadata (edgefirst-decoder schema, EDGEAI-2329). Additive to schema_version: 2.
input.preprocessing parses into InputSpec::preprocessing: normalization as a preset (unsigned, signed, raw, imagenet) or per-channel mean/std on 0–255 values, resize (stretch, letterbox), interpolation, and letterbox with placement (center, top_left), pad and pad_space (pixel, normalized). Normalization::mean_std resolves either form to per-channel coefficients, and SchemaV2::validate checks them.
decoder: semantic (DecoderKind::Semantic and DecoderType::Semantic, code 2; C EF_DECODER_TYPE_SEMANTIC, Python DecoderType.Semantic) and the output fields upsample (method, align_corners), decode_mode (accurate, fast), threshold and background_index, held in LogicalOutput::semantic.
Reserved mask-classification outputs: types class_logits and mask_logits, the num_queries dimension (EF_DIM_NAME_NUM_QUERIES, 11), and the no_object_index and mask_activation fields. They parse but are not decoded yet.
int64 dtype (DType::Int64) for class maps from an in-graph argmax. C: EF_INFER_DTYPE_INT64 (an alias of EF_DTYPE_I64). Python and schema JSON: "i64", with "int64" also accepted.
to_legacy_config_outputs returns NotSupported for the semantic decoder and the reserved types.
Hardware H.264 encoding (edgefirst-codec, new video module).VideoEncoder compresses image tensors with any V4L2 stateful memory-to-memory encoder, found by capability (i.MX 8M Plus vsi_v4l2, i.MX 95 Wave6). EDGEFIRST_CODEC_V4L2_ENCODER names the device node to use.
DMA-BUF sources are imported without a copy. NV12 goes through the two-plane NV12M layout where the device offers it, so FrameOptions::crop encodes a region of a larger source and separately allocated chroma planes work. encode() returns once the device has read the source.
EncoderConfig sets the size, input format, frame rate, bitrate, GOP, profile and level. repeat_headers puts the SPS and PPS on every IDR, including forced key frames (FrameOptions::keyframe) on devices that repeat them only at the start of a GOP.
EncodedFrame carries the Annex B access unit, a key-frame flag and the caller's PTS, which is returned unchanged even though V4L2 keeps timestamps at microsecond resolution.
flush() drains the encoder with V4L2_ENC_CMD_STOP and returns every pending access unit; the next frame starts a new stream with an IDR.
Example video-encode, and device-gated tests in crates/codec/tests/video_encode.rs.
Hardware H.264 decoding (edgefirst-codecvideo module).VideoDecoder decodes access units with any V4L2 stateful memory-to-memory decoder, found by capability (i.MX 8M Plus vsi_v4l2, i.MX 95 Wave6). EDGEFIRST_CODEC_V4L2_DECODER names the device node to use, and access_units() splits an Annex B byte stream.
decode() returns every DecodeEvent that is ready: decoded pictures in presentation order with the caller's PTS, and Reconfigured with the StreamInfo (visible size, crop in the coded picture, colorimetry, pool) when the stream's format becomes known or changes mid-stream.
A DecodedFrame lends an NV12 DMA-BUF tensor of the visible picture over the device's buffer, without a copy; the buffer goes back to the device when the frame is dropped. Frames import into the image crate's GPU paths without a copy.
OutputPool::Allocate { count } sizes the pool for the frames the caller holds, plus the device's minimum and one buffer to decode into; OutputPool::Provided decodes into the caller's DMA-BUFs. Where a device will not import DMA-BUFs, its own buffers are exported instead (StreamInfo::memory).
flush() drains with V4L2_DEC_CMD_STOP and restarts both queues; the next access unit starts a new sequence.
Example video-decode, device-gated tests in crates/codec/tests/video_decode.rs that compare every picture with a reference decode, and crates/image/tests/decoded_video_import.rs for the GPU import.
Named C codes for every image and decoder option.edgefirst/image.h declares ef_rotation, ef_flip, ef_color_mode, ef_fit and ef_compute_backend; edgefirst/decoder.h declares ef_nms, ef_decoder_type, ef_decoder_version, ef_output_type, ef_dim_name, ef_match_metric, ef_merge_mode and ef_model_source. The functions keep their uint32_t parameters.
One numbering per option vocabulary (edgefirst-image, edgefirst-decoder).Rotation, Flip, ColorMode, ComputeBackend, the new FitMode (with Fit::mode and Fit::from_mode), Nms, DecoderType, DecoderVersion, the new configs::OutputType (with ConfigOutput::output_type), DimName, MatchMetric, MergeMode and ModelSource now carry code(), from_code(), as_str(), from_str_code() and all(). The C enumerators and the Python enum values are checked against code() when the bindings are built. configs::NMS_OFF_CODE (0) is the code for no NMS, and Nms::option_code / Nms::from_option_code convert Option<Nms>. schema::DType, schema::NmsMode, schema::DecoderVersion and schema::DecoderKind take their codes from the same declarations.
Changed
Per-scale decode skips anchors below the score threshold (edgefirst-decoder, EDGEAI-2349). Before decoding, each anchor's raw integer class scores are compared with the score threshold mapped into the quantized domain, and only the anchors that can reach it have their boxes, scores and mask coefficients decoded. Detections are unchanged. On a VisDrone YOLOv8n 1344×768 smart model on i.MX 95, at the validation threshold of 0.001, about 9% of the anchors are decoded and the per-scale stage drops from 16.9 to 3.7 ms per frame. Float score outputs, and score thresholds of 0 or 1, are decoded in full as before.
Breaking (Rust): the tensor backend is no longer a Cargo feature. Rust consumers always get the statically linked implementation and name nothing. The dynamic backend, which the C libraries and Python wheels use to share one libedgefirst_tensor, is selected by --cfg edgefirst_tensor_dynamic, set only for those builds by a .cargo/config.toml in each of their crate directories (see ARCHITECTURE.md, Tensor Backend Selection). Migration: remove "static" from any features list naming it on edgefirst-tensor, -codec, -image, -decoder or -tracker; default-features = false, features = ["static"] becomes default-features = false.
The C libraries and Python wheels are built from their crate directories.image-capi, codec-capi, decoder-capi and the tensor, codec, image and decoder wheels select the dynamic backend through their own .cargo/config.toml, which Cargo and maturin read when run from that directory (make capi-libs, make package, make wheel, make install-python and pip install crates/python-<name>/ do). From the repository root they need --config .cargo/tensor-dynamic.toml and fail with a message naming the fix otherwise. tensor-capi refuses the dynamic backend.
Release artifacts link with fat LTO in a single codegen unit. The release profile, used for the published C libraries, wheels and crates' release builds, sets lto = "fat" and codegen-units = 1; the five C libraries total about a fifth less. release now runs only for shipped artifacts: CI and test builds, including the PR packaging smoke tests, use the profiling profile (thin LTO, line tables), and make package PACKAGE_PROFILE=profiling packages a test build.
edgefirst-image: faster CPU normalization and output-pad fill. The f32 affine kernel and the letterbox pad fill no longer rely on inlining to vectorize, so they stay fast under fat LTO; a 720p NV12 → 640×640 normalized planar f32 convert takes 1.05 ms instead of 1.41 ms on x86_64.
A test that cannot exercise what it tests reports WARNING: SKIPPED: <reason>, and an allocation the host should serve fails the test instead of skipping it. A DMA-BUF allocation must succeed whenever the heap probe says the heap is usable. Only an allocation that can legitimately fail on the host skips, with a warning. .github/scripts/test_warnings.py collects the warnings into the CI step summary, and scripts/on-target-run.sh lists them per board. See TESTING.md.
Breaking (edgefirst-image): convert, convert_deferred and convert_with_fence take Option<ConvertOptions> in place of rotation, flip and crop; None is the default convert. ConvertOptions::new().rotation(r).flip(f).crop(c) rebuilds the old arguments. MaskOverlay::with_letterbox_crop(&crop, ..) is now with_convert_options(&options, ..). Rotation and Flip implement Default. Python keeps its positional arguments.
Breaking (C ABI): the convert functions take const ef_convert_options * in place of rotation, flip and const ef_crop *, and ef_crop is removed. Unknown codes are refused by the setters. A convert whose options cannot apply to the destination returns EINVAL rather than EIO. ef_image_abi_version is now 3.
edgefirst-image: an i8 or u8 destination's Quantization now shapes its output. A destination carrying quantization other than scale 1/255 with zero point -128 (i8) or 0 (u8) is quantized with it, on the CPU or GL path; before, it got the plain mapping regardless.
Breaking: the cross-package tensor descriptor's kind uses the TensorMemory codes, and ABI_VERSION is 2.TensorDesc::kind had its own numbering, in which 1 meant DMA-BUF while TensorMemory code 1 is Shm. protocol::kind now re-exports the TensorMemory wire constants: MEM=0, SHM=1, DMABUF=2, IOSURFACE=3, PBO=4, CUDA=5, D3D11_TEXTURE=6. kind::HOST is replaced by kind::MEM and kind::SHM, which a producer now reports separately, and TensorDesc::is_host() tests for either. kind::CUDA_DEVICE is renamed kind::CUDA. A DmaBuf tensor still reports the concrete backing: DMABUF on Linux and Android, IOSURFACE on macOS and iOS, D3D11_TEXTURE on Windows. An importer refuses a version-1 descriptor, so edgefirst.* Python packages from this release do not exchange tensors with packages from 0.35.x or earlier. The capsule name (edgefirst_tensor_v2) and layout are unchanged.
Breaking: PixelFormat::to_fourcc() returns V4L2 pixel format codes.Rgb is now RGB3 (was RGB ), Rgba is AB24 (was RGBA), Bgra is AR24 (was BGRA) and Grey is GREY (was Y800); the YUV formats are unchanged. from_fourcc() accepts the new codes only. The descriptor's fourcc field carries the same codes. PixelFormat's Display text is unchanged.
Breaking (edgefirst-tensor-abi): EfStorageKind::D3d11Texture and EfCompressionRequest.EfStorageKind gains a variant, which breaks an exhaustive match on it.
Breaking (edgefirst-decoder): schema additions for semantic segmentation.InputSpec gains preprocessing and LogicalOutput gains semantic, so struct literals need the new fields (None and Default::default()). DecoderKind, DecoderType, LogicalType, PhysicalType, DType and DimName gain variants, which breaks exhaustive matches.
Breaking (edgefirst-codec):CodecError gains NoDevice, InvalidConfig, InvalidInput, Device and Timeout for video encoding, and PoolExhausted for video decoding. Exhaustive matches on CodecError need new arms.
Breaking (Python): Flip and Nms use the C numbering.Flip.Vertical is now 1 and Flip.Horizontal 2, matching EF_FLIP_*; they were swapped. Nms.Auto is now 1, Nms.ClassAware 2 and Nms.ClassAgnostic 3, matching EF_NMS_*; they were 2, 1 and 0. Code that uses the members by name is unaffected; code that passes or compares their integer values must be updated.
Breaking (C ABI): model I/O dtype codes are the tensor dtype codes.ef_infer_signals_add_input and ef_infer_signals_add_output take an EF_DTYPE_* code. EF_INFER_DTYPE_* are now aliases of EF_DTYPE_* instead of 0x100–0x107; code compiled against the old header passes values that are now EINVAL and must be rebuilt. EF_DTYPE_U64 and EF_DTYPE_F64 are EINVAL. ef_decoder_abi_version is now 3.
Breaking (C API): unknown option codes are refused. An unknown color_mode passed to ef_image_processor_draw_decoded_masks or ef_image_processor_draw_proto_masks returns EINVAL; it was drawn as EF_COLOR_MODE_CLASS. An unknown ef_tiling_config.fit makes the tiling functions fail; any non-zero value used to mean letterbox. An unknown dims code passed to ef_decoder_params_add_output returns -1; it was read as an unrecognised axis. EF_DIM_NAME_UNKNOWN (10) still names one. ef_image_abi_version is now 2; the decoder change is covered by ef_decoder_abi_version 3.
Breaking (schema JSON): model dtypes are written with the tensor dtype names.schema::DType serializes as "i8", "u8", "i16", "u16", "i32", "u32", "f16", "f32" and "i64", the strings edgefirst_tensor::DType::as_str uses; it wrote "int8", "uint8", ... "float32", "int64". Both spellings are accepted when reading a schema and by the Python infer_ultralytics_schema, so existing edgefirst.json files still load. A consumer that reads JSON produced by the HAL, such as the inferred schema, sees the new names.
Breaking: CPU access declarations are enforced, and the default is ReadWrite. A tensor's CpuAccess covers all CPU access to it, including the HAL's own CPU work (the CPU image backend, G2D's CPU post-passes, codec decodes, decoder outputs). A CPU map, pin or sync bracket whose direction it does not cover — a read of a Write or None tensor, a write of a Read or None tensor — fails with the new Error::CpuAccessDenied { declared, requested } on both tensor backends; it was a warning on the Rust side.
Default: ReadWrite everywhere: CpuAccess::default(), ImageDesc::new and ef_tensor_image_desc_new, and the Python Tensor.image, ImageProcessor.create_image, alloc_tile_batch, Tensor.from_d3d11_texture and from_d3d11_shared_handle (access="readwrite"). None, Read and Write remain explicit modes for shared memory, DMA-BUF, IOSurface and Windows NT handles.
Cost of the default on GPU-only pipelines: a D3D11 import or allocation creates a staging texture; an Android gralloc allocation requests CPU_READ_OFTEN | CPU_WRITE_OFTEN, which may give linear, CPU-cached memory without vendor tiling or compression; a DMA-BUF gets a cached mapping with both sync directions. Linux DMA-BUF allocation is unchanged. Pass access="none" (CpuAccess::None) for buffers only hardware touches. A compression request (Compression/Python compression=) needs access="none" passed explicitly, in Rust and Python alike.
Write-only maps: a Write map is valid only for a pass that overwrites the whole buffer. TensorTrait gains cpu_access, view_origin, is_view, write_access, map_for_write and map_for_partial_write; map_for_write maps a whole tensor Write on a write-only declaration and a view ReadWrite, and map_for_partial_write always maps ReadWrite. A tensor rebuilt from a view's descriptor is a view too: the descriptor reports the new VIEW flag bit and import_descriptor marks the alias with the new Tensor::mark_view (C ef_tensor_mark_view, read with ef_tensor_is_view), so a Python convert into a Write-declared view is refused as it is in Rust.
create_image and alloc_tile_batch add to the declaration what the processor's own convert may do to the image as a destination on the CPU: Write when it may fill the whole image (CPU backend, host memory, a format or F16/F32 dtype the GPU does not render), and ReadWrite when a pass reads back what it wrote or writes only part of it (an I8 destination through the CPU or G2D, a Bgra destination through the CPU, a G2D Rgb letterbox border). alloc_tile_batch adds any predicted write as ReadWrite, since tiles are written through views. A PBO image is always ReadWrite. Nothing is added for use as a convert source or for a CPU mask draw.
Up-front checks: a CPU convert (source Read; destination Write, or ReadWrite for a view, a placement with no fill colour, the I8 bias and the BGRA swizzle), a G2D convert (ReadWrite for I8 and for an Rgb letterbox border), a CPU mask draw (destination ReadWrite), decode_into on the CPU decoders and the V4L2 scratch copy-out (Write) and decode_semantic (input Read, outputs Write) refuse an uncovered caller tensor before doing any work. A V4L2 decode whose zero-copy path is declined and whose copy-out the declaration refuses returns CpuAccessDenied instead of falling back to the CPU decoder, and resets the device stream without counting a hardware failure.
A destination left without CPU access needs the GPU to accept the whole convert: when GL or G2D declines it and the CPU fallback is refused for access, convert returns the new Error::CpuFallbackDenied { declined, denied } naming the GPU's reason.
The cross-package descriptor reports the declaration in three new flags bits (CPU_ACCESS_DECLARED, CPU_READ, CPU_WRITE), and import_descriptor narrows the imported alias to it with the new restrict_cpu_access (C ef_tensor_restrict_cpu_access), which never widens, so a Python or C alias refuses what its producer refuses. A Windows D3D11 texture import opens the texture with the producer's declaration, so a Write-declared texture is imported without a staging texture.
Rust: new helpers CpuAccess::require, union and intersection, and cpu_access_denied() on edgefirst_tensor::Error, edgefirst_image::Error, CodecError and DecoderError, which finds the refusal in any form. DecoderError implements From<edgefirst_tensor::Error>. The new variants edgefirst_tensor::Error::CpuAccessDenied, edgefirst_image::Error::CpuFallbackDenied and DecoderError::Tensor break exhaustive matches on those enums. map_for_write is a TensorTrait method, so callers need the trait in scope. map() and map_mut() ask for ReadWrite and fail on a Read- or Write-declared tensor; use map_read() or map_for_write(). The CpuAccess::None refusal message is <op>: CpuAccess::None is not a CPU access direction.
Python: a refused access raises PermissionError (an OSError) instead of RuntimeError, from maps, pins, convert, the draw and tiling calls, Tensor.decode_image/decode_image_file, decode_into, and Decoder.decode, decode_tracked, decode_proto and decode_semantic. Tensor.map()/try_map(), whose default is "readwrite", raise on a "read"- or "write"-declared tensor; pass access=. The new read-only Tensor.declared_cpu_access reports the declaration ("none", "read", "write" or "readwrite").
C: see the C ABI entries below.
Migration: declare the access you use, or rely on the new ReadWrite default; code that relied on None as the default must pass it explicitly. On Android, pass None for buffers you never map.
edgefirst-image: CPU Interpolation::Bilinear samples two taps per axis at every scale, matching the GL backend and OpenCV INTER_LINEAR; it was antialiased below 1×, as PIL is. CPU downscales are sharper and their output bytes change, and the cropped-convert golden fixtures are regenerated; upscales are unchanged.
Breaking (C ABI): image, codec and decoder functions return EACCES for a refused CPU access.ef_image_processor_convert, _convert_deferred, _convert_fence, _convert_fence_handle, both draw calls, ef_image_processor_tile_one, ef_image_decoder_decode_into, ef_codec_save_jpeg, ef_dequantize, ef_decoder_decode, ef_decoder_decode_tracked and ef_decoder_decode_semantic return EACCES where they returned EIO or EBADMSG. ef_image_processor_tile_into sets errno when it returns NULL: EINVAL for bad arguments, EACCES for a refusal, otherwise the image errno.
Breaking (C ABI): image errors map to one errno set. The convert, draw and tiling calls return ENOTSUP for an unsupported format, an unsupported or unimplemented operation, or no converter (they returned EIO), and EINVAL for an invalid crop, an invalid shape or a tensor with no pixel format. A BGRA background onto a zero-copy draw destination returns ENOTSUP (it returned EIO).
Breaking (C ABI): CPU access declarations are enforced, and ef_tensor_abi_version is now 2.ef_tensor_map, ef_tensor_try_map and ef_tensor_sync_for_* return EACCES, and ef_tensor_copy_to-EACCES, with a message naming both accesses, for any direction the tensor's declared ef_cpu_access does not cover — reads included; only writes were refused before. ef_tensor_image_desc_new defaults to EF_CPU_ACCESS_READ_WRITE (it was EF_CPU_ACCESS_NONE), so a compression request must set EF_CPU_ACCESS_NONE explicitly. ef_unplanned_cpu_access_count is removed (see Removed). ef_tensor_cpu_access reads the declaration and ef_tensor_restrict_cpu_access narrows it; ef_tensor_is_view reports a view, including one ef_tensor_mark_view marked after a descriptor import.
Removed
Breaking (Rust, C ABI): unplanned_cpu_access_count() and C ef_unplanned_cpu_access_count are removed. No CPU access can exceed its declaration any more, so there is nothing to count (see Changed). ef_tensor_abi_version is now 2.
The static and dynamic Cargo features of edgefirst-tensor, and the static feature of edgefirst-codec, edgefirst-image, edgefirst-decoder and edgefirst-tracker (see Changed).
The dma_test_formats and g2d_test_formats features of edgefirst-image. The DMA-BUF and G2D test tiers always build and skip at run time without the hardware; HAL_TEST_REQUIRE_DMA=1 and the new HAL_TEST_REQUIRE_G2D=1 turn those skips into failures.
The dynamic-test-link feature of edgefirst-tensor. Under the dynamic backend the crate's own test binaries now always link libedgefirst_tensor.
Fixed
edgefirst-image did not compile without its opengl feature.ImageProcessor::convert_with_fence returns CompletionFence, which was only exported with opengl; the type is now always exported. The nightly lane checks every pair of features again.
edgefirst-codec: building edgefirst-image (or another crate that depends on the codec with default-features = false) on its own failed to compile the codec. The codec chose between two code paths with its own static feature, because edgefirst-tensor's static and dynamic backends offered different methods. Both backends now offer TensorDyn::chroma_dyn, TensorDyn::dmabuf and an inherent Tensor::from_fd, so the codec has no backend-specific code. CI now checks each published crate on its own (make check-standalone).
edgefirst-image: a GL PlanarRgb/PlanarRgba letterbox whose pad channels differ left the band unfilled. The single-pass planar route (DMA-BUF destinations) filled the band only when all channels of the pad were equal, so a pad such as [0, 64, 255, 255] left it holding the destination's previous contents. Each plane is now filled with its own value.