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}