diff options
Diffstat (limited to 'src/pipeline/cache.rs')
-rw-r--r-- | src/pipeline/cache.rs | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/src/pipeline/cache.rs b/src/pipeline/cache.rs new file mode 100644 index 0000000..9b3ac77 --- /dev/null +++ b/src/pipeline/cache.rs @@ -0,0 +1,452 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +//! Cache the pipeline objects to disk for faster reloads. +//! +//! A pipeline cache is an opaque type that allow you to cache your graphics and compute +//! pipelines on the disk. +//! +//! You can create either an empty cache or a cache from some initial data. Whenever you create a +//! graphics or compute pipeline, you have the possibility to pass a reference to that cache. +//! The Vulkan implementation will then look in the cache for an existing entry, or add one if it +//! doesn't exist. +//! +//! Once that is done, you can extract the data from the cache and store it. See the documentation +//! of [`get_data`](struct.PipelineCache.html#method.get_data) for example of how to store the data +//! on the disk, and [`with_data`](struct.PipelineCache.html#method.with_data) for how to reload it. + +use crate::check_errors; +use crate::device::Device; +use crate::OomError; +use crate::VulkanObject; +use std::mem::MaybeUninit; +use std::ptr; +use std::sync::Arc; + +/// Opaque cache that contains pipeline objects. +/// +/// See [the documentation of the module](index.html) for more info. +pub struct PipelineCache { + device: Arc<Device>, + cache: ash::vk::PipelineCache, +} + +impl PipelineCache { + /// Builds a new pipeline cache from existing data. The data must have been previously obtained + /// with [`get_data`](#method.get_data). + /// + /// The data passed to this function will most likely be blindly trusted by the Vulkan + /// implementation. Therefore you can easily crash your application or the system by passing + /// wrong data. Hence why this function is unsafe. + /// + /// # Example + /// + /// This example loads a cache from a file, if it exists. + /// See [`get_data`](#method.get_data) for how to store the data in a file. + /// TODO: there's a header in the cached data that must be checked ; talk about this + /// + /// ``` + /// # use std::sync::Arc; + /// # use vulkano::device::Device; + /// use std::fs::File; + /// use std::io::Read; + /// use vulkano::pipeline::cache::PipelineCache; + /// # let device: Arc<Device> = return; + /// + /// let data = { + /// let file = File::open("pipeline_cache.bin"); + /// if let Ok(mut file) = file { + /// let mut data = Vec::new(); + /// if let Ok(_) = file.read_to_end(&mut data) { + /// Some(data) + /// } else { None } + /// } else { None } + /// }; + /// + /// let cache = if let Some(data) = data { + /// // This is unsafe because there is no way to be sure that the file contains valid data. + /// unsafe { PipelineCache::with_data(device.clone(), &data).unwrap() } + /// } else { + /// PipelineCache::empty(device.clone()).unwrap() + /// }; + /// ``` + #[inline] + pub unsafe fn with_data( + device: Arc<Device>, + initial_data: &[u8], + ) -> Result<Arc<PipelineCache>, OomError> { + PipelineCache::new_impl(device, Some(initial_data)) + } + + /// Builds a new empty pipeline cache. + /// + /// # Example + /// + /// ``` + /// # use std::sync::Arc; + /// # use vulkano::device::Device; + /// use vulkano::pipeline::cache::PipelineCache; + /// # let device: Arc<Device> = return; + /// let cache = PipelineCache::empty(device.clone()).unwrap(); + /// ``` + #[inline] + pub fn empty(device: Arc<Device>) -> Result<Arc<PipelineCache>, OomError> { + unsafe { PipelineCache::new_impl(device, None) } + } + + // Actual implementation of the constructor. + unsafe fn new_impl( + device: Arc<Device>, + initial_data: Option<&[u8]>, + ) -> Result<Arc<PipelineCache>, OomError> { + let fns = device.fns(); + + let cache = { + let infos = ash::vk::PipelineCacheCreateInfo { + flags: ash::vk::PipelineCacheCreateFlags::empty(), + initial_data_size: initial_data.map(|d| d.len()).unwrap_or(0), + p_initial_data: initial_data + .map(|d| d.as_ptr() as *const _) + .unwrap_or(ptr::null()), + ..Default::default() + }; + + let mut output = MaybeUninit::uninit(); + check_errors(fns.v1_0.create_pipeline_cache( + device.internal_object(), + &infos, + ptr::null(), + output.as_mut_ptr(), + ))?; + output.assume_init() + }; + + Ok(Arc::new(PipelineCache { + device: device.clone(), + cache: cache, + })) + } + + /// Merges other pipeline caches into this one. + /// + /// It is `self` that is modified here. The pipeline caches passed as parameter are untouched. + /// + /// # Panic + /// + /// - Panics if `self` is included in the list of other pipelines. + /// + // FIXME: vkMergePipelineCaches is not thread safe for the destination cache + // TODO: write example + pub fn merge<'a, I>(&self, pipelines: I) -> Result<(), OomError> + where + I: IntoIterator<Item = &'a &'a Arc<PipelineCache>>, + { + unsafe { + let fns = self.device.fns(); + + let pipelines = pipelines + .into_iter() + .map(|pipeline| { + assert!(&***pipeline as *const _ != &*self as *const _); + pipeline.cache + }) + .collect::<Vec<_>>(); + + check_errors(fns.v1_0.merge_pipeline_caches( + self.device.internal_object(), + self.cache, + pipelines.len() as u32, + pipelines.as_ptr(), + ))?; + + Ok(()) + } + } + + /// Obtains the data from the cache. + /// + /// This data can be stored and then reloaded and passed to `PipelineCache::with_data`. + /// + /// # Example + /// + /// This example stores the data of a pipeline cache on the disk. + /// See [`with_data`](#method.with_data) for how to reload it. + /// + /// ``` + /// use std::fs; + /// use std::fs::File; + /// use std::io::Write; + /// # use std::sync::Arc; + /// # use vulkano::pipeline::cache::PipelineCache; + /// + /// # let cache: Arc<PipelineCache> = return; + /// // If an error happens (eg. no permission for the file) we simply skip storing the cache. + /// if let Ok(data) = cache.get_data() { + /// if let Ok(mut file) = File::create("pipeline_cache.bin.tmp") { + /// if let Ok(_) = file.write_all(&data) { + /// let _ = fs::rename("pipeline_cache.bin.tmp", "pipeline_cache.bin"); + /// } else { + /// let _ = fs::remove_file("pipeline_cache.bin.tmp"); + /// } + /// } + /// } + /// ``` + pub fn get_data(&self) -> Result<Vec<u8>, OomError> { + unsafe { + let fns = self.device.fns(); + + let mut num = 0; + check_errors(fns.v1_0.get_pipeline_cache_data( + self.device.internal_object(), + self.cache, + &mut num, + ptr::null_mut(), + ))?; + + let mut data: Vec<u8> = Vec::with_capacity(num as usize); + check_errors(fns.v1_0.get_pipeline_cache_data( + self.device.internal_object(), + self.cache, + &mut num, + data.as_mut_ptr() as *mut _, + ))?; + data.set_len(num as usize); + + Ok(data) + } + } +} + +unsafe impl VulkanObject for PipelineCache { + type Object = ash::vk::PipelineCache; + + #[inline] + fn internal_object(&self) -> ash::vk::PipelineCache { + self.cache + } +} + +impl Drop for PipelineCache { + #[inline] + fn drop(&mut self) { + unsafe { + let fns = self.device.fns(); + fns.v1_0 + .destroy_pipeline_cache(self.device.internal_object(), self.cache, ptr::null()); + } + } +} + +#[cfg(test)] +mod tests { + use crate::pipeline::cache::PipelineCache; + use crate::pipeline::shader::ShaderModule; + use crate::pipeline::shader::SpecializationConstants; + use crate::pipeline::ComputePipeline; + use std::{ffi::CStr, sync::Arc}; + + #[test] + fn merge_self_forbidden() { + let (device, queue) = gfx_dev_and_queue!(); + let pipeline = PipelineCache::empty(device).unwrap(); + assert_should_panic!({ + pipeline.merge(&[&pipeline]).unwrap(); + }); + } + + #[test] + fn cache_returns_same_data() { + let (device, queue) = gfx_dev_and_queue!(); + + let cache = PipelineCache::empty(device.clone()).unwrap(); + + let module = unsafe { + /* + * #version 450 + * void main() { + * } + */ + const MODULE: [u8; 192] = [ + 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, + 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100, 46, 52, 53, 48, 0, + 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, + 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, + 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, + 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, + 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, + 5, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0, + ]; + ShaderModule::new(device.clone(), &MODULE).unwrap() + }; + + let shader = unsafe { + static NAME: [u8; 5] = [109, 97, 105, 110, 0]; // "main" + module.compute_entry_point( + CStr::from_ptr(NAME.as_ptr() as *const _), + [], + None, + <()>::descriptors(), + ) + }; + + let pipeline = Arc::new( + ComputePipeline::new(device.clone(), &shader, &(), Some(cache.clone())).unwrap(), + ); + + let cache_data = cache.get_data().unwrap(); + let second_data = cache.get_data().unwrap(); + + assert_eq!(cache_data, second_data); + } + + #[test] + fn cache_returns_different_data() { + let (device, queue) = gfx_dev_and_queue!(); + + let cache = PipelineCache::empty(device.clone()).unwrap(); + + let first_module = unsafe { + /* + * #version 450 + * void main() { + * } + */ + const MODULE: [u8; 192] = [ + 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, + 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100, 46, 52, 53, 48, 0, + 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, + 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, + 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, + 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, + 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, + 5, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0, + ]; + ShaderModule::new(device.clone(), &MODULE).unwrap() + }; + + let first_shader = unsafe { + static NAME: [u8; 5] = [109, 97, 105, 110, 0]; // "main" + first_module.compute_entry_point( + CStr::from_ptr(NAME.as_ptr() as *const _), + [], + None, + <()>::descriptors(), + ) + }; + + let second_module = unsafe { + /* + * #version 450 + * + * void main() { + * uint idx = gl_GlobalInvocationID.x; + * } + */ + const SECOND_MODULE: [u8; 432] = [ + 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, + 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100, 46, 52, 53, 48, + 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, + 0, 109, 97, 105, 110, 0, 0, 0, 0, 11, 0, 0, 0, 16, 0, 6, 0, 4, 0, 0, 0, 17, 0, 0, + 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, + 4, 0, 4, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 5, 0, 3, 0, 8, 0, 0, 0, 105, 100, + 120, 0, 5, 0, 8, 0, 11, 0, 0, 0, 103, 108, 95, 71, 108, 111, 98, 97, 108, 73, 110, + 118, 111, 99, 97, 116, 105, 111, 110, 73, 68, 0, 0, 0, 71, 0, 4, 0, 11, 0, 0, 0, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 56, 0, 1, 0, + ]; + ShaderModule::new(device.clone(), &SECOND_MODULE).unwrap() + }; + + let second_shader = unsafe { + static NAME: [u8; 5] = [109, 97, 105, 110, 0]; // "main" + second_module.compute_entry_point( + CStr::from_ptr(NAME.as_ptr() as *const _), + [], + None, + <()>::descriptors(), + ) + }; + + let pipeline = Arc::new( + ComputePipeline::new(device.clone(), &first_shader, &(), Some(cache.clone())).unwrap(), + ); + + let cache_data = cache.get_data().unwrap(); + + let second_pipeline = Arc::new( + ComputePipeline::new(device.clone(), &second_shader, &(), Some(cache.clone())).unwrap(), + ); + + let second_data = cache.get_data().unwrap(); + + if cache_data.is_empty() { + assert_eq!(cache_data, second_data); + } else { + assert_ne!(cache_data, second_data); + } + } + + #[test] + fn cache_data_does_not_change() { + let (device, queue) = gfx_dev_and_queue!(); + + let cache = PipelineCache::empty(device.clone()).unwrap(); + + let module = unsafe { + /* + * #version 450 + * void main() { + * } + */ + const MODULE: [u8; 192] = [ + 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, + 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100, 46, 52, 53, 48, 0, + 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, + 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, + 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, + 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, + 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, + 5, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0, + ]; + ShaderModule::new(device.clone(), &MODULE).unwrap() + }; + + let shader = unsafe { + static NAME: [u8; 5] = [109, 97, 105, 110, 0]; // "main" + module.compute_entry_point( + CStr::from_ptr(NAME.as_ptr() as *const _), + [], + None, + <()>::descriptors(), + ) + }; + + let pipeline = Arc::new( + ComputePipeline::new(device.clone(), &shader, &(), Some(cache.clone())).unwrap(), + ); + + let cache_data = cache.get_data().unwrap(); + + let second_pipeline = Arc::new( + ComputePipeline::new(device.clone(), &shader, &(), Some(cache.clone())).unwrap(), + ); + + let second_data = cache.get_data().unwrap(); + + assert_eq!(cache_data, second_data); + } +} |