#![cfg(all(target_os = "linux", feature = "wgpu-test"))]
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::sync::Arc;
use std::time::{Duration, Instant};
use gpui::{
AnyWindowHandle, AppContext as _, Context, IntoElement, Render, Window, div, gpu_canvas,
prelude::*, px, size,
};
use gpui_interop::{WgpuSharedBufferRing, attach};
use gpui_wgpu::{CosmicTextSystem, WgpuHeadlessRenderer};
const EDGE: u32 = 512;
const FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
struct Producer {
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
ring: WgpuSharedBufferRing,
}
struct View {
producer: Rc<RefCell<Option<Producer>>>,
built: Rc<Cell<bool>>,
}
impl Render for View {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let producer = self.producer.clone();
let built = self.built.clone();
div().size_full().child(
gpu_canvas(move |gpu| {
let mut slot = producer.borrow_mut();
if slot.is_none() {
let Some(lent) = gpu.try_device::<WgpuHeadlessRenderer>() else {
return;
};
let Ok(interop) = attach(Some(lent)) else {
return;
};
let Some((device, queue)) = interop.adapter().wgpu() else {
return;
};
let Ok(ring) = WgpuSharedBufferRing::new(&device, EDGE, EDGE, FORMAT) else {
return;
};
*slot = Some(Producer {
device,
queue,
ring,
});
built.set(true);
}
let producer = slot.as_mut().expect("built above");
if let Some(handle) = producer.ring.present() {
gpu.paint_surface(handle);
}
})
.size_full(),
)
}
}
fn submit(producer: &mut Producer, passes: u32) {
let frame = producer.ring.begin().expect("a free slot");
let mut encoder = producer
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("ring test frame"),
});
for _ in 0..passes {
let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("ring test clear"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: frame.view(),
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
}
frame
.submit(&producer.queue, Some(encoder.finish()))
.expect("submit a frame");
}
fn wait_for_present(ring: &mut WgpuSharedBufferRing) -> Option<gpui_engine::DmaBufHandle> {
let deadline = Instant::now() + Duration::from_secs(5);
while Instant::now() < deadline {
if let Some(handle) = ring.present() {
return Some(handle);
}
std::thread::sleep(Duration::from_millis(2));
}
None
}
#[test]
fn a_slow_frame_never_hands_gpui_a_pending_fence() {
if !gpui::current_headless_renderer().is_ok_and(|renderer| renderer.is_some()) {
eprintln!("skipping: no headless renderer (no Vulkan adapter)");
return;
}
let text_system = Arc::new(CosmicTextSystem::new("fallback"));
let mut cx = gpui::HeadlessAppContext::with_platform(text_system, Arc::new(()), || {
gpui::current_headless_renderer()
});
let built = Rc::new(Cell::new(false));
let producer = Rc::new(RefCell::new(None));
let window = cx
.open_window(size(px(EDGE as f32), px(EDGE as f32)), |_window, cx| {
cx.new(|_| View {
producer: producer.clone(),
built: built.clone(),
})
})
.expect("a window to composite into");
let window: AnyWindowHandle = window.into();
cx.run_until_parked();
cx.update_window(window, |_, window, cx| {
let _ = window.draw(cx);
})
.expect("draw a frame");
if !built.get() {
eprintln!("skipping: the renderer lent no exportable wgpu device");
return;
}
let mut producer = producer
.borrow_mut()
.take()
.expect("the first paint built the ring");
submit(&mut producer, 1);
let first = wait_for_present(&mut producer.ring).expect("the first frame becomes ready");
assert!(
first.acquire_fence.is_none(),
"present must strip the acquire fence: GPUI can still poll it otherwise"
);
submit(&mut producer, 2000);
let during = producer.ring.present();
if let Some(handle) = &during {
assert!(
handle.acquire_fence.is_none(),
"a frame still in flight must not be presented with its fence"
);
}
assert_eq!(
producer.ring.pending(),
1,
"the slow frame should still be in flight, so present repeated the last good frame"
);
let during = during.expect("the last good frame is repeated while the slow frame runs");
assert!(
during.acquire_fence.is_none(),
"the repeated frame must be fence-free"
);
let last = producer.ring.last().expect("a last good frame exists");
assert!(last.acquire_fence.is_none(), "last() is fence-free too");
let slow =
wait_for_present(&mut producer.ring).expect("the slow frame eventually becomes ready");
assert!(slow.acquire_fence.is_none());
}