use std::os::fd::{FromRawFd, OwnedFd};
use std::sync::Arc;
use anyhow::{Result, anyhow, bail};
use ash::vk;
use gpui_engine::{DmaBufHandle, SurfaceFormatKind};
use crate::linux::{ExportDevice, Tiling, allocate_export_image};
pub struct WgpuSharedBuffer {
texture: wgpu::Texture,
view: wgpu::TextureView,
handle: DmaBufHandle,
format: wgpu::TextureFormat,
width: u32,
height: u32,
device: ash::Device,
semaphore: vk::Semaphore,
external_semaphore_fd: ash::khr::external_semaphore_fd::Device,
}
impl WgpuSharedBuffer {
pub fn new(
device: &wgpu::Device,
width: u32,
height: u32,
format: wgpu::TextureFormat,
) -> Result<Self> {
if width == 0 || height == 0 {
bail!(
"an exported wgpu surface must have a non-zero extent, but {width}x{height} was asked for"
);
}
let (surface_format, vk_format) = formats(format)?;
let hal_guard = unsafe { device.as_hal::<wgpu::hal::vulkan::Api>() }
.ok_or_else(|| anyhow!("an exported wgpu surface needs a Vulkan wgpu device"))?;
let hal: &wgpu::hal::vulkan::Device = &hal_guard;
ensure_extensions(hal)?;
let instance = hal.shared_instance().raw_instance();
let raw_device = hal.raw_device();
let external_memory_fd = ash::khr::external_memory_fd::Device::new(instance, raw_device);
let drm_format_modifier =
ash::ext::image_drm_format_modifier::Device::new(instance, raw_device);
let mut export_semaphore = vk::ExportSemaphoreCreateInfo::default()
.handle_types(vk::ExternalSemaphoreHandleTypeFlags::SYNC_FD);
let semaphore = match unsafe {
raw_device.create_semaphore(
&vk::SemaphoreCreateInfo::default().push_next(&mut export_semaphore),
None,
)
} {
Ok(semaphore) => semaphore,
Err(error) => return Err(anyhow!("create the acquire semaphore: {error:?}")),
};
let export_device = WgpuExportDevice {
hal,
external_memory_fd,
drm_format_modifier,
};
let (image, memory, handle) = match allocate_export_image(
&export_device,
width,
height,
surface_format,
vk_format,
Tiling::Tiled,
true,
) {
Ok(allocated) => allocated,
Err(error) => {
unsafe { raw_device.destroy_semaphore(semaphore, None) };
return Err(error);
}
};
let size = wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
};
let hal_descriptor = wgpu::hal::TextureDescriptor {
label: Some("gpui_interop_wgpu_shared_buffer"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUses::COLOR_TARGET,
memory_flags: wgpu::hal::MemoryFlags::empty(),
view_formats: Vec::new(),
};
let owner = raw_device.clone();
let drop_callback: wgpu::hal::DropCallback = Box::new(move || {
unsafe {
owner.destroy_image(image, None);
owner.free_memory(memory, None);
}
});
let hal_texture = unsafe {
hal.texture_from_raw(
image,
&hal_descriptor,
Some(drop_callback),
wgpu::hal::vulkan::TextureMemory::External,
)
};
let texture = unsafe {
device.create_texture_from_hal::<wgpu::hal::vulkan::Api>(
hal_texture,
&wgpu::TextureDescriptor {
label: Some("gpui_interop_wgpu_shared_buffer"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
},
wgpu::TextureUses::UNINITIALIZED,
)
};
let view = texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("gpui_interop_wgpu_shared_buffer_view"),
..Default::default()
});
Ok(Self {
texture,
view,
handle,
format,
width,
height,
device: raw_device.clone(),
semaphore,
external_semaphore_fd: ash::khr::external_semaphore_fd::Device::new(
instance, raw_device,
),
})
}
pub fn texture(&self) -> &wgpu::Texture {
&self.texture
}
pub fn view(&self) -> &wgpu::TextureView {
&self.view
}
pub fn format(&self) -> wgpu::TextureFormat {
self.format
}
pub fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn handle(&self) -> DmaBufHandle {
self.handle.clone()
}
pub fn submit(
&mut self,
queue: &wgpu::Queue,
commands: impl IntoIterator<Item = wgpu::CommandBuffer>,
) -> Result<DmaBufHandle> {
let hal_queue = unsafe { queue.as_hal::<wgpu::hal::vulkan::Api>() }
.ok_or_else(|| anyhow!("an exported wgpu surface needs a Vulkan wgpu queue"))?;
hal_queue.add_signal_semaphore(self.semaphore, None);
queue.submit(commands);
let fd = unsafe {
self.external_semaphore_fd.get_semaphore_fd(
&vk::SemaphoreGetFdInfoKHR::default()
.semaphore(self.semaphore)
.handle_type(vk::ExternalSemaphoreHandleTypeFlags::SYNC_FD),
)
}
.map_err(|error| anyhow!("export the acquire fence: {error:?}"))?;
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
let mut handle = self.handle.clone();
handle.acquire_fence = Some(Arc::new(fd));
Ok(handle)
}
}
impl Drop for WgpuSharedBuffer {
fn drop(&mut self) {
unsafe { self.device.destroy_semaphore(self.semaphore, None) };
}
}
struct WgpuExportDevice<'a> {
hal: &'a wgpu::hal::vulkan::Device,
external_memory_fd: ash::khr::external_memory_fd::Device,
drm_format_modifier: ash::ext::image_drm_format_modifier::Device,
}
impl ExportDevice for WgpuExportDevice<'_> {
fn instance(&self) -> &ash::Instance {
self.hal.shared_instance().raw_instance()
}
fn physical_device(&self) -> vk::PhysicalDevice {
self.hal.raw_physical_device()
}
fn device(&self) -> &ash::Device {
self.hal.raw_device()
}
fn external_memory_fd(&self) -> &ash::khr::external_memory_fd::Device {
&self.external_memory_fd
}
fn drm_format_modifier(&self) -> &ash::ext::image_drm_format_modifier::Device {
&self.drm_format_modifier
}
fn renderer_bound(&self) -> bool {
true
}
}
fn formats(format: wgpu::TextureFormat) -> Result<(SurfaceFormatKind, vk::Format)> {
match format {
wgpu::TextureFormat::Rgba8UnormSrgb => {
Ok((SurfaceFormatKind::rgba8(), vk::Format::R8G8B8A8_SRGB))
}
wgpu::TextureFormat::Bgra8UnormSrgb => {
Ok((SurfaceFormatKind::bgra8(), vk::Format::B8G8R8A8_SRGB))
}
_ => bail!(
"an exported wgpu surface must sample as sRGB (Rgba8UnormSrgb or Bgra8UnormSrgb), \
but {format:?} was asked for"
),
}
}
fn ensure_extensions(device: &wgpu::hal::vulkan::Device) -> Result<()> {
let enabled = device.enabled_device_extensions();
for required in [
vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME,
vk::KHR_EXTERNAL_MEMORY_FD_NAME,
vk::EXT_EXTERNAL_MEMORY_DMA_BUF_NAME,
vk::KHR_EXTERNAL_SEMAPHORE_FD_NAME,
] {
if !enabled.contains(&required) {
bail!(
"an exported wgpu surface needs {} on the wgpu device; the renderer's own wgpu \
device has it, a plain request_device does not",
required.to_string_lossy()
);
}
}
Ok(())
}