Skip to main content

BufferRing

Struct BufferRing 

Source
pub struct BufferRing { /* private fields */ }
Expand description

A producer-agnostic pool of scanout slots with buffer age and damage accumulation.

The ring owns slot lifecycle and staleness; the producer owns the actual buffers and indexes them by slot. That split is what lets the same ring sit under a dumb buffer, a GBM surface or a Vulkan swapchain without knowing which.

§What it is for

Redrawing a whole frame when three pixels changed wastes bandwidth, and on a memory-bound part that is the difference between hitting vblank and missing it. Partial repaint avoids it, but only if the producer knows how stale the buffer it has just been handed is – a buffer two frames old needs everything that changed in those two frames repainted, not just this frame’s damage. That is the EGL_EXT_buffer_age contract, and this is the bookkeeping behind it.

Getting it wrong under-paints: the frame is composed of pixels from different moments, and the error persists until something forces a full repaint. So every path that cannot prove what a slot needs returns Repaint::Full.

§Threading

Not synchronized, by design. Drive acquire, present and release from the one thread the producer renders on – the present loop. &mut self on the mutating methods makes sharing it across threads a compile error rather than a race.

Implementations§

Source§

impl BufferRing

Source

pub const DEFAULT_MAX_SLOTS: usize = 3

How deep a ring grows unless the caller says otherwise.

Source

pub const DEFAULT_MAX_DAMAGE_RECTS: usize = 16

How many rectangles a union may hold unless the caller says otherwise. See BufferRing::new for what happens when a union exceeds this.

Source

pub fn new(max_slots: usize, max_damage_rects: usize) -> Self

A ring that grows to max_slots, with unions capped at max_damage_rects.

max_slots is clamped to at least one: a ring with no slots can never hand one out, and a caller asking for zero has made an arithmetic mistake rather than a request.

The rect cap is a bandwidth-versus-bookkeeping knob. A union that exceeds it collapses to Repaint::Full, on the reasoning that scattered small rectangles cost more in per-rect overhead than the bandwidth they save.

Source

pub fn len(&self) -> usize

How many slots the ring has grown so far.

Source

pub fn is_empty(&self) -> bool

Whether the ring has grown any slots yet.

Source

pub const fn max_slots(&self) -> usize

The cap this ring grows to.

Source

pub fn in_flight(&self) -> usize

How many slots the producer is currently drawing into.

Source

pub fn age(&self, slot: usize) -> u32

How many presents have happened since this slot was last presented.

Zero means either that it has never been presented, so its contents are undefined, or that it is the slot presented last, which is current. acquire is what tells them apart where it matters to a producer: it never hands out the slot presented last, and answers Repaint::Full for one never presented.

Source

pub fn acquire(&mut self) -> Option<Lease>

Take a slot for the next frame, or None if every one is busy.

Prefers the freshest free slot, because freshest means least to repaint. Grows the ring if nothing is free and it is below capacity. Never returns the scanning slot.

None is not an error: it means the producer is ahead of the display and should retry after the next vblank.

Source

pub fn present(&mut self, slot: usize, frame_damage: &[Rect])

Commit a slot as the new scanout buffer.

frame_damage is what changed in this frame against the previous one – not against whatever was in this slot, which is what the ring works out for itself.

The outgoing scanning slot becomes releasable rather than free: its flip has not landed, so the display engine may still be reading it. Freeing it here is the mistake drmkit-scene’s invariant 1 (release only after the replacing flip lands) forbids, in a different place.

Source

pub fn release(&mut self, slot: usize)

Return a replaced slot to the free pool.

Call it once the flip that replaced this slot has landed, or once its render has been abandoned. The scanning slot is ignored rather than freed – releasing what is on screen is not a request this can honor.

Trait Implementations§

Source§

impl Debug for BufferRing

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for BufferRing

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.