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`](struct.PipelineCache.html#method.get_data) for example of how to store the data 22 //! on the disk, and [`with_data`](struct.PipelineCache.html#method.with_data) for how to reload it. 23 24 use crate::check_errors; 25 use crate::device::Device; 26 use crate::OomError; 27 use crate::VulkanObject; 28 use std::mem::MaybeUninit; 29 use std::ptr; 30 use std::sync::Arc; 31 32 /// Opaque cache that contains pipeline objects. 33 /// 34 /// See [the documentation of the module](index.html) for more info. 35 pub struct PipelineCache { 36 device: Arc<Device>, 37 cache: ash::vk::PipelineCache, 38 } 39 40 impl PipelineCache { 41 /// Builds a new pipeline cache from existing data. The data must have been previously obtained 42 /// with [`get_data`](#method.get_data). 43 /// 44 /// The data passed to this function will most likely be blindly trusted by the Vulkan 45 /// implementation. Therefore you can easily crash your application or the system by passing 46 /// wrong data. Hence why this function is unsafe. 47 /// 48 /// # Example 49 /// 50 /// This example loads a cache from a file, if it exists. 51 /// See [`get_data`](#method.get_data) for how to store the data in a file. 52 /// TODO: there's a header in the cached data that must be checked ; talk about this 53 /// 54 /// ``` 55 /// # use std::sync::Arc; 56 /// # use vulkano::device::Device; 57 /// use std::fs::File; 58 /// use std::io::Read; 59 /// use vulkano::pipeline::cache::PipelineCache; 60 /// # let device: Arc<Device> = return; 61 /// 62 /// let data = { 63 /// let file = File::open("pipeline_cache.bin"); 64 /// if let Ok(mut file) = file { 65 /// let mut data = Vec::new(); 66 /// if let Ok(_) = file.read_to_end(&mut data) { 67 /// Some(data) 68 /// } else { None } 69 /// } else { None } 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 /// # Example 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 check_errors(fns.v1_0.create_pipeline_cache( 122 device.internal_object(), 123 &infos, 124 ptr::null(), 125 output.as_mut_ptr(), 126 ))?; 127 output.assume_init() 128 }; 129 130 Ok(Arc::new(PipelineCache { 131 device: device.clone(), 132 cache: cache, 133 })) 134 } 135 136 /// Merges other pipeline caches into this one. 137 /// 138 /// It is `self` that is modified here. The pipeline caches passed as parameter are untouched. 139 /// 140 /// # Panic 141 /// 142 /// - Panics if `self` is included in the list of other pipelines. 143 /// 144 // FIXME: vkMergePipelineCaches is not thread safe for the destination cache 145 // TODO: write example merge<'a, I>(&self, pipelines: I) -> Result<(), OomError> where I: IntoIterator<Item = &'a &'a Arc<PipelineCache>>,146 pub fn merge<'a, I>(&self, pipelines: I) -> Result<(), OomError> 147 where 148 I: IntoIterator<Item = &'a &'a Arc<PipelineCache>>, 149 { 150 unsafe { 151 let fns = self.device.fns(); 152 153 let pipelines = pipelines 154 .into_iter() 155 .map(|pipeline| { 156 assert!(&***pipeline as *const _ != &*self as *const _); 157 pipeline.cache 158 }) 159 .collect::<Vec<_>>(); 160 161 check_errors(fns.v1_0.merge_pipeline_caches( 162 self.device.internal_object(), 163 self.cache, 164 pipelines.len() as u32, 165 pipelines.as_ptr(), 166 ))?; 167 168 Ok(()) 169 } 170 } 171 172 /// Obtains the data from the cache. 173 /// 174 /// This data can be stored and then reloaded and passed to `PipelineCache::with_data`. 175 /// 176 /// # Example 177 /// 178 /// This example stores the data of a pipeline cache on the disk. 179 /// See [`with_data`](#method.with_data) for how to reload it. 180 /// 181 /// ``` 182 /// use std::fs; 183 /// use std::fs::File; 184 /// use std::io::Write; 185 /// # use std::sync::Arc; 186 /// # use vulkano::pipeline::cache::PipelineCache; 187 /// 188 /// # let cache: Arc<PipelineCache> = return; 189 /// // If an error happens (eg. no permission for the file) we simply skip storing the cache. 190 /// if let Ok(data) = cache.get_data() { 191 /// if let Ok(mut file) = File::create("pipeline_cache.bin.tmp") { 192 /// if let Ok(_) = file.write_all(&data) { 193 /// let _ = fs::rename("pipeline_cache.bin.tmp", "pipeline_cache.bin"); 194 /// } else { 195 /// let _ = fs::remove_file("pipeline_cache.bin.tmp"); 196 /// } 197 /// } 198 /// } 199 /// ``` get_data(&self) -> Result<Vec<u8>, OomError>200 pub fn get_data(&self) -> Result<Vec<u8>, OomError> { 201 unsafe { 202 let fns = self.device.fns(); 203 204 let mut num = 0; 205 check_errors(fns.v1_0.get_pipeline_cache_data( 206 self.device.internal_object(), 207 self.cache, 208 &mut num, 209 ptr::null_mut(), 210 ))?; 211 212 let mut data: Vec<u8> = Vec::with_capacity(num as usize); 213 check_errors(fns.v1_0.get_pipeline_cache_data( 214 self.device.internal_object(), 215 self.cache, 216 &mut num, 217 data.as_mut_ptr() as *mut _, 218 ))?; 219 data.set_len(num as usize); 220 221 Ok(data) 222 } 223 } 224 } 225 226 unsafe impl VulkanObject for PipelineCache { 227 type Object = ash::vk::PipelineCache; 228 229 #[inline] internal_object(&self) -> ash::vk::PipelineCache230 fn internal_object(&self) -> ash::vk::PipelineCache { 231 self.cache 232 } 233 } 234 235 impl Drop for PipelineCache { 236 #[inline] drop(&mut self)237 fn drop(&mut self) { 238 unsafe { 239 let fns = self.device.fns(); 240 fns.v1_0 241 .destroy_pipeline_cache(self.device.internal_object(), self.cache, ptr::null()); 242 } 243 } 244 } 245 246 #[cfg(test)] 247 mod tests { 248 use crate::pipeline::cache::PipelineCache; 249 use crate::pipeline::shader::ShaderModule; 250 use crate::pipeline::shader::SpecializationConstants; 251 use crate::pipeline::ComputePipeline; 252 use std::{ffi::CStr, sync::Arc}; 253 254 #[test] merge_self_forbidden()255 fn merge_self_forbidden() { 256 let (device, queue) = gfx_dev_and_queue!(); 257 let pipeline = PipelineCache::empty(device).unwrap(); 258 assert_should_panic!({ 259 pipeline.merge(&[&pipeline]).unwrap(); 260 }); 261 } 262 263 #[test] cache_returns_same_data()264 fn cache_returns_same_data() { 265 let (device, queue) = gfx_dev_and_queue!(); 266 267 let cache = PipelineCache::empty(device.clone()).unwrap(); 268 269 let module = unsafe { 270 /* 271 * #version 450 272 * void main() { 273 * } 274 */ 275 const MODULE: [u8; 192] = [ 276 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, 277 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100, 46, 52, 53, 48, 0, 278 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, 279 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, 280 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, 281 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, 282 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, 283 5, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0, 284 ]; 285 ShaderModule::new(device.clone(), &MODULE).unwrap() 286 }; 287 288 let shader = unsafe { 289 static NAME: [u8; 5] = [109, 97, 105, 110, 0]; // "main" 290 module.compute_entry_point( 291 CStr::from_ptr(NAME.as_ptr() as *const _), 292 [], 293 None, 294 <()>::descriptors(), 295 ) 296 }; 297 298 let pipeline = Arc::new( 299 ComputePipeline::new(device.clone(), &shader, &(), Some(cache.clone())).unwrap(), 300 ); 301 302 let cache_data = cache.get_data().unwrap(); 303 let second_data = cache.get_data().unwrap(); 304 305 assert_eq!(cache_data, second_data); 306 } 307 308 #[test] cache_returns_different_data()309 fn cache_returns_different_data() { 310 let (device, queue) = gfx_dev_and_queue!(); 311 312 let cache = PipelineCache::empty(device.clone()).unwrap(); 313 314 let first_module = unsafe { 315 /* 316 * #version 450 317 * void main() { 318 * } 319 */ 320 const MODULE: [u8; 192] = [ 321 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, 322 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100, 46, 52, 53, 48, 0, 323 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, 324 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, 325 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, 326 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, 327 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, 328 5, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0, 329 ]; 330 ShaderModule::new(device.clone(), &MODULE).unwrap() 331 }; 332 333 let first_shader = unsafe { 334 static NAME: [u8; 5] = [109, 97, 105, 110, 0]; // "main" 335 first_module.compute_entry_point( 336 CStr::from_ptr(NAME.as_ptr() as *const _), 337 [], 338 None, 339 <()>::descriptors(), 340 ) 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::new(device.clone(), &SECOND_MODULE).unwrap() 372 }; 373 374 let second_shader = unsafe { 375 static NAME: [u8; 5] = [109, 97, 105, 110, 0]; // "main" 376 second_module.compute_entry_point( 377 CStr::from_ptr(NAME.as_ptr() as *const _), 378 [], 379 None, 380 <()>::descriptors(), 381 ) 382 }; 383 384 let pipeline = Arc::new( 385 ComputePipeline::new(device.clone(), &first_shader, &(), Some(cache.clone())).unwrap(), 386 ); 387 388 let cache_data = cache.get_data().unwrap(); 389 390 let second_pipeline = Arc::new( 391 ComputePipeline::new(device.clone(), &second_shader, &(), Some(cache.clone())).unwrap(), 392 ); 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::new(device.clone(), &MODULE).unwrap() 426 }; 427 428 let shader = unsafe { 429 static NAME: [u8; 5] = [109, 97, 105, 110, 0]; // "main" 430 module.compute_entry_point( 431 CStr::from_ptr(NAME.as_ptr() as *const _), 432 [], 433 None, 434 <()>::descriptors(), 435 ) 436 }; 437 438 let pipeline = Arc::new( 439 ComputePipeline::new(device.clone(), &shader, &(), Some(cache.clone())).unwrap(), 440 ); 441 442 let cache_data = cache.get_data().unwrap(); 443 444 let second_pipeline = Arc::new( 445 ComputePipeline::new(device.clone(), &shader, &(), Some(cache.clone())).unwrap(), 446 ); 447 448 let second_data = cache.get_data().unwrap(); 449 450 assert_eq!(cache_data, second_data); 451 } 452 } 453