Skip to main content

vulkano/pipeline/
cache.rs

1//! Cache the pipeline objects to disk for faster reloads.
2//!
3//! A pipeline cache is an opaque type that allow you to cache your graphics and compute
4//! pipelines on the disk.
5//!
6//! You can create either an empty cache or a cache from some initial data. Whenever you create a
7//! graphics or compute pipeline, you have the possibility to pass a reference to that cache.
8//! The Vulkan implementation will then look in the cache for an existing entry, or add one if it
9//! doesn't exist.
10//!
11//! Once that is done, you can extract the data from the cache and store it. See the documentation
12//! of [`get_data`](PipelineCache::get_data) for example of how to store
13//! the data on the disk, and [`new`](PipelineCache::new) for how to reload
14//! it.
15
16use crate::{
17    device::{Device, DeviceOwned},
18    instance::InstanceOwnedDebugWrapper,
19    macros::{impl_id_counter, vulkan_bitflags},
20    Validated, ValidationError, VulkanError, VulkanObject,
21};
22use smallvec::SmallVec;
23use std::{mem::MaybeUninit, num::NonZeroU64, ptr, sync::Arc};
24
25/// Opaque cache that contains pipeline objects.
26///
27/// See [the documentation of the module](crate::pipeline::cache) for more info.
28#[derive(Debug)]
29pub struct PipelineCache {
30    device: InstanceOwnedDebugWrapper<Arc<Device>>,
31    handle: ash::vk::PipelineCache,
32    id: NonZeroU64,
33
34    flags: PipelineCacheCreateFlags,
35}
36
37impl PipelineCache {
38    /// Builds a new pipeline cache.
39    ///
40    /// # Safety
41    ///
42    /// - The data in `create_info.initial_data` must be valid data that was previously retrieved
43    ///   using [`get_data`](PipelineCache::get_data).
44    ///
45    /// # Examples
46    ///
47    /// This example loads a cache from a file, if it exists.
48    /// See [`get_data`](#method.get_data) for how to store the data in a file.
49    ///
50    /// ```
51    /// # use std::sync::Arc;
52    /// # use vulkano::device::Device;
53    /// use std::{fs::File, io::Read};
54    /// use vulkano::pipeline::cache::{PipelineCache, PipelineCacheCreateInfo};
55    /// # let device: Arc<Device> = return;
56    ///
57    /// let initial_data = {
58    ///     let file = File::open("pipeline_cache.bin");
59    ///     if let Ok(mut file) = file {
60    ///         let mut data = Vec::new();
61    ///         if let Ok(_) = file.read_to_end(&mut data) {
62    ///             data
63    ///         } else {
64    ///             Vec::new()
65    ///         }
66    ///     } else {
67    ///         Vec::new()
68    ///     }
69    /// };
70    ///
71    /// // This is unsafe because there is no way to be sure that the file contains valid data.
72    /// let cache = unsafe {
73    ///     PipelineCache::new(
74    ///         device.clone(),
75    ///         PipelineCacheCreateInfo {
76    ///             initial_data,
77    ///             ..Default::default()
78    ///         },
79    ///     )
80    /// }
81    /// .unwrap();
82    /// ```
83    #[inline]
84    pub unsafe fn new(
85        device: Arc<Device>,
86        create_info: PipelineCacheCreateInfo,
87    ) -> Result<Arc<PipelineCache>, Validated<VulkanError>> {
88        Self::validate_new(&device, &create_info)?;
89
90        Ok(unsafe { Self::new_unchecked(device, create_info) }?)
91    }
92
93    fn validate_new(
94        device: &Device,
95        create_info: &PipelineCacheCreateInfo,
96    ) -> Result<(), Box<ValidationError>> {
97        create_info
98            .validate(device)
99            .map_err(|err| err.add_context("create_info"))?;
100
101        Ok(())
102    }
103
104    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
105    pub unsafe fn new_unchecked(
106        device: Arc<Device>,
107        create_info: PipelineCacheCreateInfo,
108    ) -> Result<Arc<PipelineCache>, VulkanError> {
109        let create_info_vk = create_info.to_vk();
110
111        let handle = {
112            let fns = device.fns();
113            let mut output = MaybeUninit::uninit();
114            unsafe {
115                (fns.v1_0.create_pipeline_cache)(
116                    device.handle(),
117                    &create_info_vk,
118                    ptr::null(),
119                    output.as_mut_ptr(),
120                )
121            }
122            .result()
123            .map_err(VulkanError::from)?;
124            unsafe { output.assume_init() }
125        };
126
127        Ok(unsafe { Self::from_handle(device, handle, create_info) })
128    }
129
130    /// Creates a new `PipelineCache` from a raw object handle.
131    ///
132    /// # Safety
133    ///
134    /// - `handle` must be a valid Vulkan object handle created from `device`.
135    /// - `create_info` must match the info used to create the object.
136    pub unsafe fn from_handle(
137        device: Arc<Device>,
138        handle: ash::vk::PipelineCache,
139        create_info: PipelineCacheCreateInfo,
140    ) -> Arc<PipelineCache> {
141        let PipelineCacheCreateInfo {
142            flags,
143            initial_data: _,
144            _ne: _,
145        } = create_info;
146
147        Arc::new(PipelineCache {
148            device: InstanceOwnedDebugWrapper(device),
149            handle,
150            id: Self::next_id(),
151
152            flags,
153        })
154    }
155
156    /// Returns the flags that the pipeline cache was created with.
157    pub fn flags(&self) -> PipelineCacheCreateFlags {
158        self.flags
159    }
160
161    /// Obtains the data from the cache.
162    ///
163    /// This data can be stored and then reloaded and passed to `PipelineCache::new`.
164    ///
165    /// # Examples
166    ///
167    /// ```
168    /// use std::{fs, fs::File, io::Write};
169    /// # use std::sync::Arc;
170    /// # use vulkano::pipeline::cache::PipelineCache;
171    ///
172    /// # let cache: Arc<PipelineCache> = return;
173    /// // If an error happens (eg. no permission for the file) we simply skip storing the cache.
174    /// if let Ok(data) = cache.get_data() {
175    ///     if let Ok(mut file) = File::create("pipeline_cache.bin.tmp") {
176    ///         if let Ok(_) = file.write_all(&data) {
177    ///             let _ = fs::rename("pipeline_cache.bin.tmp", "pipeline_cache.bin");
178    ///         } else {
179    ///             let _ = fs::remove_file("pipeline_cache.bin.tmp");
180    ///         }
181    ///     }
182    /// }
183    /// ```
184    #[inline]
185    pub fn get_data(&self) -> Result<Vec<u8>, VulkanError> {
186        let fns = self.device.fns();
187
188        let data = loop {
189            let mut count = 0;
190            unsafe {
191                (fns.v1_0.get_pipeline_cache_data)(
192                    self.device.handle(),
193                    self.handle,
194                    &mut count,
195                    ptr::null_mut(),
196                )
197            }
198            .result()
199            .map_err(VulkanError::from)?;
200
201            let mut data: Vec<u8> = Vec::with_capacity(count);
202            let result = unsafe {
203                (fns.v1_0.get_pipeline_cache_data)(
204                    self.device.handle(),
205                    self.handle,
206                    &mut count,
207                    data.as_mut_ptr().cast(),
208                )
209            };
210
211            match result {
212                ash::vk::Result::SUCCESS => {
213                    unsafe { data.set_len(count) };
214                    break data;
215                }
216                ash::vk::Result::INCOMPLETE => (),
217                err => return Err(VulkanError::from(err)),
218            }
219        };
220
221        Ok(data)
222    }
223
224    /// Merges other pipeline caches into this one.
225    ///
226    /// It is `self` that is modified here. The pipeline caches passed as parameter are untouched.
227    // FIXME: vkMergePipelineCaches is not thread safe for the destination cache
228    // TODO: write example
229    pub fn merge<'a>(
230        &self,
231        src_caches: impl IntoIterator<Item = &'a PipelineCache>,
232    ) -> Result<(), Validated<VulkanError>> {
233        let src_caches: SmallVec<[_; 8]> = src_caches.into_iter().collect();
234        self.validate_merge(&src_caches)?;
235
236        Ok(unsafe { self.merge_unchecked(src_caches) }?)
237    }
238
239    fn validate_merge(&self, src_caches: &[&PipelineCache]) -> Result<(), Box<ValidationError>> {
240        for (index, &src_cache) in src_caches.iter().enumerate() {
241            if src_cache == self {
242                return Err(Box::new(ValidationError {
243                    context: format!("src_caches[{}]", index).into(),
244                    problem: "equals `self`".into(),
245                    vuids: &["VUID-vkMergePipelineCaches-dstCache-00770"],
246                    ..Default::default()
247                }));
248            }
249        }
250
251        Ok(())
252    }
253
254    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
255    pub unsafe fn merge_unchecked<'a>(
256        &self,
257        src_caches: impl IntoIterator<Item = &'a PipelineCache>,
258    ) -> Result<(), VulkanError> {
259        let src_caches_vk: SmallVec<[_; 8]> =
260            src_caches.into_iter().map(VulkanObject::handle).collect();
261
262        let fns = self.device.fns();
263        unsafe {
264            (fns.v1_0.merge_pipeline_caches)(
265                self.device.handle(),
266                self.handle,
267                src_caches_vk.len() as u32,
268                src_caches_vk.as_ptr(),
269            )
270        }
271        .result()
272        .map_err(VulkanError::from)?;
273
274        Ok(())
275    }
276}
277
278impl Drop for PipelineCache {
279    #[inline]
280    fn drop(&mut self) {
281        let fns = self.device.fns();
282        unsafe {
283            (fns.v1_0.destroy_pipeline_cache)(self.device.handle(), self.handle, ptr::null())
284        };
285    }
286}
287
288unsafe impl VulkanObject for PipelineCache {
289    type Handle = ash::vk::PipelineCache;
290
291    #[inline]
292    fn handle(&self) -> Self::Handle {
293        self.handle
294    }
295}
296
297unsafe impl DeviceOwned for PipelineCache {
298    #[inline]
299    fn device(&self) -> &Arc<Device> {
300        &self.device
301    }
302}
303
304impl_id_counter!(PipelineCache);
305
306/// Parameters to create a new `PipelineCache`.
307#[derive(Clone, Debug)]
308pub struct PipelineCacheCreateInfo {
309    /// Additional properties of the pipeline cache.
310    ///
311    /// The default value is empty.
312    pub flags: PipelineCacheCreateFlags,
313
314    /// The initial data to provide to the cache.
315    ///
316    /// If this is not empty, then the data must have been previously retrieved by calling
317    /// [`PipelineCache::get_data`].
318    ///
319    /// The data passed to this function will most likely be blindly trusted by the Vulkan
320    /// implementation. Therefore you can easily crash your application or the system by passing
321    /// wrong data.
322    ///
323    /// The default value is empty.
324    pub initial_data: Vec<u8>,
325
326    pub _ne: crate::NonExhaustive,
327}
328
329impl Default for PipelineCacheCreateInfo {
330    #[inline]
331    fn default() -> Self {
332        Self {
333            flags: PipelineCacheCreateFlags::empty(),
334            initial_data: Vec::new(),
335            _ne: crate::NonExhaustive(()),
336        }
337    }
338}
339
340impl PipelineCacheCreateInfo {
341    pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
342        let &Self {
343            flags,
344            initial_data: _,
345            _ne,
346        } = self;
347
348        flags.validate_device(device).map_err(|err| {
349            err.add_context("flags")
350                .set_vuids(&["VUID-VkPipelineCacheCreateInfo-flags-parameter"])
351        })?;
352
353        Ok(())
354    }
355
356    pub(crate) fn to_vk(&self) -> ash::vk::PipelineCacheCreateInfo<'_> {
357        let &Self {
358            flags,
359            ref initial_data,
360            _ne: _,
361        } = self;
362
363        let mut val_vk = ash::vk::PipelineCacheCreateInfo::default().flags(flags.into());
364
365        if !initial_data.is_empty() {
366            val_vk = val_vk.initial_data(initial_data);
367        }
368
369        val_vk
370    }
371}
372
373vulkan_bitflags! {
374    #[non_exhaustive]
375
376    /// Flags specifying additional properties of a pipeline cache.
377    PipelineCacheCreateFlags = PipelineCacheCreateFlags(u32);
378
379    /* TODO: enable
380    // TODO: document
381    EXTERNALLY_SYNCHRONIZED = EXTERNALLY_SYNCHRONIZED
382    RequiresOneOf([
383        RequiresAllOf([APIVersion(V1_3)]),
384        RequiresAllOf([DeviceExtension(ext_pipeline_creation_cache_control)]),
385    ]), */
386}
387
388#[cfg(test)]
389mod tests {
390    use crate::{
391        pipeline::{
392            cache::PipelineCache, compute::ComputePipelineCreateInfo,
393            layout::PipelineDescriptorSetLayoutCreateInfo, ComputePipeline, PipelineLayout,
394            PipelineShaderStageCreateInfo,
395        },
396        shader::{ShaderModule, ShaderModuleCreateInfo},
397    };
398
399    #[test]
400    fn merge_self_forbidden() {
401        let (device, _queue) = gfx_dev_and_queue!();
402        let pipeline = unsafe { PipelineCache::new(device, Default::default()) }.unwrap();
403        match pipeline.merge([pipeline.as_ref()]) {
404            Err(_) => (),
405            Ok(_) => panic!(),
406        }
407    }
408
409    #[test]
410    fn cache_returns_same_data() {
411        let (device, _queue) = gfx_dev_and_queue!();
412
413        let cache = unsafe { PipelineCache::new(device.clone(), Default::default()) }.unwrap();
414
415        let cs = {
416            /*
417             * #version 450
418             * void main() {
419             * }
420             */
421            const MODULE: [u32; 48] = [
422                119734787, 65536, 524298, 6, 0, 131089, 1, 393227, 1, 1280527431, 1685353262,
423                808793134, 0, 196622, 0, 1, 327695, 5, 4, 1852399981, 0, 393232, 4, 17, 1, 1, 1,
424                196611, 2, 450, 262149, 4, 1852399981, 0, 131091, 2, 196641, 3, 2, 327734, 2, 4, 0,
425                3, 131320, 5, 65789, 65592,
426            ];
427            let module =
428                unsafe { ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&MODULE)) }
429                    .unwrap();
430            module.entry_point("main").unwrap()
431        };
432
433        let _pipeline = {
434            let stage = PipelineShaderStageCreateInfo::new(cs);
435            let layout = PipelineLayout::new(
436                device.clone(),
437                PipelineDescriptorSetLayoutCreateInfo::from_stages([&stage])
438                    .into_pipeline_layout_create_info(device.clone())
439                    .unwrap(),
440            )
441            .unwrap();
442            ComputePipeline::new(
443                device,
444                Some(cache.clone()),
445                ComputePipelineCreateInfo::stage_layout(stage, layout),
446            )
447            .unwrap()
448        };
449
450        let cache_data = cache.get_data().unwrap();
451        let second_data = cache.get_data().unwrap();
452
453        assert_eq!(cache_data, second_data);
454    }
455
456    #[test]
457    fn cache_returns_different_data() {
458        let (device, _queue) = gfx_dev_and_queue!();
459
460        let cache = unsafe { PipelineCache::new(device.clone(), Default::default()) }.unwrap();
461
462        let _first_pipeline = {
463            let cs = {
464                /*
465                 * #version 450
466                 * void main() {
467                 * }
468                 */
469                const MODULE: [u32; 48] = [
470                    119734787, 65536, 524298, 6, 0, 131089, 1, 393227, 1, 1280527431, 1685353262,
471                    808793134, 0, 196622, 0, 1, 327695, 5, 4, 1852399981, 0, 393232, 4, 17, 1, 1,
472                    1, 196611, 2, 450, 262149, 4, 1852399981, 0, 131091, 2, 196641, 3, 2, 327734,
473                    2, 4, 0, 3, 131320, 5, 65789, 65592,
474                ];
475                let module = unsafe {
476                    ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&MODULE))
477                }
478                .unwrap();
479                module.entry_point("main").unwrap()
480            };
481
482            let stage = PipelineShaderStageCreateInfo::new(cs);
483            let layout = PipelineLayout::new(
484                device.clone(),
485                PipelineDescriptorSetLayoutCreateInfo::from_stages([&stage])
486                    .into_pipeline_layout_create_info(device.clone())
487                    .unwrap(),
488            )
489            .unwrap();
490            ComputePipeline::new(
491                device.clone(),
492                Some(cache.clone()),
493                ComputePipelineCreateInfo::stage_layout(stage, layout),
494            )
495            .unwrap()
496        };
497
498        let cache_data = cache.get_data().unwrap();
499
500        let _second_pipeline = {
501            let cs = {
502                /*
503                 * #version 450
504                 *
505                 * void main() {
506                 *     uint idx = gl_GlobalInvocationID.x;
507                 * }
508                 */
509                const MODULE: [u32; 108] = [
510                    119734787, 65536, 524298, 16, 0, 131089, 1, 393227, 1, 1280527431, 1685353262,
511                    808793134, 0, 196622, 0, 1, 393231, 5, 4, 1852399981, 0, 11, 393232, 4, 17, 1,
512                    1, 1, 196611, 2, 450, 262149, 4, 1852399981, 0, 196613, 8, 7890025, 524293, 11,
513                    1197436007, 1633841004, 1986939244, 1952539503, 1231974249, 68, 262215, 11, 11,
514                    28, 131091, 2, 196641, 3, 2, 262165, 6, 32, 0, 262176, 7, 7, 6, 262167, 9, 6,
515                    3, 262176, 10, 1, 9, 262203, 10, 11, 1, 262187, 6, 12, 0, 262176, 13, 1, 6,
516                    327734, 2, 4, 0, 3, 131320, 5, 262203, 7, 8, 7, 327745, 13, 14, 11, 12, 262205,
517                    6, 15, 14, 196670, 8, 15, 65789, 65592,
518                ];
519                let module = unsafe {
520                    ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&MODULE))
521                }
522                .unwrap();
523                module.entry_point("main").unwrap()
524            };
525
526            let stage = PipelineShaderStageCreateInfo::new(cs);
527            let layout = PipelineLayout::new(
528                device.clone(),
529                PipelineDescriptorSetLayoutCreateInfo::from_stages([&stage])
530                    .into_pipeline_layout_create_info(device.clone())
531                    .unwrap(),
532            )
533            .unwrap();
534            ComputePipeline::new(
535                device,
536                Some(cache.clone()),
537                ComputePipelineCreateInfo::stage_layout(stage, layout),
538            )
539            .unwrap()
540        };
541
542        let second_data = cache.get_data().unwrap();
543
544        if cache_data.is_empty() {
545            assert_eq!(cache_data, second_data);
546        } else {
547            assert_ne!(cache_data, second_data);
548        }
549    }
550
551    #[test]
552    fn cache_data_does_not_change() {
553        let (device, _queue) = gfx_dev_and_queue!();
554
555        let cache = unsafe { PipelineCache::new(device.clone(), Default::default()) }.unwrap();
556
557        let cs = {
558            /*
559             * #version 450
560             * void main() {
561             * }
562             */
563            const MODULE: [u32; 48] = [
564                119734787, 65536, 524298, 6, 0, 131089, 1, 393227, 1, 1280527431, 1685353262,
565                808793134, 0, 196622, 0, 1, 327695, 5, 4, 1852399981, 0, 393232, 4, 17, 1, 1, 1,
566                196611, 2, 450, 262149, 4, 1852399981, 0, 131091, 2, 196641, 3, 2, 327734, 2, 4, 0,
567                3, 131320, 5, 65789, 65592,
568            ];
569            let module =
570                unsafe { ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&MODULE)) }
571                    .unwrap();
572            module.entry_point("main").unwrap()
573        };
574
575        let _first_pipeline = {
576            let stage = PipelineShaderStageCreateInfo::new(cs.clone());
577            let layout = PipelineLayout::new(
578                device.clone(),
579                PipelineDescriptorSetLayoutCreateInfo::from_stages([&stage])
580                    .into_pipeline_layout_create_info(device.clone())
581                    .unwrap(),
582            )
583            .unwrap();
584            ComputePipeline::new(
585                device.clone(),
586                Some(cache.clone()),
587                ComputePipelineCreateInfo::stage_layout(stage, layout),
588            )
589            .unwrap()
590        };
591
592        let cache_data = cache.get_data().unwrap();
593
594        let _second_pipeline = {
595            let stage = PipelineShaderStageCreateInfo::new(cs);
596            let layout = PipelineLayout::new(
597                device.clone(),
598                PipelineDescriptorSetLayoutCreateInfo::from_stages([&stage])
599                    .into_pipeline_layout_create_info(device.clone())
600                    .unwrap(),
601            )
602            .unwrap();
603            ComputePipeline::new(
604                device,
605                Some(cache.clone()),
606                ComputePipelineCreateInfo::stage_layout(stage, layout),
607            )
608            .unwrap()
609        };
610
611        let second_data = cache.get_data().unwrap();
612
613        assert_eq!(cache_data, second_data);
614    }
615}