1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 use std::cell::RefCell;
6 use std::ffi::OsString;
7 use std::fs::{create_dir_all, read_dir, read_to_string, File};
8 use std::io::{Error, ErrorKind};
9 use std::io::{Read, Write};
10 use std::path::{Path, PathBuf};
14 use nsstring::nsAString;
15 use rayon::ThreadPool;
16 use webrender::{ProgramBinary, ProgramCache, ProgramCacheObserver, ProgramSourceDigest};
18 const MAX_LOAD_TIME_MS: u64 = 400;
20 fn deserialize_program_binary(path: &Path) -> Result<Arc<ProgramBinary>, Error> {
22 let mut file = File::open(path)?;
23 file.read_to_end(&mut buf)?;
25 if buf.len() <= 8 + 4 {
26 return Err(Error::new(ErrorKind::InvalidData, "File size is too small"));
28 let magic = &buf[0..4];
29 let hash = &buf[4..8 + 4];
30 let data = &buf[8 + 4..];
32 // Check if magic + version are correct.
33 let mv: u32 = bincode::deserialize(&magic).unwrap();
34 if mv != MAGIC_AND_VERSION {
35 return Err(Error::new(
36 ErrorKind::InvalidData,
37 "File data is invalid (magic+version)",
41 // Check if hash is correct
42 let hash: u64 = bincode::deserialize(&hash).unwrap();
43 let hash_data = fxhash::hash64(&data);
44 if hash != hash_data {
45 return Err(Error::new(ErrorKind::InvalidData, "File data is invalid (hash)"));
48 // Deserialize ProgramBinary
49 let binary = match bincode::deserialize(&data) {
52 return Err(Error::new(
53 ErrorKind::InvalidData,
54 "Failed to deserialize ProgramBinary",
62 #[cfg(target_os = "windows")]
63 fn get_cache_path_from_prof_path(prof_path: &nsAString) -> Option<PathBuf> {
64 if prof_path.is_empty() {
65 // Empty means that we do not use disk cache.
69 use std::os::windows::prelude::*;
71 let prof_path = OsString::from_wide(prof_path.as_ref());
72 let mut cache_path = PathBuf::from(&prof_path);
73 cache_path.push("shader-cache");
78 #[cfg(not(target_os = "windows"))]
79 fn get_cache_path_from_prof_path(prof_path: &nsAString) -> Option<PathBuf> {
80 if prof_path.is_empty() {
81 // Empty means that we do not use disk cache.
85 let utf8 = String::from_utf16(prof_path.as_ref()).unwrap();
86 let prof_path = OsString::from(utf8);
87 let mut cache_path = PathBuf::from(&prof_path);
88 cache_path.push("shader-cache");
93 struct WrProgramBinaryDiskCache {
95 workers: Arc<ThreadPool>,
96 cached_shader_filenames: Vec<OsString>,
99 // Magic number + version. Increment the version when the binary format changes.
100 const MAGIC: u32 = 0xB154AD30; // BI-SHADE + version.
101 const VERSION: u32 = 2;
102 const MAGIC_AND_VERSION: u32 = MAGIC + VERSION;
104 const WHITELIST_FILENAME: &str = "startup_shaders";
105 const WHITELIST_SEPARATOR: &str = "\n";
107 /// Helper to convert a closure returning a `Result` to one that returns void.
108 /// This allows the enclosed code to use the question-mark operator in a
109 /// context where the calling function doesn't expect a `Result`.
110 #[allow(unused_must_use)]
111 fn result_to_void<F: FnOnce() -> Result<(), ()>>(f: F) {
115 impl WrProgramBinaryDiskCache {
117 fn new(cache_path: PathBuf, workers: &Arc<ThreadPool>) -> Self {
118 WrProgramBinaryDiskCache {
120 workers: Arc::clone(workers),
121 cached_shader_filenames: Vec::new(),
125 /// Saves the specified binaries to the on-disk shader cache.
126 fn save_shaders_to_disk(&mut self, entries: Vec<Arc<ProgramBinary>>) {
127 info!("Saving binaries to on-disk shader cache");
129 // Write the entries to disk on a worker thread.
130 for entry in entries {
131 let file_name = entry.source_digest().to_string();
132 let file_path = self.cache_path.join(&file_name);
134 self.workers.spawn(move || {
135 result_to_void(move || {
136 info!("Writing shader: {}", file_name);
138 use std::time::Instant;
139 let start = Instant::now();
142 bincode::serialize(&*entry).map_err(|e| error!("shader-cache: Failed to serialize: {}", e))?;
145 File::create(&file_path).map_err(|e| error!("shader-cache: Failed to create file: {}", e))?;
147 // Write magic + version.
148 let mv = MAGIC_AND_VERSION;
149 let mv = bincode::serialize(&mv).unwrap();
150 assert!(mv.len() == 4);
152 .map_err(|e| error!("shader-cache: Failed to write magic + version: {}", e))?;
155 let hash = fxhash::hash64(&data);
156 let hash = bincode::serialize(&hash).unwrap();
157 assert!(hash.len() == 8);
158 file.write_all(&hash)
159 .map_err(|e| error!("shader-cache: Failed to write hash: {}", e))?;
161 // Write serialized data
162 file.write_all(&data)
163 .map_err(|e| error!("shader-cache: Failed to write program binary: {}", e))?;
165 info!("Wrote shader {} in {:?}", file_name, start.elapsed());
172 /// Writes the whitelist containing the set of startup shaders to disk.
173 fn set_startup_shaders(&mut self, entries: Vec<Arc<ProgramBinary>>) {
174 let whitelist = entries
176 .map(|e| e.source_digest().to_string())
177 .collect::<Vec<String>>()
178 .join(WHITELIST_SEPARATOR);
180 let mut whitelist_path = self.cache_path.clone();
181 whitelist_path.push(WHITELIST_FILENAME);
182 self.workers.spawn(move || {
183 result_to_void(move || {
184 info!("Writing startup shader whitelist");
185 File::create(&whitelist_path)
186 .and_then(|mut file| file.write_all(whitelist.as_bytes()))
187 .map_err(|e| error!("shader-cache: Failed to write startup whitelist: {}", e))?;
193 pub fn try_load_shader_from_disk(&mut self, filename: &str, program_cache: &Rc<ProgramCache>) {
194 if let Some(index) = self.cached_shader_filenames.iter().position(|e| e == filename) {
195 let mut path = self.cache_path.clone();
198 self.cached_shader_filenames.swap_remove(index);
200 info!("Loading shader: {}", filename);
202 match deserialize_program_binary(&path) {
204 program_cache.load_program_binary(program);
207 error!("shader-cache: Failed to deserialize program binary: {}", err);
211 info!("shader-cache: Program binary not found in disk cache");
215 pub fn try_load_startup_shaders_from_disk(&mut self, program_cache: &Rc<ProgramCache>) {
216 use std::time::Instant;
217 let start = Instant::now();
219 // Load and parse the whitelist if it exists
220 let mut whitelist_path = self.cache_path.clone();
221 whitelist_path.push(WHITELIST_FILENAME);
222 let whitelist = match read_to_string(&whitelist_path) {
223 Ok(whitelist) => whitelist
224 .split(WHITELIST_SEPARATOR)
225 .map(|s| s.to_string())
226 .collect::<Vec<String>>(),
228 info!("shader-cache: Could not read startup whitelist: {}", err);
232 info!("Loaded startup shader whitelist in {:?}", start.elapsed());
234 self.cached_shader_filenames = read_dir(&self.cache_path)
237 "shader-cache: Error reading directory whilst loading startup shaders: {}",
243 .filter_map(Result::ok)
244 .map(|entry| entry.file_name())
245 .filter(|filename| filename != WHITELIST_FILENAME)
246 .collect::<Vec<OsString>>();
248 // Load whitelisted program binaries if they exist
249 for entry in &whitelist {
250 self.try_load_shader_from_disk(&entry, program_cache);
252 let elapsed = start.elapsed();
253 info!("Loaded shader in {:?}", elapsed);
254 let elapsed_ms = (elapsed.as_secs() * 1_000) + elapsed.subsec_millis() as u64;
256 if elapsed_ms > MAX_LOAD_TIME_MS {
257 // Loading the startup shaders is taking too long, so bail out now.
258 // Additionally clear the list of remaining shaders cached on disk,
259 // so that we do not attempt to load any on demand during rendering.
260 error!("shader-cache: Timed out before finishing loads");
261 self.cached_shader_filenames.clear();
268 pub struct WrProgramCacheObserver {
269 disk_cache: Rc<RefCell<WrProgramBinaryDiskCache>>,
272 impl WrProgramCacheObserver {
274 fn new(disk_cache: Rc<RefCell<WrProgramBinaryDiskCache>>) -> Self {
275 WrProgramCacheObserver { disk_cache }
279 impl ProgramCacheObserver for WrProgramCacheObserver {
280 fn save_shaders_to_disk(&self, entries: Vec<Arc<ProgramBinary>>) {
281 self.disk_cache.borrow_mut().save_shaders_to_disk(entries);
284 fn set_startup_shaders(&self, entries: Vec<Arc<ProgramBinary>>) {
285 self.disk_cache.borrow_mut().set_startup_shaders(entries);
288 fn try_load_shader_from_disk(&self, digest: &ProgramSourceDigest, program_cache: &Rc<ProgramCache>) {
289 let filename = digest.to_string();
292 .try_load_shader_from_disk(&filename, program_cache);
295 fn notify_program_binary_failed(&self, _program_binary: &Arc<ProgramBinary>) {
296 error!("shader-cache: Failed program_binary");
300 pub struct WrProgramCache {
301 pub program_cache: Rc<ProgramCache>,
302 disk_cache: Option<Rc<RefCell<WrProgramBinaryDiskCache>>>,
305 impl WrProgramCache {
306 pub fn new(prof_path: &nsAString, workers: &Arc<ThreadPool>) -> Self {
307 let cache_path = get_cache_path_from_prof_path(prof_path);
308 let use_disk_cache = cache_path.as_ref().map_or(false, |p| create_dir_all(p).is_ok());
309 let (disk_cache, program_cache_observer) = if use_disk_cache {
310 let cache = Rc::new(RefCell::new(WrProgramBinaryDiskCache::new(
314 let obs = Box::new(WrProgramCacheObserver::new(Rc::clone(&cache))) as Box<dyn ProgramCacheObserver>;
315 (Some(cache), Some(obs))
319 let program_cache = ProgramCache::new(program_cache_observer);
327 pub fn rc_get(&self) -> &Rc<ProgramCache> {
331 pub fn try_load_startup_shaders_from_disk(&self) {
332 if let Some(ref disk_cache) = self.disk_cache {
335 .try_load_startup_shaders_from_disk(&self.program_cache);
337 error!("shader-cache: Shader disk cache is not supported");
342 pub fn remove_disk_cache(prof_path: &nsAString) -> Result<(), Error> {
343 use std::time::Instant;
345 if let Some(cache_path) = get_cache_path_from_prof_path(prof_path) {
346 if cache_path.exists() {
347 let start = Instant::now();
348 remove_dir_all::remove_dir_all(&cache_path)?;
349 info!("removed all disk cache shaders in {:?}", start.elapsed());