styx_core/buffer.rs
1use smallvec::{SmallVec, smallvec};
2use std::{
3 num::NonZeroU32,
4 sync::{Arc, Mutex},
5};
6
7use crate::{format::MediaFormat, metrics::Metrics};
8
9/// External backing for frames when zero-copy sharing external memory.
10///
11/// Implementations can map DMA buffers or other shared memory into slices.
12pub trait ExternalBacking: Send + Sync {
13 /// Borrow a plane by index; lifetime must be tied to `self`.
14 fn plane_data(&self, index: usize) -> Option<&[u8]>;
15}
16
17/// Metadata associated with a frame.
18///
19/// # Example
20/// ```rust
21/// use styx_core::prelude::{ColorSpace, FourCc, FrameMeta, MediaFormat, Resolution};
22///
23/// let res = Resolution::new(640, 480).unwrap();
24/// let fmt = MediaFormat::new(FourCc::new(*b"RG24"), res, ColorSpace::Srgb);
25/// let meta = FrameMeta::new(fmt, 123);
26/// assert_eq!(meta.timestamp, 123);
27/// ```
28#[derive(Debug, Clone)]
29pub struct FrameMeta {
30 /// Format describing layout and resolution.
31 pub format: MediaFormat,
32 /// Timestamp in ticks or nanoseconds (caller-defined).
33 pub timestamp: u64,
34}
35
36impl FrameMeta {
37 /// Create metadata with the given format and timestamp.
38 pub fn new(format: MediaFormat, timestamp: u64) -> Self {
39 Self { format, timestamp }
40 }
41}
42
43/// Handle to a pooled buffer.
44///
45/// When dropped, the buffer is returned to the originating pool so downstream
46/// stages can reuse memory without reallocations.
47///
48/// # Example
49/// ```rust
50/// use styx_core::prelude::BufferPool;
51///
52/// let pool = BufferPool::with_capacity(2, 1024);
53/// let mut lease = pool.lease();
54/// lease.resize(16);
55/// assert_eq!(lease.len(), 16);
56/// ```
57pub struct BufferLease {
58 pool: Arc<PoolInner>,
59 buf: Option<Vec<u8>>,
60}
61
62impl BufferLease {
63 /// Borrow as an immutable slice.
64 pub fn as_slice(&self) -> &[u8] {
65 self.buf.as_deref().unwrap_or(&[])
66 }
67
68 /// Borrow as a mutable slice.
69 pub fn as_mut_slice(&mut self) -> &mut [u8] {
70 self.buf.as_deref_mut().unwrap_or(&mut [])
71 }
72
73 /// Current length of the buffer.
74 pub fn len(&self) -> usize {
75 self.buf.as_ref().map(|b| b.len()).unwrap_or(0)
76 }
77
78 /// Whether the buffer is empty.
79 pub fn is_empty(&self) -> bool {
80 self.len() == 0
81 }
82
83 /// Ensure the buffer capacity fits `len` bytes and set its length.
84 pub fn resize(&mut self, len: usize) {
85 if let Some(buf) = self.buf.as_mut() {
86 if buf.capacity() < len {
87 buf.reserve(len - buf.capacity());
88 }
89 buf.resize(len, 0);
90 }
91 }
92
93 /// Set length without initializing; caller must fully write before read.
94 ///
95 /// # Safety
96 /// The buffer contents are uninitialized for any newly exposed bytes.
97 pub unsafe fn resize_uninit(&mut self, len: usize) {
98 if let Some(buf) = self.buf.as_mut() {
99 if buf.capacity() < len {
100 buf.reserve(len - buf.capacity());
101 }
102 unsafe {
103 buf.set_len(len);
104 }
105 }
106 }
107
108 /// Replace the leased backing buffer with an owned `Vec<u8>`.
109 ///
110 /// This is useful for "zero-copy moves" where a stage already owns a `Vec<u8>` and wants to
111 /// hand it off as a pooled buffer without another full-frame memcpy.
112 pub fn replace_owned(&mut self, buf: Vec<u8>) {
113 if let Some(old) = self.buf.take() {
114 self.pool.recycle(old);
115 }
116 self.buf = Some(buf);
117 }
118
119 fn take(mut self) -> Vec<u8> {
120 self.buf.take().unwrap_or_default()
121 }
122}
123
124impl Drop for BufferLease {
125 fn drop(&mut self) {
126 if let Some(buf) = self.buf.take() {
127 self.pool.recycle(buf);
128 }
129 }
130}
131
132/// Simple buffer pool that hands out reusable owned buffers.
133///
134/// # Example
135/// ```rust
136/// use styx_core::prelude::BufferPool;
137///
138/// let pool = BufferPool::with_limits(4, 1 << 20, 8);
139/// let _lease = pool.lease();
140/// ```
141#[derive(Clone)]
142pub struct BufferPool {
143 inner: Arc<PoolInner>,
144 metrics: Arc<Metrics>,
145}
146
147impl BufferPool {
148 /// Create a pool with `capacity` preallocated buffers of `chunk_size` bytes.
149 pub fn with_capacity(capacity: usize, chunk_size: usize) -> Self {
150 Self::with_limits(capacity, chunk_size, capacity)
151 }
152
153 /// Create a pool with `capacity` preallocated buffers and a maximum retained free list.
154 pub fn with_limits(capacity: usize, chunk_size: usize, max_free: usize) -> Self {
155 let mut free = Vec::with_capacity(capacity);
156 for _ in 0..capacity {
157 free.push(vec![0; chunk_size]);
158 }
159 Self {
160 inner: Arc::new(PoolInner {
161 free: Mutex::new(free),
162 chunk_size,
163 max_free,
164 }),
165 metrics: Arc::new(Metrics::default()),
166 }
167 }
168
169 /// Acquire a buffer, allocating if the pool is empty.
170 pub fn lease(&self) -> BufferLease {
171 let buf = self
172 .inner
173 .free
174 .lock()
175 .unwrap()
176 .pop()
177 .inspect(|_| {
178 self.metrics.hit();
179 })
180 .unwrap_or_else(|| {
181 self.metrics.miss();
182 self.metrics.alloc();
183 vec![0; self.inner.chunk_size]
184 });
185 BufferLease {
186 pool: self.inner.clone(),
187 buf: Some(buf),
188 }
189 }
190
191 /// Access metrics counters for this pool.
192 pub fn metrics(&self) -> BufferPoolMetrics {
193 BufferPoolMetrics(self.metrics.clone())
194 }
195}
196
197struct PoolInner {
198 free: Mutex<Vec<Vec<u8>>>,
199 chunk_size: usize,
200 max_free: usize,
201}
202
203impl PoolInner {
204 fn recycle(&self, mut buf: Vec<u8>) {
205 buf.clear();
206 let mut free = self.free.lock().unwrap();
207 if free.len() < self.max_free {
208 free.push(buf);
209 }
210 }
211}
212
213/// Observability for buffer pool behavior.
214///
215/// # Example
216/// ```rust
217/// use styx_core::prelude::BufferPool;
218///
219/// let pool = BufferPool::with_capacity(1, 128);
220/// let metrics = pool.metrics();
221/// let _ = metrics.hits();
222/// ```
223#[derive(Clone)]
224pub struct BufferPoolMetrics(Arc<Metrics>);
225
226impl BufferPoolMetrics {
227 pub fn hits(&self) -> u64 {
228 self.0.hits()
229 }
230
231 pub fn misses(&self) -> u64 {
232 self.0.misses()
233 }
234
235 pub fn allocations(&self) -> u64 {
236 self.0.allocations()
237 }
238}
239
240/// Plane view over a buffer.
241///
242/// Accessed via `FrameLease::planes`.
243#[derive(Debug, Clone, Copy)]
244pub struct Plane<'a> {
245 data: &'a [u8],
246 stride: usize,
247}
248
249/// Mutable plane view.
250///
251/// Accessed via `FrameLease::planes_mut`.
252#[derive(Debug)]
253pub struct PlaneMut<'a> {
254 data: &'a mut [u8],
255 stride: usize,
256}
257
258impl<'a> Plane<'a> {
259 /// Access the raw bytes.
260 pub fn data(&self) -> &'a [u8] {
261 self.data
262 }
263
264 /// Stride in bytes for this plane.
265 pub fn stride(&self) -> usize {
266 self.stride
267 }
268}
269
270impl<'a> PlaneMut<'a> {
271 /// Mutable access to plane bytes.
272 pub fn data(&mut self) -> &mut [u8] {
273 self.data
274 }
275
276 /// Stride in bytes for this plane.
277 pub fn stride(&self) -> usize {
278 self.stride
279 }
280}
281
282/// Plane layout information stored with a frame.
283///
284/// # Example
285/// ```rust
286/// use std::num::NonZeroU32;
287/// use styx_core::prelude::plane_layout_from_dims;
288///
289/// let layout = plane_layout_from_dims(
290/// NonZeroU32::new(4).unwrap(),
291/// NonZeroU32::new(4).unwrap(),
292/// 3,
293/// );
294/// assert_eq!(layout.stride, 12);
295/// ```
296#[derive(Debug, Clone, Copy)]
297pub struct PlaneLayout {
298 /// Byte offset into the owning buffer.
299 pub offset: usize,
300 /// Length of the plane in bytes.
301 pub len: usize,
302 /// Stride in bytes.
303 pub stride: usize,
304}
305
306/// Frame container holding one or more planes plus metadata.
307///
308/// # Example
309/// ```rust
310/// use styx_core::prelude::*;
311///
312/// let pool = BufferPool::with_capacity(1, 256);
313/// let res = Resolution::new(4, 4).unwrap();
314/// let fmt = MediaFormat::new(FourCc::new(*b"RG24"), res, ColorSpace::Srgb);
315/// let layout = plane_layout_from_dims(res.width, res.height, 3);
316/// let meta = FrameMeta::new(fmt, 0);
317/// let frame = FrameLease::single_plane(meta, pool.lease(), layout.len, layout.stride);
318/// assert_eq!(frame.planes().len(), 1);
319/// ```
320pub struct FrameLease {
321 meta: FrameMeta,
322 buffers: SmallVec<[BufferLease; 3]>,
323 layouts: SmallVec<[PlaneLayout; 3]>,
324 external: Option<Arc<dyn ExternalBacking>>,
325}
326
327impl FrameLease {
328 /// Construct a single-plane frame using the provided buffer.
329 pub fn single_plane(
330 meta: FrameMeta,
331 mut buffer: BufferLease,
332 len: usize,
333 stride: usize,
334 ) -> Self {
335 buffer.resize(len);
336 Self {
337 meta,
338 layouts: smallvec![PlaneLayout {
339 offset: 0,
340 len,
341 stride,
342 }],
343 buffers: smallvec![buffer],
344 external: None,
345 }
346 }
347
348 /// Construct a single-plane frame assuming the caller will fully initialize the buffer.
349 ///
350 /// # Safety
351 /// The caller must write every byte of the buffer before the frame is read.
352 pub unsafe fn single_plane_uninit(
353 meta: FrameMeta,
354 mut buffer: BufferLease,
355 len: usize,
356 stride: usize,
357 ) -> Self {
358 unsafe {
359 buffer.resize_uninit(len);
360 }
361 Self {
362 meta,
363 layouts: smallvec![PlaneLayout {
364 offset: 0,
365 len,
366 stride,
367 }],
368 buffers: smallvec![buffer],
369 external: None,
370 }
371 }
372
373 /// Construct a multi-plane frame from a list of buffers and layouts.
374 pub fn multi_plane(
375 meta: FrameMeta,
376 buffers: SmallVec<[BufferLease; 3]>,
377 layouts: SmallVec<[PlaneLayout; 3]>,
378 ) -> Self {
379 debug_assert_eq!(buffers.len(), layouts.len());
380 Self {
381 meta,
382 buffers,
383 layouts,
384 external: None,
385 }
386 }
387
388 /// Construct a frame from an external backing (zero-copy).
389 pub fn from_external(
390 meta: FrameMeta,
391 layouts: SmallVec<[PlaneLayout; 3]>,
392 backing: Arc<dyn ExternalBacking>,
393 ) -> Self {
394 Self {
395 meta,
396 buffers: SmallVec::new(),
397 layouts,
398 external: Some(backing),
399 }
400 }
401
402 /// Metadata describing this frame.
403 pub fn meta(&self) -> &FrameMeta {
404 &self.meta
405 }
406
407 /// Returns true when the frame data is backed by an external zero-copy mapping (e.g. DMA / mmap).
408 ///
409 /// In this case the frame does not own CPU buffers, so consumers that need an owned `Vec<u8>`
410 /// must copy.
411 pub fn is_external(&self) -> bool {
412 self.external.is_some()
413 }
414
415 /// Iterate planes as borrowed slices (zero-copy).
416 pub fn planes(&self) -> SmallVec<[Plane<'_>; 3]> {
417 if let Some(backing) = &self.external {
418 self.layouts
419 .iter()
420 .enumerate()
421 .map(|(idx, layout)| {
422 let slice = backing
423 .plane_data(idx)
424 .map(|s| {
425 let end = layout.offset.saturating_add(layout.len);
426 s.get(layout.offset..end).unwrap_or(&[])
427 })
428 .unwrap_or(&[]);
429 Plane {
430 data: slice,
431 stride: layout.stride,
432 }
433 })
434 .collect()
435 } else {
436 self.layouts
437 .iter()
438 .zip(self.buffers.iter())
439 .map(|(layout, buf)| {
440 let slice = buf
441 .as_slice()
442 .get(layout.offset..layout.offset + layout.len)
443 .unwrap_or(&[]);
444 Plane {
445 data: slice,
446 stride: layout.stride,
447 }
448 })
449 .collect()
450 }
451 }
452
453 /// Iterate mutable planes for in-place writes.
454 pub fn planes_mut(&mut self) -> SmallVec<[PlaneMut<'_>; 3]> {
455 if self.external.is_some() {
456 return self
457 .layouts
458 .iter()
459 .map(|layout| PlaneMut {
460 data: &mut [],
461 stride: layout.stride,
462 })
463 .collect();
464 }
465 self.layouts
466 .iter()
467 .zip(self.buffers.iter_mut())
468 .map(|(layout, buf)| {
469 let len = layout.offset + layout.len;
470 if buf.len() < len {
471 buf.resize(len);
472 }
473 let slice = buf
474 .as_mut_slice()
475 .get_mut(layout.offset..layout.offset + layout.len)
476 .unwrap_or(&mut []);
477 PlaneMut {
478 data: slice,
479 stride: layout.stride,
480 }
481 })
482 .collect()
483 }
484
485 /// Return a copy of plane layouts.
486 pub fn layouts(&self) -> SmallVec<[PlaneLayout; 3]> {
487 self.layouts.clone()
488 }
489
490 /// Return strides for each plane.
491 pub fn plane_strides(&self) -> SmallVec<[usize; 3]> {
492 self.layouts.iter().map(|l| l.stride).collect()
493 }
494
495 /// Convert into owned buffers and metadata.
496 #[allow(clippy::type_complexity)]
497 pub fn into_parts(
498 self,
499 ) -> (
500 FrameMeta,
501 SmallVec<[PlaneLayout; 3]>,
502 SmallVec<[Vec<u8>; 3]>,
503 ) {
504 let layouts = self.layouts.clone();
505 if self.external.is_some() {
506 (self.meta, layouts, SmallVec::new())
507 } else {
508 let buffers = self.buffers.into_iter().map(|lease| lease.take()).collect();
509 (self.meta, layouts, buffers)
510 }
511 }
512}
513
514/// Helper for building geometry consistently.
515///
516/// # Example
517/// ```rust
518/// use std::num::NonZeroU32;
519/// use styx_core::prelude::plane_layout_from_dims;
520///
521/// let layout = plane_layout_from_dims(
522/// NonZeroU32::new(2).unwrap(),
523/// NonZeroU32::new(3).unwrap(),
524/// 4,
525/// );
526/// assert_eq!(layout.len, 24);
527/// ```
528pub fn plane_layout_from_dims(
529 width: NonZeroU32,
530 height: NonZeroU32,
531 bytes_per_pixel: usize,
532) -> PlaneLayout {
533 let stride = width.get() as usize * bytes_per_pixel;
534 let len = stride * height.get() as usize;
535 PlaneLayout {
536 offset: 0,
537 len,
538 stride,
539 }
540}
541
542/// Helper to construct a layout when stride is already known.
543///
544/// # Example
545/// ```rust
546/// use std::num::NonZeroU32;
547/// use styx_core::prelude::plane_layout_with_stride;
548///
549/// let layout = plane_layout_with_stride(
550/// NonZeroU32::new(2).unwrap(),
551/// NonZeroU32::new(3).unwrap(),
552/// 8,
553/// );
554/// assert_eq!(layout.len, 24);
555/// ```
556pub fn plane_layout_with_stride(
557 _width: NonZeroU32,
558 height: NonZeroU32,
559 stride: usize,
560) -> PlaneLayout {
561 let len = stride * height.get() as usize;
562 PlaneLayout {
563 offset: 0,
564 len,
565 stride,
566 }
567}