use std::collections::VecDeque;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::sync::Arc;
use std::time::{Duration, Instant};
use anyhow::{Result, bail};
use gpui_engine::DmaBufHandle;
use crate::wgpu_dmabuf::WgpuSharedBuffer;
const RELEASE_TIMEOUT: Duration = Duration::from_millis(250);
pub struct WgpuSharedBufferRing {
slots: Vec<Slot>,
width: u32,
height: u32,
format: wgpu::TextureFormat,
pending: VecDeque<(usize, DmaBufHandle)>,
last_good: Option<(usize, DmaBufHandle)>,
}
struct Slot {
buffer: WgpuSharedBuffer,
release: Option<Arc<OwnedFd>>,
submitted_at: Option<Instant>,
handed_at: Option<Instant>,
}
impl Slot {
fn available(&self, now: Instant) -> bool {
let Some(release) = &self.release else {
return true;
};
if signalled(release) {
return true;
}
self.handed_at
.or(self.submitted_at)
.is_some_and(|at| now.duration_since(at) >= RELEASE_TIMEOUT)
}
}
pub struct RingFrame<'a> {
ring: &'a mut WgpuSharedBufferRing,
index: usize,
}
impl RingFrame<'_> {
pub fn view(&self) -> &wgpu::TextureView {
self.ring.slots[self.index].buffer.view()
}
pub fn submit(
self,
queue: &wgpu::Queue,
commands: impl IntoIterator<Item = wgpu::CommandBuffer>,
) -> Result<()> {
self.ring.submit(self.index, queue, commands)
}
}
impl WgpuSharedBufferRing {
pub fn new(
device: &wgpu::Device,
width: u32,
height: u32,
format: wgpu::TextureFormat,
) -> Result<Self> {
Self::with_slots(device, width, height, format, 3)
}
pub fn with_slots(
device: &wgpu::Device,
width: u32,
height: u32,
format: wgpu::TextureFormat,
slots: usize,
) -> Result<Self> {
if slots < 2 {
bail!("a shared-buffer ring needs at least two slots, but {slots} were asked for");
}
let mut ring = Self {
slots: Vec::with_capacity(slots),
width,
height,
format,
pending: VecDeque::new(),
last_good: None,
};
for _ in 0..slots {
ring.slots.push(Slot {
buffer: WgpuSharedBuffer::new(device, width, height, format)?,
release: None,
submitted_at: None,
handed_at: None,
});
}
Ok(ring)
}
pub fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn format(&self) -> wgpu::TextureFormat {
self.format
}
pub fn len(&self) -> usize {
self.slots.len()
}
pub fn is_empty(&self) -> bool {
self.slots.is_empty()
}
pub fn pending(&self) -> usize {
self.pending.len()
}
pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) -> Result<()> {
if (width, height) == (self.width, self.height) {
return Ok(());
}
let count = self.slots.len();
let mut slots = Vec::with_capacity(count);
for _ in 0..count {
slots.push(Slot {
buffer: WgpuSharedBuffer::new(device, width, height, self.format)?,
release: None,
submitted_at: None,
handed_at: None,
});
}
self.slots = slots;
self.pending.clear();
self.last_good = None;
self.width = width;
self.height = height;
Ok(())
}
pub fn begin(&mut self) -> Option<RingFrame<'_>> {
let now = Instant::now();
self.expire(now);
let last = self.last_good.as_ref().map(|(index, _)| *index);
let index = (0..self.slots.len()).find(|&index| {
Some(index) != last
&& !self.pending.iter().any(|(pending, _)| *pending == index)
&& self.slots[index].available(now)
})?;
Some(RingFrame { ring: self, index })
}
pub fn present(&mut self) -> Option<DmaBufHandle> {
let now = Instant::now();
self.expire(now);
let ready = self
.pending
.iter()
.rposition(|(_, handle)| fence_ready(handle));
if let Some(position) = ready {
self.pending.drain(..position);
let (index, mut handle) = self.pending.pop_front().expect("just found");
handle.acquire_fence = None;
self.slots[index].handed_at = Some(now);
self.last_good = Some((index, handle.clone()));
return Some(handle);
}
self.last_good.as_ref().map(|(_, handle)| handle.clone())
}
pub fn last(&self) -> Option<DmaBufHandle> {
self.last_good.as_ref().map(|(_, handle)| handle.clone())
}
fn submit(
&mut self,
index: usize,
queue: &wgpu::Queue,
commands: impl IntoIterator<Item = wgpu::CommandBuffer>,
) -> Result<()> {
let slot = &mut self.slots[index];
let mut handle = slot.buffer.submit(queue, commands)?;
let release = release_descriptor()?;
handle.release = Some(Arc::clone(&release));
slot.release = Some(release);
slot.submitted_at = Some(Instant::now());
slot.handed_at = None;
self.pending.push_back((index, handle));
Ok(())
}
fn expire(&mut self, now: Instant) {
if self.pending.is_empty() {
return;
}
let pending = std::mem::take(&mut self.pending);
for (index, handle) in pending {
let expired = self.slots[index]
.submitted_at
.is_some_and(|at| now.duration_since(at) >= RELEASE_TIMEOUT);
if !expired {
self.pending.push_back((index, handle));
}
}
}
}
fn release_descriptor() -> Result<Arc<OwnedFd>> {
let fd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) };
if fd < 0 {
return Err(std::io::Error::last_os_error().into());
}
Ok(Arc::new(unsafe { OwnedFd::from_raw_fd(fd) }))
}
fn signalled(fd: &OwnedFd) -> bool {
let mut descriptor = libc::pollfd {
fd: fd.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
loop {
match unsafe { libc::poll(&mut descriptor, 1, 0) } {
1.. => return true,
0 => return false,
_ => {
if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted {
continue;
}
return false;
}
}
}
}
fn fence_ready(handle: &DmaBufHandle) -> bool {
handle
.acquire_fence
.as_ref()
.is_none_or(|fence| signalled(fence))
}