struct FramePool {
inner: Mutex<PoolInner>,
cv: Condvar,
stop: AtomicBool,
generation: AtomicU64,
}Expand description
Demand-driven capture -> encode handoff: a bounded single-slot channel over a fixed set of pooled shm surfaces.
The capture thread writes into a pooled surface and publishes it into the single slot; the
encode thread takes it. Capture stays at most one frame ahead of encode because acquire and
publish BLOCK (bounded) until the encoder frees a surface / drains the slot, throttling capture
to the encode rate. Since X11 capture is pull-based (no backlog), throttling — rather than
capturing-then-dropping — means a full-resolution shm round-trip is never spent on a frame that
would be discarded, while the next capture still overlaps the current encode (the throughput win).
The encoder therefore only ever sees a contiguous frame stream, keeping the H.264 reference chain
valid.
An x264 session encoding a surface’s picture in place reads it through the next encode as
well, so the encode thread keeps that surface (held) until an encode no longer reads it: of
the POOL_N surfaces one is then held, one encoded, and the third left for the capture to
convert the next frame into while the encode runs, the capture still one frame ahead.
generation is bumped by the capture thread each time the backing shm surfaces are destroyed and
recreated. Published frames carry it so the encode thread rebuilds its pipeline — dropping encoder
state keyed to surface base pointers (e.g. NVENC’s pinned-host registrations) — even when a resize
flap lands back on the old dimensions and the recreated segments reuse the old virtual addresses.
Fields§
§inner: Mutex<PoolInner>§cv: Condvar§stop: AtomicBool§generation: AtomicU64Implementations§
Source§impl FramePool
impl FramePool
Sourcefn new(n: usize) -> Self
fn new(n: usize) -> Self
Create a pool with n free surfaces, an empty handoff slot, and generation 0.
Sourcefn bump_generation(&self)
fn bump_generation(&self)
Capture: record that the surfaces were recreated by advancing the generation. Only
called after drain_for_resize succeeded, so no frame from the previous generation is still
in flight.
Sourcefn generation(&self) -> u64
fn generation(&self) -> u64
Read the current surface generation, stamped onto each published frame.
Sourcefn acquire(&self, stop: &AtomicBool) -> Option<usize>
fn acquire(&self, stop: &AtomicBool) -> Option<usize>
Capture: claim a free surface to write the next frame into, or None on stop.
Blocks (bounded, re-checking stop every 20ms) until a surface is free, which throttles
capture to the encode rate so a full-resolution capture is never spent on a frame that would
be dropped. The bounded re-check means a stop that races the wait cannot hang the thread.
Sourcefn publish(&self, frame: RawFrame, stop: &AtomicBool) -> bool
fn publish(&self, frame: RawFrame, stop: &AtomicBool) -> bool
Capture: publish the just-captured frame into the single slot, or false (frame
discarded) on stop.
Blocks (bounded, re-checking stop every 20ms) until the encode thread has taken the previous
frame, so capture stays at most one frame ahead and never drops under normal flow.
Sourcefn take(&self, release: impl FnMut()) -> Option<RawFrame>
fn take(&self, release: impl FnMut()) -> Option<RawFrame>
Encode: block until a frame is available (Some) or stop is signaled (None).
The wait is bounded (re-checking stop every 20ms) as defense-in-depth against a lost wakeup,
so a stop that races the park can never leave the encode thread blocked forever. A drain
waiting for the held surface is answered here: release stops the encoder reading it,
with the lock dropped, and the surface returns to the free list.
Sourcefn recycle(&self, idx: usize)
fn recycle(&self, idx: usize)
Encode: return a surface to the free list after it has been encoded, waking any waiter
parked in acquire / publish / drain_for_resize.
Sourcefn settle(&self, back: &[usize], held: Option<usize>)
fn settle(&self, back: &[usize], held: Option<usize>)
Encode: after a frame’s encode, return the surfaces back to the free list and keep
held, the one whose picture the encoder reads through its next encode, out of it.
Sourcefn drain_for_resize(&self, n: usize, stop: &AtomicBool) -> bool
fn drain_for_resize(&self, n: usize, stop: &AtomicBool) -> bool
Capture: before recreating surfaces (auto-adjust / region resize), reclaim the pending slot and wait until every surface is back in the free list, so no surface is destroyed while the encode thread is still reading it.
Reclaiming the slot returns any un-taken frame to the free list; the loop then waits until
free.len() == n, i.e. the encode thread has finished any in-flight frame and, asked
through release, returned the surface its encoder held. The wait is bounded
and also breaks on stop — pool shutdown OR the external stop — so a panicked or dead encode
thread that never recycles cannot wedge the capture thread here forever; a requested stop
unblocks it. Returns true if it fully drained (safe to recreate surfaces), or false if it
aborted on stop — in which case the caller tears down, which joins the encode thread before
destroying surfaces, so the resize-safety guarantee still holds.
Sourcefn shutdown(&self)
fn shutdown(&self)
Signal every waiter to stop and wake them, ending the encode thread’s take loop.
The inner mutex is acquired BEFORE storing stop and notifying, which closes the lost-wakeup
window: with the lock held, take is either still before its stop check or already parked on
the condvar (and will receive the notify), never in the gap between the two. The notify is
issued after the guard is dropped so the woken thread does not immediately re-block on the lock
this call holds.
Auto Trait Implementations§
impl !Freeze for FramePool
impl RefUnwindSafe for FramePool
impl Send for FramePool
impl Sync for FramePool
impl Unpin for FramePool
impl UnsafeUnpin for FramePool
impl UnwindSafe for FramePool
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.§impl<T> DowncastSync for T
impl<T> DowncastSync for T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more