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