1 // Copyright (c) 2016 The vulkano developers
2 // Licensed under the Apache License, Version 2.0
3 // <LICENSE-APACHE or
4 // https://www.apache.org/licenses/LICENSE-2.0> or the MIT
5 // license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
6 // at your option. All files in the project carrying such
7 // notice may not be copied, modified, or distributed except
8 // according to those terms.
9 
10 //! Cache the pipeline objects to disk for faster reloads.
11 //!
12 //! A pipeline cache is an opaque type that allow you to cache your graphics and compute
13 //! pipelines on the disk.
14 //!
15 //! You can create either an empty cache or a cache from some initial data. Whenever you create a
16 //! graphics or compute pipeline, you have the possibility to pass a reference to that cache.
17 //! The Vulkan implementation will then look in the cache for an existing entry, or add one if it
18 //! doesn't exist.
19 //!
20 //! Once that is done, you can extract the data from the cache and store it. See the documentation
21 //! of [`get_data`](crate::pipeline::cache::PipelineCache::get_data) for example of how to store the data
22 //! on the disk, and [`with_data`](crate::pipeline::cache::PipelineCache::with_data) for how to reload it.
23 
24 use crate::{device::Device, OomError, VulkanError, VulkanObject};
25 use std::{mem::MaybeUninit, ptr, sync::Arc};
26 
27 /// Opaque cache that contains pipeline objects.
28 ///
29 /// See [the documentation of the module](crate::pipeline::cache) for more info.
30 #[derive(Debug)]
31 pub struct PipelineCache {
32     device: Arc<Device>,
33     cache: ash::vk::PipelineCache,
34 }
35 
36 impl PipelineCache {
37     /// Builds a new pipeline cache from existing data. The data must have been previously obtained
38     /// with [`get_data`](#method.get_data).
39     ///
40     /// The data passed to this function will most likely be blindly trusted by the Vulkan
41     /// implementation. Therefore you can easily crash your application or the system by passing
42     /// wrong data. Hence why this function is unsafe.
43     ///
44     /// # Examples
45     ///
46     /// This example loads a cache from a file, if it exists.
47     /// See [`get_data`](#method.get_data) for how to store the data in a file.
48     /// TODO: there's a header in the cached data that must be checked ; talk about this
49     ///
50     /// ```
51     /// # use std::sync::Arc;
52     /// # use vulkano::device::Device;
53     /// use std::fs::File;
54     /// use std::io::Read;
55     /// use vulkano::pipeline::cache::PipelineCache;
56     /// # let device: Arc<Device> = return;
57     ///
58     /// let data = {
59     ///     let file = File::open("pipeline_cache.bin");
60     ///     if let Ok(mut file) = file {
61     ///         let mut data = Vec::new();
62     ///         if let Ok(_) = file.read_to_end(&mut data) {
63     ///             Some(data)
64     ///         } else {
65     ///             None
66     ///         }
67     ///     } else {
68     ///         None
69     ///     }
70     /// };
71     ///
72     /// let cache = if let Some(data) = data {
73     ///     // This is unsafe because there is no way to be sure that the file contains valid data.
74     ///     unsafe { PipelineCache::with_data(device.clone(), &data).unwrap() }
75     /// } else {
76     ///     PipelineCache::empty(device.clone()).unwrap()
77     /// };
78     /// ```
79     #[inline]
with_data( device: Arc<Device>, initial_data: &[u8], ) -> Result<Arc<PipelineCache>, OomError>80     pub unsafe fn with_data(
81         device: Arc<Device>,
82         initial_data: &[u8],
83     ) -> Result<Arc<PipelineCache>, OomError> {
84         PipelineCache::new_impl(device, Some(initial_data))
85     }
86 
87     /// Builds a new empty pipeline cache.
88     ///
89     /// # Examples
90     ///
91     /// ```
92     /// # use std::sync::Arc;
93     /// # use vulkano::device::Device;
94     /// use vulkano::pipeline::cache::PipelineCache;
95     /// # let device: Arc<Device> = return;
96     /// let cache = PipelineCache::empty(device.clone()).unwrap();
97     /// ```
98     #[inline]
empty(device: Arc<Device>) -> Result<Arc<PipelineCache>, OomError>99     pub fn empty(device: Arc<Device>) -> Result<Arc<PipelineCache>, OomError> {
100         unsafe { PipelineCache::new_impl(device, None) }
101     }
102 
103     // Actual implementation of the constructor.
new_impl( device: Arc<Device>, initial_data: Option<&[u8]>, ) -> Result<Arc<PipelineCache>, OomError>104     unsafe fn new_impl(
105         device: Arc<Device>,
106         initial_data: Option<&[u8]>,
107     ) -> Result<Arc<PipelineCache>, OomError> {
108         let fns = device.fns();
109 
110         let cache = {
111             let infos = ash::vk::PipelineCacheCreateInfo {
112                 flags: ash::vk::PipelineCacheCreateFlags::empty(),
113                 initial_data_size: initial_data.map(|d| d.len()).unwrap_or(0),
114                 p_initial_data: initial_data
115                     .map(|d| d.as_ptr() as *const _)
116                     .unwrap_or(ptr::null()),
117                 ..Default::default()
118             };
119 
120             let mut output = MaybeUninit::uninit();
121             (fns.v1_0.create_pipeline_cache)(
122                 device.handle(),
123                 &infos,
124                 ptr::null(),
125                 output.as_mut_ptr(),
126             )
127             .result()
128             .map_err(VulkanError::from)?;
129             output.assume_init()
130         };
131 
132         Ok(Arc::new(PipelineCache {
133             device: device.clone(),
134             cache,
135         }))
136     }
137 
138     /// Merges other pipeline caches into this one.
139     ///
140     /// It is `self` that is modified here. The pipeline caches passed as parameter are untouched.
141     ///
142     /// # Panics
143     ///
144     /// - Panics if `self` is included in the list of other pipelines.
145     ///
146     // FIXME: vkMergePipelineCaches is not thread safe for the destination cache
147     // TODO: write example
merge<'a>( &self, pipelines: impl IntoIterator<Item = &'a &'a Arc<PipelineCache>>, ) -> Result<(), OomError>148     pub fn merge<'a>(
149         &self,
150         pipelines: impl IntoIterator<Item = &'a &'a Arc<PipelineCache>>,
151     ) -> Result<(), OomError> {
152         unsafe {
153             let fns = self.device.fns();
154 
155             let pipelines = pipelines
156                 .into_iter()
157                 .map(|pipeline| {
158                     assert!(&***pipeline as *const _ != self as *const _);
159                     pipeline.cache
160                 })
161                 .collect::<Vec<_>>();
162 
163             (fns.v1_0.merge_pipeline_caches)(
164                 self.device.handle(),
165                 self.cache,
166                 pipelines.len() as u32,
167                 pipelines.as_ptr(),
168             )
169             .result()
170             .map_err(VulkanError::from)?;
171 
172             Ok(())
173         }
174     }
175 
176     /// Obtains the data from the cache.
177     ///
178     /// This data can be stored and then reloaded and passed to `PipelineCache::with_data`.
179     ///
180     /// # Examples
181     ///
182     /// This example stores the data of a pipeline cache on the disk.
183     /// See [`with_data`](#method.with_data) for how to reload it.
184     ///
185     /// ```
186     /// use std::fs;
187     /// use std::fs::File;
188     /// use std::io::Write;
189     /// # use std::sync::Arc;
190     /// # use vulkano::pipeline::cache::PipelineCache;
191     ///
192     /// # let cache: Arc<PipelineCache> = return;
193     /// // If an error happens (eg. no permission for the file) we simply skip storing the cache.
194     /// if let Ok(data) = cache.get_data() {
195     ///     if let Ok(mut file) = File::create("pipeline_cache.bin.tmp") {
196     ///         if let Ok(_) = file.write_all(&data) {
197     ///             let _ = fs::rename("pipeline_cache.bin.tmp", "pipeline_cache.bin");
198     ///         } else {
199     ///             let _ = fs::remove_file("pipeline_cache.bin.tmp");
200     ///         }
201     ///     }
202     /// }
203     /// ```
204     #[inline]
get_data(&self) -> Result<Vec<u8>, OomError>205     pub fn get_data(&self) -> Result<Vec<u8>, OomError> {
206         let fns = self.device.fns();
207 
208         let data = unsafe {
209             loop {
210                 let mut count = 0;
211                 (fns.v1_0.get_pipeline_cache_data)(
212                     self.device.handle(),
213                     self.cache,
214                     &mut count,
215                     ptr::null_mut(),
216                 )
217                 .result()
218                 .map_err(VulkanError::from)?;
219 
220                 let mut data: Vec<u8> = Vec::with_capacity(count);
221                 let result = (fns.v1_0.get_pipeline_cache_data)(
222                     self.device.handle(),
223                     self.cache,
224                     &mut count,
225                     data.as_mut_ptr() as *mut _,
226                 );
227 
228                 match result {
229                     ash::vk::Result::SUCCESS => {
230                         data.set_len(count);
231                         break data;
232                     }
233                     ash::vk::Result::INCOMPLETE => (),
234                     err => return Err(VulkanError::from(err).into()),
235                 }
236             }
237         };
238 
239         Ok(data)
240     }
241 }
242 
243 unsafe impl VulkanObject for PipelineCache {
244     type Handle = ash::vk::PipelineCache;
245 
246     #[inline]
handle(&self) -> Self::Handle247     fn handle(&self) -> Self::Handle {
248         self.cache
249     }
250 }
251 
252 impl Drop for PipelineCache {
253     #[inline]
drop(&mut self)254     fn drop(&mut self) {
255         unsafe {
256             let fns = self.device.fns();
257             (fns.v1_0.destroy_pipeline_cache)(self.device.handle(), self.cache, ptr::null());
258         }
259     }
260 }
261 
262 #[cfg(test)]
263 mod tests {
264     use crate::{
265         pipeline::{cache::PipelineCache, ComputePipeline},
266         shader::ShaderModule,
267     };
268 
269     #[test]
merge_self_forbidden()270     fn merge_self_forbidden() {
271         let (device, _queue) = gfx_dev_and_queue!();
272         let pipeline = PipelineCache::empty(device).unwrap();
273         assert_should_panic!({
274             pipeline.merge(&[&pipeline]).unwrap();
275         });
276     }
277 
278     #[test]
cache_returns_same_data()279     fn cache_returns_same_data() {
280         let (device, _queue) = gfx_dev_and_queue!();
281 
282         let cache = PipelineCache::empty(device.clone()).unwrap();
283 
284         let module = unsafe {
285             /*
286              * #version 450
287              * void main() {
288              * }
289              */
290             const MODULE: [u8; 192] = [
291                 3, 2, 35, 7, 0, 0, 1, 0, 10, 0, 8, 0, 6, 0, 0, 0, 0, 0, 0, 0, 17, 0, 2, 0, 1, 0, 0,
292                 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100, 46, 52, 53, 48, 0,
293                 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 15, 0, 5, 0, 5, 0, 0, 0, 4, 0, 0, 0,
294                 109, 97, 105, 110, 0, 0, 0, 0, 16, 0, 6, 0, 4, 0, 0, 0, 17, 0, 0, 0, 1, 0, 0, 0, 1,
295                 0, 0, 0, 1, 0, 0, 0, 3, 0, 3, 0, 2, 0, 0, 0, 194, 1, 0, 0, 5, 0, 4, 0, 4, 0, 0, 0,
296                 109, 97, 105, 110, 0, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0, 33, 0, 3, 0, 3, 0, 0, 0, 2,
297                 0, 0, 0, 54, 0, 5, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 248, 0, 2, 0,
298                 5, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0,
299             ];
300             ShaderModule::from_bytes(device.clone(), &MODULE).unwrap()
301         };
302 
303         let _pipeline = ComputePipeline::new(
304             device,
305             module.entry_point("main").unwrap(),
306             &(),
307             Some(cache.clone()),
308             |_| {},
309         )
310         .unwrap();
311 
312         let cache_data = cache.get_data().unwrap();
313         let second_data = cache.get_data().unwrap();
314 
315         assert_eq!(cache_data, second_data);
316     }
317 
318     #[test]
cache_returns_different_data()319     fn cache_returns_different_data() {
320         let (device, _queue) = gfx_dev_and_queue!();
321 
322         let cache = PipelineCache::empty(device.clone()).unwrap();
323 
324         let first_module = unsafe {
325             /*
326              * #version 450
327              * void main() {
328              * }
329              */
330             const MODULE: [u8; 192] = [
331                 3, 2, 35, 7, 0, 0, 1, 0, 10, 0, 8, 0, 6, 0, 0, 0, 0, 0, 0, 0, 17, 0, 2, 0, 1, 0, 0,
332                 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100, 46, 52, 53, 48, 0,
333                 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 15, 0, 5, 0, 5, 0, 0, 0, 4, 0, 0, 0,
334                 109, 97, 105, 110, 0, 0, 0, 0, 16, 0, 6, 0, 4, 0, 0, 0, 17, 0, 0, 0, 1, 0, 0, 0, 1,
335                 0, 0, 0, 1, 0, 0, 0, 3, 0, 3, 0, 2, 0, 0, 0, 194, 1, 0, 0, 5, 0, 4, 0, 4, 0, 0, 0,
336                 109, 97, 105, 110, 0, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0, 33, 0, 3, 0, 3, 0, 0, 0, 2,
337                 0, 0, 0, 54, 0, 5, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 248, 0, 2, 0,
338                 5, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0,
339             ];
340             ShaderModule::from_bytes(device.clone(), &MODULE).unwrap()
341         };
342 
343         let second_module = unsafe {
344             /*
345              * #version 450
346              *
347              * void main() {
348              *     uint idx = gl_GlobalInvocationID.x;
349              * }
350              */
351             const SECOND_MODULE: [u8; 432] = [
352                 3, 2, 35, 7, 0, 0, 1, 0, 10, 0, 8, 0, 16, 0, 0, 0, 0, 0, 0, 0, 17, 0, 2, 0, 1, 0,
353                 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100, 46, 52, 53, 48,
354                 0, 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 15, 0, 6, 0, 5, 0, 0, 0, 4, 0, 0,
355                 0, 109, 97, 105, 110, 0, 0, 0, 0, 11, 0, 0, 0, 16, 0, 6, 0, 4, 0, 0, 0, 17, 0, 0,
356                 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 3, 0, 2, 0, 0, 0, 194, 1, 0, 0, 5, 0,
357                 4, 0, 4, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 5, 0, 3, 0, 8, 0, 0, 0, 105, 100,
358                 120, 0, 5, 0, 8, 0, 11, 0, 0, 0, 103, 108, 95, 71, 108, 111, 98, 97, 108, 73, 110,
359                 118, 111, 99, 97, 116, 105, 111, 110, 73, 68, 0, 0, 0, 71, 0, 4, 0, 11, 0, 0, 0,
360                 11, 0, 0, 0, 28, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0, 33, 0, 3, 0, 3, 0, 0, 0, 2, 0,
361                 0, 0, 21, 0, 4, 0, 6, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 32, 0, 4, 0, 7, 0, 0, 0, 7,
362                 0, 0, 0, 6, 0, 0, 0, 23, 0, 4, 0, 9, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 32, 0, 4, 0,
363                 10, 0, 0, 0, 1, 0, 0, 0, 9, 0, 0, 0, 59, 0, 4, 0, 10, 0, 0, 0, 11, 0, 0, 0, 1, 0,
364                 0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 32, 0, 4, 0, 13, 0, 0, 0,
365                 1, 0, 0, 0, 6, 0, 0, 0, 54, 0, 5, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0,
366                 0, 248, 0, 2, 0, 5, 0, 0, 0, 59, 0, 4, 0, 7, 0, 0, 0, 8, 0, 0, 0, 7, 0, 0, 0, 65,
367                 0, 5, 0, 13, 0, 0, 0, 14, 0, 0, 0, 11, 0, 0, 0, 12, 0, 0, 0, 61, 0, 4, 0, 6, 0, 0,
368                 0, 15, 0, 0, 0, 14, 0, 0, 0, 62, 0, 3, 0, 8, 0, 0, 0, 15, 0, 0, 0, 253, 0, 1, 0,
369                 56, 0, 1, 0,
370             ];
371             ShaderModule::from_bytes(device.clone(), &SECOND_MODULE).unwrap()
372         };
373 
374         let _pipeline = ComputePipeline::new(
375             device.clone(),
376             first_module.entry_point("main").unwrap(),
377             &(),
378             Some(cache.clone()),
379             |_| {},
380         )
381         .unwrap();
382 
383         let cache_data = cache.get_data().unwrap();
384 
385         let _second_pipeline = ComputePipeline::new(
386             device,
387             second_module.entry_point("main").unwrap(),
388             &(),
389             Some(cache.clone()),
390             |_| {},
391         )
392         .unwrap();
393 
394         let second_data = cache.get_data().unwrap();
395 
396         if cache_data.is_empty() {
397             assert_eq!(cache_data, second_data);
398         } else {
399             assert_ne!(cache_data, second_data);
400         }
401     }
402 
403     #[test]
cache_data_does_not_change()404     fn cache_data_does_not_change() {
405         let (device, _queue) = gfx_dev_and_queue!();
406 
407         let cache = PipelineCache::empty(device.clone()).unwrap();
408 
409         let module = unsafe {
410             /*
411              * #version 450
412              * void main() {
413              * }
414              */
415             const MODULE: [u8; 192] = [
416                 3, 2, 35, 7, 0, 0, 1, 0, 10, 0, 8, 0, 6, 0, 0, 0, 0, 0, 0, 0, 17, 0, 2, 0, 1, 0, 0,
417                 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100, 46, 52, 53, 48, 0,
418                 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 15, 0, 5, 0, 5, 0, 0, 0, 4, 0, 0, 0,
419                 109, 97, 105, 110, 0, 0, 0, 0, 16, 0, 6, 0, 4, 0, 0, 0, 17, 0, 0, 0, 1, 0, 0, 0, 1,
420                 0, 0, 0, 1, 0, 0, 0, 3, 0, 3, 0, 2, 0, 0, 0, 194, 1, 0, 0, 5, 0, 4, 0, 4, 0, 0, 0,
421                 109, 97, 105, 110, 0, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0, 33, 0, 3, 0, 3, 0, 0, 0, 2,
422                 0, 0, 0, 54, 0, 5, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 248, 0, 2, 0,
423                 5, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0,
424             ];
425             ShaderModule::from_bytes(device.clone(), &MODULE).unwrap()
426         };
427 
428         let _pipeline = ComputePipeline::new(
429             device.clone(),
430             module.entry_point("main").unwrap(),
431             &(),
432             Some(cache.clone()),
433             |_| {},
434         )
435         .unwrap();
436 
437         let cache_data = cache.get_data().unwrap();
438 
439         let _second_pipeline = ComputePipeline::new(
440             device,
441             module.entry_point("main").unwrap(),
442             &(),
443             Some(cache.clone()),
444             |_| {},
445         )
446         .unwrap();
447 
448         let second_data = cache.get_data().unwrap();
449 
450         assert_eq!(cache_data, second_data);
451     }
452 }
453