#![cfg(all(target_os = "linux", feature = "wgpu-test"))]
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::sync::Arc;
use gpui::{
AnyWindowHandle, AppContext as _, Context, Corners, IntoElement, Render, Window, div,
gpu_canvas, prelude::*, px, size,
};
use gpui_interop::{WgpuSharedBuffer, WgpuSurface, attach};
use gpui_wgpu::{CosmicTextSystem, ImportedTextureExt as _, WgpuHeadlessRenderer};
const EDGE: u32 = 64;
const CLEAR: wgpu::Color = wgpu::Color {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
const EXPECTED: [u8; 4] = [255, 0, 0, 255];
fn gpu_preference() -> gpui::GpuPreference {
std::env::var("GPUI_TEST_DEVICE_ID")
.ok()
.and_then(|value| u32::from_str_radix(value.trim_start_matches("0x"), 16).ok())
.map(|device_id| gpui::GpuPreference::Device(gpui::GpuId::by_device(device_id)))
.unwrap_or(gpui::GpuPreference::Auto)
}
struct Producer {
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
surface: WgpuSurface,
}
struct View {
producer: Rc<RefCell<Option<Producer>>>,
built: Rc<Cell<bool>>,
same_device: Rc<Cell<bool>>,
gpu: Rc<RefCell<Option<String>>>,
}
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();
let same_device = self.same_device.clone();
let gpu_name = self.gpu.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.clone())) else {
return;
};
let Some((device, queue)) = interop.adapter().wgpu() else {
return;
};
same_device.set(Arc::ptr_eq(&device, &lent.0) && Arc::ptr_eq(&queue, &lent.1));
if let Some(id) = interop.adapter().gpu_id() {
*gpu_name.borrow_mut() = Some(id.name);
}
let Ok(surface) = WgpuSurface::new(&device, EDGE, EDGE) else {
return;
};
*slot = Some(Producer {
device,
queue,
surface,
});
built.set(true);
}
let producer = slot.as_mut().expect("built above");
let resized = producer
.surface
.resize(&producer.device, EDGE, EDGE)
.expect("a same-size resize");
assert!(!resized, "a same-size resize should be a no-op");
let mut encoder =
producer
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("gpui_interop_wgpu_surface_test"),
});
{
let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("clear the produced view"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: producer.surface.view(),
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(CLEAR),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
}
producer.queue.submit(Some(encoder.finish()));
let handle = producer
.surface
.view()
.to_imported_handle()
.expect("the surface is an sRGB, bindable view");
gpu.paint_texture(handle, Corners::default(), 1.0, false);
})
.size_full(),
)
}
}
#[test]
fn a_wgpu_frame_composites_on_the_renderers_own_device() {
let preference = gpu_preference();
if !gpui::current_headless_renderer_with(preference.clone())
.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 factory = preference;
let mut cx = gpui::HeadlessAppContext::with_platform(text_system, Arc::new(()), move || {
gpui::current_headless_renderer_with(factory.clone())
});
let built = Rc::new(Cell::new(false));
let same_device = Rc::new(Cell::new(false));
let gpu = 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: Rc::new(RefCell::new(None)),
built: built.clone(),
same_device: same_device.clone(),
gpu: gpu.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 wgpu device");
return;
}
eprintln!(
"using {}",
gpu.borrow().as_deref().unwrap_or("an unnamed adapter")
);
assert!(
same_device.get(),
"Adapter::wgpu should return the renderer's own device and queue, not a second pair"
);
let image = cx.capture_screenshot(window).expect("read the frame back");
assert_eq!(
image.get_pixel(EDGE / 2, EDGE / 2).0,
EXPECTED,
"the wgpu-rendered surface did not composite through the same-device path"
);
}
struct SharedProducer {
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
buffer: WgpuSharedBuffer,
}
struct SharedView {
producer: Rc<RefCell<Option<SharedProducer>>>,
built: Rc<Cell<bool>>,
error: Rc<RefCell<Option<String>>>,
tiled: Rc<Cell<bool>>,
clear: Rc<Cell<[f32; 4]>>,
gpu: Rc<RefCell<Option<String>>>,
}
impl Render for SharedView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let producer = self.producer.clone();
let built = self.built.clone();
let error = self.error.clone();
let tiled = self.tiled.clone();
let clear = self.clear.clone();
let gpu_name = self.gpu.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;
};
if let Some(id) = interop.adapter().gpu_id() {
*gpu_name.borrow_mut() = Some(id.name);
}
match WgpuSharedBuffer::new(
&device,
EDGE,
EDGE,
wgpu::TextureFormat::Rgba8UnormSrgb,
) {
Ok(buffer) => {
*slot = Some(SharedProducer {
device,
queue,
buffer,
});
built.set(true);
}
Err(build) => {
*error.borrow_mut() = Some(build.to_string());
return;
}
}
}
let producer = slot.as_mut().expect("built above");
let [r, g, b, a] = clear.get();
let mut encoder =
producer
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("gpui_interop_wgpu_shared_buffer_test"),
});
{
let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("clear the exported view"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: producer.buffer.view(),
depth_slice: None,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: f64::from(r),
g: f64::from(g),
b: f64::from(b),
a: f64::from(a),
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
}
let handle = producer
.buffer
.submit(&producer.queue, Some(encoder.finish()))
.expect("an acquire fence");
assert!(
handle.acquire_fence.is_some(),
"a submitted frame should carry an acquire fence"
);
tiled.set(handle.modifier != 0);
gpu.paint_surface(handle);
})
.size_full(),
)
}
}
#[test]
fn an_exported_wgpu_frame_composites_as_a_dmabuf() {
let preference = gpu_preference();
if !gpui::current_headless_renderer_with(preference.clone())
.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 factory = preference;
let mut cx = gpui::HeadlessAppContext::with_platform(text_system, Arc::new(()), move || {
gpui::current_headless_renderer_with(factory.clone())
});
let built = Rc::new(Cell::new(false));
let error = Rc::new(RefCell::new(None));
let tiled = Rc::new(Cell::new(false));
let clear = Rc::new(Cell::new([1.0, 0.0, 0.0, 1.0]));
let gpu = Rc::new(RefCell::new(None));
let window = cx
.open_window(size(px(EDGE as f32), px(EDGE as f32)), |_window, cx| {
cx.new(|_| SharedView {
producer: Rc::new(RefCell::new(None)),
built: built.clone(),
error: error.clone(),
tiled: tiled.clone(),
clear: clear.clone(),
gpu: gpu.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's wgpu device cannot export a dma-buf: {}",
error.borrow().as_deref().unwrap_or("no device"),
);
return;
}
eprintln!(
"using {}",
gpu.borrow().as_deref().unwrap_or("an unnamed adapter")
);
assert!(
tiled.get(),
"the exported image should be on a DRM modifier, not LINEAR"
);
let image = cx.capture_screenshot(window).expect("read the frame back");
assert_eq!(
image.get_pixel(EDGE / 2, EDGE / 2).0,
EXPECTED,
"the exported wgpu frame did not composite through the dma-buf path"
);
clear.set([0.0, 1.0, 0.0, 1.0]);
cx.update_window(window, |_, window, cx| {
let _ = window.draw(cx);
})
.expect("draw a second frame");
let image = cx
.capture_screenshot(window)
.expect("read the second frame back");
assert_eq!(
image.get_pixel(EDGE / 2, EDGE / 2).0,
[0, 255, 0, 255],
"the second exported frame did not composite"
);
}