atagen

monolith

public
Star0
Watch0

Commit 7de49c2aa70b

tarball

atagen <boss@atagen.co>2026-10-16 09:33 UTC

11 files changed+949-105
render-vk: import, render into and allocate dmabufs

Dmabuf images are acquired from the foreign queue family before each use and released after, so other devices and processes see coherent contents without implicit sync. Exporting a fence as a sync_file resets the Vulkan fence, so completion is tracked through the file afterwards. The renderer doubles as a smithay Allocator, which the nested backend uses for its swapchain.
Modified file: Cargo.lock+1 additions
@@ -428,6 +428,7 @@ dependencies = [
428428 "ash",
429429 "glam",
430430 "gpu-allocator",
431+ "rustix",
431432 "smithay",
432433 "thiserror 2.0.21",
433434 ]
Modified file: crates/render-vk/Cargo.toml+1 additions
@@ -11,6 +11,7 @@ workspace = true
1111 [dependencies]
1212 ash = "0.38"
1313 glam = { version = "0.33", default-features = false, features = ["std"] }
14+rustix = { version = "1", features = ["event"] }
1415 gpu-allocator = { version = "0.27", default-features = false, features = ["vulkan"] }
1516 smithay.workspace = true
1617 thiserror = "2"
Modified file: crates/render-vk/src/device.rs+35 additions−5 deletions
@@ -11,18 +11,31 @@ pub(crate) struct Shared {
1111 pub device: Device,
1212 }
1313
14+/// Loaders for the extensions that dmabuf import, export and fence export
15+/// need. Absent when the device lacks any of them.
16+pub(crate) struct DmabufExt {
17+ pub memory_fd: ash::khr::external_memory_fd::Device,
18+ pub fence_fd: ash::khr::external_fence_fd::Device,
19+ pub modifier: ash::ext::image_drm_format_modifier::Device,
20+}
21+
22+const DMABUF_EXTENSIONS: [&std::ffi::CStr; 5] = [
23+ ash::khr::external_memory_fd::NAME,
24+ ash::ext::external_memory_dma_buf::NAME,
25+ ash::ext::image_drm_format_modifier::NAME,
26+ ash::ext::queue_family_foreign::NAME,
27+ ash::khr::external_fence_fd::NAME,
28+];
29+
1430 pub(crate) struct Device {
1531 pub _entry: ash::Entry,
1632 pub instance: ash::Instance,
17- #[expect(
18- dead_code,
19- reason = "dmabuf import queries format properties through it"
20- )]
2133 pub physical: vk::PhysicalDevice,
2234 pub device: ash::Device,
2335 pub queue: Mutex<vk::Queue>,
2436 pub queue_family: u32,
2537 pub push_descriptor: ash::khr::push_descriptor::Device,
38+ pub dmabuf: Option<DmabufExt>,
2639 pub name: String,
2740 }
2841
@@ -61,7 +74,18 @@ impl Shared {
6174 let queues = [vk::DeviceQueueCreateInfo::default()
6275 .queue_family_index(queue_family)
6376 .queue_priorities(&priorities)];
64- let extensions = [ash::khr::push_descriptor::NAME.as_ptr()];
77+ let available: Vec<_> = unsafe { instance.enumerate_device_extension_properties(physical) }
78+ .unwrap_or_default()
79+ .iter()
80+ .filter_map(|e| e.extension_name_as_c_str().ok().map(ToOwned::to_owned))
81+ .collect();
82+ let has_dmabuf = DMABUF_EXTENSIONS
83+ .iter()
84+ .all(|n| available.iter().any(|a| a.as_c_str() == *n));
85+ let mut extensions = vec![ash::khr::push_descriptor::NAME.as_ptr()];
86+ if has_dmabuf {
87+ extensions.extend(DMABUF_EXTENSIONS.iter().map(|n| n.as_ptr()));
88+ }
6589 let mut features13 = vk::PhysicalDeviceVulkan13Features::default()
6690 .dynamic_rendering(true)
6791 .synchronization2(true);
@@ -78,6 +102,11 @@ impl Shared {
78102 };
79103 let queue = unsafe { device.get_device_queue(queue_family, 0) };
80104 let push_descriptor = ash::khr::push_descriptor::Device::new(&instance, &device);
105+ let dmabuf = has_dmabuf.then(|| DmabufExt {
106+ memory_fd: ash::khr::external_memory_fd::Device::new(&instance, &device),
107+ fence_fd: ash::khr::external_fence_fd::Device::new(&instance, &device),
108+ modifier: ash::ext::image_drm_format_modifier::Device::new(&instance, &device),
109+ });
81110 let allocator = Allocator::new(&AllocatorCreateDesc {
82111 instance: instance.clone(),
83112 device: device.clone(),
@@ -94,6 +123,7 @@ impl Shared {
94123 queue: Mutex::new(queue),
95124 queue_family,
96125 push_descriptor,
126+ dmabuf,
97127 name,
98128 };
99129 Ok(Self {
Added file: crates/render-vk/src/dmabuf.rs+392 additions
@@ -0,0 +1,392 @@
1+use crate::Error;
2+use crate::device::Shared;
3+use crate::format::{SUPPORTED, vk_format};
4+use crate::image::{ImageInner, Memory};
5+use ash::vk;
6+use smithay::backend::allocator::dmabuf::{Dmabuf, DmabufFlags};
7+use smithay::backend::allocator::format::FormatSet;
8+use smithay::backend::allocator::{Allocator, Buffer, Format, Fourcc, Modifier};
9+use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd};
10+use std::sync::Arc;
11+
12+/// Which (format, modifier) pairs the device can import for sampling and
13+/// render into, queried once at startup.
14+#[derive(Debug, Default)]
15+pub(crate) struct DmabufCaps {
16+ pub sample: FormatSet,
17+ pub render: FormatSet,
18+}
19+
20+pub(crate) const RENDER_USAGE: vk::ImageUsageFlags = vk::ImageUsageFlags::from_raw(
21+ vk::ImageUsageFlags::COLOR_ATTACHMENT.as_raw()
22+ | vk::ImageUsageFlags::SAMPLED.as_raw()
23+ | vk::ImageUsageFlags::TRANSFER_SRC.as_raw(),
24+);
25+pub(crate) const SAMPLE_USAGE: vk::ImageUsageFlags = vk::ImageUsageFlags::SAMPLED;
26+
27+impl DmabufCaps {
28+ pub fn query(shared: &Shared) -> Self {
29+ if shared.device.dmabuf.is_none() {
30+ return Self::default();
31+ }
32+ let (mut sample, mut render) = (Vec::new(), Vec::new());
33+ for fourcc in SUPPORTED {
34+ let Some(fmt) = vk_format(fourcc) else {
35+ continue;
36+ };
37+ for props in modifier_properties(shared, fmt.format) {
38+ if props.drm_format_modifier_plane_count != 1 {
39+ continue;
40+ }
41+ let features = props.drm_format_modifier_tiling_features;
42+ let format = Format {
43+ code: fourcc,
44+ modifier: Modifier::from(props.drm_format_modifier),
45+ };
46+ let supports =
47+ |usage| external_support(shared, fmt.format, props.drm_format_modifier, usage);
48+ if features.contains(vk::FormatFeatureFlags::SAMPLED_IMAGE)
49+ && supports(SAMPLE_USAGE).importable
50+ {
51+ sample.push(format);
52+ }
53+ let renderable = vk::FormatFeatureFlags::COLOR_ATTACHMENT
54+ | vk::FormatFeatureFlags::COLOR_ATTACHMENT_BLEND;
55+ let s = supports(RENDER_USAGE);
56+ if features.contains(renderable) && s.importable && s.exportable {
57+ render.push(format);
58+ }
59+ }
60+ }
61+ Self {
62+ sample: sample.into_iter().collect(),
63+ render: render.into_iter().collect(),
64+ }
65+ }
66+}
67+
68+fn modifier_properties(
69+ shared: &Shared,
70+ format: vk::Format,
71+) -> Vec<vk::DrmFormatModifierPropertiesEXT> {
72+ let dev = &shared.device;
73+ let mut list = vk::DrmFormatModifierPropertiesListEXT::default();
74+ let mut props = vk::FormatProperties2::default().push_next(&mut list);
75+ unsafe {
76+ dev.instance
77+ .get_physical_device_format_properties2(dev.physical, format, &mut props)
78+ };
79+ let count = list.drm_format_modifier_count as usize;
80+ let mut out = vec![vk::DrmFormatModifierPropertiesEXT::default(); count];
81+ let mut list =
82+ vk::DrmFormatModifierPropertiesListEXT::default().drm_format_modifier_properties(&mut out);
83+ let mut props = vk::FormatProperties2::default().push_next(&mut list);
84+ unsafe {
85+ dev.instance
86+ .get_physical_device_format_properties2(dev.physical, format, &mut props)
87+ };
88+ out
89+}
90+
91+struct External {
92+ importable: bool,
93+ exportable: bool,
94+}
95+
96+fn external_support(
97+ shared: &Shared,
98+ format: vk::Format,
99+ modifier: u64,
100+ usage: vk::ImageUsageFlags,
101+) -> External {
102+ let dev = &shared.device;
103+ let mut modifier_info = vk::PhysicalDeviceImageDrmFormatModifierInfoEXT::default()
104+ .drm_format_modifier(modifier)
105+ .sharing_mode(vk::SharingMode::EXCLUSIVE);
106+ let mut external_info = vk::PhysicalDeviceExternalImageFormatInfo::default()
107+ .handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
108+ let info = vk::PhysicalDeviceImageFormatInfo2::default()
109+ .format(format)
110+ .ty(vk::ImageType::TYPE_2D)
111+ .tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT)
112+ .usage(usage)
113+ .push_next(&mut external_info)
114+ .push_next(&mut modifier_info);
115+ let mut external_props = vk::ExternalImageFormatProperties::default();
116+ let mut props = vk::ImageFormatProperties2::default().push_next(&mut external_props);
117+ let ok = unsafe {
118+ dev.instance
119+ .get_physical_device_image_format_properties2(dev.physical, &info, &mut props)
120+ }
121+ .is_ok();
122+ let features = external_props
123+ .external_memory_properties
124+ .external_memory_features;
125+ External {
126+ importable: ok && features.contains(vk::ExternalMemoryFeatureFlags::IMPORTABLE),
127+ exportable: ok && features.contains(vk::ExternalMemoryFeatureFlags::EXPORTABLE),
128+ }
129+}
130+
131+fn memory_type(shared: &Shared, bits: u32, prefer: vk::MemoryPropertyFlags) -> Option<u32> {
132+ let props = unsafe {
133+ shared
134+ .device
135+ .instance
136+ .get_physical_device_memory_properties(shared.device.physical)
137+ };
138+ let types = &props.memory_types[..props.memory_type_count as usize];
139+ let allowed = |i: usize| bits & (1 << i) != 0;
140+ (0..types.len())
141+ .find(|&i| allowed(i) && types[i].property_flags.contains(prefer))
142+ .or_else(|| (0..types.len()).find(|&i| allowed(i)))
143+ .map(|i| i as u32)
144+}
145+
146+fn image_info<'a>(
147+ fourcc: Fourcc,
148+ size: (u32, u32),
149+ usage: vk::ImageUsageFlags,
150+) -> Result<vk::ImageCreateInfo<'a>, Error> {
151+ let fmt = vk_format(fourcc).ok_or(Error::UnsupportedFormat(fourcc))?;
152+ Ok(vk::ImageCreateInfo::default()
153+ .image_type(vk::ImageType::TYPE_2D)
154+ .format(fmt.format)
155+ .extent(vk::Extent3D {
156+ width: size.0,
157+ height: size.1,
158+ depth: 1,
159+ })
160+ .mip_levels(1)
161+ .array_layers(1)
162+ .samples(vk::SampleCountFlags::TYPE_1)
163+ .tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT)
164+ .usage(usage)
165+ .sharing_mode(vk::SharingMode::EXCLUSIVE)
166+ .initial_layout(vk::ImageLayout::UNDEFINED))
167+}
168+
169+/// Imports a single-plane dmabuf as an image, without copying.
170+pub(crate) fn import(
171+ shared: &Arc<Shared>,
172+ caps: &DmabufCaps,
173+ dmabuf: &Dmabuf,
174+ usage: vk::ImageUsageFlags,
175+) -> Result<Arc<ImageInner>, Error> {
176+ let ext = shared
177+ .device
178+ .dmabuf
179+ .as_ref()
180+ .ok_or(Error::DmabufUnsupported)?;
181+ let format = dmabuf.format();
182+ let set = if usage.contains(vk::ImageUsageFlags::COLOR_ATTACHMENT) {
183+ &caps.render
184+ } else {
185+ &caps.sample
186+ };
187+ if dmabuf.num_planes() != 1 || !set.contains(&format) {
188+ return Err(Error::UnsupportedDmabuf(format));
189+ }
190+ let (Some(fd), Some(offset), Some(stride)) = (
191+ dmabuf.handles().next(),
192+ dmabuf.offsets().next(),
193+ dmabuf.strides().next(),
194+ ) else {
195+ return Err(Error::UnsupportedDmabuf(format));
196+ };
197+ let size = (dmabuf.width(), dmabuf.height());
198+ let d = &shared.device.device;
199+ let layouts = [vk::SubresourceLayout {
200+ offset: u64::from(offset),
201+ size: 0,
202+ row_pitch: u64::from(stride),
203+ array_pitch: 0,
204+ depth_pitch: 0,
205+ }];
206+ let mut explicit = vk::ImageDrmFormatModifierExplicitCreateInfoEXT::default()
207+ .drm_format_modifier(u64::from(format.modifier))
208+ .plane_layouts(&layouts);
209+ let mut external = vk::ExternalMemoryImageCreateInfo::default()
210+ .handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
211+ let info = image_info(format.code, size, usage)?
212+ .push_next(&mut external)
213+ .push_next(&mut explicit);
214+ let image = unsafe { d.create_image(&info, None)? };
215+ let destroy = || unsafe { d.destroy_image(image, None) };
216+ let owned = match fd.try_clone_to_owned() {
217+ Ok(o) => o,
218+ Err(_) => {
219+ destroy();
220+ return Err(Error::UnsupportedDmabuf(format));
221+ }
222+ };
223+ let mut fd_props = vk::MemoryFdPropertiesKHR::default();
224+ if let Err(e) = unsafe {
225+ ext.memory_fd.get_memory_fd_properties(
226+ vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT,
227+ owned.as_raw_fd(),
228+ &mut fd_props,
229+ )
230+ } {
231+ destroy();
232+ return Err(e.into());
233+ }
234+ let requirements = unsafe { d.get_image_memory_requirements(image) };
235+ let Some(type_index) = memory_type(
236+ shared,
237+ requirements.memory_type_bits & fd_props.memory_type_bits,
238+ vk::MemoryPropertyFlags::empty(),
239+ ) else {
240+ destroy();
241+ return Err(Error::UnsupportedDmabuf(format));
242+ };
243+ let raw = owned.into_raw_fd();
244+ let mut import = vk::ImportMemoryFdInfoKHR::default()
245+ .handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT)
246+ .fd(raw);
247+ let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(image);
248+ let alloc = vk::MemoryAllocateInfo::default()
249+ .allocation_size(requirements.size)
250+ .memory_type_index(type_index)
251+ .push_next(&mut import)
252+ .push_next(&mut dedicated);
253+ let memory = match unsafe { d.allocate_memory(&alloc, None) } {
254+ Ok(m) => m,
255+ Err(e) => {
256+ drop(unsafe { OwnedFd::from_raw_fd(raw) });
257+ destroy();
258+ return Err(e.into());
259+ }
260+ };
261+ ImageInner::finish(
262+ shared,
263+ image,
264+ Memory::Imported(memory),
265+ memory,
266+ 0,
267+ format.code,
268+ size.0,
269+ size.1,
270+ true,
271+ )
272+}
273+
274+/// Allocates a dmabuf the device can render into, picking among `modifiers`.
275+pub(crate) fn export(
276+ shared: &Arc<Shared>,
277+ caps: &DmabufCaps,
278+ fourcc: Fourcc,
279+ size: (u32, u32),
280+ modifiers: &[Modifier],
281+) -> Result<Dmabuf, Error> {
282+ let ext = shared
283+ .device
284+ .dmabuf
285+ .as_ref()
286+ .ok_or(Error::DmabufUnsupported)?;
287+ let allowed: Vec<u64> = modifiers
288+ .iter()
289+ .filter(|&&modifier| {
290+ caps.render.contains(&Format {
291+ code: fourcc,
292+ modifier,
293+ })
294+ })
295+ .map(|&m| u64::from(m))
296+ .collect();
297+ if allowed.is_empty() {
298+ return Err(Error::UnsupportedFormat(fourcc));
299+ }
300+ let d = &shared.device.device;
301+ let mut list =
302+ vk::ImageDrmFormatModifierListCreateInfoEXT::default().drm_format_modifiers(&allowed);
303+ let mut external = vk::ExternalMemoryImageCreateInfo::default()
304+ .handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
305+ let info = image_info(fourcc, size, RENDER_USAGE)?
306+ .push_next(&mut external)
307+ .push_next(&mut list);
308+ let image = unsafe { d.create_image(&info, None)? };
309+ let result = (|| -> Result<Dmabuf, Error> {
310+ let requirements = unsafe { d.get_image_memory_requirements(image) };
311+ let type_index = memory_type(
312+ shared,
313+ requirements.memory_type_bits,
314+ vk::MemoryPropertyFlags::DEVICE_LOCAL,
315+ )
316+ .ok_or(Error::UnsupportedFormat(fourcc))?;
317+ let mut export = vk::ExportMemoryAllocateInfo::default()
318+ .handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
319+ let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(image);
320+ let alloc = vk::MemoryAllocateInfo::default()
321+ .allocation_size(requirements.size)
322+ .memory_type_index(type_index)
323+ .push_next(&mut export)
324+ .push_next(&mut dedicated);
325+ let memory = unsafe { d.allocate_memory(&alloc, None)? };
326+ let exported = (|| -> Result<Dmabuf, Error> {
327+ unsafe { d.bind_image_memory(image, memory, 0)? };
328+ let raw = unsafe {
329+ ext.memory_fd.get_memory_fd(
330+ &vk::MemoryGetFdInfoKHR::default()
331+ .memory(memory)
332+ .handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT),
333+ )?
334+ };
335+ let fd = unsafe { OwnedFd::from_raw_fd(raw) };
336+ let mut props = vk::ImageDrmFormatModifierPropertiesEXT::default();
337+ unsafe {
338+ ext.modifier
339+ .get_image_drm_format_modifier_properties(image, &mut props)?
340+ };
341+ let layout = unsafe {
342+ d.get_image_subresource_layout(
343+ image,
344+ vk::ImageSubresource::default()
345+ .aspect_mask(vk::ImageAspectFlags::MEMORY_PLANE_0_EXT),
346+ )
347+ };
348+ let mut builder = Dmabuf::builder(
349+ (size.0 as i32, size.1 as i32),
350+ fourcc,
351+ Modifier::from(props.drm_format_modifier),
352+ DmabufFlags::empty(),
353+ );
354+ builder.add_plane(fd, layout.offset as u32, layout.row_pitch as u32);
355+ builder.build().ok_or(Error::UnsupportedFormat(fourcc))
356+ })();
357+ unsafe { d.free_memory(memory, None) };
358+ exported
359+ })();
360+ unsafe { d.destroy_image(image, None) };
361+ result
362+}
363+
364+/// Allocates render-capable dmabufs, for swapchains.
365+#[derive(Clone, Debug)]
366+pub struct VulkanAllocator {
367+ pub(crate) shared: Arc<Shared>,
368+ pub(crate) caps: Arc<DmabufCaps>,
369+}
370+
371+impl Allocator for VulkanAllocator {
372+ type Buffer = Dmabuf;
373+ type Error = Error;
374+
375+ fn create_buffer(
376+ &mut self,
377+ width: u32,
378+ height: u32,
379+ fourcc: Fourcc,
380+ modifiers: &[Modifier],
381+ ) -> Result<Dmabuf, Error> {
382+ export(&self.shared, &self.caps, fourcc, (width, height), modifiers)
383+ }
384+}
385+
386+impl std::fmt::Debug for Shared {
387+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
388+ f.debug_struct("Shared")
389+ .field("device", &self.device.name)
390+ .finish()
391+ }
392+}
Modified file: crates/render-vk/src/error.rs+4 additions
@@ -17,6 +17,10 @@ pub enum Error {
1717 BufferTooSmall { needed: usize, got: usize },
1818 #[error("texture belongs to a different renderer")]
1919 ForeignTexture,
20+ #[error("the device cannot share memory through dmabufs")]
21+ DmabufUnsupported,
22+ #[error("cannot import dmabuf with format {0:?}")]
23+ UnsupportedDmabuf(smithay::backend::allocator::Format),
2024 #[error("interrupted while waiting for a sync point")]
2125 Interrupted,
2226 #[error("invalid size {0}x{1}")]
Modified file: crates/render-vk/src/frame.rs+1 additions−1 deletions
@@ -159,7 +159,7 @@ impl Frame for VulkanFrame<'_, '_> {
159159 if !Arc::ptr_eq(&texture.0.shared, self.renderer.shared()) {
160160 return Err(Error::ForeignTexture);
161161 }
162- if dst.size.w <= 0 || dst.size.h <= 0 || Arc::ptr_eq(&texture.0, &self.target.0.0) {
162+ if dst.size.w <= 0 || dst.size.h <= 0 || Arc::ptr_eq(&texture.0, &self.target.0) {
163163 return Ok(());
164164 }
165165 let scissors = self.scissors(dst, damage);
Modified file: crates/render-vk/src/image.rs+74 additions−29 deletions
@@ -7,18 +7,28 @@ use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme};
77 use smithay::backend::allocator::Fourcc;
88 use smithay::backend::renderer::{Texture, TextureMapping};
99 use std::fmt;
10+use std::marker::PhantomData;
1011 use std::sync::{Arc, Mutex};
1112
13+pub(crate) enum Memory {
14+ Allocated(Allocation),
15+ /// Memory imported from a dmabuf, owned directly.
16+ Imported(vk::DeviceMemory),
17+}
18+
1219 pub(crate) struct ImageInner {
1320 pub shared: Arc<Shared>,
1421 pub image: vk::Image,
1522 pub view: vk::ImageView,
16- pub allocation: Mutex<Option<Allocation>>,
23+ pub memory: Mutex<Option<Memory>>,
1724 pub fourcc: Fourcc,
1825 pub format: vk::Format,
1926 pub width: u32,
2027 pub height: u32,
2128 pub layout: Mutex<vk::ImageLayout>,
29+ /// Shared with other devices or processes through a dmabuf. Every use
30+ /// acquires the image from the foreign queue family and releases it after.
31+ pub external: bool,
2232 }
2333
2434 impl fmt::Debug for ImageInner {
@@ -27,6 +37,7 @@ impl fmt::Debug for ImageInner {
2737 .field("image", &self.image)
2838 .field("fourcc", &self.fourcc)
2939 .field("size", &(self.width, self.height))
40+ .field("external", &self.external)
3041 .finish()
3142 }
3243 }
@@ -38,18 +49,17 @@ impl Drop for ImageInner {
3849 d.destroy_image_view(self.view, None);
3950 d.destroy_image(self.image, None);
4051 }
41- if let Some(a) = self
42- .allocation
43- .lock()
44- .unwrap_or_else(|e| e.into_inner())
45- .take()
46- {
47- let _ = self
48- .shared
49- .allocator
50- .lock()
51- .unwrap_or_else(|e| e.into_inner())
52- .free(a);
52+ match self.memory.lock().unwrap_or_else(|e| e.into_inner()).take() {
53+ Some(Memory::Allocated(a)) => {
54+ let _ = self
55+ .shared
56+ .allocator
57+ .lock()
58+ .unwrap_or_else(|e| e.into_inner())
59+ .free(a);
60+ }
61+ Some(Memory::Imported(m)) => unsafe { d.free_memory(m, None) },
62+ None => {}
5363 }
5464 }
5565 }
@@ -101,25 +111,57 @@ impl ImageInner {
101111 return Err(e.into());
102112 }
103113 };
104- unsafe { d.bind_image_memory(image, allocation.memory(), allocation.offset())? };
105- let view_info = vk::ImageViewCreateInfo::default()
106- .image(image)
107- .view_type(vk::ImageViewType::TYPE_2D)
108- .format(fmt.format)
109- .components(components(fmt.opaque))
110- .subresource_range(color_range());
111- let view = unsafe { d.create_image_view(&view_info, None)? };
112- Ok(Arc::new(Self {
114+ let memory = Memory::Allocated(allocation);
115+ let (mem, offset) = match &memory {
116+ Memory::Allocated(a) => (unsafe { a.memory() }, a.offset()),
117+ Memory::Imported(m) => (*m, 0),
118+ };
119+ Self::finish(
120+ shared, image, memory, mem, offset, fourcc, width, height, false,
121+ )
122+ }
123+
124+ /// Binds memory, creates the view and wraps everything up. Takes
125+ /// ownership of `image` and `memory`, releasing both on failure.
126+ #[expect(clippy::too_many_arguments)]
127+ pub fn finish(
128+ shared: &Arc<Shared>,
129+ image: vk::Image,
130+ memory: Memory,
131+ mem: vk::DeviceMemory,
132+ offset: u64,
133+ fourcc: Fourcc,
134+ width: u32,
135+ height: u32,
136+ external: bool,
137+ ) -> Result<Arc<Self>, Error> {
138+ let fmt = vk_format(fourcc).ok_or(Error::UnsupportedFormat(fourcc))?;
139+ let d = &shared.device.device;
140+ let mut inner = Self {
113141 shared: shared.clone(),
114142 image,
115- view,
116- allocation: Mutex::new(Some(allocation)),
143+ view: vk::ImageView::null(),
144+ memory: Mutex::new(Some(memory)),
117145 fourcc,
118146 format: fmt.format,
119147 width,
120148 height,
121- layout: Mutex::new(vk::ImageLayout::UNDEFINED),
122- }))
149+ layout: Mutex::new(if external {
150+ vk::ImageLayout::GENERAL
151+ } else {
152+ vk::ImageLayout::UNDEFINED
153+ }),
154+ external,
155+ };
156+ unsafe { d.bind_image_memory(image, mem, offset)? };
157+ let view_info = vk::ImageViewCreateInfo::default()
158+ .image(image)
159+ .view_type(vk::ImageViewType::TYPE_2D)
160+ .format(fmt.format)
161+ .components(components(fmt.opaque))
162+ .subresource_range(color_range());
163+ inner.view = unsafe { d.create_image_view(&view_info, None)? };
164+ Ok(Arc::new(inner))
123165 }
124166
125167 pub fn layout(&self) -> vk::ImageLayout {
@@ -232,9 +274,12 @@ impl VulkanImage {
232274 }
233275 }
234276
235-/// A bound render target.
277+/// A bound render target, borrowing whatever it was bound from.
236278 #[derive(Debug)]
237-pub struct VulkanTarget<'a>(pub(crate) &'a mut VulkanImage);
279+pub struct VulkanTarget<'a>(
280+ pub(crate) Arc<ImageInner>,
281+ pub(crate) PhantomData<&'a mut ()>,
282+);
238283
239284 /// Pixels read back from the GPU.
240285 #[derive(Debug)]
@@ -266,7 +311,7 @@ macro_rules! texture_impl {
266311
267312 texture_impl!(VulkanTexture, |s| s.0);
268313 texture_impl!(VulkanImage, |s| s.0);
269-texture_impl!(VulkanTarget<'_>, |s| s.0.0);
314+texture_impl!(VulkanTarget<'_>, |s| s.0);
270315 texture_impl!(VulkanMapping, |s| s);
271316
272317 impl TextureMapping for VulkanMapping {
Modified file: crates/render-vk/src/lib.rs+2 additions
@@ -4,6 +4,7 @@
44 //! command buffer when it finishes, returning a fence as its sync point.
55
66 mod device;
7+mod dmabuf;
78 mod error;
89 mod format;
910 mod frame;
@@ -13,6 +14,7 @@ mod renderer;
1314 mod sync;
1415 mod transform;
1516
17+pub use dmabuf::VulkanAllocator;
1618 pub use error::Error;
1719 pub use frame::VulkanFrame;
1820 pub use image::{VulkanImage, VulkanMapping, VulkanTarget, VulkanTexture};
Modified file: crates/render-vk/src/renderer.rs+229 additions−52 deletions
@@ -1,5 +1,6 @@
11 use crate::Error;
22 use crate::device::Shared;
3+use crate::dmabuf::{self, DmabufCaps, RENDER_USAGE, SAMPLE_USAGE, VulkanAllocator};
34 use crate::format::{BYTES_PER_PIXEL, SUPPORTED};
45 use crate::frame::{Op, VulkanFrame};
56 use crate::image::{
@@ -10,23 +11,29 @@ use crate::sync::{FenceInner, VkFence};
1011 use ash::vk;
1112 use gpu_allocator::MemoryLocation;
1213 use smithay::backend::allocator::Fourcc;
14+use smithay::backend::allocator::dmabuf::{Dmabuf, WeakDmabuf};
15+use smithay::backend::allocator::format::FormatSet;
1316 use smithay::backend::renderer::sync::SyncPoint;
1417 use smithay::backend::renderer::{
15- Bind, ContextId, DebugFlags, ExportMem, ImportMem, Offscreen, Renderer, RendererSuper, Texture,
16- TextureFilter,
18+ Bind, ContextId, DebugFlags, ExportMem, ImportDma, ImportDmaWl, ImportMem, Offscreen, Renderer,
19+ RendererSuper, Texture, TextureFilter,
1720 };
1821 use smithay::utils::{Buffer, Physical, Rectangle, Size, Transform};
22+use std::collections::HashMap;
1923 use std::fmt;
24+use std::marker::PhantomData;
2025 use std::sync::Arc;
2126
2227 struct Pending {
2328 fence: Arc<FenceInner>,
2429 cmd: vk::CommandBuffer,
25- _keep: Vec<VulkanTexture>,
30+ _keep: Vec<Arc<ImageInner>>,
2631 }
2732
2833 pub struct VulkanRenderer {
2934 pending: Vec<Pending>,
35+ dmabuf_textures: HashMap<WeakDmabuf, VulkanTexture>,
36+ dmabuf_targets: HashMap<WeakDmabuf, Arc<ImageInner>>,
3037 pipelines: Pipelines,
3138 pool: vk::CommandPool,
3239 samplers: [vk::Sampler; 4],
@@ -34,6 +41,7 @@ pub struct VulkanRenderer {
3441 mag_filter: TextureFilter,
3542 debug: DebugFlags,
3643 context: ContextId<VulkanTexture>,
44+ caps: Arc<DmabufCaps>,
3745 shared: Arc<Shared>,
3846 }
3947
@@ -78,6 +86,8 @@ impl VulkanRenderer {
7886 }
7987 Ok(Self {
8088 pending: Vec::new(),
89+ dmabuf_textures: HashMap::new(),
90+ dmabuf_targets: HashMap::new(),
8191 pipelines: Pipelines::new(&shared)?,
8292 pool,
8393 samplers,
@@ -85,6 +95,7 @@ impl VulkanRenderer {
8595 mag_filter: TextureFilter::Linear,
8696 debug: DebugFlags::empty(),
8797 context: ContextId::new(),
98+ caps: Arc::new(DmabufCaps::query(&shared)),
8899 shared,
89100 })
90101 }
@@ -94,6 +105,24 @@ impl VulkanRenderer {
94105 &self.shared.device.name
95106 }
96107
108+ /// Whether dmabufs can be imported, rendered into and allocated.
109+ pub fn supports_dmabuf(&self) -> bool {
110+ self.caps.render.iter().next().is_some()
111+ }
112+
113+ /// Formats and modifiers this renderer can render into as dmabufs.
114+ pub fn render_formats(&self) -> FormatSet {
115+ self.caps.render.clone()
116+ }
117+
118+ /// An allocator for render-capable dmabufs on this device.
119+ pub fn allocator(&self) -> VulkanAllocator {
120+ VulkanAllocator {
121+ shared: self.shared.clone(),
122+ caps: self.caps.clone(),
123+ }
124+ }
125+
97126 pub(crate) fn shared(&self) -> &Arc<Shared> {
98127 &self.shared
99128 }
@@ -117,6 +146,8 @@ impl VulkanRenderer {
117146 unsafe { d.free_command_buffers(pool, &[p.cmd]) };
118147 false
119148 });
149+ self.dmabuf_textures.retain(|k, _| !k.is_gone());
150+ self.dmabuf_targets.retain(|k, _| !k.is_gone());
120151 }
121152
122153 fn begin(&self) -> Result<vk::CommandBuffer, Error> {
@@ -151,17 +182,21 @@ impl VulkanRenderer {
151182 .queue
152183 .lock()
153184 .unwrap_or_else(|e| e.into_inner());
154- unsafe { d.queue_submit2(*queue, &[submit], fence.fence)? };
185+ unsafe { d.queue_submit2(*queue, &[submit], fence.fence())? };
155186 Ok(fence)
156187 }
157188
158189 /// Records, submits and waits for a short command buffer.
159190 fn one_shot(
160191 &mut self,
161- record: impl FnOnce(&ash::Device, vk::CommandBuffer),
192+ record: impl FnOnce(&ash::Device, vk::CommandBuffer, u32),
162193 ) -> Result<(), Error> {
163194 let cmd = self.begin()?;
164- record(&self.shared.device.device, cmd);
195+ record(
196+ &self.shared.device.device,
197+ cmd,
198+ self.shared.device.queue_family,
199+ );
165200 let fence = self.end_and_submit(cmd)?;
166201 let res = fence.wait();
167202 unsafe {
@@ -179,18 +214,15 @@ impl VulkanRenderer {
179214 ops: Vec<Op>,
180215 ) -> Result<SyncPoint, Error> {
181216 self.reap();
182- let image = target.0.0.clone();
183- let mut keep = vec![VulkanTexture(image.clone())];
184- let mut transitions = Vec::new();
217+ let image = target.0.clone();
218+ let mut sampled: Vec<Arc<ImageInner>> = Vec::new();
185219 for op in &ops {
186220 if let Op::Draw {
187221 texture: Some(t), ..
188222 } = op
223+ && !sampled.iter().any(|s| Arc::ptr_eq(s, &t.0))
189224 {
190- if t.0.layout() != vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL {
191- transitions.push(t.clone());
192- }
193- keep.push(t.clone());
225+ sampled.push(t.0.clone());
194226 }
195227 }
196228 let mut pipelines = Vec::with_capacity(ops.len());
@@ -202,25 +234,19 @@ impl VulkanRenderer {
202234 }
203235 let cmd = self.begin()?;
204236 let d = &self.shared.device.device;
237+ let family = self.shared.device.queue_family;
205238 let push = &self.shared.device.push_descriptor;
206239 let (w, h) = (image.width, image.height);
207240 let sampler = self.sampler();
208241 unsafe {
209- for t in &transitions {
210- barrier(
211- d,
212- cmd,
213- t.0.image,
214- t.0.layout(),
215- vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
216- );
217- t.0.set_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
242+ for t in &sampled {
243+ acquire(d, cmd, family, t, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
218244 }
219- barrier(
245+ acquire(
220246 d,
221247 cmd,
222- image.image,
223- image.layout(),
248+ family,
249+ &image,
224250 vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
225251 );
226252 let attachments = [vk::RenderingAttachmentInfo::default()
@@ -318,13 +344,23 @@ impl VulkanRenderer {
318344 }
319345 }
320346 d.cmd_end_rendering(cmd);
347+ release(
348+ d,
349+ cmd,
350+ family,
351+ &image,
352+ vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
353+ );
354+ for t in &sampled {
355+ release(d, cmd, family, t, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
356+ }
321357 }
322- image.set_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
323358 let fence = self.end_and_submit(cmd)?;
359+ sampled.push(image);
324360 self.pending.push(Pending {
325361 fence: fence.clone(),
326362 cmd,
327- _keep: keep,
363+ _keep: sampled,
328364 });
329365 Ok(SyncPoint::from(VkFence(fence)))
330366 }
@@ -336,6 +372,9 @@ impl VulkanRenderer {
336372 region: Rectangle<i32, Buffer>,
337373 flipped: bool,
338374 ) -> Result<(), Error> {
375+ if image.external {
376+ return Err(Error::ForeignTexture);
377+ }
339378 let width = image.width as usize;
340379 let needed = width * image.height as usize * BYTES_PER_PIXEL;
341380 if data.len() < needed {
@@ -357,7 +396,6 @@ impl VulkanRenderer {
357396 let from = if flipped { rows - 1 - y } else { y };
358397 dst[y * row..(y + 1) * row].copy_from_slice(&data[from * row..(from + 1) * row]);
359398 }
360- let old = image.layout();
361399 let copy = vk::BufferImageCopy::default()
362400 .buffer_offset(
363401 ((region.loc.y as usize * width + region.loc.x as usize) * BYTES_PER_PIXEL) as u64,
@@ -378,26 +416,31 @@ impl VulkanRenderer {
378416 height: region.size.h as u32,
379417 depth: 1,
380418 });
381- let (img, buffer) = (image.image, staging.buffer);
382- self.one_shot(|d, cmd| unsafe {
383- barrier(d, cmd, img, old, vk::ImageLayout::TRANSFER_DST_OPTIMAL);
419+ let buffer = staging.buffer;
420+ let target = image.clone();
421+ self.one_shot(|d, cmd, family| unsafe {
422+ acquire(
423+ d,
424+ cmd,
425+ family,
426+ &target,
427+ vk::ImageLayout::TRANSFER_DST_OPTIMAL,
428+ );
384429 d.cmd_copy_buffer_to_image(
385430 cmd,
386431 buffer,
387- img,
432+ target.image,
388433 vk::ImageLayout::TRANSFER_DST_OPTIMAL,
389434 &[copy],
390435 );
391- barrier(
436+ acquire(
392437 d,
393438 cmd,
394- img,
395- vk::ImageLayout::TRANSFER_DST_OPTIMAL,
439+ family,
440+ &target,
396441 vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
397442 );
398- })?;
399- image.set_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
400- Ok(())
443+ })
401444 }
402445
403446 fn read_back(
@@ -421,7 +464,6 @@ impl VulkanRenderer {
421464 MemoryLocation::GpuToCpu,
422465 )?;
423466 if w > 0 && h > 0 {
424- let old = image.layout();
425467 let copy = vk::BufferImageCopy::default()
426468 .image_subresource(
427469 vk::ImageSubresourceLayers::default()
@@ -438,18 +480,31 @@ impl VulkanRenderer {
438480 height: h,
439481 depth: 1,
440482 });
441- let (img, buffer) = (image.image, staging.buffer);
442- self.one_shot(|d, cmd| unsafe {
443- barrier(d, cmd, img, old, vk::ImageLayout::TRANSFER_SRC_OPTIMAL);
483+ let buffer = staging.buffer;
484+ let source = image.clone();
485+ self.one_shot(|d, cmd, family| unsafe {
486+ acquire(
487+ d,
488+ cmd,
489+ family,
490+ &source,
491+ vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
492+ );
444493 d.cmd_copy_image_to_buffer(
445494 cmd,
446- img,
495+ source.image,
447496 vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
448497 buffer,
449498 &[copy],
450499 );
500+ release(
501+ d,
502+ cmd,
503+ family,
504+ &source,
505+ vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
506+ );
451507 })?;
452- image.set_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL);
453508 }
454509 let mut data = staging.bytes()[..w as usize * h as usize * BYTES_PER_PIXEL].to_vec();
455510 if swap {
@@ -498,14 +553,17 @@ fn sampler_filter(bit: usize) -> TextureFilter {
498553 }
499554 }
500555
501-/// A full barrier between all earlier and later work on the image. Coarse,
502-/// but correct for every transition the renderer makes.
556+/// A full barrier between all earlier and later work on the image, optionally
557+/// transferring ownership between queue families. Coarse, but correct for
558+/// every transition the renderer makes.
503559 unsafe fn barrier(
504560 d: &ash::Device,
505561 cmd: vk::CommandBuffer,
506562 image: vk::Image,
507563 old: vk::ImageLayout,
508564 new: vk::ImageLayout,
565+ src_family: u32,
566+ dst_family: u32,
509567 ) {
510568 let b = [vk::ImageMemoryBarrier2::default()
511569 .src_stage_mask(vk::PipelineStageFlags2::ALL_COMMANDS)
@@ -514,6 +572,8 @@ unsafe fn barrier(
514572 .dst_access_mask(vk::AccessFlags2::MEMORY_READ | vk::AccessFlags2::MEMORY_WRITE)
515573 .old_layout(old)
516574 .new_layout(new)
575+ .src_queue_family_index(src_family)
576+ .dst_queue_family_index(dst_family)
517577 .image(image)
518578 .subresource_range(color_range())];
519579 unsafe {
@@ -524,6 +584,70 @@ unsafe fn barrier(
524584 };
525585 }
526586
587+/// Moves an image into `layout` for use on this queue. Dmabuf images come
588+/// from the foreign queue family in the general layout, others continue from
589+/// their tracked layout.
590+unsafe fn acquire(
591+ d: &ash::Device,
592+ cmd: vk::CommandBuffer,
593+ family: u32,
594+ image: &ImageInner,
595+ layout: vk::ImageLayout,
596+) {
597+ if image.external {
598+ unsafe {
599+ barrier(
600+ d,
601+ cmd,
602+ image.image,
603+ vk::ImageLayout::GENERAL,
604+ layout,
605+ vk::QUEUE_FAMILY_FOREIGN_EXT,
606+ family,
607+ )
608+ };
609+ } else {
610+ let old = image.layout();
611+ if old != layout {
612+ unsafe {
613+ barrier(
614+ d,
615+ cmd,
616+ image.image,
617+ old,
618+ layout,
619+ vk::QUEUE_FAMILY_IGNORED,
620+ vk::QUEUE_FAMILY_IGNORED,
621+ )
622+ };
623+ image.set_layout(layout);
624+ }
625+ }
626+}
627+
628+/// Hands a dmabuf image back to the foreign queue family after use.
629+unsafe fn release(
630+ d: &ash::Device,
631+ cmd: vk::CommandBuffer,
632+ family: u32,
633+ image: &ImageInner,
634+ layout: vk::ImageLayout,
635+) {
636+ if image.external {
637+ unsafe {
638+ barrier(
639+ d,
640+ cmd,
641+ image.image,
642+ layout,
643+ vk::ImageLayout::GENERAL,
644+ family,
645+ vk::QUEUE_FAMILY_FOREIGN_EXT,
646+ )
647+ };
648+ }
649+}
650+
527651 /// Whether reading `from` as `to` needs red and blue swapped.
528652 fn needs_swizzle(from: Fourcc, to: Fourcc) -> Result<bool, Error> {
529653 let bgr = |f: Fourcc| match f {
@@ -577,7 +701,7 @@ impl Renderer for VulkanRenderer {
577701 where
578702 'buffer: 'frame,
579703 {
580- self.own(&framebuffer.0.0)?;
704+ self.own(&framebuffer.0)?;
581705 self.reap();
582706 Ok(VulkanFrame::new(
583707 self,
@@ -595,12 +719,38 @@ impl Renderer for VulkanRenderer {
595719 self.reap();
596720 Ok(())
597721 }
722+
723+ fn invalidate_caches(&mut self) -> Result<(), Error> {
724+ self.reap();
725+ self.dmabuf_textures.clear();
726+ self.dmabuf_targets.clear();
727+ Ok(())
728+ }
598729 }
599730
600731 impl Bind<VulkanImage> for VulkanRenderer {
601732 fn bind<'a>(&mut self, target: &'a mut VulkanImage) -> Result<VulkanTarget<'a>, Error> {
602733 self.own(&target.0)?;
603- Ok(VulkanTarget(target))
734+ Ok(VulkanTarget(target.0.clone(), PhantomData))
735+ }
736+}
737+
738+impl Bind<Dmabuf> for VulkanRenderer {
739+ fn bind<'a>(&mut self, target: &'a mut Dmabuf) -> Result<VulkanTarget<'a>, Error> {
740+ let key = target.weak();
741+ let image = match self.dmabuf_targets.get(&key) {
742+ Some(image) => image.clone(),
743+ None => {
744+ let image = dmabuf::import(&self.shared, &self.caps, target, RENDER_USAGE)?;
745+ self.dmabuf_targets.insert(key, image.clone());
746+ image
747+ }
748+ };
749+ Ok(VulkanTarget(image, PhantomData))
750+ }
751+
752+ fn supported_formats(&self) -> Option<FormatSet> {
753+ Some(self.caps.render.clone())
604754 }
605755 }
606756
@@ -664,6 +814,33 @@ impl ImportMem for VulkanRenderer {
664814 }
665815 }
666816
817+impl ImportDma for VulkanRenderer {
818+ fn dmabuf_formats(&self) -> FormatSet {
819+ self.caps.sample.clone()
820+ }
821+
822+ fn import_dmabuf(
823+ &mut self,
824+ buffer: &Dmabuf,
825+ _damage: Option<&[Rectangle<i32, Buffer>]>,
826+ ) -> Result<VulkanTexture, Error> {
827+ let key = buffer.weak();
828+ if let Some(t) = self.dmabuf_textures.get(&key) {
829+ return Ok(t.clone());
830+ }
831+ let texture = VulkanTexture(dmabuf::import(
832+ &self.shared,
833+ &self.caps,
834+ buffer,
835+ SAMPLE_USAGE,
836+ )?);
837+ self.dmabuf_textures.insert(key, texture.clone());
838+ Ok(texture)
839+ }
840+}
841+
842+impl ImportDmaWl for VulkanRenderer {}
843+
667844 impl ExportMem for VulkanRenderer {
668845 type TextureMapping = VulkanMapping;
669846
@@ -673,8 +850,8 @@ impl ExportMem for VulkanRenderer {
673850 region: Rectangle<i32, Buffer>,
674851 format: Fourcc,
675852 ) -> Result<VulkanMapping, Error> {
676- self.own(&target.0.0)?;
677- let image = target.0.0.clone();
853+ self.own(&target.0)?;
854+ let image = target.0.clone();
678855 self.read_back(&image, region, format)
679856 }
680857
@@ -688,8 +865,8 @@ impl ExportMem for VulkanRenderer {
688865 self.read_back(&texture.0, region, format)
689866 }
690867
691- fn can_read_texture(&mut self, _texture: &VulkanTexture) -> Result<bool, Error> {
692- Ok(true)
868+ fn can_read_texture(&mut self, texture: &VulkanTexture) -> Result<bool, Error> {
869+ Ok(texture.0.external || texture.0.layout() != vk::ImageLayout::UNDEFINED)
693870 }
694871
695872 fn map_texture<'a>(&mut self, mapping: &'a VulkanMapping) -> Result<&'a [u8], Error> {
Modified file: crates/render-vk/src/sync.rs+81 additions−18 deletions
@@ -1,38 +1,101 @@
11 use crate::device::Shared;
22 use ash::vk;
3+use rustix::event::{PollFd, PollFlags, poll};
34 use smithay::backend::renderer::sync::{Fence, Interrupted};
45 use std::fmt;
5-use std::sync::Arc;
6+use std::os::fd::{FromRawFd, OwnedFd};
7+use std::sync::{Arc, Mutex};
8+
9+enum State {
10+ Pending,
11+ /// Exporting a sync_file resets the Vulkan fence, so from then on the
12+ /// file is the only record of completion. `None` means it had already
13+ /// signalled when exported.
14+ Exported(Option<OwnedFd>),
15+}
616
717 pub(crate) struct FenceInner {
8- pub shared: Arc<Shared>,
9- pub fence: vk::Fence,
18+ shared: Arc<Shared>,
19+ fence: vk::Fence,
20+ exportable: bool,
21+ state: Mutex<State>,
1022 }
1123
1224 impl FenceInner {
1325 pub fn new(shared: &Arc<Shared>) -> Result<Arc<Self>, vk::Result> {
14- let fence = unsafe {
15- shared
16- .device
17- .device
18- .create_fence(&vk::FenceCreateInfo::default(), None)?
19- };
26+ let exportable = shared.device.dmabuf.is_some();
27+ let mut export = vk::ExportFenceCreateInfo::default()
28+ .handle_types(vk::ExternalFenceHandleTypeFlags::SYNC_FD);
29+ let mut info = vk::FenceCreateInfo::default();
30+ if exportable {
31+ info = info.push_next(&mut export);
32+ }
33+ let fence = unsafe { shared.device.device.create_fence(&info, None)? };
2034 Ok(Arc::new(Self {
2135 shared: shared.clone(),
2236 fence,
37+ exportable,
38+ state: Mutex::new(State::Pending),
2339 }))
2440 }
2541
42+ pub fn fence(&self) -> vk::Fence {
43+ self.fence
44+ }
45+
2646 pub fn signaled(&self) -> bool {
27- unsafe { self.shared.device.device.get_fence_status(self.fence) }.unwrap_or(true)
47+ match &*self.state.lock().unwrap_or_else(|e| e.into_inner()) {
48+ State::Pending => {
49+ unsafe { self.shared.device.device.get_fence_status(self.fence) }.unwrap_or(true)
50+ }
51+ State::Exported(None) => true,
52+ State::Exported(Some(fd)) => readable(fd, Some(&Default::default())),
53+ }
2854 }
2955
3056 pub fn wait(&self) -> Result<(), vk::Result> {
31- unsafe {
32- self.shared
33- .device
34- .device
35- .wait_for_fences(&[self.fence], true, u64::MAX)
57+ match &*self.state.lock().unwrap_or_else(|e| e.into_inner()) {
58+ State::Pending => unsafe {
59+ self.shared
60+ .device
61+ .device
62+ .wait_for_fences(&[self.fence], true, u64::MAX)
63+ },
64+ State::Exported(None) => Ok(()),
65+ State::Exported(Some(fd)) => {
66+ readable(fd, None);
67+ Ok(())
68+ }
69+ }
70+ }
71+
72+ fn export(&self) -> Option<OwnedFd> {
73+ if !self.exportable {
74+ return None;
75+ }
76+ let ext = self.shared.device.dmabuf.as_ref()?;
77+ let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
78+ if let State::Pending = *state {
79+ let info = vk::FenceGetFdInfoKHR::default()
80+ .fence(self.fence)
81+ .handle_type(vk::ExternalFenceHandleTypeFlags::SYNC_FD);
82+ let raw = unsafe { ext.fence_fd.get_fence_fd(&info) }.ok()?;
83+ *state = State::Exported((raw >= 0).then(|| unsafe { OwnedFd::from_raw_fd(raw) }));
84+ }
85+ match &*state {
86+ State::Exported(Some(fd)) => fd.try_clone().ok(),
87+ _ => None,
88+ }
89+ }
90+}
91+
92+fn readable(fd: &OwnedFd, timeout: Option<&rustix::event::Timespec>) -> bool {
93+ let mut fds = [PollFd::new(fd, PollFlags::IN)];
94+ loop {
95+ match poll(&mut fds, timeout) {
96+ Ok(n) => return n > 0,
97+ Err(rustix::io::Errno::INTR) => continue,
98+ Err(_) => return true,
3699 }
37100 }
38101 }
@@ -62,10 +125,10 @@ impl Fence for VkFence {
62125 }
63126
64127 fn is_exportable(&self) -> bool {
65- false
128+ self.0.exportable
66129 }
67130
68- fn export(&self) -> Option<std::os::fd::OwnedFd> {
69- None
131+ fn export(&self) -> Option<OwnedFd> {
132+ self.0.export()
70133 }
71134 }
Added file: crates/render-vk/tests/dmabuf.rs+129 additions
@@ -0,0 +1,129 @@
1+//! Dmabuf allocation, rendering, import and fence export. Skips on devices
2+//! without dmabuf support, such as lavapipe inside the nix sandbox.
3+
4+use monolith_render_vk::VulkanRenderer;
5+use smithay::backend::allocator::dmabuf::Dmabuf;
6+use smithay::backend::allocator::{Allocator, Fourcc, Modifier};
7+use smithay::backend::renderer::{
8+ Bind, Color32F, ExportMem, Frame, ImportDma, Offscreen, Renderer,
9+};
10+use smithay::utils::{Rectangle, Transform};
11+
12+const SIZE: i32 = 32;
13+
14+fn renderer() -> Option<VulkanRenderer> {
15+ let r = VulkanRenderer::new().expect("no usable Vulkan device");
16+ if !r.supports_dmabuf() {
17+ eprintln!("skipping, {} cannot share dmabufs", r.device_name());
18+ return None;
19+ }
20+ Some(r)
21+}
22+
23+fn dmabuf(r: &VulkanRenderer) -> Dmabuf {
24+ let modifiers: Vec<Modifier> = r
25+ .render_formats()
26+ .iter()
27+ .filter(|f| f.code == Fourcc::Argb8888)
28+ .map(|f| f.modifier)
29+ .collect();
30+ r.allocator()
31+ .create_buffer(SIZE as u32, SIZE as u32, Fourcc::Argb8888, &modifiers)
32+ .unwrap()
33+}
34+
35+fn fill(
36+ r: &mut VulkanRenderer,
37+ buf: &mut Dmabuf,
38+ colour: [f32; 4],
39+) -> smithay::backend::renderer::sync::SyncPoint {
40+ let mut target = r.bind(buf).unwrap();
41+ let mut frame = r
42+ .render(&mut target, (SIZE, SIZE).into(), Transform::Normal)
43+ .unwrap();
44+ frame
45+ .clear(
46+ Color32F::from(colour),
47+ &[Rectangle::from_size((SIZE, SIZE).into())],
48+ )
49+ .unwrap();
50+ frame.finish().unwrap()
51+}
52+
53+fn all_rgba(bytes: &[u8], want: [u8; 4]) -> bool {
54+ bytes.as_chunks::<4>().0.iter().all(|p| *p == want)
55+}
56+
57+#[test]
58+fn renders_into_an_allocated_dmabuf_and_reads_it_back() {
59+ let Some(mut r) = renderer() else { return };
60+ let mut buf = dmabuf(&r);
61+ fill(&mut r, &mut buf, [1.0, 0.0, 0.0, 1.0]).wait().unwrap();
62+ let target = r.bind(&mut buf).unwrap();
63+ let mapping = r
64+ .copy_framebuffer(
65+ &target,
66+ Rectangle::from_size((SIZE, SIZE).into()),
67+ Fourcc::Abgr8888,
68+ )
69+ .unwrap();
70+ assert!(all_rgba(r.map_texture(&mapping).unwrap(), [255, 0, 0, 255]));
71+}
72+
73+#[test]
74+fn samples_an_imported_dmabuf() {
75+ let Some(mut r) = renderer() else { return };
76+ let mut buf = dmabuf(&r);
77+ fill(&mut r, &mut buf, [0.0, 0.0, 1.0, 1.0]).wait().unwrap();
78+ let texture = r.import_dmabuf(&buf, None).unwrap();
79+ let mut out = r
80+ .create_buffer(Fourcc::Argb8888, (SIZE, SIZE).into())
81+ .unwrap();
82+ let mut target = r.bind(&mut out).unwrap();
83+ {
84+ let full = Rectangle::from_size((SIZE, SIZE).into());
85+ let mut frame = r
86+ .render(&mut target, (SIZE, SIZE).into(), Transform::Normal)
87+ .unwrap();
88+ frame
89+ .render_texture_from_to(
90+ &texture,
91+ Rectangle::from_size((SIZE as f64, SIZE as f64).into()),
92+ full,
93+ &[full],
94+ &[],
95+ Transform::Normal,
96+ 1.0,
97+ )
98+ .unwrap();
99+ frame.finish().unwrap().wait().unwrap();
100+ }
101+ let mapping = r
102+ .copy_framebuffer(
103+ &target,
104+ Rectangle::from_size((SIZE, SIZE).into()),
105+ Fourcc::Abgr8888,
106+ )
107+ .unwrap();
108+ assert!(all_rgba(r.map_texture(&mapping).unwrap(), [0, 0, 255, 255]));
109+}
110+
111+#[test]
112+fn imports_are_cached_per_buffer() {
113+ let Some(mut r) = renderer() else { return };
114+ let buf = dmabuf(&r);
115+ let a = r.import_dmabuf(&buf, None).unwrap();
116+ let b = r.import_dmabuf(&buf, None).unwrap();
117+ assert_eq!(format!("{a:?}"), format!("{b:?}"));
118+}
119+
120+#[test]
121+fn frame_fences_export_as_sync_files() {
122+ let Some(mut r) = renderer() else { return };
123+ let mut buf = dmabuf(&r);
124+ let sync = fill(&mut r, &mut buf, [0.0, 1.0, 0.0, 1.0]);
125+ assert!(sync.is_exportable());
126+ let _fd = sync.export();
127+ sync.wait().unwrap();
128+ assert!(sync.is_reached());
129+}