Robots Atlas>ROBOTS ATLAS
Robotics

rosidl::Buffer

2026ExperimentalPublished: 22 September 2026Updated: 22 September 2026Published
Key innovation
Brings a typed message-field container into ROS 2 core that abstracts the memory backend (CPU/CUDA/ROCm), enabling zero-copy transport of accelerator memory between nodes instead of forcing a copy into a host-side std::vector.
Category
Robotics
Abstraction level
Building block
Operation level
Robot controlInference
Use cases
Zero-copy transport of images, point clouds and tensors in GPU perception pipelines (ROS 2)Data exchange between ROS 2 nodes on the same device without host-device copiesuint8[] message fields (e.g. sensor_msgs/msg/Image.data) with a CUDA/ROCm backendIntegrating accelerated memory transport with Fast DDS and ZenohTensor exchange between C++ and Python (planned rclpy support)

How it works

1) The ROS 2 code generator emits rosidl::Buffer<uint8_t> instead of std::vector<uint8_t> for uint8[] fields. 2) Buffer<T> uses the PIMPL idiom and delegates to BufferImplBase<T>; by default it creates a CpuBufferImpl<T> wrapping std::vector<T>. 3) The backend is set once at construction (there is no post-construction setter, to avoid race conditions). 4) On CPU the full std::vector API and implicit conversion are available; on non-CPU backends element access throws std::runtime_error and a host copy is obtained via to_vector(). 5) Vendor backends (BufferBackend) are discovered and loaded via pluginlib (BufferBackendRegistry). 6) During communication the serialization layer calls create_descriptor_with_endpoint(); when both peers share a backend a descriptor (<= 4096 B, e.g. a GPU-memory IPC handle) is transmitted and reconstructed via from_descriptor_with_endpoint() without copying the data; when the peer does not support the backend the descriptor is nullptr and it falls back to CPU serialization. Endpoints advertise supported backends and negotiate compatibility (on_creating_endpoint / on_discovering_endpoint).

Problem solved

In ROS 2, binary message fields (uint8[]) were represented as host-memory std::vector<T>; for large payloads (images, point clouds, tensors) this forced expensive CPU-accelerator copies and prevented native zero-copy transport of GPU memory between nodes without out-of-tree solutions.

Components

Buffer<T>Message-field container (PIMPL)

Templated container replacing std::vector<T> for uint8[] fields. For the CPU backend it exposes the full std::vector API and implicit conversion to std::vector<T>&; the all-backend API includes size(), get_backend_type(), get_impl(), to_vector(). Value semantics: deep copy via clone().

BufferImplBase<T>Abstract backend implementation base

Minimal base class (get_backend_type(), size(), to_cpu(), clone()). Concrete backends: CpuBufferImpl, CUDA, ROCm, etc.

Official

CpuBufferImpl<T>Default CPU implementation

Implementation wrapping std::vector<T>; the default backend providing full backward compatibility.

Official

BufferBackendVendor backend interface

Abstract interface (rosidl_buffer_backend package). Creates and reads descriptors (create_descriptor_with_endpoint / from_descriptor_with_endpoint, <= 4096 B), handles endpoint discovery/negotiation hooks (on_creating_endpoint, on_discovering_endpoint) and stays RMW-agnostic.

Official

BufferBackendRegistryBackend registry (pluginlib)

The rosidl_buffer_backend_registry package. Dynamically discovers and instantiates backends via pluginlib (create_backend_instance, get_backend_names).

Implementation

Implementation pitfalls
Element access on a non-CPU backend throwsHigh

operator[], at(), iterators and other std::vector operations throw std::runtime_error for non-CPU backends.

Fix:Check get_backend_type() or use to_vector()/the backend API; do not assume a CPU backend.
to_vector() on a GPU backend forces a host-device copyMedium

to_vector() is an escape hatch returning a CPU copy, which defeats the zero-copy benefit.

Fix:Avoid to_vector() on the zero-copy path; operate on data on the accelerator side.
Backend descriptor limited to 4096 bytesLow

Every descriptor a backend produces must serialize to at most kMaxBufferDescriptorSize (4096 B).

No shared backend -> fallback to CPU serializationMedium

When the peer does not support the backend, create_descriptor_with_endpoint() returns nullptr and transport falls back to standard CPU serialization (losing zero-copy).

Fix:Ensure compatible backends on both sides; monitor endpoint negotiation.

Evolution

2026
Working prototype of native buffers (accelerated memory transport)

ROS Discourse thread presenting a working prototype of zero-copy accelerator-memory transport for uint8[] fields, led by NVIDIA engineers.

2026
rosidl_buffer and rosidl_buffer_backend added to ROS 2 core
Inflection point

PR #941 (merged 2026-03-31) introduces rosidl::Buffer<T>, BufferImplBase<T>, CpuBufferImpl<T> and BufferBackend as the core native-buffer types.

2026
C++ generator emits rosidl::Buffer for uint8[] fields

rosidl_buffer 5.1.4 (2026-04-09): the C++ generator path starts emitting rosidl::Buffer for uint8[] fields (PR #942).

Hyperparameters (configurable axes)

Memory backendCritical

Buffer backend type set at construction: 'cpu' (default), 'cuda', 'rocm', 'demo'. Set once, with no post-construction setter.

cpu
cuda
rocm
Element type THigh

Template parameter of Buffer<T>. The C++ generator emits rosidl::Buffer<uint8_t> for uint8[] fields.

uint8_t
Max descriptor sizeMedium

Upper bound on the serialized backend descriptor: 4096 bytes.

4096 B

Hardware requirements

Primary

The CUDA backend allows holding and transporting GPU memory without host copies (zero-copy via descriptors), which is the feature's main goal.

Good fit

The default CPU backend and pluggable architecture make the container itself independent of any specific hardware.