Skip to content

Commit 857de06

Browse files
committed
feat(docs): some vision pipeline producer documentation added, standardizing on doxygen commenting style.
1 parent b11b808 commit 857de06

9 files changed

Lines changed: 952 additions & 473 deletions

File tree

docs/software/producer_pipeline.md

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# Producer pipeline: camera → RGA → output pool (thread boundary)
2+
3+
This document describes the **per-frame hot path** through the producer side of the vision pipeline:
4+
5+
**V4L2 camera capture (kernel ring)****DMA-BUF-backed source frame****RGA preprocess (src→dst blit/convert/resize/letterbox)****publish into output buffer pool** (handoff to consumer).
6+
7+
The core design pattern is **scope-bound capability tokens** (RAII) enforcing protocols:
8+
9+
- **Borrow (camera ring slot):** `DQBUF` must pair with exactly one `QBUF`.
10+
- **Lease (pool slot):** `acquire_write` must end in either `publish()` (commit) or `cancel` (rollback).
11+
- **Own (fd lifetime):** `DmabufAllocation` owns/close()s the DMA-BUF fd.
12+
13+
---
14+
15+
## Glossary
16+
17+
- **DMA-BUF fd**: a file descriptor that refers to a shareable memory allocation (potentially used by multiple devices).
18+
- **FrameDescriptor**: *non-owning metadata view* over the camera DMA-BUF (format, strides, offsets, dims, timestamp).
19+
- **ImageBuffer**: *non-owning metadata view* over an output DMA-BUF (format, strides, dims).
20+
- **FrameLease**: a move-only token representing “I currently hold a dequeued camera ring slot and must requeue it.”
21+
- **WriteLease**: a move-only token representing “I have exclusive write access to one output pool slot until I publish or cancel.”
22+
23+
---
24+
25+
## 0) One-time setup (build the rings)
26+
27+
### Camera side (kernel-owned ring)
28+
Typical V4L2 streaming setup (conceptual):
29+
30+
1. `open()` device
31+
2. `VIDIOC_S_FMT` (e.g., NV12)
32+
3. `VIDIOC_REQBUFS` (N capture slots)
33+
4. For each slot:
34+
- `VIDIOC_QUERYBUF` (get size/index)
35+
- optionally `VIDIOC_EXPBUF` (export a DMA-BUF fd for that slot)
36+
- `VIDIOC_QBUF` (enqueue for capture)
37+
5. `VIDIOC_STREAMON`
38+
39+
Result: the kernel owns a ring of **N capture slots**. Your app temporarily borrows slots between `DQBUF` and `QBUF`.
40+
41+
### Output side (app-owned pool)
42+
Allocate **M output buffers** up front (DMA-HEAP → DMA-BUF fds), store them in `ImageBufferPool`.
43+
44+
Each pool slot is managed by a small state machine (conceptual):
45+
46+
- `Free` → (producer acquire) → `Writing` → (publish) → `Published` → (consumer acquire) → `Reading` → (release) → `Free`
47+
48+
This pool is the **thread boundary** between producer and consumer.
49+
50+
---
51+
52+
## 1) Dequeue a camera frame (borrow a kernel ring slot)
53+
54+
Producer calls `dequeue_lease()` which performs `VIDIOC_DQBUF` internally.
55+
56+
Possible outcomes:
57+
58+
- **EAGAIN / no frame ready**`NoFrame` (idle tick)
59+
- **Success** → you receive a `v4l2_buffer` with:
60+
- `index` (which ring slot)
61+
- timestamp/sequence metadata
62+
- bytesused/per-plane info (driver-dependent)
63+
64+
On success, you create:
65+
66+
- `FrameDescriptor` (metadata view: fd + layout + dims + timestamp)
67+
- `FrameLease` (borrow token: must requeue this `index` exactly once)
68+
69+
**Invariant:** Every successful `DQBUF` must be paired with exactly one `QBUF` of the same `index`.
70+
71+
---
72+
73+
## 2) Acquire an output buffer (lease exclusive write access)
74+
75+
Producer calls `acquire_write_lease()` on `ImageBufferPool`.
76+
77+
On success you receive a `WriteLease` token with:
78+
79+
- destination `ImageBuffer` view (fd + layout + dims)
80+
- exclusive right to write into that pool slot
81+
82+
If the producer exits early (return/error/throw), `WriteLease` destructor must roll back:
83+
84+
- if `publish()` was not called → `cancel_write()` → slot returns to `Free`
85+
86+
**Invariant:** Every successful write-acquire ends in exactly one of:
87+
- `publish()` (commit)
88+
- auto-cancel on destructor (rollback)
89+
90+
---
91+
92+
## 3) Synchronization / coherency (DMA-BUF “physics”)
93+
94+
Even without CPU touching pixels, the devices must observe correct ordering:
95+
96+
- RGA must not read **src** before the camera finished writing it.
97+
- Consumer must not read **dst** before RGA finished writing it.
98+
99+
Two broad approaches:
100+
101+
- **Implicit sync** (fences handled by drivers)
102+
- **Explicit sync** via `DMA_BUF_IOCTL_SYNC` (start/end for read/write)
103+
104+
Whether explicit sync is required depends on the exact driver stack and memory path, but this stage is where you put those boundaries if needed.
105+
106+
---
107+
108+
## 4) RGA preprocess (the data movement)
109+
110+
Inputs:
111+
112+
- **src**: `FrameDescriptor` describing camera DMA-BUF (often NV12, multi-plane)
113+
- **dst**: `ImageBuffer` describing pool DMA-BUF (often RGB/BGR, model-ready)
114+
115+
RGA performs a hardware blit with operations like:
116+
117+
- colorspace conversion (NV12 → RGB/BGR)
118+
- resize / scale
119+
- crop
120+
- letterbox/padding (fit aspect ratio into model input size)
121+
- rotation (if needed)
122+
123+
Conceptually:
124+
125+
```
126+
RGA:
127+
src(dmabuf fd + offsets/strides + WxH + fmt)
128+
--> dst(dmabuf fd + offsets/strides + W'H' + fmt)
129+
```
130+
131+
At the end of this stage, the transformed pixels physically reside in the **destination pool slot**.
132+
133+
---
134+
135+
## 5) Publish (commit the write lease; cross the thread boundary)
136+
137+
If RGA succeeds, the producer calls `dst_lease.publish()`.
138+
139+
A good `publish()` typically does:
140+
141+
1. Attach/finalize per-frame metadata:
142+
- timestamp/sequence
143+
- original dims and model dims
144+
- letterbox transform parameters (scale + pad offsets), if used
145+
2. Transition state: `Writing → Published`
146+
3. Notify consumer (optional): condvar/eventfd or “latest index” atomic
147+
148+
**This is the exact moment the buffer becomes visible to the consumer.**
149+
150+
After publish:
151+
- producer must treat the slot as immutable
152+
- consumer is allowed to acquire it for reading
153+
154+
If RGA fails:
155+
- do **not** publish
156+
- `WriteLease` destructor auto-cancels → slot returns to `Free`
157+
158+
---
159+
160+
## 6) Requeue the camera slot (return the borrowed ring slot)
161+
162+
After RGA is done reading the camera buffer, the producer must `VIDIOC_QBUF` the same ring `index`.
163+
164+
With RAII:
165+
- `FrameLease` destructor calls `release()` which performs `QBUF`
166+
167+
This prevents ring starvation even on early returns.
168+
169+
> Note: if you want to *surface* `QBUF` failures into a return status, you must ensure cleanup runs **before** returning from the function (e.g., by using an inner scope). Destructors can’t reliably “return a status” after the caller has already received the function’s return value.
170+
171+
---
172+
173+
## 7) Consumer side (other side of the boundary)
174+
175+
Consumer obtains a read capability (conceptually a `ReadLease`) from the pool:
176+
177+
- `acquire_read()` / `acquire_latest()` → get `ImageBuffer` view over the published DMA-BUF
178+
- run inference / postprocess
179+
- release read lease → slot transitions back to `Free` (or your chosen recycling policy)
180+
181+
**Consumer must treat published buffers as read-only.**
182+
183+
---
184+
185+
## Summary timeline (one frame)
186+
187+
1. `DQBUF``FrameLease(src)`
188+
2. `acquire_write_lease()``WriteLease(dst)`
189+
3. (optional) DMA-BUF sync start
190+
4. RGA preprocess: `src → dst`
191+
5. (optional) DMA-BUF sync end
192+
6. `dst.publish()`**thread boundary crossed here**
193+
7. `FrameLease` releases → `QBUF`
194+
195+
---
196+
197+
## Compact diagram
198+
199+
```
200+
Producer thread:
201+
202+
[V4L2 device] --DQBUF--> FrameLease{FrameDescriptor -> src dmabuf}
203+
|
204+
v
205+
acquire_write_lease()
206+
|
207+
v
208+
WriteLease{ImageBuffer -> dst dmabuf} --RGA--> (dst filled)
209+
|
210+
v
211+
publish() ✅ boundary
212+
|
213+
v
214+
FrameLease dtor -> QBUF (return ring slot)
215+
216+
Consumer thread:
217+
218+
acquire_read() -> ReadLease{ImageBuffer -> dst dmabuf} -> infer -> release
219+
```

0 commit comments

Comments
 (0)