aboutsummaryrefslogtreecommitdiff
path: root/src/pipeline/cache.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/pipeline/cache.rs')
-rw-r--r--src/pipeline/cache.rs452
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);
+ }
+}