Skip to main content

impeller_hal_vulkan/
device.rs

1//! Instance and device bring-up, and the capability detection everything
2//! above the HAL branches on.
3//!
4//! Capability detection is the load-bearing part. The presentation layer
5//! decides whether it can allocate its own scanout buffers or must import from
6//! GBM, and whether the frame loop can stay explicit or needs a CPU wait
7//! before commit, purely from what is reported here. Getting a bit wrong does
8//! not fail loudly — it silently selects the slower path, or worse, selects
9//! the fast path on a driver that cannot support it.
10
11use crate::render::PipelineCache;
12use crate::validation::{self, ValidationLog, ValidationMessage, VALIDATION_LAYER};
13use ash::vk;
14use impeller_hal::{Capabilities, DmaBufSupport, Error, Result, SampleCounts, SyncSupport};
15use std::collections::HashSet;
16use std::ffi::{c_char, CStr, CString};
17use std::sync::Arc;
18
19/// Which physical device to use.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub enum DevicePreference {
22    /// Prefer discrete, then integrated, then anything else.
23    #[default]
24    Auto,
25    /// Prefer a software rasterizer.
26    ///
27    /// The deterministic reference used for golden comparison: a CPU device
28    /// produces the same pixels everywhere, which is what makes it usable as
29    /// the oracle other backends are diffed against.
30    Software,
31    /// A specific index into the enumeration order.
32    Index(usize),
33}
34
35/// How to create a context.
36#[derive(Debug, Clone, Copy, Default)]
37pub struct ContextConfig {
38    pub device: DevicePreference,
39    /// Request the validation layer and capture what it reports.
40    ///
41    /// Off by default because it costs real time per call. Tests turn it on
42    /// and assert the log is clean, which is what keeps "validation-clean"
43    /// from depending on someone reading stderr.
44    pub validation: bool,
45}
46
47/// Instance extensions a presentation target may need.
48///
49/// `VK_KHR_surface` first because every other one depends on it. The platform
50/// ones follow, and the headless surface last: it creates a surface with no
51/// window behind it, which is what makes the swapchain path — capability
52/// queries, format negotiation, acquire, present, recreation — checkable on a
53/// machine with no display at all.
54///
55/// Creating windows is not this project's business, any more than mode setting
56/// is. A caller brings a surface; these are what let one exist.
57pub fn surface_extensions() -> &'static [&'static str] {
58    &[
59        "VK_KHR_surface",
60        "VK_KHR_wayland_surface",
61        "VK_KHR_xcb_surface",
62        "VK_KHR_xlib_surface",
63        "VK_EXT_headless_surface",
64    ]
65}
66
67/// Semaphores a submission waits on and signals.
68///
69/// Grouped rather than passed loose so that adding a timeline value or a second
70/// stage mask later changes one type rather than every signature between here
71/// and a presentation target.
72///
73/// Vulkan-specific on purpose. The HAL's own synchronization is a fence and an
74/// exported `sync_file`, which is what crosses to a display controller; a
75/// swapchain's semaphores never leave the device and have no counterpart on a
76/// backend that has no swapchain. A Vulkan presentation target using Vulkan
77/// semaphores is not the layering violation that a renderer asking "is this
78/// Vulkan?" would be.
79#[derive(Debug, Clone, Copy, Default)]
80pub struct FrameSync<'a> {
81    /// Waited on before the color attachment is written.
82    pub wait: &'a [vk::Semaphore],
83    /// Signalled when the submission completes.
84    pub signal: &'a [vk::Semaphore],
85    /// Leave the target in the layout a presentation engine reads from.
86    ///
87    /// Folded into the render pass rather than done as a transition afterwards,
88    /// and not as an optimization: a separate transition is a separate
89    /// submission, and nothing orders it after a render that has not been
90    /// waited for. Doing it here makes the ordering the render pass's, which is
91    /// where it can be expressed without a stall.
92    pub presents: bool,
93}
94
95/// Extensions this backend asks for when the device offers them.
96///
97/// Absence is not an error — it selects a different path, and which one is
98/// reported through [`Capabilities`] rather than inferred from the backend.
99mod ext {
100    pub const EXTERNAL_MEMORY_DMA_BUF: &str = "VK_EXT_external_memory_dma_buf";
101    pub const EXTERNAL_MEMORY_FD: &str = "VK_KHR_external_memory_fd";
102    pub const IMAGE_DRM_FORMAT_MODIFIER: &str = "VK_EXT_image_drm_format_modifier";
103    pub const EXTERNAL_FENCE_FD: &str = "VK_KHR_external_fence_fd";
104    pub const EXTERNAL_SEMAPHORE_FD: &str = "VK_KHR_external_semaphore_fd";
105    pub const PHYSICAL_DEVICE_DRM: &str = "VK_EXT_physical_device_drm";
106    /// Promoted to core in 1.2, so on the 1.1 baseline it must be requested
107    /// explicitly as a dependency of the modifier extension.
108    pub const IMAGE_FORMAT_LIST: &str = "VK_KHR_image_format_list";
109    /// The separable blend modes, which no combination of blend factors can
110    /// express. Unrelated to the DRM path; gated the same way because the
111    /// answer to "can this device do it" is a capability either way.
112    pub const BLEND_OPERATION_ADVANCED: &str = "VK_EXT_blend_operation_advanced";
113    /// Presenting into a surface. Absent on a device that can render but not
114    /// display, which is a normal thing for a compute-only or headless card to
115    /// be.
116    pub const SWAPCHAIN: &str = "VK_KHR_swapchain";
117}
118
119/// Extensions each wanted extension depends on, beyond what the 1.1 baseline
120/// already provides as core.
121///
122/// The specification requires every dependency to appear in the enable list
123/// too, and a device created without them is invalid even when it appears to
124/// work. Omitting one here surfaces only under validation, which is why the
125/// resolution below drops any extension whose dependencies are unavailable
126/// rather than enabling it regardless.
127fn required_dependencies(name: &str) -> &'static [&'static str] {
128    match name {
129        ext::IMAGE_DRM_FORMAT_MODIFIER => &[ext::IMAGE_FORMAT_LIST],
130        ext::EXTERNAL_MEMORY_DMA_BUF => &[ext::EXTERNAL_MEMORY_FD],
131        _ => &[],
132    }
133}
134
135/// Whether this device can do every separable blend mode, coherently.
136///
137/// Three conditions, all required, and all reported as one flag because a
138/// caller cannot do anything useful with two of the three. `allOperations`
139/// covers the modes themselves; the coherent feature is what lets overlapping
140/// draws share a pass without a barrier between them; and the attachment limit
141/// has to cover what a pass actually binds. Anything short of all three reports
142/// false, and the modes are then refused rather than approximated.
143fn probe_advanced_blend(
144    instance: &ash::Instance,
145    physical_device: vk::PhysicalDevice,
146    enabled: &HashSet<String>,
147) -> bool {
148    if !enabled.contains(ext::BLEND_OPERATION_ADVANCED) {
149        return false;
150    }
151
152    let mut properties = vk::PhysicalDeviceBlendOperationAdvancedPropertiesEXT::default();
153    let mut properties2 = vk::PhysicalDeviceProperties2::default().push_next(&mut properties);
154    unsafe { instance.get_physical_device_properties2(physical_device, &mut properties2) };
155
156    let mut features = vk::PhysicalDeviceBlendOperationAdvancedFeaturesEXT::default();
157    let mut features2 = vk::PhysicalDeviceFeatures2::default().push_next(&mut features);
158    unsafe { instance.get_physical_device_features2(physical_device, &mut features2) };
159
160    properties.advanced_blend_all_operations == vk::TRUE
161        && features.advanced_blend_coherent_operations == vk::TRUE
162        && properties.advanced_blend_max_color_attachments >= 1
163}
164
165/// Expand the wanted set to include dependencies, dropping anything whose
166/// dependencies this device does not offer.
167fn resolve_extensions(wanted: &[&str], available: &HashSet<String>) -> HashSet<String> {
168    let mut enabled = HashSet::new();
169    for name in wanted {
170        if !available.contains(*name) {
171            continue;
172        }
173        let deps = required_dependencies(name);
174        if deps.iter().any(|d| !available.contains(*d)) {
175            // Enabling this would produce an invalid device. Skipping it means
176            // the capability it backs is reported false, which is the honest
177            // answer.
178            continue;
179        }
180        enabled.insert((*name).to_string());
181        enabled.extend(deps.iter().map(|d| (*d).to_string()));
182    }
183    enabled
184}
185
186/// A Vulkan device, its queue, and what it can do.
187pub struct VulkanContext {
188    // The messenger must be destroyed before the log it points at is dropped,
189    // and before the instance that owns it.
190    debug_messenger: Option<(ash::ext::debug_utils::Instance, vk::DebugUtilsMessengerEXT)>,
191    validation_log: Arc<ValidationLog>,
192    /// Whether the layer was asked to check synchronization as well as calls.
193    ///
194    /// Recorded because a clean validation log means two quite different things
195    /// depending on it, and nothing else distinguishes them: a run with only
196    /// core validation reports every call well formed and says nothing at all
197    /// about whether one access was ordered against the next.
198    sync_validation: bool,
199    // Declaration order is destruction order: the device must outlive nothing
200    // and the instance must outlive the device, so Drop tears down in reverse.
201    // The allocator must release its memory before the device goes away, which
202    // is why it is an Option -- Drop takes it and drops it explicitly first.
203    allocator: Option<gpu_allocator::vulkan::Allocator>,
204    pipelines: PipelineCache,
205    command_pool: vk::CommandPool,
206    device: ash::Device,
207    physical_device: vk::PhysicalDevice,
208    queue: vk::Queue,
209    queue_family_index: u32,
210    capabilities: Capabilities,
211    /// Texture sampling objects, built the first time anything samples.
212    ///
213    /// Lazy rather than eager because a context that never draws an image
214    /// should not allocate a sampler, a layout, and a one-pixel texture to sit
215    /// unused -- and every context created by a test that only fills shapes is
216    /// exactly that.
217    descriptor_layout: Option<vk::DescriptorSetLayout>,
218    /// Built on the first batch, like the sampling layout above it.
219    material_layout: Option<vk::DescriptorSetLayout>,
220    /// Fragment modules a caller registered, by the index they were given.
221    ///
222    /// Kept by the context rather than travelling with a batch because
223    /// registering one leads to a pipeline, and a pipeline outlives every draw
224    /// that uses it. The payload is kept rather than the module: a module is
225    /// consumed by pipeline creation, and the same program may be needed again
226    /// for a different blend, sample count or clip role.
227    runtime_programs: Vec<Vec<u32>>,
228    /// `minUniformBufferOffsetAlignment`, which sets the stride between one
229    /// draw's paint and the next.
230    uniform_alignment: u64,
231    sampler: Option<vk::Sampler>,
232    placeholder: Option<crate::resource::VulkanTexture>,
233    /// Pool, view and set for the placeholder, owned by the context rather than
234    /// by a submission so that a deferred submission may use it: a per-
235    /// submission pool would have to survive until a fence the caller retires
236    /// whenever it chooses.
237    placeholder_binding: Option<(vk::DescriptorPool, vk::ImageView, vk::DescriptorSet)>,
238    enabled_extensions: HashSet<String>,
239    instance: ash::Instance,
240    _entry: ash::Entry,
241}
242
243impl VulkanContext {
244    /// Create a context, selecting a physical device by preference.
245    pub fn new(preference: DevicePreference) -> Result<Self> {
246        Self::with_config(ContextConfig {
247            device: preference,
248            validation: false,
249        })
250    }
251
252    /// Create a context with explicit configuration.
253    pub fn with_config(config: ContextConfig) -> Result<Self> {
254        let preference = config.device;
255        // SAFETY: the loader is dlopened and the returned entry points are
256        // used only for the lifetime of `entry`, which this struct owns.
257        let entry = unsafe { ash::Entry::load() }.map_err(|e| Error::Backend {
258            backend: "vulkan",
259            detail: format!("loader: {e}"),
260        })?;
261
262        let app_name =
263            CString::new("impeller-rs").expect("no interior NUL in an extension or layer name");
264        let app_info = vk::ApplicationInfo::default()
265            .application_name(&app_name)
266            .engine_name(&app_name)
267            // 1.1 is the floor the project targets. Later features are used
268            // when present rather than required.
269            .api_version(vk::make_api_version(0, 1, 1, 0));
270
271        // The layer and the debug-utils extension are only requested when both
272        // are actually present, so asking for validation on a machine without
273        // the SDK degrades to running without it rather than failing to start.
274        let want_validation = config.validation
275            && layer_available(&entry, VALIDATION_LAYER)
276            && instance_extension_available(&entry, "VK_EXT_debug_utils");
277
278        let layer_name =
279            CString::new(VALIDATION_LAYER).expect("no interior NUL in an extension or layer name");
280        let layer_ptrs: Vec<*const c_char> = if want_validation {
281            vec![layer_name.as_ptr()]
282        } else {
283            Vec::new()
284        };
285        // Surface extensions are requested wherever the loader offers them, so
286        // that a caller who later wants to present into a window finds the
287        // instance already able to. Enabling one that goes unused costs
288        // nothing; discovering it was missing costs recreating the instance,
289        // and by then the device and every resource on it exist too.
290        let mut instance_extensions: Vec<CString> = Vec::new();
291        if want_validation {
292            instance_extensions.push(
293                CString::new("VK_EXT_debug_utils")
294                    .expect("no interior NUL in an extension or layer name"),
295            );
296            // Carries the request for synchronization validation below. Its
297            // absence is not fatal: the chained struct is then ignored and what
298            // is lost is the extra checking rather than the instance.
299            if layer_extension_available(&entry, VALIDATION_LAYER, "VK_EXT_validation_features") {
300                instance_extensions.push(
301                    CString::new("VK_EXT_validation_features")
302                        .expect("no interior NUL in an extension or layer name"),
303                );
304            }
305        }
306        for &name in surface_extensions() {
307            if instance_extension_available(&entry, name) {
308                instance_extensions.push(
309                    CString::new(name).expect("no interior NUL in an extension or layer name"),
310                );
311            }
312        }
313        let ext_ptrs: Vec<*const c_char> = instance_extensions.iter().map(|s| s.as_ptr()).collect();
314
315        // Synchronization validation, which the layer does not do by default.
316        // Core validation checks that each call is well formed; this checks
317        // that one access is ordered against the next -- a missing barrier, a
318        // read of an image the GPU has not finished writing. That is the class
319        // of mistake this renderer is most exposed to, because it synchronizes
320        // explicitly rather than through a driver that hides it, and the class
321        // whose symptom is a correct picture on the device it was written on
322        // and a wrong one elsewhere.
323        // Asked of the layer, not of the loader. `VK_EXT_validation_features` is
324        // the validation layer's own extension, so enumerating without naming
325        // the layer does not find it -- which reads as "unavailable" on a
326        // machine where it is installed and working.
327        let sync_validation_available = want_validation
328            && layer_extension_available(&entry, VALIDATION_LAYER, "VK_EXT_validation_features");
329        // Synchronization only. Best-practices checking is worth running by
330        // hand and was -- it is what found the barrier scopes -- but it is not
331        // left on: much of its advice is vendor-specific, so a suite that
332        // failed on it would fail on somebody else's GPU for reasons that are
333        // not defects.
334        let sync_validation = [vk::ValidationFeatureEnableEXT::SYNCHRONIZATION_VALIDATION];
335        let mut validation_features =
336            vk::ValidationFeaturesEXT::default().enabled_validation_features(&sync_validation);
337
338        let mut create_info = vk::InstanceCreateInfo::default()
339            .application_info(&app_info)
340            .enabled_layer_names(&layer_ptrs)
341            .enabled_extension_names(&ext_ptrs);
342        if sync_validation_available {
343            create_info = create_info.push_next(&mut validation_features);
344        }
345        let instance = unsafe { entry.create_instance(&create_info, None) }
346            .map_err(|e| backend_err("create_instance", e))?;
347
348        let validation_log = Arc::new(ValidationLog::default());
349        let debug_messenger = if want_validation {
350            let loader = ash::ext::debug_utils::Instance::new(&entry, &instance);
351            let info = validation::messenger_create_info()
352                .user_data(Arc::as_ptr(&validation_log) as *mut std::ffi::c_void);
353            match unsafe { loader.create_debug_utils_messenger(&info, None) } {
354                Ok(m) => Some((loader, m)),
355                Err(e) => {
356                    unsafe { instance.destroy_instance(None) };
357                    return Err(backend_err("create_debug_utils_messenger", e));
358                }
359            }
360        } else {
361            None
362        };
363
364        let physical_device = match select_physical_device(&instance, preference) {
365            Ok(pd) => pd,
366            Err(e) => {
367                unsafe { instance.destroy_instance(None) };
368                return Err(e);
369            }
370        };
371
372        Self::finish(
373            entry,
374            instance,
375            physical_device,
376            debug_messenger,
377            validation_log,
378            sync_validation_available,
379        )
380    }
381
382    #[allow(clippy::too_many_arguments)]
383    fn finish(
384        entry: ash::Entry,
385        instance: ash::Instance,
386        physical_device: vk::PhysicalDevice,
387        debug_messenger: Option<(ash::ext::debug_utils::Instance, vk::DebugUtilsMessengerEXT)>,
388        validation_log: Arc<ValidationLog>,
389        sync_validation: bool,
390    ) -> Result<Self> {
391        let available = match device_extensions(&instance, physical_device) {
392            Ok(set) => set,
393            Err(e) => {
394                unsafe { instance.destroy_instance(None) };
395                return Err(e);
396            }
397        };
398
399        // Request every extension the device offers that anything here can use.
400        // Enabling one that goes unused costs nothing; discovering later that it
401        // was not enabled costs a device recreation.
402        let wanted = [
403            ext::EXTERNAL_MEMORY_DMA_BUF,
404            ext::EXTERNAL_MEMORY_FD,
405            ext::IMAGE_DRM_FORMAT_MODIFIER,
406            ext::EXTERNAL_FENCE_FD,
407            ext::EXTERNAL_SEMAPHORE_FD,
408            ext::PHYSICAL_DEVICE_DRM,
409            ext::BLEND_OPERATION_ADVANCED,
410            ext::SWAPCHAIN,
411        ];
412        let enabled = resolve_extensions(&wanted, &available);
413        let advanced_blend = probe_advanced_blend(&instance, physical_device, &enabled);
414
415        let queue_family_index = match select_queue_family(&instance, physical_device) {
416            Ok(i) => i,
417            Err(e) => {
418                unsafe { instance.destroy_instance(None) };
419                return Err(e);
420            }
421        };
422
423        let enabled_cstrings: Vec<CString> = enabled
424            .iter()
425            .map(|s| CString::new(s.as_str()).expect("no interior NUL in an extension name"))
426            .collect();
427        let enabled_ptrs: Vec<*const c_char> =
428            enabled_cstrings.iter().map(|s| s.as_ptr()).collect();
429
430        let priorities = [1.0f32];
431        let queue_info = vk::DeviceQueueCreateInfo::default()
432            .queue_family_index(queue_family_index)
433            .queue_priorities(&priorities);
434        let queue_infos = [queue_info];
435        let mut device_info = vk::DeviceCreateInfo::default()
436            .queue_create_infos(&queue_infos)
437            .enabled_extension_names(&enabled_ptrs);
438
439        // Coherent advanced blending has to be asked for at device creation.
440        // Without it, overlapping draws in one pass need an explicit barrier
441        // between them; the pipeline below refuses the modes outright unless
442        // this came back enabled, so there is no path where a batch silently
443        // reads a destination another draw has not finished writing.
444        let mut advanced_features = vk::PhysicalDeviceBlendOperationAdvancedFeaturesEXT::default()
445            .advanced_blend_coherent_operations(true);
446        if advanced_blend {
447            device_info = device_info.push_next(&mut advanced_features);
448        }
449
450        let device = match unsafe { instance.create_device(physical_device, &device_info, None) } {
451            Ok(d) => d,
452            Err(e) => {
453                unsafe { instance.destroy_instance(None) };
454                return Err(backend_err("create_device", e));
455            }
456        };
457
458        let queue = unsafe { device.get_device_queue(queue_family_index, 0) };
459        let capabilities =
460            detect_capabilities(&instance, physical_device, &enabled, advanced_blend);
461
462        let allocator =
463            gpu_allocator::vulkan::Allocator::new(&gpu_allocator::vulkan::AllocatorCreateDesc {
464                instance: instance.clone(),
465                device: device.clone(),
466                physical_device,
467                debug_settings: Default::default(),
468                buffer_device_address: false,
469                allocation_sizes: Default::default(),
470            })
471            .map_err(|e| Error::Backend {
472                backend: "vulkan",
473                detail: format!("allocator: {e}"),
474            })?;
475
476        let pool_info = vk::CommandPoolCreateInfo::default()
477            .queue_family_index(queue_family_index)
478            .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER);
479        let command_pool = unsafe { device.create_command_pool(&pool_info, None) }
480            .map_err(|e| backend_err("create_command_pool", e))?;
481
482        let uniform_alignment = unsafe { instance.get_physical_device_properties(physical_device) }
483            .limits
484            .min_uniform_buffer_offset_alignment;
485
486        Ok(Self {
487            debug_messenger,
488            validation_log,
489            sync_validation,
490            allocator: Some(allocator),
491            pipelines: PipelineCache::default(),
492            command_pool,
493            device,
494            physical_device,
495            queue,
496            queue_family_index,
497            capabilities,
498            descriptor_layout: None,
499            material_layout: None,
500            runtime_programs: Vec::new(),
501            uniform_alignment,
502            sampler: None,
503            placeholder: None,
504            placeholder_binding: None,
505            enabled_extensions: enabled,
506            instance,
507            _entry: entry,
508        })
509    }
510
511    pub fn capabilities(&self) -> &Capabilities {
512        &self.capabilities
513    }
514
515    /// The descriptor set layout every pipeline is built against.
516    /// The layout for the set carrying a draw's paint.
517    ///
518    /// Lazy like the sampling layout beside it, and for the same reason: a
519    /// context that never records a batch should build nothing.
520    /// Register a caller's fragment program and return the name for it.
521    ///
522    /// Nothing is built here. A pipeline needs a render pass, a blend mode and
523    /// a sample count that only a draw knows, so the payload is kept and the
524    /// pipelines appear as the combinations actually used do.
525    pub fn register_program(&mut self, program: &impeller_hal::RuntimeProgram) -> Result<u32> {
526        if program.spirv.is_empty() {
527            return Err(impeller_hal::Error::Unsupported(
528                "a runtime program needs a SPIR-V fragment module for this backend",
529            ));
530        }
531        // The same payload gives the same name back. See the trait's note:
532        // a program leads to pipelines keyed by it, so a caller registering
533        // one per frame would grow the cache without bound.
534        if let Some(existing) = self
535            .runtime_programs
536            .iter()
537            .position(|held| held.as_slice() == program.spirv.as_slice())
538        {
539            return Ok(existing as u32);
540        }
541        self.runtime_programs.push(program.spirv.clone());
542        Ok((self.runtime_programs.len() - 1) as u32)
543    }
544
545    /// The payload a program was registered with.
546    pub(crate) fn runtime_program(&self, id: u32) -> Result<&[u32]> {
547        self.runtime_programs
548            .get(id as usize)
549            .map(Vec::as_slice)
550            .ok_or(impeller_hal::Error::Unsupported(
551                "a draw names a runtime program that was never registered",
552            ))
553    }
554
555    pub(crate) fn material_layout(&mut self) -> Result<vk::DescriptorSetLayout> {
556        if self.material_layout.is_none() {
557            self.material_layout = Some(crate::materials::create_layout(&self.device)?);
558        }
559        Ok(self.material_layout.expect("just created"))
560    }
561
562    /// The smallest offset a dynamic uniform buffer binding may use.
563    ///
564    /// Read once at creation rather than per submission: it is a property of
565    /// the device, and querying it inside the record path would mean a call
566    /// into the loader for a number that cannot change.
567    pub(crate) fn uniform_alignment(&self) -> u64 {
568        self.uniform_alignment
569    }
570
571    pub(crate) fn descriptor_layout(&mut self) -> Result<vk::DescriptorSetLayout> {
572        if self.descriptor_layout.is_none() {
573            self.descriptor_layout = Some(crate::sampling::create_descriptor_layout(&self.device)?);
574        }
575        Ok(self.descriptor_layout.expect("just created"))
576    }
577
578    /// The one sampler every image draw uses.
579    pub(crate) fn sampler(&mut self) -> Result<vk::Sampler> {
580        if self.sampler.is_none() {
581            self.sampler = Some(crate::sampling::create_sampler(&self.device)?);
582        }
583        Ok(self.sampler.expect("just created"))
584    }
585
586    /// A one-pixel opaque white texture, bound where a draw samples nothing.
587    ///
588    /// Left in a shader-readable layout for good: nothing writes to it after
589    /// creation, so it needs no transition at any later point and cannot be
590    /// caught in the wrong layout by a pass that happened to run first.
591    ///
592    /// White rather than transparent so that a bug binding it in place of a
593    /// real texture shows up as a blank shape rather than as nothing at all.
594    pub(crate) fn placeholder_texture(&mut self) -> Result<vk::Image> {
595        if self.placeholder.is_none() {
596            let mut texture = self.create_texture(&impeller_hal::TextureDescriptor::offscreen(
597                impeller_hal::Extent2D::new(1, 1),
598                impeller_hal::PixelFormat::Rgba8Unorm,
599            ))?;
600            self.write_texture(&mut texture, &[255, 255, 255, 255])?;
601
602            let device = self.device.clone();
603            let cmd = self.begin_one_shot()?;
604            crate::resource::transition(
605                &device,
606                cmd,
607                texture.raw_image(),
608                texture.layout(),
609                vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
610            );
611            if let Err(e) = self.submit_one_shot(cmd) {
612                self.destroy_texture(texture);
613                return Err(e);
614            }
615            texture.set_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
616            self.placeholder = Some(texture);
617        }
618        // The image handle rather than the texture, so a caller holding it is
619        // not also holding a borrow of the context it needs to keep using.
620        Ok(self.placeholder.as_ref().expect("just created").raw_image())
621    }
622
623    /// The descriptor set bound where a draw samples nothing.
624    /// The view of the one-pixel white texture, for a binding nothing fills.
625    ///
626    /// A layout declaring four images has to have four written whatever a draw
627    /// reads, so the ones a program does not declare get this. It is the same
628    /// view the placeholder set uses, made on the same first call.
629    pub(crate) fn placeholder_view(&mut self) -> Result<vk::ImageView> {
630        self.placeholder_set()?;
631        Ok(self
632            .placeholder_binding
633            .expect("placeholder_set just made it")
634            .1)
635    }
636
637    pub(crate) fn placeholder_set(&mut self) -> Result<vk::DescriptorSet> {
638        if let Some((_, _, set)) = self.placeholder_binding {
639            return Ok(set);
640        }
641        let image = self.placeholder_texture()?;
642        let layout = self.descriptor_layout()?;
643        let sampler = self.sampler()?;
644        let binding = crate::sampling::create_placeholder_binding(
645            &self.device.clone(),
646            image,
647            layout,
648            sampler,
649        )?;
650        self.placeholder_binding = Some(binding);
651        Ok(binding.2)
652    }
653
654    /// Whether the validation layer is actually installed and reporting.
655    ///
656    /// Requesting validation on a machine without the layer yields false here
657    /// rather than an error, so tests can skip instead of failing on a machine
658    /// that simply lacks the SDK.
659    /// Whether the layer was asked to check synchronization as well as calls.
660    ///
661    /// Core validation checks that each call is well formed. Synchronization
662    /// validation checks that one access is ordered against the next, which is
663    /// a separate feature the layer does not enable by default -- and it is the
664    /// one that matters most to a renderer that synchronizes explicitly, since
665    /// a missing barrier renders correctly on the device it was written on.
666    ///
667    /// A caller asserting a clean log wants to know this: without it the log
668    /// being empty says only that every call was well formed.
669    pub fn sync_validation_active(&self) -> bool {
670        self.sync_validation
671    }
672
673    pub fn validation_active(&self) -> bool {
674        self.debug_messenger.is_some()
675    }
676
677    /// Everything the layer has reported for this context so far.
678    pub fn validation_messages(&self) -> Vec<ValidationMessage> {
679        self.validation_log.messages()
680    }
681
682    /// Whether the layer has reported no errors.
683    pub fn validation_clean(&self) -> bool {
684        self.validation_log.is_clean()
685    }
686
687    /// A handle on the log that outlives this context.
688    ///
689    /// Every other accessor here reads the log *through* the context, which
690    /// cannot see the last thing the layer has to say. A child object that
691    /// outlives its device is reported at `vkDestroyDevice`, which happens
692    /// inside this context's own drop, and by then there is nothing left to
693    /// ask -- so a whole class of fault was structurally invisible to every
694    /// test rather than merely untested. Holding this across the drop is what
695    /// makes teardown assertable.
696    ///
697    /// The messenger is destroyed after the device, deliberately, so the
698    /// callback is still installed while those reports are made.
699    pub fn validation_log(&self) -> Arc<ValidationLog> {
700        Arc::clone(&self.validation_log)
701    }
702
703    pub fn queue_family_index(&self) -> u32 {
704        self.queue_family_index
705    }
706
707    /// Whether a device extension was enabled at device creation.
708    pub fn has_extension(&self, name: &str) -> bool {
709        self.enabled_extensions.contains(name)
710    }
711
712    pub fn raw_device(&self) -> &ash::Device {
713        &self.device
714    }
715
716    /// The loader, for a presentation target that needs an instance extension
717    /// this crate does not itself use.
718    pub fn raw_entry(&self) -> &ash::Entry {
719        &self._entry
720    }
721
722    pub fn raw_instance(&self) -> &ash::Instance {
723        &self.instance
724    }
725
726    pub fn raw_physical_device(&self) -> vk::PhysicalDevice {
727        self.physical_device
728    }
729
730    pub fn raw_queue(&self) -> vk::Queue {
731        self.queue
732    }
733
734    pub(crate) fn pipeline_cache(&self) -> &PipelineCache {
735        &self.pipelines
736    }
737
738    pub(crate) fn pipeline_cache_mut(&mut self) -> &mut PipelineCache {
739        &mut self.pipelines
740    }
741
742    pub(crate) fn allocator_mut(&mut self) -> &mut gpu_allocator::vulkan::Allocator {
743        self.allocator
744            .as_mut()
745            .expect("allocator is taken only in Drop")
746    }
747
748    /// Submit a recorded command buffer with an exportable fence, without
749    /// waiting.
750    pub(crate) fn submit_exportable(
751        &self,
752        cmd: vk::CommandBuffer,
753        framebuffer: vk::Framebuffer,
754        view: vk::ImageView,
755        sync: FrameSync<'_>,
756    ) -> Result<crate::fence::VulkanFence> {
757        unsafe { self.device.end_command_buffer(cmd) }
758            .map_err(|e| backend_err("end_command_buffer", e))?;
759
760        // The fence is plain: it is what decides when this submission's
761        // resources may be released, and exporting would reset it.
762        let info = vk::FenceCreateInfo::default();
763        let fence = unsafe { self.device.create_fence(&info, None) }
764            .map_err(|e| backend_err("create_fence", e))?;
765
766        // The semaphore is the exportable half. Signalled by the same
767        // submission, so its payload represents exactly the same completion,
768        // and resetting it on export costs nothing because nothing else waits
769        // on it.
770        let can_export = self.has_extension(ext::EXTERNAL_SEMAPHORE_FD);
771        let semaphore = if can_export {
772            let mut export_info = vk::ExportSemaphoreCreateInfo::default()
773                .handle_types(vk::ExternalSemaphoreHandleTypeFlags::SYNC_FD);
774            let info = vk::SemaphoreCreateInfo::default().push_next(&mut export_info);
775            match unsafe { self.device.create_semaphore(&info, None) } {
776                Ok(semaphore) => Some(semaphore),
777                Err(e) => {
778                    unsafe { self.device.destroy_fence(fence, None) };
779                    return Err(backend_err("create_semaphore", e));
780                }
781            }
782        } else {
783            None
784        };
785
786        let cmds = [cmd];
787        // The exportable semaphore and whatever the caller asked to signal are
788        // both signalled by this one submission, so their payloads represent
789        // exactly the same completion. A presentation target waits on its own;
790        // a scanout path exports ours.
791        let signals: Vec<vk::Semaphore> = semaphore
792            .into_iter()
793            .chain(sync.signal.iter().copied())
794            .collect();
795        // Color output is the only stage that touches the attachment, so
796        // earlier stages may run before the wait is satisfied. Waiting at the
797        // top of the pipe instead would serialize vertex work behind an image
798        // the vertex stage never reads.
799        let wait_stages: Vec<vk::PipelineStageFlags> =
800            vec![vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT; sync.wait.len()];
801        let submit = vk::SubmitInfo::default()
802            .command_buffers(&cmds)
803            .wait_semaphores(sync.wait)
804            .wait_dst_stage_mask(&wait_stages)
805            .signal_semaphores(&signals);
806        if let Err(e) = unsafe { self.device.queue_submit(self.queue, &[submit], fence) } {
807            unsafe {
808                if let Some(semaphore) = semaphore {
809                    self.device.destroy_semaphore(semaphore, None);
810                }
811                self.device.destroy_fence(fence, None);
812            }
813            return Err(backend_err("queue_submit", e));
814        }
815
816        let loader = can_export
817            .then(|| ash::khr::external_semaphore_fd::Device::new(&self.instance, &self.device));
818        Ok(crate::fence::VulkanFence::new(
819            self.device.clone(),
820            fence,
821            self.command_pool,
822            cmd,
823            framebuffer,
824            view,
825            semaphore,
826            loader,
827        ))
828    }
829
830    /// Allocate and begin a command buffer for a single submission.
831    pub(crate) fn begin_one_shot(&self) -> Result<vk::CommandBuffer> {
832        let info = vk::CommandBufferAllocateInfo::default()
833            .command_pool(self.command_pool)
834            .level(vk::CommandBufferLevel::PRIMARY)
835            .command_buffer_count(1);
836        let buffers = unsafe { self.device.allocate_command_buffers(&info) }
837            .map_err(|e| backend_err("allocate_command_buffers", e))?;
838        let cmd = buffers[0];
839
840        let begin = vk::CommandBufferBeginInfo::default()
841            .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
842        unsafe { self.device.begin_command_buffer(cmd, &begin) }
843            .map_err(|e| backend_err("begin_command_buffer", e))?;
844        Ok(cmd)
845    }
846
847    /// End, submit, and wait for a one-shot command buffer, then free it.
848    ///
849    /// Blocking here is correct for setup and readback and wrong for the frame
850    /// loop, which is fence-driven and never waits on the device. Nothing in
851    /// this path runs per frame.
852    pub(crate) fn submit_one_shot(&self, cmd: vk::CommandBuffer) -> Result<()> {
853        let result = self.submit_and_wait(cmd);
854        // SAFETY: the submission was waited on above, or never made, so the
855        // buffer is no longer in use either way.
856        unsafe { self.device.free_command_buffers(self.command_pool, &[cmd]) };
857        result
858    }
859
860    fn submit_and_wait(&self, cmd: vk::CommandBuffer) -> Result<()> {
861        unsafe { self.device.end_command_buffer(cmd) }
862            .map_err(|e| backend_err("end_command_buffer", e))?;
863
864        let fence_info = vk::FenceCreateInfo::default();
865        let fence = unsafe { self.device.create_fence(&fence_info, None) }
866            .map_err(|e| backend_err("create_fence", e))?;
867
868        let cmds = [cmd];
869        let submit = vk::SubmitInfo::default().command_buffers(&cmds);
870        let submitted = unsafe { self.device.queue_submit(self.queue, &[submit], fence) };
871
872        let result = match submitted {
873            Ok(()) => unsafe {
874                self.device
875                    .wait_for_fences(&[fence], true, u64::MAX)
876                    .map_err(|e| match e {
877                        vk::Result::TIMEOUT => Error::Timeout {
878                            what: "a fence to signal",
879                        },
880                        vk::Result::ERROR_DEVICE_LOST => Error::DeviceLost,
881                        other => backend_err("wait_for_fences", other),
882                    })
883            },
884            Err(e) => Err(backend_err("queue_submit", e)),
885        };
886
887        unsafe { self.device.destroy_fence(fence, None) };
888        result
889    }
890}
891
892impl Drop for VulkanContext {
893    fn drop(&mut self) {
894        // The allocator must free its memory while the device is still alive,
895        // so it is dropped explicitly before anything else is destroyed.
896        self.pipelines.destroy(&self.device);
897        // Before the allocator, since the placeholder holds an allocation.
898        if let Some(texture) = self.placeholder.take() {
899            self.destroy_texture(texture);
900        }
901        // SAFETY: nothing is in flight, and no descriptor set still refers to
902        // either of these -- every other pool is destroyed with the submission
903        // that made it.
904        unsafe {
905            if let Some((pool, view, _)) = self.placeholder_binding.take() {
906                self.device.destroy_descriptor_pool(pool, None);
907                self.device.destroy_image_view(view, None);
908            }
909            if let Some(sampler) = self.sampler.take() {
910                self.device.destroy_sampler(sampler, None);
911            }
912            // Both layouts, and they are separate fields because they are
913            // separate sets: the texture and sampler in one, the material's
914            // uniform block in the other. This one was missing for as long as
915            // the second set has existed, and the leak it left was one layout
916            // per device -- invisible to every test, since a context is
917            // destroyed at the end of a process that is about to exit anyway,
918            // and reported by the validation layer at `vkDestroyDevice` as a
919            // child object outliving its parent.
920            for layout in [self.descriptor_layout.take(), self.material_layout.take()]
921                .into_iter()
922                .flatten()
923            {
924                self.device.destroy_descriptor_set_layout(layout, None);
925            }
926        }
927        drop(self.allocator.take());
928        // SAFETY: every submission this context made was waited on before the
929        // call that made it returned, so nothing is in flight. Objects are
930        // destroyed inside-out: pool, then device, then the instance that
931        // created it.
932        unsafe {
933            self.device.destroy_command_pool(self.command_pool, None);
934            self.device.destroy_device(None);
935            // Before the instance, and before the log the callback points at.
936            if let Some((loader, messenger)) = self.debug_messenger.take() {
937                loader.destroy_debug_utils_messenger(messenger, None);
938            }
939            self.instance.destroy_instance(None);
940        }
941    }
942}
943
944impl std::fmt::Debug for VulkanContext {
945    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
946        f.debug_struct("VulkanContext")
947            .field("device", &self.capabilities.device_name)
948            .field("driver", &self.capabilities.driver_name)
949            .finish_non_exhaustive()
950    }
951}
952
953fn layer_available(entry: &ash::Entry, name: &str) -> bool {
954    let Ok(layers) = (unsafe { entry.enumerate_instance_layer_properties() }) else {
955        return false;
956    };
957    layers.iter().any(|l| {
958        // SAFETY: the loader guarantees a NUL-terminated name.
959        unsafe { CStr::from_ptr(l.layer_name.as_ptr()) }
960            .to_str()
961            .map(|s| s == name)
962            .unwrap_or(false)
963    })
964}
965
966/// Whether a layer provides an instance extension.
967///
968/// Separate from the loader-wide query because a layer's own extensions are
969/// only listed when that layer is named: asking the loader about one and
970/// getting "no" says nothing about whether the layer has it.
971fn layer_extension_available(entry: &ash::Entry, layer: &str, name: &str) -> bool {
972    let Ok(layer_name) = CString::new(layer) else {
973        return false;
974    };
975    let Ok(exts) = (unsafe { entry.enumerate_instance_extension_properties(Some(&layer_name)) })
976    else {
977        return false;
978    };
979    exts.iter().any(|e| {
980        // SAFETY: the loader guarantees a NUL-terminated name.
981        unsafe { CStr::from_ptr(e.extension_name.as_ptr()) }
982            .to_str()
983            .map(|s| s == name)
984            .unwrap_or(false)
985    })
986}
987
988fn instance_extension_available(entry: &ash::Entry, name: &str) -> bool {
989    let Ok(exts) = (unsafe { entry.enumerate_instance_extension_properties(None) }) else {
990        return false;
991    };
992    exts.iter().any(|e| {
993        // SAFETY: the loader guarantees a NUL-terminated name.
994        unsafe { CStr::from_ptr(e.extension_name.as_ptr()) }
995            .to_str()
996            .map(|s| s == name)
997            .unwrap_or(false)
998    })
999}
1000
1001fn backend_err(what: &str, e: vk::Result) -> Error {
1002    Error::Backend {
1003        backend: "vulkan",
1004        detail: format!("{what}: {e:?}"),
1005    }
1006}
1007
1008fn device_extensions(instance: &ash::Instance, pd: vk::PhysicalDevice) -> Result<HashSet<String>> {
1009    let props = unsafe { instance.enumerate_device_extension_properties(pd) }
1010        .map_err(|e| backend_err("enumerate_device_extension_properties", e))?;
1011    Ok(props
1012        .iter()
1013        .filter_map(|p| {
1014            // SAFETY: the driver guarantees a NUL-terminated name.
1015            let raw = unsafe { CStr::from_ptr(p.extension_name.as_ptr()) };
1016            raw.to_str().ok().map(|s| s.to_owned())
1017        })
1018        .collect())
1019}
1020
1021fn select_physical_device(
1022    instance: &ash::Instance,
1023    preference: DevicePreference,
1024) -> Result<vk::PhysicalDevice> {
1025    let devices = unsafe { instance.enumerate_physical_devices() }
1026        .map_err(|e| backend_err("enumerate_physical_devices", e))?;
1027    if devices.is_empty() {
1028        return Err(Error::Backend {
1029            backend: "vulkan",
1030            detail: "no physical devices".into(),
1031        });
1032    }
1033
1034    match preference {
1035        DevicePreference::Index(i) => devices.get(i).copied().ok_or(Error::Backend {
1036            backend: "vulkan",
1037            detail: format!("device index {i} out of range ({} present)", devices.len()),
1038        }),
1039        DevicePreference::Auto => Ok(*devices
1040            .iter()
1041            .max_by_key(|pd| {
1042                let props = unsafe { instance.get_physical_device_properties(**pd) };
1043                match props.device_type {
1044                    vk::PhysicalDeviceType::DISCRETE_GPU => 3,
1045                    vk::PhysicalDeviceType::INTEGRATED_GPU => 2,
1046                    vk::PhysicalDeviceType::VIRTUAL_GPU => 1,
1047                    _ => 0,
1048                }
1049            })
1050            .expect("non-empty")),
1051        DevicePreference::Software => devices
1052            .iter()
1053            .find(|pd| {
1054                let props = unsafe { instance.get_physical_device_properties(**pd) };
1055                props.device_type == vk::PhysicalDeviceType::CPU
1056            })
1057            .copied()
1058            .ok_or(Error::Unsupported("no software rasterizer present")),
1059    }
1060}
1061
1062fn select_queue_family(instance: &ash::Instance, pd: vk::PhysicalDevice) -> Result<u32> {
1063    let families = unsafe { instance.get_physical_device_queue_family_properties(pd) };
1064    families
1065        .iter()
1066        .position(|f| f.queue_flags.contains(vk::QueueFlags::GRAPHICS))
1067        .map(|i| i as u32)
1068        .ok_or(Error::Unsupported("no graphics queue family"))
1069}
1070
1071fn detect_capabilities(
1072    instance: &ash::Instance,
1073    pd: vk::PhysicalDevice,
1074    enabled: &HashSet<String>,
1075    advanced_blend: bool,
1076) -> Capabilities {
1077    // Modifier queries need the extension enabled; without it the only honest
1078    // answer about layouts is that nothing is known.
1079    let modifiers_known = enabled.contains(ext::IMAGE_DRM_FORMAT_MODIFIER);
1080    let props = unsafe { instance.get_physical_device_properties(pd) };
1081    let limits = props.limits;
1082
1083    // Sample counts a target can use are the intersection of what color and
1084    // depth attachments support. Reporting the color count alone would promise
1085    // a configuration that fails once depth is attached.
1086    let sample_mask =
1087        limits.framebuffer_color_sample_counts & limits.framebuffer_depth_sample_counts;
1088
1089    // dma-buf export needs both the fd machinery and the dma-buf handle type;
1090    // either alone is useless. Negotiating a scanout-capable layout
1091    // additionally needs explicit modifiers.
1092    let fd = enabled.contains(ext::EXTERNAL_MEMORY_FD);
1093    let dma_buf = enabled.contains(ext::EXTERNAL_MEMORY_DMA_BUF);
1094    let modifiers = enabled.contains(ext::IMAGE_DRM_FORMAT_MODIFIER);
1095
1096    // Every one of the four matters and the set is not arbitrary: a layer is
1097    // drawn into and composited, so it needs to be an attachment and to blend
1098    // as one; and it is then sampled, with a linear filter, by the pass above
1099    // it. A device offering three of the four cannot carry a floating-point
1100    // layer through the pipeline this renderer has.
1101    let wanted = vk::FormatFeatureFlags::COLOR_ATTACHMENT
1102        | vk::FormatFeatureFlags::COLOR_ATTACHMENT_BLEND
1103        | vk::FormatFeatureFlags::SAMPLED_IMAGE
1104        | vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR;
1105    let float_render_targets = unsafe {
1106        instance
1107            .get_physical_device_format_properties(pd, vk::Format::R16G16B16A16_SFLOAT)
1108            .optimal_tiling_features
1109            .contains(wanted)
1110    };
1111
1112    Capabilities {
1113        advanced_blend,
1114        float_render_targets,
1115        max_texture_size: limits.max_image_dimension2_d,
1116        sample_counts: SampleCounts::from_mask(sample_mask.as_raw()),
1117        dma_buf: DmaBufSupport {
1118            import: fd && dma_buf,
1119            export: fd && dma_buf,
1120            modifiers,
1121        },
1122        sync: SyncSupport {
1123            // Export comes from a semaphore, not a fence: exporting a SYNC_FD
1124            // resets what it came from, and a reset fence can never be waited
1125            // on for retirement. So this reports the extension actually used.
1126            export_sync_file: enabled.contains(ext::EXTERNAL_SEMAPHORE_FD),
1127            // Import is the other direction — taking an out-fence from a
1128            // display commit and waiting on it — which is a fence operation.
1129            import_sync_file: enabled.contains(ext::EXTERNAL_FENCE_FD),
1130        },
1131        render_formats: if modifiers_known {
1132            crate::external::render_formats(instance, pd)
1133        } else {
1134            Vec::new()
1135        },
1136        device_name: device_name(&props),
1137        driver_name: format!("vulkan {}", api_version_string(props.api_version)),
1138        software: props.device_type == vk::PhysicalDeviceType::CPU,
1139    }
1140}
1141
1142fn device_name(props: &vk::PhysicalDeviceProperties) -> String {
1143    // SAFETY: the driver guarantees a NUL-terminated name.
1144    unsafe { CStr::from_ptr(props.device_name.as_ptr()) }
1145        .to_string_lossy()
1146        .into_owned()
1147}
1148
1149fn api_version_string(v: u32) -> String {
1150    format!(
1151        "{}.{}.{}",
1152        vk::api_version_major(v),
1153        vk::api_version_minor(v),
1154        vk::api_version_patch(v)
1155    )
1156}