Skip to main content

impeller_hal_vulkan/
external.rs

1//! Exporting images as dma-bufs.
2//!
3//! This is the first of the two requirements the HAL reserved from day one, and
4//! the point where it stops being a reservation. The DRM presentation path
5//! needs images the display controller can scan out, which means an image whose
6//! memory layout was agreed with the display side and whose memory can be
7//! handed over as a file descriptor.
8//!
9//! Two things have to be true at once, and both are set at creation rather than
10//! discovered later. The image must be created with an explicit format modifier
11//! chosen from what the device offers, because an optimally-tiled image has a
12//! layout only the GPU understands. And its memory must be allocated as
13//! exportable, because a normal allocation cannot be turned into a dma-buf after
14//! the fact.
15
16use crate::device::VulkanContext;
17use crate::resource::{backend_err, vk_format, TextureMemory, VulkanTexture};
18use ash::vk;
19use impeller_hal::{
20    Error, Extent2D, FormatModifierSet, Modifier, PixelFormat, Result, TextureUsage,
21};
22
23/// Ask the device which layouts it can use for a format.
24///
25/// This is the render side's half of format negotiation. Without it the only
26/// safe assumption is linear, which works everywhere and wastes bandwidth
27/// everywhere.
28pub fn query_format_modifiers(
29    instance: &ash::Instance,
30    physical_device: vk::PhysicalDevice,
31    format: PixelFormat,
32) -> Option<FormatModifierSet> {
33    let fourcc = format.fourcc()?;
34
35    // Two-pass query: the first call reports how many entries there are, the
36    // second fills them in.
37    let mut list = vk::DrmFormatModifierPropertiesListEXT::default();
38    let mut properties = vk::FormatProperties2::default().push_next(&mut list);
39    // SAFETY: the chained structure is initialized and outlives the call.
40    unsafe {
41        instance.get_physical_device_format_properties2(
42            physical_device,
43            vk_format(format),
44            &mut properties,
45        );
46    }
47
48    let count = list.drm_format_modifier_count as usize;
49    if count == 0 {
50        return None;
51    }
52    let mut entries = vec![vk::DrmFormatModifierPropertiesEXT::default(); count];
53    list.p_drm_format_modifier_properties = entries.as_mut_ptr();
54    let mut properties = vk::FormatProperties2::default().push_next(&mut list);
55    // SAFETY: the buffer is sized by the count the first call reported.
56    unsafe {
57        instance.get_physical_device_format_properties2(
58            physical_device,
59            vk_format(format),
60            &mut properties,
61        );
62    }
63
64    let modifiers: Vec<Modifier> = entries
65        .iter()
66        .take(count)
67        // Only layouts that can actually be rendered into are useful here; the
68        // device also reports ones it can merely sample from.
69        .filter(|e| {
70            e.drm_format_modifier_tiling_features
71                .contains(vk::FormatFeatureFlags::COLOR_ATTACHMENT)
72        })
73        .map(|e| Modifier(e.drm_format_modifier))
74        .collect();
75
76    if modifiers.is_empty() {
77        return None;
78    }
79    Some(FormatModifierSet::new(fourcc, modifiers))
80}
81
82impl VulkanContext {
83    /// Allocate an image that can be exported as a dma-buf.
84    ///
85    /// `modifiers` are the layouts the other side will accept, in preference
86    /// order; the driver picks one it can also use. Passing the negotiated set
87    /// rather than a single modifier is what lets the driver choose the best
88    /// layout both sides share.
89    pub fn create_exportable_texture(
90        &mut self,
91        extent: Extent2D,
92        format: PixelFormat,
93        modifiers: &[Modifier],
94    ) -> Result<VulkanTexture> {
95        if !self.capabilities().dma_buf.can_allocate_scanout() {
96            return Err(Error::Unsupported(
97                "exporting images requires dma-buf export and explicit modifiers",
98            ));
99        }
100        if modifiers.is_empty() {
101            return Err(Error::Unsupported("no candidate modifiers were offered"));
102        }
103        if !self.capabilities().can_allocate(extent) {
104            return Err(Error::LimitExceeded {
105                what: "texture dimension",
106                requested: extent.width.max(extent.height) as u64,
107                limit: self.capabilities().max_texture_size as u64,
108            });
109        }
110
111        let device = self.raw_device().clone();
112        let raw_modifiers: Vec<u64> = modifiers.iter().map(|m| m.0).collect();
113
114        let mut modifier_list = vk::ImageDrmFormatModifierListCreateInfoEXT::default()
115            .drm_format_modifiers(&raw_modifiers);
116        let mut external = vk::ExternalMemoryImageCreateInfo::default()
117            .handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
118
119        let info = vk::ImageCreateInfo::default()
120            .image_type(vk::ImageType::TYPE_2D)
121            .format(vk_format(format))
122            .extent(vk::Extent3D {
123                width: extent.width,
124                height: extent.height,
125                depth: 1,
126            })
127            .mip_levels(1)
128            .array_layers(1)
129            .samples(vk::SampleCountFlags::TYPE_1)
130            // Not OPTIMAL: the layout is the negotiated modifier, which is the
131            // whole point. An optimally-tiled image has a layout only this GPU
132            // understands and cannot be shared.
133            .tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT)
134            .usage(vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_SRC)
135            .sharing_mode(vk::SharingMode::EXCLUSIVE)
136            .initial_layout(vk::ImageLayout::UNDEFINED)
137            .push_next(&mut modifier_list)
138            .push_next(&mut external);
139
140        // SAFETY: the chained structures outlive the call, and every object is
141        // destroyed on the failure paths below.
142        let image = unsafe { device.create_image(&info, None) }
143            .map_err(|e| backend_err("create_image (exportable)", e))?;
144
145        let requirements = unsafe { device.get_image_memory_requirements(image) };
146        let memory_type = match self.find_memory_type(
147            requirements.memory_type_bits,
148            vk::MemoryPropertyFlags::DEVICE_LOCAL,
149        ) {
150            Some(index) => index,
151            None => {
152                unsafe { device.destroy_image(image, None) };
153                return Err(Error::Backend {
154                    backend: "vulkan",
155                    detail: "no device-local memory type accepts an exportable image".into(),
156                });
157            }
158        };
159
160        // Dedicated rather than suballocated: a dma-buf hands over a whole
161        // allocation, so an image sharing memory with others cannot be exported
162        // without exporting them too.
163        let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(image);
164        let mut export = vk::ExportMemoryAllocateInfo::default()
165            .handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
166        let allocate = vk::MemoryAllocateInfo::default()
167            .allocation_size(requirements.size)
168            .memory_type_index(memory_type)
169            .push_next(&mut dedicated)
170            .push_next(&mut export);
171
172        let memory = match unsafe { device.allocate_memory(&allocate, None) } {
173            Ok(memory) => memory,
174            Err(e) => {
175                unsafe { device.destroy_image(image, None) };
176                return Err(backend_err("allocate_memory (exportable)", e));
177            }
178        };
179
180        if let Err(e) = unsafe { device.bind_image_memory(image, memory, 0) } {
181            unsafe {
182                device.free_memory(memory, None);
183                device.destroy_image(image, None);
184            }
185            return Err(backend_err("bind_image_memory (exportable)", e));
186        }
187
188        Ok(VulkanTexture {
189            image,
190            memory: TextureMemory::Dedicated(memory),
191            extent,
192            format,
193            layout: std::cell::Cell::new(vk::ImageLayout::UNDEFINED),
194            // An exportable image is scanned out, not sampled, and a modifier
195            // negotiated with another device says nothing about a chain.
196            mip_levels: 1,
197            usage: TextureUsage {
198                render_target: true,
199                transfer: true,
200                scanout: true,
201                ..TextureUsage::default()
202            },
203        })
204    }
205
206    /// The layout the driver actually chose for an exportable image.
207    pub fn texture_modifier(&self, texture: &VulkanTexture) -> Result<Modifier> {
208        if !texture.usage.scanout {
209            return Err(Error::Unsupported(
210                "only an exportable image has a negotiated modifier",
211            ));
212        }
213        let loader = ash::ext::image_drm_format_modifier::Device::new(
214            self.raw_instance(),
215            self.raw_device(),
216        );
217        let mut properties = vk::ImageDrmFormatModifierPropertiesEXT::default();
218        // SAFETY: the image was created with DRM_FORMAT_MODIFIER_EXT tiling,
219        // which is what this query requires.
220        unsafe {
221            loader
222                .get_image_drm_format_modifier_properties(texture.image, &mut properties)
223                .map_err(|e| backend_err("get_image_drm_format_modifier_properties", e))?;
224        }
225        Ok(Modifier(properties.drm_format_modifier))
226    }
227
228    /// Export an image's memory as a dma-buf.
229    ///
230    /// The returned descriptor carries everything the display side needs to
231    /// build a framebuffer: the file descriptor, the format, the layout the
232    /// driver chose, and the plane's offset and stride.
233    #[cfg(unix)]
234    pub fn export_texture(
235        &self,
236        texture: &VulkanTexture,
237    ) -> Result<impeller_hal::ExternalImageDesc> {
238        use std::os::fd::FromRawFd;
239
240        if !texture.usage.scanout {
241            return Err(Error::Unsupported(
242                "only an image created for export can be exported",
243            ));
244        }
245        let fourcc = texture.format.fourcc().ok_or(Error::Unsupported(
246            "this format has no scanout representation",
247        ))?;
248        let modifier = self.texture_modifier(texture)?;
249
250        let memory = match &texture.memory {
251            TextureMemory::Dedicated(memory) => *memory,
252            TextureMemory::Pooled(_) => {
253                return Err(Error::Unsupported(
254                    "a suballocated image cannot be exported on its own",
255                ))
256            }
257            // A swapchain image's memory belongs to the presentation engine,
258            // which never offered a handle to it.
259            TextureMemory::Borrowed => {
260                return Err(Error::Unsupported(
261                    "an image this backend did not allocate cannot be exported",
262                ))
263            }
264        };
265
266        let loader =
267            ash::khr::external_memory_fd::Device::new(self.raw_instance(), self.raw_device());
268        let info = vk::MemoryGetFdInfoKHR::default()
269            .memory(memory)
270            .handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
271        // SAFETY: the memory was allocated with a matching export handle type.
272        let raw =
273            unsafe { loader.get_memory_fd(&info) }.map_err(|e| backend_err("get_memory_fd", e))?;
274
275        // Ownership transfers to the caller here: the driver hands over a new
276        // descriptor, and closing it is the importer's job.
277        // SAFETY: the driver returned a fresh, owned descriptor.
278        let fd = unsafe { std::os::fd::OwnedFd::from_raw_fd(raw) };
279
280        let subresource =
281            vk::ImageSubresource::default().aspect_mask(vk::ImageAspectFlags::MEMORY_PLANE_0_EXT);
282        // SAFETY: a modifier image's plane layout is queried this way.
283        let layout = unsafe {
284            self.raw_device()
285                .get_image_subresource_layout(texture.image, subresource)
286        };
287
288        Ok(impeller_hal::ExternalImageDesc {
289            planes: vec![impeller_hal::DmaBufPlane {
290                fd,
291                offset: layout.offset as u32,
292                stride: layout.row_pitch as u32,
293            }],
294            fourcc,
295            modifier,
296        })
297    }
298
299    fn find_memory_type(&self, type_bits: u32, flags: vk::MemoryPropertyFlags) -> Option<u32> {
300        // SAFETY: the physical device outlives this context.
301        let properties = unsafe {
302            self.raw_instance()
303                .get_physical_device_memory_properties(self.raw_physical_device())
304        };
305        (0..properties.memory_type_count).find(|i| {
306            let usable = type_bits & (1 << i) != 0;
307            usable
308                && properties.memory_types[*i as usize]
309                    .property_flags
310                    .contains(flags)
311        })
312    }
313}
314
315/// The formats this device can render into and export, for negotiation.
316pub fn render_formats(
317    instance: &ash::Instance,
318    physical_device: vk::PhysicalDevice,
319) -> Vec<FormatModifierSet> {
320    [
321        PixelFormat::Bgra8Unorm,
322        PixelFormat::Rgba8Unorm,
323        PixelFormat::Rgb10A2Unorm,
324    ]
325    .into_iter()
326    .filter_map(|format| query_format_modifiers(instance, physical_device, format))
327    .collect()
328}