use crate::platform::{self, SlotResources, SyncPoint};
use crate::ring::{Lease, Ring, Writing};
use crate::{AcquireError, SurfaceDesc, SurfaceStats, Transport};
use anyhow::{Result, ensure};
use gpui::{Bounds, Corners, CustomContent, CustomGeometry, DevicePixels, Pixels, Size, Window};
use std::any::Any;
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Weak};
use std::time::Duration;
struct Shared {
desc: SurfaceDesc,
ring: Ring,
slots: Vec<Arc<SlotResources>>,
frames: Mutex<VecDeque<Arc<SurfaceFrame>>>,
on_present: Mutex<Option<Arc<dyn Fn() + Send + Sync>>>,
invalid: AtomicBool,
feedback: Arc<Feedback>,
}
#[derive(Default)]
struct Feedback {
composited: std::sync::atomic::AtomicU64,
unshown: std::sync::atomic::AtomicU64,
latency_us: std::sync::atomic::AtomicU64,
unsupported: Mutex<Option<String>>,
}
#[derive(Clone)]
pub struct EngineSurface(Arc<Shared>);
impl std::fmt::Debug for EngineSurface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EngineSurface")
.field("desc", &self.0.desc)
.field("stats", &self.stats())
.finish()
}
}
pub struct SurfaceSlot {
writing: Writing,
pub(crate) resources: Arc<SlotResources>,
desc: SurfaceDesc,
}
impl SurfaceSlot {
pub fn index(&self) -> usize {
self.writing.index()
}
pub fn desc(&self) -> SurfaceDesc {
self.desc
}
}
impl EngineSurface {
pub(crate) fn from_slots(desc: SurfaceDesc, slots: Vec<Arc<SlotResources>>) -> Result<Self> {
desc.validate()?;
ensure!(slots.len() == desc.slots, "slot count mismatch");
Ok(Self(Arc::new(Shared {
desc,
ring: Ring::new(desc.slots),
slots,
frames: Mutex::new(VecDeque::new()),
on_present: Mutex::new(None),
invalid: AtomicBool::new(false),
feedback: Arc::new(Feedback::default()),
})))
}
pub fn desc(&self) -> SurfaceDesc {
self.0.desc
}
pub fn transport(&self) -> Transport {
Transport::ZeroCopy
}
pub fn acquire(&self) -> Result<SurfaceSlot, AcquireError> {
if !self.is_valid() {
return Err(AcquireError::Invalidated);
}
let writing = match self.0.ring.acquire(false) {
Some(writing) => writing,
None => {
platform::retire_completed(&self.0.slots);
self.0.ring.acquire(true).ok_or(AcquireError::WouldBlock)?
}
};
let resources = self.0.slots[writing.index()].clone();
Ok(SurfaceSlot {
writing,
resources,
desc: self.0.desc,
})
}
pub fn present(&self, slot: SurfaceSlot, sync: SyncPoint) -> Result<()> {
ensure!(self.is_valid(), AcquireError::Invalidated);
ensure!(
self.0.ring.owns(&slot.writing),
"slot belongs to another surface"
);
let lease = self.0.ring.publish(slot.writing);
let frame = Arc::new(SurfaceFrame {
desc: self.0.desc,
resources: slot.resources,
sync,
_lease: lease,
presented_at: std::time::Instant::now(),
composited: AtomicBool::new(false),
feedback: self.0.feedback.clone(),
});
let ready = frame.sync.ready();
let waiter = (!ready).then(|| Arc::downgrade(&frame));
let superseded = {
let mut frames = self.0.frames.lock().unwrap();
let superseded = if ready {
frames.drain(..).collect()
} else {
Vec::new()
};
frames.push_back(frame);
superseded
};
drop(superseded);
if let Some(frame) = waiter {
spawn_waiter(Arc::downgrade(&self.0), frame);
}
notify(&self.0);
Ok(())
}
pub fn content(&self) -> Option<Arc<dyn CustomContent>> {
self.content_for(true)
}
pub fn preview_content(&self) -> Option<Arc<dyn CustomContent>> {
self.content_for(false)
}
pub fn unsupported(&self) -> Option<String> {
self.0.feedback.unsupported.lock().unwrap().clone()
}
fn content_for(&self, wide: bool) -> Option<Arc<dyn CustomContent>> {
if self.0.feedback.unsupported.lock().unwrap().is_some() {
return None;
}
let (frame, superseded) = {
let mut frames = self.0.frames.lock().unwrap();
let newest_ready = frames.iter().rposition(|frame| frame.sync.ready())?;
let superseded: Vec<_> = frames.drain(..newest_ready).collect();
(frames[0].clone(), superseded)
};
drop(superseded);
Some(Arc::new(FrameView { frame, wide }))
}
pub fn paint(
&self,
window: &mut Window,
bounds: Bounds<Pixels>,
corner_radii: Corners<Pixels>,
) {
let wide = window.color_status().effective() == gpui::WindowColorMode::WideGamutSdr;
if let Some(content) = self.content_for(wide) {
window.paint_custom(
CustomGeometry::RoundedRect {
bounds,
corner_radii,
},
content,
);
}
}
pub fn on_present(&self, callback: impl Fn() + Send + Sync + 'static) {
*self.0.on_present.lock().unwrap() = Some(Arc::new(callback));
}
pub fn stats(&self) -> SurfaceStats {
let mut stats = self.0.ring.stats();
stats.waiting = self
.0
.frames
.lock()
.unwrap()
.iter()
.filter(|frame| !frame.sync.ready())
.count();
let feedback = &self.0.feedback;
stats.composited = feedback.composited.load(Ordering::Relaxed);
stats.unshown = feedback.unshown.load(Ordering::Relaxed);
stats.composite_latency_ms = feedback.latency_us.load(Ordering::Relaxed) as f32 / 1000.;
stats
}
pub fn invalidate(&self) {
self.0.invalid.store(true, Ordering::Release);
for slot in &self.0.slots {
slot.release_engine();
}
}
pub fn is_valid(&self) -> bool {
!self.0.invalid.load(Ordering::Acquire)
}
}
fn notify(shared: &Shared) {
let callback = shared.on_present.lock().unwrap().clone();
if let Some(callback) = callback {
callback();
}
}
fn spawn_waiter(shared: Weak<Shared>, frame: Weak<SurfaceFrame>) {
let _ = std::thread::Builder::new()
.name("gpu-interop-wait".into())
.spawn(move || {
loop {
let Some(waiting) = frame.upgrade() else {
return;
};
let reached = waiting.sync.wait_reached(Duration::from_millis(100));
drop(waiting);
let Some(shared) = shared.upgrade() else {
return;
};
if reached {
notify(&shared);
return;
}
}
});
}
struct SurfaceFrame {
desc: SurfaceDesc,
resources: Arc<SlotResources>,
sync: SyncPoint,
_lease: Arc<Lease>,
presented_at: std::time::Instant,
composited: AtomicBool,
feedback: Arc<Feedback>,
}
impl SurfaceFrame {
fn note_composited(&self) {
if !self.composited.swap(true, Ordering::AcqRel) {
let feedback = &self.feedback;
feedback.composited.fetch_add(1, Ordering::Relaxed);
feedback.latency_us.store(
self.presented_at.elapsed().as_micros() as u64,
Ordering::Relaxed,
);
}
}
}
impl Drop for SurfaceFrame {
fn drop(&mut self) {
if !self.composited.load(Ordering::Acquire) {
self.feedback.unshown.fetch_add(1, Ordering::Relaxed);
}
}
}
impl std::fmt::Debug for SurfaceFrame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SurfaceFrame")
.field("desc", &self.desc)
.field("sync", &self.sync)
.finish()
}
}
#[derive(Debug)]
struct FrameView {
frame: Arc<SurfaceFrame>,
wide: bool,
}
impl CustomContent for FrameView {
fn image_source_size(&self) -> Option<Size<DevicePixels>> {
Some(self.frame.desc.extent())
}
fn image_color_mode(&self) -> gpui::ImageColorMode {
self.frame.desc.format.color_mode(self.wide)
}
fn prepare(&self, backend: &dyn Any, _: Size<DevicePixels>) -> Result<Box<dyn Any>> {
let frame = &self.frame;
let prepared = platform::prepare_frame(
&frame.desc,
&frame.resources,
&frame.sync,
self.image_color_mode(),
backend,
);
match &prepared {
Ok(_) => frame.note_composited(),
Err(error) => {
if let Some(reason) = error.downcast_ref::<crate::Unsupported>() {
let mut latched = frame.feedback.unsupported.lock().unwrap();
latched.get_or_insert_with(|| reason.0.clone());
}
}
}
prepared
}
}