summaryrefslogtreecommitdiffstats
path: root/gfx/webrender_bindings/src/program_cache.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/webrender_bindings/src/program_cache.rs')
-rw-r--r--gfx/webrender_bindings/src/program_cache.rs355
1 files changed, 355 insertions, 0 deletions
diff --git a/gfx/webrender_bindings/src/program_cache.rs b/gfx/webrender_bindings/src/program_cache.rs
new file mode 100644
index 0000000000..5db61fc8b5
--- /dev/null
+++ b/gfx/webrender_bindings/src/program_cache.rs
@@ -0,0 +1,355 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::cell::RefCell;
+use std::ffi::OsString;
+use std::fs::{create_dir_all, read_dir, read_to_string, File};
+use std::io::{Error, ErrorKind};
+use std::io::{Read, Write};
+use std::path::{Path, PathBuf};
+use std::rc::Rc;
+use std::sync::Arc;
+
+use bincode;
+use fxhash;
+use nsstring::nsAString;
+use rayon::ThreadPool;
+use webrender::{ProgramBinary, ProgramCache, ProgramCacheObserver, ProgramSourceDigest};
+
+const MAX_LOAD_TIME_MS: u64 = 400;
+
+fn deserialize_program_binary(path: &Path) -> Result<Arc<ProgramBinary>, Error> {
+ let mut buf = vec![];
+ let mut file = File::open(path)?;
+ file.read_to_end(&mut buf)?;
+
+ if buf.len() <= 8 + 4 {
+ return Err(Error::new(ErrorKind::InvalidData, "File size is too small"));
+ }
+ let magic = &buf[0..4];
+ let hash = &buf[4..8 + 4];
+ let data = &buf[8 + 4..];
+
+ // Check if magic + version are correct.
+ let mv: u32 = bincode::deserialize(&magic).unwrap();
+ if mv != MAGIC_AND_VERSION {
+ return Err(Error::new(
+ ErrorKind::InvalidData,
+ "File data is invalid (magic+version)",
+ ));
+ }
+
+ // Check if hash is correct
+ let hash: u64 = bincode::deserialize(&hash).unwrap();
+ let hash_data = fxhash::hash64(&data);
+ if hash != hash_data {
+ return Err(Error::new(ErrorKind::InvalidData, "File data is invalid (hash)"));
+ }
+
+ // Deserialize ProgramBinary
+ let binary = match bincode::deserialize(&data) {
+ Ok(binary) => binary,
+ Err(_) => {
+ return Err(Error::new(
+ ErrorKind::InvalidData,
+ "Failed to deserialize ProgramBinary",
+ ))
+ },
+ };
+
+ Ok(Arc::new(binary))
+}
+
+#[cfg(target_os = "windows")]
+fn get_cache_path_from_prof_path(prof_path: &nsAString) -> Option<PathBuf> {
+ if prof_path.is_empty() {
+ // Empty means that we do not use disk cache.
+ return None;
+ }
+
+ use std::os::windows::prelude::*;
+
+ let prof_path = OsString::from_wide(prof_path.as_ref());
+ let mut cache_path = PathBuf::from(&prof_path);
+ cache_path.push("shader-cache");
+
+ Some(cache_path)
+}
+
+#[cfg(not(target_os = "windows"))]
+fn get_cache_path_from_prof_path(prof_path: &nsAString) -> Option<PathBuf> {
+ if prof_path.is_empty() {
+ // Empty means that we do not use disk cache.
+ return None;
+ }
+
+ let utf8 = String::from_utf16(prof_path.as_ref()).unwrap();
+ let prof_path = OsString::from(utf8);
+ let mut cache_path = PathBuf::from(&prof_path);
+ cache_path.push("shader-cache");
+
+ Some(cache_path)
+}
+
+struct WrProgramBinaryDiskCache {
+ cache_path: PathBuf,
+ workers: Arc<ThreadPool>,
+ cached_shader_filenames: Vec<OsString>,
+}
+
+// Magic number + version. Increment the version when the binary format changes.
+const MAGIC: u32 = 0xB154AD30; // BI-SHADE + version.
+const VERSION: u32 = 2;
+const MAGIC_AND_VERSION: u32 = MAGIC + VERSION;
+
+const WHITELIST_FILENAME: &str = "startup_shaders";
+const WHITELIST_SEPARATOR: &str = "\n";
+
+/// Helper to convert a closure returning a `Result` to one that returns void.
+/// This allows the enclosed code to use the question-mark operator in a
+/// context where the calling function doesn't expect a `Result`.
+#[allow(unused_must_use)]
+fn result_to_void<F: FnOnce() -> Result<(), ()>>(f: F) {
+ f();
+}
+
+impl WrProgramBinaryDiskCache {
+ #[allow(dead_code)]
+ fn new(cache_path: PathBuf, workers: &Arc<ThreadPool>) -> Self {
+ WrProgramBinaryDiskCache {
+ cache_path,
+ workers: Arc::clone(workers),
+ cached_shader_filenames: Vec::new(),
+ }
+ }
+
+ /// Saves the specified binaries to the on-disk shader cache.
+ fn save_shaders_to_disk(&mut self, entries: Vec<Arc<ProgramBinary>>) {
+ info!("Saving binaries to on-disk shader cache");
+
+ // Write the entries to disk on a worker thread.
+ for entry in entries {
+ let file_name = entry.source_digest().to_string();
+ let file_path = self.cache_path.join(&file_name);
+
+ self.workers.spawn(move || {
+ result_to_void(move || {
+ info!("Writing shader: {}", file_name);
+
+ use std::time::Instant;
+ let start = Instant::now();
+
+ let data: Vec<u8> =
+ bincode::serialize(&*entry).map_err(|e| error!("shader-cache: Failed to serialize: {}", e))?;
+
+ let mut file =
+ File::create(&file_path).map_err(|e| error!("shader-cache: Failed to create file: {}", e))?;
+
+ // Write magic + version.
+ let mv = MAGIC_AND_VERSION;
+ let mv = bincode::serialize(&mv).unwrap();
+ assert!(mv.len() == 4);
+ file.write_all(&mv)
+ .map_err(|e| error!("shader-cache: Failed to write magic + version: {}", e))?;
+
+ // Write hash
+ let hash = fxhash::hash64(&data);
+ let hash = bincode::serialize(&hash).unwrap();
+ assert!(hash.len() == 8);
+ file.write_all(&hash)
+ .map_err(|e| error!("shader-cache: Failed to write hash: {}", e))?;
+
+ // Write serialized data
+ file.write_all(&data)
+ .map_err(|e| error!("shader-cache: Failed to write program binary: {}", e))?;
+
+ info!("Wrote shader {} in {:?}", file_name, start.elapsed());
+ Ok(())
+ })
+ });
+ }
+ }
+
+ /// Writes the whitelist containing the set of startup shaders to disk.
+ fn set_startup_shaders(&mut self, entries: Vec<Arc<ProgramBinary>>) {
+ let whitelist = entries
+ .iter()
+ .map(|e| e.source_digest().to_string())
+ .collect::<Vec<String>>()
+ .join(WHITELIST_SEPARATOR);
+
+ let mut whitelist_path = self.cache_path.clone();
+ whitelist_path.push(WHITELIST_FILENAME);
+ self.workers.spawn(move || {
+ result_to_void(move || {
+ info!("Writing startup shader whitelist");
+ File::create(&whitelist_path)
+ .and_then(|mut file| file.write_all(whitelist.as_bytes()))
+ .map_err(|e| error!("shader-cache: Failed to write startup whitelist: {}", e))?;
+ Ok(())
+ })
+ });
+ }
+
+ pub fn try_load_shader_from_disk(&mut self, filename: &str, program_cache: &Rc<ProgramCache>) {
+ if let Some(index) = self.cached_shader_filenames.iter().position(|e| e == filename) {
+ let mut path = self.cache_path.clone();
+ path.push(filename);
+
+ self.cached_shader_filenames.swap_remove(index);
+
+ info!("Loading shader: {}", filename);
+
+ match deserialize_program_binary(&path) {
+ Ok(program) => {
+ program_cache.load_program_binary(program);
+ },
+ Err(err) => {
+ error!("shader-cache: Failed to deserialize program binary: {}", err);
+ },
+ };
+ } else {
+ info!("shader-cache: Program binary not found in disk cache");
+ }
+ }
+
+ pub fn try_load_startup_shaders_from_disk(&mut self, program_cache: &Rc<ProgramCache>) {
+ use std::time::Instant;
+ let start = Instant::now();
+
+ // Load and parse the whitelist if it exists
+ let mut whitelist_path = self.cache_path.clone();
+ whitelist_path.push(WHITELIST_FILENAME);
+ let whitelist = match read_to_string(&whitelist_path) {
+ Ok(whitelist) => whitelist
+ .split(WHITELIST_SEPARATOR)
+ .map(|s| s.to_string())
+ .collect::<Vec<String>>(),
+ Err(err) => {
+ info!("shader-cache: Could not read startup whitelist: {}", err);
+ Vec::new()
+ },
+ };
+ info!("Loaded startup shader whitelist in {:?}", start.elapsed());
+
+ self.cached_shader_filenames = read_dir(&self.cache_path)
+ .map_err(|err| {
+ error!(
+ "shader-cache: Error reading directory whilst loading startup shaders: {}",
+ err
+ )
+ })
+ .into_iter()
+ .flatten()
+ .filter_map(Result::ok)
+ .map(|entry| entry.file_name())
+ .filter(|filename| filename != WHITELIST_FILENAME)
+ .collect::<Vec<OsString>>();
+
+ // Load whitelisted program binaries if they exist
+ for entry in &whitelist {
+ self.try_load_shader_from_disk(&entry, program_cache);
+
+ let elapsed = start.elapsed();
+ info!("Loaded shader in {:?}", elapsed);
+ let elapsed_ms = (elapsed.as_secs() * 1_000) + elapsed.subsec_millis() as u64;
+
+ if elapsed_ms > MAX_LOAD_TIME_MS {
+ // Loading the startup shaders is taking too long, so bail out now.
+ // Additionally clear the list of remaining shaders cached on disk,
+ // so that we do not attempt to load any on demand during rendering.
+ error!("shader-cache: Timed out before finishing loads");
+ self.cached_shader_filenames.clear();
+ break;
+ }
+ }
+ }
+}
+
+pub struct WrProgramCacheObserver {
+ disk_cache: Rc<RefCell<WrProgramBinaryDiskCache>>,
+}
+
+impl WrProgramCacheObserver {
+ #[allow(dead_code)]
+ fn new(disk_cache: Rc<RefCell<WrProgramBinaryDiskCache>>) -> Self {
+ WrProgramCacheObserver { disk_cache }
+ }
+}
+
+impl ProgramCacheObserver for WrProgramCacheObserver {
+ fn save_shaders_to_disk(&self, entries: Vec<Arc<ProgramBinary>>) {
+ self.disk_cache.borrow_mut().save_shaders_to_disk(entries);
+ }
+
+ fn set_startup_shaders(&self, entries: Vec<Arc<ProgramBinary>>) {
+ self.disk_cache.borrow_mut().set_startup_shaders(entries);
+ }
+
+ fn try_load_shader_from_disk(&self, digest: &ProgramSourceDigest, program_cache: &Rc<ProgramCache>) {
+ let filename = digest.to_string();
+ self.disk_cache
+ .borrow_mut()
+ .try_load_shader_from_disk(&filename, program_cache);
+ }
+
+ fn notify_program_binary_failed(&self, _program_binary: &Arc<ProgramBinary>) {
+ error!("shader-cache: Failed program_binary");
+ }
+}
+
+pub struct WrProgramCache {
+ pub program_cache: Rc<ProgramCache>,
+ disk_cache: Option<Rc<RefCell<WrProgramBinaryDiskCache>>>,
+}
+
+impl WrProgramCache {
+ pub fn new(prof_path: &nsAString, workers: &Arc<ThreadPool>) -> Self {
+ let cache_path = get_cache_path_from_prof_path(prof_path);
+ let use_disk_cache = cache_path.as_ref().map_or(false, |p| create_dir_all(p).is_ok());
+ let (disk_cache, program_cache_observer) = if use_disk_cache {
+ let cache = Rc::new(RefCell::new(WrProgramBinaryDiskCache::new(
+ cache_path.unwrap(),
+ workers,
+ )));
+ let obs = Box::new(WrProgramCacheObserver::new(Rc::clone(&cache))) as Box<dyn ProgramCacheObserver>;
+ (Some(cache), Some(obs))
+ } else {
+ (None, None)
+ };
+ let program_cache = ProgramCache::new(program_cache_observer);
+
+ WrProgramCache {
+ program_cache,
+ disk_cache,
+ }
+ }
+
+ pub fn rc_get(&self) -> &Rc<ProgramCache> {
+ &self.program_cache
+ }
+
+ pub fn try_load_startup_shaders_from_disk(&self) {
+ if let Some(ref disk_cache) = self.disk_cache {
+ disk_cache
+ .borrow_mut()
+ .try_load_startup_shaders_from_disk(&self.program_cache);
+ } else {
+ error!("shader-cache: Shader disk cache is not supported");
+ }
+ }
+}
+
+pub fn remove_disk_cache(prof_path: &nsAString) -> Result<(), Error> {
+ use std::time::Instant;
+
+ if let Some(cache_path) = get_cache_path_from_prof_path(prof_path) {
+ if cache_path.exists() {
+ let start = Instant::now();
+ remove_dir_all::remove_dir_all(&cache_path)?;
+ info!("removed all disk cache shaders in {:?}", start.elapsed());
+ }
+ }
+ Ok(())
+}