/* 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::fs::File; use std::path::{Path, PathBuf}; use api::{ExternalImageData, ImageDescriptor}; #[cfg(feature = "png")] use api::ImageFormat; use api::units::TexelRect; #[cfg(feature = "png")] use api::units::DeviceIntSize; #[cfg(feature = "capture")] use crate::print_tree::{PrintableTree, PrintTree}; use crate::render_api::CaptureBits; use ron; use serde; #[derive(Clone)] pub struct CaptureConfig { pub root: PathBuf, pub bits: CaptureBits, /// Scene sequence ID when capturing multiple frames. Zero for a single frame capture. pub scene_id: u32, /// Frame sequence ID when capturing multiple frames. Zero for a single frame capture. pub frame_id: u32, /// Resource sequence ID when capturing multiple frames. Zero for a single frame capture. pub resource_id: u32, #[cfg(feature = "capture")] pretty: ron::ser::PrettyConfig, } impl CaptureConfig { #[cfg(any(feature = "capture", feature = "replay"))] pub fn new(root: PathBuf, bits: CaptureBits) -> Self { CaptureConfig { root, bits, scene_id: 0, frame_id: 0, resource_id: 0, #[cfg(feature = "capture")] pretty: ron::ser::PrettyConfig::new() .with_enumerate_arrays(true) .with_indentor(" ".to_string()), } } #[cfg(feature = "capture")] pub fn prepare_scene(&mut self) { use std::fs::create_dir_all; self.scene_id += 1; let _ = create_dir_all(&self.scene_root()); } #[cfg(feature = "capture")] pub fn prepare_frame(&mut self) { use std::fs::create_dir_all; self.frame_id += 1; let _ = create_dir_all(&self.frame_root()); } #[cfg(feature = "capture")] pub fn prepare_resource(&mut self) { use std::fs::create_dir_all; self.resource_id += 1; let _ = create_dir_all(&self.resource_root()); } #[cfg(any(feature = "capture", feature = "replay"))] pub fn scene_root(&self) -> PathBuf { if self.scene_id > 0 { let path = format!("scenes/{:05}", self.scene_id); self.root.join(path) } else { self.root.clone() } } #[cfg(any(feature = "capture", feature = "replay"))] pub fn frame_root(&self) -> PathBuf { if self.frame_id > 0 { let path = format!("frames/{:05}", self.frame_id); self.scene_root().join(path) } else { self.root.clone() } } #[cfg(any(feature = "capture", feature = "replay"))] pub fn resource_root(&self) -> PathBuf { if self.resource_id > 0 { let path = format!("resources/{:05}", self.resource_id); self.root.join(path) } else { self.root.clone() } } #[cfg(feature = "capture")] pub fn serialize_for_scene(&self, data: &T, name: P) where T: serde::Serialize, P: AsRef, { self.serialize(data, self.scene_root(), name) } #[cfg(feature = "capture")] pub fn serialize_for_frame(&self, data: &T, name: P) where T: serde::Serialize, P: AsRef, { self.serialize(data, self.frame_root(), name) } #[cfg(feature = "capture")] pub fn serialize_for_resource(&self, data: &T, name: P) where T: serde::Serialize, P: AsRef, { self.serialize(data, self.resource_root(), name) } #[cfg(feature = "capture")] pub fn file_path_for_frame

(&self, name: P, ext: &str) -> PathBuf where P: AsRef { self.frame_root().join(name).with_extension(ext) } #[cfg(feature = "capture")] fn serialize(&self, data: &T, path: PathBuf, name: P) where T: serde::Serialize, P: AsRef, { use std::io::Write; let ron = ron::ser::to_string_pretty(data, self.pretty.clone()) .unwrap(); let mut file = File::create(path.join(name).with_extension("ron")) .unwrap(); write!(file, "{}\n", ron) .unwrap(); } #[cfg(feature = "capture")] fn serialize_tree(data: &T, root: PathBuf, name: P) where T: PrintableTree, P: AsRef { let path = root .join(name) .with_extension("tree"); let file = File::create(path) .unwrap(); let mut pt = PrintTree::new_with_sink("", file); data.print_with(&mut pt); } #[cfg(feature = "capture")] pub fn serialize_tree_for_frame(&self, data: &T, name: P) where T: PrintableTree, P: AsRef { Self::serialize_tree(data, self.frame_root(), name) } #[cfg(feature = "replay")] fn deserialize(root: &PathBuf, name: P) -> Option where T: for<'a> serde::Deserialize<'a>, P: AsRef, { use std::io::Read; let mut string = String::new(); let path = root .join(name.as_ref()) .with_extension("ron"); File::open(path) .ok()? .read_to_string(&mut string) .unwrap(); match ron::de::from_str(&string) { Ok(out) => Some(out), Err(e) => panic!("File {:?} deserialization failed: {:?}", name.as_ref(), e), } } #[cfg(feature = "replay")] pub fn deserialize_for_scene(&self, name: P) -> Option where T: for<'a> serde::Deserialize<'a>, P: AsRef, { Self::deserialize(&self.scene_root(), name) } #[cfg(feature = "replay")] pub fn deserialize_for_frame(&self, name: P) -> Option where T: for<'a> serde::Deserialize<'a>, P: AsRef, { Self::deserialize(&self.frame_root(), name) } #[cfg(feature = "replay")] pub fn deserialize_for_resource(&self, name: P) -> Option where T: for<'a> serde::Deserialize<'a>, P: AsRef, { Self::deserialize(&self.resource_root(), name) } #[cfg(feature = "png")] pub fn save_png( path: PathBuf, size: DeviceIntSize, format: ImageFormat, stride: Option, data: &[u8], ) { use png::{BitDepth, ColorType, Encoder}; use std::io::BufWriter; use std::borrow::Cow; // `png` expects let data = match stride { Some(stride) if stride != format.bytes_per_pixel() * size.width => { let mut unstrided = Vec::new(); for y in 0..size.height { let start = (y * stride) as usize; unstrided.extend_from_slice(&data[start..start+(size.width * format.bytes_per_pixel()) as usize]); } Cow::from(unstrided) } _ => Cow::from(data), }; let color_type = match format { ImageFormat::RGBA8 => ColorType::RGBA, ImageFormat::BGRA8 => { warn!("Unable to swizzle PNG of BGRA8 type"); ColorType::RGBA }, ImageFormat::R8 => ColorType::Grayscale, ImageFormat::RG8 => ColorType::GrayscaleAlpha, _ => { error!("Unable to save PNG of {:?}", format); return; } }; let w = BufWriter::new(File::create(path).unwrap()); let mut enc = Encoder::new(w, size.width as u32, size.height as u32); enc.set_color(color_type); enc.set_depth(BitDepth::Eight); enc .write_header() .unwrap() .write_image_data(&*data) .unwrap(); } } /// An image that `ResourceCache` is unable to resolve during a capture. /// The image has to be transferred to `Renderer` and locked with the /// external image handler to get the actual contents and serialize them. #[derive(Deserialize, Serialize)] pub struct ExternalCaptureImage { pub short_path: String, pub descriptor: ImageDescriptor, pub external: ExternalImageData, } /// A short description of an external image to be saved separately as /// "externals/XX.ron", redirecting into a specific texture/blob with /// the corresponding UV rectangle. #[derive(Deserialize, Serialize)] pub struct PlainExternalImage { /// Path to the RON file describing the texel data. pub data: String, /// External image data source. pub external: ExternalImageData, /// UV sub-rectangle of the image. pub uv: TexelRect, }