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/. */
4 
5 #[macro_use]
6 extern crate clap;
7 #[macro_use]
8 extern crate log;
9 #[macro_use]
10 extern crate serde;
11 #[macro_use]
12 extern crate tracy_rs;
13 
14 mod angle;
15 mod blob;
16 mod egl;
17 mod parse_function;
18 mod perf;
19 mod png;
20 mod premultiply;
21 mod rawtest;
22 mod reftest;
23 mod test_invalidation;
24 mod test_shaders;
25 mod wrench;
26 mod yaml_frame_reader;
27 mod yaml_helper;
28 
29 use gleam::gl;
30 #[cfg(feature = "software")]
31 use gleam::gl::Gl;
32 use crate::perf::PerfHarness;
33 use crate::png::save_flipped;
34 use crate::rawtest::RawtestHarness;
35 use crate::reftest::{ReftestHarness, ReftestOptions};
36 use std::fs;
37 #[cfg(feature = "headless")]
38 use std::ffi::CString;
39 #[cfg(feature = "headless")]
40 use std::mem;
41 use std::os::raw::c_void;
42 use std::path::{Path, PathBuf};
43 use std::process;
44 use std::ptr;
45 use std::rc::Rc;
46 #[cfg(feature = "software")]
47 use std::slice;
48 use std::sync::mpsc::{channel, Sender, Receiver};
49 use webrender::DebugFlags;
50 use webrender::api::*;
51 use webrender::render_api::*;
52 use webrender::api::units::*;
53 use winit::dpi::{LogicalPosition, LogicalSize};
54 use winit::VirtualKeyCode;
55 use crate::wrench::{CapturedSequence, Wrench, WrenchThing};
56 use crate::yaml_frame_reader::YamlFrameReader;
57 
58 pub const PLATFORM_DEFAULT_FACE_NAME: &str = "Arial";
59 
60 pub static mut CURRENT_FRAME_NUMBER: u32 = 0;
61 
62 #[cfg(feature = "headless")]
63 pub struct HeadlessContext {
64     width: i32,
65     height: i32,
66     _context: osmesa_sys::OSMesaContext,
67     _buffer: Vec<u32>,
68 }
69 
70 #[cfg(not(feature = "headless"))]
71 pub struct HeadlessContext {
72     width: i32,
73     height: i32,
74 }
75 
76 impl HeadlessContext {
77     #[cfg(feature = "headless")]
new(width: i32, height: i32) -> Self78     fn new(width: i32, height: i32) -> Self {
79         let mut attribs = Vec::new();
80 
81         attribs.push(osmesa_sys::OSMESA_PROFILE);
82         attribs.push(osmesa_sys::OSMESA_CORE_PROFILE);
83         attribs.push(osmesa_sys::OSMESA_CONTEXT_MAJOR_VERSION);
84         attribs.push(3);
85         attribs.push(osmesa_sys::OSMESA_CONTEXT_MINOR_VERSION);
86         attribs.push(3);
87         attribs.push(osmesa_sys::OSMESA_DEPTH_BITS);
88         attribs.push(24);
89         attribs.push(0);
90 
91         let context =
92             unsafe { osmesa_sys::OSMesaCreateContextAttribs(attribs.as_ptr(), ptr::null_mut()) };
93 
94         assert!(!context.is_null());
95 
96         let mut buffer = vec![0; (width * height) as usize];
97 
98         unsafe {
99             let ret = osmesa_sys::OSMesaMakeCurrent(
100                 context,
101                 buffer.as_mut_ptr() as *mut _,
102                 gl::UNSIGNED_BYTE,
103                 width,
104                 height,
105             );
106             assert!(ret != 0);
107         };
108 
109         HeadlessContext {
110             width,
111             height,
112             _context: context,
113             _buffer: buffer,
114         }
115     }
116 
117     #[cfg(not(feature = "headless"))]
new(width: i32, height: i32) -> Self118     fn new(width: i32, height: i32) -> Self {
119         HeadlessContext { width, height }
120     }
121 
122     #[cfg(feature = "headless")]
get_proc_address(s: &str) -> *const c_void123     fn get_proc_address(s: &str) -> *const c_void {
124         let c_str = CString::new(s).expect("Unable to create CString");
125         unsafe { mem::transmute(osmesa_sys::OSMesaGetProcAddress(c_str.as_ptr())) }
126     }
127 
128     #[cfg(not(feature = "headless"))]
get_proc_address(_: &str) -> *const c_void129     fn get_proc_address(_: &str) -> *const c_void {
130         ptr::null() as *const _
131     }
132 }
133 
134 #[cfg(not(feature = "software"))]
135 mod swgl {
136     pub struct Context;
137 }
138 
139 pub enum WindowWrapper {
140     WindowedContext(glutin::WindowedContext<glutin::PossiblyCurrent>, Rc<dyn gl::Gl>, Option<swgl::Context>),
141     Angle(winit::Window, angle::Context, Rc<dyn gl::Gl>, Option<swgl::Context>),
142     Headless(HeadlessContext, Rc<dyn gl::Gl>, Option<swgl::Context>),
143 }
144 
145 pub struct HeadlessEventIterater;
146 
147 impl WindowWrapper {
148     #[cfg(feature = "software")]
upload_software_to_native(&self)149     fn upload_software_to_native(&self) {
150         if matches!(*self, WindowWrapper::Headless(..)) { return }
151         let swgl = match self.software_gl() {
152             Some(swgl) => swgl,
153             None => return,
154         };
155         swgl.finish();
156         let gl = self.native_gl();
157         let tex = gl.gen_textures(1)[0];
158         gl.bind_texture(gl::TEXTURE_2D, tex);
159         let (data_ptr, w, h, stride) = swgl.get_color_buffer(0, true);
160         assert!(stride == w * 4);
161         let buffer = unsafe { slice::from_raw_parts(data_ptr as *const u8, w as usize * h as usize * 4) };
162         gl.tex_image_2d(gl::TEXTURE_2D, 0, gl::RGBA8 as gl::GLint, w, h, 0, gl::BGRA, gl::UNSIGNED_BYTE, Some(buffer));
163         let fb = gl.gen_framebuffers(1)[0];
164         gl.bind_framebuffer(gl::READ_FRAMEBUFFER, fb);
165         gl.framebuffer_texture_2d(gl::READ_FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, tex, 0);
166         gl.blit_framebuffer(0, 0, w, h, 0, 0, w, h, gl::COLOR_BUFFER_BIT, gl::NEAREST);
167         gl.delete_framebuffers(&[fb]);
168         gl.delete_textures(&[tex]);
169         gl.finish();
170     }
171 
172     #[cfg(not(feature = "software"))]
upload_software_to_native(&self)173     fn upload_software_to_native(&self) {
174     }
175 
swap_buffers(&self)176     fn swap_buffers(&self) {
177         match *self {
178             WindowWrapper::WindowedContext(ref windowed_context, _, _) => {
179                 windowed_context.swap_buffers().unwrap()
180             }
181             WindowWrapper::Angle(_, ref context, _, _) => context.swap_buffers().unwrap(),
182             WindowWrapper::Headless(_, _, _) => {}
183         }
184     }
185 
get_inner_size(&self) -> DeviceIntSize186     fn get_inner_size(&self) -> DeviceIntSize {
187         fn inner_size(window: &winit::Window) -> DeviceIntSize {
188             let size = window
189                 .get_inner_size()
190                 .unwrap()
191                 .to_physical(window.get_hidpi_factor());
192             DeviceIntSize::new(size.width as i32, size.height as i32)
193         }
194         match *self {
195             WindowWrapper::WindowedContext(ref windowed_context, ..) => {
196                 inner_size(windowed_context.window())
197             }
198             WindowWrapper::Angle(ref window, ..) => inner_size(window),
199             WindowWrapper::Headless(ref context, ..) => DeviceIntSize::new(context.width, context.height),
200         }
201     }
202 
hidpi_factor(&self) -> f32203     fn hidpi_factor(&self) -> f32 {
204         match *self {
205             WindowWrapper::WindowedContext(ref windowed_context, ..) => {
206                 windowed_context.window().get_hidpi_factor() as f32
207             }
208             WindowWrapper::Angle(ref window, ..) => window.get_hidpi_factor() as f32,
209             WindowWrapper::Headless(..) => 1.0,
210         }
211     }
212 
resize(&mut self, size: DeviceIntSize)213     fn resize(&mut self, size: DeviceIntSize) {
214         match *self {
215             WindowWrapper::WindowedContext(ref mut windowed_context, ..) => {
216                 windowed_context.window()
217                     .set_inner_size(LogicalSize::new(size.width as f64, size.height as f64))
218             },
219             WindowWrapper::Angle(ref mut window, ..) => {
220                 window.set_inner_size(LogicalSize::new(size.width as f64, size.height as f64))
221             },
222             WindowWrapper::Headless(..) => unimplemented!(), // requites Glutin update
223         }
224     }
225 
set_title(&mut self, title: &str)226     fn set_title(&mut self, title: &str) {
227         match *self {
228             WindowWrapper::WindowedContext(ref windowed_context, ..) => {
229                 windowed_context.window().set_title(title)
230             }
231             WindowWrapper::Angle(ref window, ..) => window.set_title(title),
232             WindowWrapper::Headless(..) => (),
233         }
234     }
235 
software_gl(&self) -> Option<&swgl::Context>236     pub fn software_gl(&self) -> Option<&swgl::Context> {
237         match *self {
238             WindowWrapper::WindowedContext(_, _, ref swgl) |
239             WindowWrapper::Angle(_, _, _, ref swgl) |
240             WindowWrapper::Headless(_, _, ref swgl) => swgl.as_ref(),
241         }
242     }
243 
native_gl(&self) -> &dyn gl::Gl244     pub fn native_gl(&self) -> &dyn gl::Gl {
245         match *self {
246             WindowWrapper::WindowedContext(_, ref gl, _) |
247             WindowWrapper::Angle(_, _, ref gl, _) |
248             WindowWrapper::Headless(_, ref gl, _) => &**gl,
249         }
250     }
251 
252     #[cfg(feature = "software")]
gl(&self) -> &dyn gl::Gl253     pub fn gl(&self) -> &dyn gl::Gl {
254         if let Some(swgl) = self.software_gl() {
255             swgl
256         } else {
257             self.native_gl()
258         }
259     }
260 
is_software(&self) -> bool261     pub fn is_software(&self) -> bool {
262         self.software_gl().is_some()
263     }
264 
265     #[cfg(not(feature = "software"))]
gl(&self) -> &dyn gl::Gl266     pub fn gl(&self) -> &dyn gl::Gl {
267         self.native_gl()
268     }
269 
clone_gl(&self) -> Rc<dyn gl::Gl>270     pub fn clone_gl(&self) -> Rc<dyn gl::Gl> {
271         match *self {
272             WindowWrapper::WindowedContext(_, ref gl, ref swgl) |
273             WindowWrapper::Angle(_, _, ref gl, ref swgl) |
274             WindowWrapper::Headless(_, ref gl, ref swgl) => {
275                 match swgl {
276                     #[cfg(feature = "software")]
277                     Some(ref swgl) => Rc::new(*swgl),
278                     None => gl.clone(),
279                     #[cfg(not(feature = "software"))]
280                     _ => panic!(),
281                 }
282             }
283         }
284     }
285 
286 
287     #[cfg(feature = "software")]
update_software(&self, dim: DeviceIntSize)288     fn update_software(&self, dim: DeviceIntSize) {
289         if let Some(swgl) = self.software_gl() {
290             swgl.init_default_framebuffer(0, 0, dim.width, dim.height, 0, std::ptr::null_mut());
291         }
292     }
293 
294     #[cfg(not(feature = "software"))]
update_software(&self, _dim: DeviceIntSize)295     fn update_software(&self, _dim: DeviceIntSize) {
296     }
297 
update(&self, wrench: &mut Wrench)298     fn update(&self, wrench: &mut Wrench) {
299         let dim = self.get_inner_size();
300         self.update_software(dim);
301         wrench.update(dim);
302     }
303 }
304 
305 #[cfg(feature = "software")]
make_software_context() -> swgl::Context306 fn make_software_context() -> swgl::Context {
307     let ctx = swgl::Context::create();
308     ctx.make_current();
309     ctx
310 }
311 
312 #[cfg(not(feature = "software"))]
make_software_context() -> swgl::Context313 fn make_software_context() -> swgl::Context {
314     panic!("software feature not enabled")
315 }
316 
make_window( size: DeviceIntSize, vsync: bool, events_loop: &Option<winit::EventsLoop>, angle: bool, gl_request: glutin::GlRequest, software: bool, ) -> WindowWrapper317 fn make_window(
318     size: DeviceIntSize,
319     vsync: bool,
320     events_loop: &Option<winit::EventsLoop>,
321     angle: bool,
322     gl_request: glutin::GlRequest,
323     software: bool,
324 ) -> WindowWrapper {
325     let sw_ctx = if software {
326         Some(make_software_context())
327     } else {
328         None
329     };
330 
331     let wrapper = if let Some(events_loop) = events_loop {
332         let context_builder = glutin::ContextBuilder::new()
333             .with_gl(gl_request)
334             .with_vsync(vsync);
335         let window_builder = winit::WindowBuilder::new()
336             .with_title("WRench")
337             .with_multitouch()
338             .with_dimensions(LogicalSize::new(size.width as f64, size.height as f64));
339 
340         if angle {
341             angle::Context::with_window(
342                 window_builder, context_builder, events_loop
343             ).map(|(_window, _context)| {
344                 unsafe {
345                     _context
346                         .make_current()
347                         .expect("unable to make context current!");
348                 }
349 
350                 let gl = match _context.get_api() {
351                     glutin::Api::OpenGl => unsafe {
352                         gl::GlFns::load_with(|symbol| _context.get_proc_address(symbol) as *const _)
353                     },
354                     glutin::Api::OpenGlEs => unsafe {
355                         gl::GlesFns::load_with(|symbol| _context.get_proc_address(symbol) as *const _)
356                     },
357                     glutin::Api::WebGl => unimplemented!(),
358                 };
359 
360                 WindowWrapper::Angle(_window, _context, gl, sw_ctx)
361             }).unwrap()
362         } else {
363             let windowed_context = context_builder
364                 .build_windowed(window_builder, events_loop)
365                 .unwrap();
366 
367             let windowed_context = unsafe {
368                 windowed_context
369                     .make_current()
370                     .expect("unable to make context current!")
371             };
372 
373             let gl = match windowed_context.get_api() {
374                 glutin::Api::OpenGl => unsafe {
375                     gl::GlFns::load_with(
376                         |symbol| windowed_context.get_proc_address(symbol) as *const _
377                     )
378                 },
379                 glutin::Api::OpenGlEs => unsafe {
380                     gl::GlesFns::load_with(
381                         |symbol| windowed_context.get_proc_address(symbol) as *const _
382                     )
383                 },
384                 glutin::Api::WebGl => unimplemented!(),
385             };
386 
387             WindowWrapper::WindowedContext(windowed_context, gl, sw_ctx)
388         }
389     } else {
390         #[cfg_attr(not(feature = "software"), allow(unused_variables))]
391         let gl = if let Some(sw_ctx) = sw_ctx {
392             #[cfg(feature = "software")]
393             {
394                 Rc::new(sw_ctx)
395             }
396             #[cfg(not(feature = "software"))]
397             {
398                 unreachable!("make_software_context() should have failed if 'software' feature is not enabled")
399             }
400         } else {
401             match gl::GlType::default() {
402                 gl::GlType::Gl => unsafe {
403                     gl::GlFns::load_with(|symbol| {
404                         HeadlessContext::get_proc_address(symbol) as *const _
405                     })
406                 },
407                 gl::GlType::Gles => unsafe {
408                     gl::GlesFns::load_with(|symbol| {
409                         HeadlessContext::get_proc_address(symbol) as *const _
410                     })
411                 },
412             }
413         };
414         WindowWrapper::Headless(HeadlessContext::new(size.width, size.height), gl, sw_ctx)
415     };
416 
417     let gl = wrapper.gl();
418 
419     gl.clear_color(0.3, 0.0, 0.0, 1.0);
420 
421     let gl_version = gl.get_string(gl::VERSION);
422     let gl_renderer = gl.get_string(gl::RENDERER);
423 
424     println!("OpenGL version {}, {}", gl_version, gl_renderer);
425     println!(
426         "hidpi factor: {}",
427         wrapper.hidpi_factor()
428     );
429 
430     wrapper
431 }
432 
433 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
434 pub enum NotifierEvent {
435     WakeUp {
436         composite_needed: bool,
437     },
438     ShutDown,
439 }
440 
441 struct Notifier {
442     tx: Sender<NotifierEvent>,
443 }
444 
445 // setup a notifier so we can wait for frames to be finished
446 impl RenderNotifier for Notifier {
clone(&self) -> Box<dyn RenderNotifier>447     fn clone(&self) -> Box<dyn RenderNotifier> {
448         Box::new(Notifier {
449             tx: self.tx.clone(),
450         })
451     }
452 
wake_up( &self, composite_needed: bool, )453     fn wake_up(
454         &self,
455         composite_needed: bool,
456     ) {
457         let msg = NotifierEvent::WakeUp {
458             composite_needed,
459         };
460         self.tx.send(msg).unwrap();
461     }
462 
shut_down(&self)463     fn shut_down(&self) {
464         self.tx.send(NotifierEvent::ShutDown).unwrap();
465     }
466 
new_frame_ready(&self, _: DocumentId, _scrolled: bool, composite_needed: bool, _render_time: Option<u64>)467     fn new_frame_ready(&self,
468                        _: DocumentId,
469                        _scrolled: bool,
470                        composite_needed: bool,
471                        _render_time: Option<u64>) {
472         // TODO(gw): Refactor wrench so that it can take advantage of cases
473         //           where no composite is required when appropriate.
474         self.wake_up(composite_needed);
475     }
476 }
477 
create_notifier() -> (Box<dyn RenderNotifier>, Receiver<NotifierEvent>)478 fn create_notifier() -> (Box<dyn RenderNotifier>, Receiver<NotifierEvent>) {
479     let (tx, rx) = channel();
480     (Box::new(Notifier { tx }), rx)
481 }
482 
rawtest(mut wrench: Wrench, window: &mut WindowWrapper, rx: Receiver<NotifierEvent>)483 fn rawtest(mut wrench: Wrench, window: &mut WindowWrapper, rx: Receiver<NotifierEvent>) {
484     RawtestHarness::new(&mut wrench, window, &rx).run();
485     wrench.shut_down(rx);
486 }
487 
reftest<'a>( mut wrench: Wrench, window: &mut WindowWrapper, subargs: &clap::ArgMatches<'a>, rx: Receiver<NotifierEvent> ) -> usize488 fn reftest<'a>(
489     mut wrench: Wrench,
490     window: &mut WindowWrapper,
491     subargs: &clap::ArgMatches<'a>,
492     rx: Receiver<NotifierEvent>
493 ) -> usize {
494     let dim = window.get_inner_size();
495     let base_manifest = if cfg!(target_os = "android") {
496         Path::new("/sdcard/wrench/reftests/reftest.list")
497     } else {
498         Path::new("reftests/reftest.list")
499     };
500     let specific_reftest = subargs.value_of("REFTEST").map(Path::new);
501     let mut reftest_options = ReftestOptions::default();
502     if let Some(allow_max_diff) = subargs.value_of("fuzz_tolerance") {
503         reftest_options.allow_max_difference = allow_max_diff.parse().unwrap_or(1);
504         reftest_options.allow_num_differences = dim.width as usize * dim.height as usize;
505     }
506     let num_failures = ReftestHarness::new(&mut wrench, window, &rx)
507         .run(base_manifest, specific_reftest, &reftest_options);
508     wrench.shut_down(rx);
509     num_failures
510 }
511 
main()512 fn main() {
513     #[cfg(feature = "env_logger")]
514     env_logger::init();
515 
516     #[cfg(target_os = "macos")]
517     {
518         use core_foundation::{self as cf, base::TCFType};
519         let i = cf::bundle::CFBundle::main_bundle().info_dictionary();
520         let mut i = unsafe { i.to_mutable() };
521         i.set(
522             cf::string::CFString::new("NSSupportsAutomaticGraphicsSwitching"),
523             cf::boolean::CFBoolean::true_value().into_CFType(),
524         );
525     }
526 
527     let args_yaml = load_yaml!("args.yaml");
528     let clap = clap::App::from_yaml(args_yaml)
529         .setting(clap::AppSettings::ArgRequiredElseHelp);
530 
531     // On android devices, attempt to read command line arguments
532     // from a text file located at /sdcard/wrench/args.
533     let args = if cfg!(target_os = "android") {
534         // get full backtraces by default because it's hard to request
535         // externally on android
536         std::env::set_var("RUST_BACKTRACE", "full");
537 
538         let mut args = vec!["wrench".to_string()];
539 
540         if let Ok(wrench_args) = fs::read_to_string("/sdcard/wrench/args") {
541             for line in wrench_args.lines() {
542                 if let Some(envvar) = line.strip_prefix("env: ") {
543                     if let Some((lhs, rhs)) = envvar.split_once('=') {
544                         std::env::set_var(lhs, rhs);
545                     } else {
546                         std::env::set_var(envvar, "");
547                     }
548 
549                     continue;
550                 }
551                 for arg in line.split_whitespace() {
552                     args.push(arg.to_string());
553                 }
554             }
555         }
556 
557         clap.get_matches_from(&args)
558     } else {
559         clap.get_matches()
560     };
561 
562     // handle some global arguments
563     let res_path = args.value_of("shaders").map(PathBuf::from);
564     let size = args.value_of("size")
565         .map(|s| if s == "720p" {
566             DeviceIntSize::new(1280, 720)
567         } else if s == "1080p" {
568             DeviceIntSize::new(1920, 1080)
569         } else if s == "4k" {
570             DeviceIntSize::new(3840, 2160)
571         } else {
572             let x = s.find('x').expect(
573                 "Size must be specified exactly as 720p, 1080p, 4k, or width x height",
574             );
575             let w = s[0 .. x].parse::<i32>().expect("Invalid size width");
576             let h = s[x + 1 ..].parse::<i32>().expect("Invalid size height");
577             DeviceIntSize::new(w, h)
578         })
579         .unwrap_or(DeviceIntSize::new(1920, 1080));
580     let chase_primitive = match args.value_of("chase") {
581         Some(s) => {
582             if s.contains(',') {
583                 let items = s
584                     .split(',')
585                     .map(|s| s.parse::<f32>().unwrap())
586                     .collect::<Vec<_>>();
587                 let rect = LayoutRect::from_origin_and_size(
588                     LayoutPoint::new(items[0], items[1]),
589                     LayoutSize::new(items[2], items[3]),
590                 );
591                 webrender::ChasePrimitive::LocalRect(rect)
592             } else {
593                 let id = s.parse::<usize>().unwrap();
594                 webrender::ChasePrimitive::Id(webrender::PrimitiveDebugId(id))
595             }
596         }
597         None => webrender::ChasePrimitive::Nothing,
598     };
599 
600     let dump_shader_source = args.value_of("dump_shader_source").map(String::from);
601 
602     let mut events_loop = if args.is_present("headless") {
603         None
604     } else {
605         Some(winit::EventsLoop::new())
606     };
607 
608     let gl_request = match args.value_of("renderer") {
609         Some("es3") => {
610             glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (3, 0))
611         }
612         Some("gl3") => {
613             glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 2))
614         }
615         Some("default") | None => {
616             glutin::GlRequest::GlThenGles {
617                 opengl_version: (3, 2),
618                 opengles_version: (3, 0),
619             }
620         }
621         Some(api) => {
622             panic!("Unexpected renderer string {}", api);
623         }
624     };
625 
626     let software = args.is_present("software");
627 
628     let mut window = make_window(
629         size,
630         args.is_present("vsync"),
631         &events_loop,
632         args.is_present("angle"),
633         gl_request,
634         software,
635     );
636     let dim = window.get_inner_size();
637 
638     let needs_frame_notifier = args.subcommand_name().map_or(false, |name| {
639         ["perf", "reftest", "png", "rawtest", "test_invalidation"].contains(&name)
640     });
641     let (notifier, rx) = if needs_frame_notifier {
642         let (notifier, rx) = create_notifier();
643         (Some(notifier), Some(rx))
644     } else {
645         (None, None)
646     };
647 
648     let mut wrench = Wrench::new(
649         &mut window,
650         events_loop.as_mut().map(|el| el.create_proxy()),
651         res_path,
652         !args.is_present("use_unoptimized_shaders"),
653         dim,
654         args.is_present("rebuild"),
655         args.is_present("no_subpixel_aa"),
656         args.is_present("verbose"),
657         args.is_present("no_scissor"),
658         args.is_present("no_batch"),
659         args.is_present("precache"),
660         args.is_present("slow_subpixel"),
661         chase_primitive,
662         dump_shader_source,
663         notifier,
664     );
665 
666     if let Some(ui_str) = args.value_of("profiler_ui") {
667         wrench.renderer.set_profiler_ui(ui_str);
668     }
669 
670     window.update(&mut wrench);
671 
672     if let Some(window_title) = wrench.take_title() {
673         if !cfg!(windows) {
674             window.set_title(&window_title);
675         }
676     }
677 
678     if let Some(subargs) = args.subcommand_matches("show") {
679         let no_block = args.is_present("no_block");
680         let no_batch = args.is_present("no_batch");
681         render(
682             &mut wrench,
683             &mut window,
684             size,
685             &mut events_loop,
686             subargs,
687             no_block,
688             no_batch,
689         );
690     } else if let Some(subargs) = args.subcommand_matches("png") {
691         let surface = match subargs.value_of("surface") {
692             Some("screen") | None => png::ReadSurface::Screen,
693             Some("gpu-cache") => png::ReadSurface::GpuCache,
694             _ => panic!("Unknown surface argument value")
695         };
696         let output_path = subargs.value_of("OUTPUT").map(PathBuf::from);
697         let reader = YamlFrameReader::new_from_args(subargs);
698         png::png(&mut wrench, surface, &mut window, reader, rx.unwrap(), output_path);
699     } else if let Some(subargs) = args.subcommand_matches("reftest") {
700         // Exit with an error code in order to ensure the CI job fails.
701         process::exit(reftest(wrench, &mut window, subargs, rx.unwrap()) as _);
702     } else if args.subcommand_matches("rawtest").is_some() {
703         rawtest(wrench, &mut window, rx.unwrap());
704         return;
705     } else if let Some(subargs) = args.subcommand_matches("perf") {
706         // Perf mode wants to benchmark the total cost of drawing
707         // a new displaty list each frame.
708         wrench.rebuild_display_lists = true;
709 
710         let as_csv = subargs.is_present("csv");
711         let auto_filename = subargs.is_present("auto-filename");
712 
713         let warmup_frames = subargs.value_of("warmup_frames").map(|s| s.parse().unwrap());
714         let sample_count = subargs.value_of("sample_count").map(|s| s.parse().unwrap());
715 
716         let harness = PerfHarness::new(&mut wrench,
717                                        &mut window,
718                                        rx.unwrap(),
719                                        warmup_frames,
720                                        sample_count);
721 
722         let benchmark = subargs.value_of("benchmark").unwrap_or("benchmarks/benchmarks.list");
723         println!("Benchmark: {}", benchmark);
724         let base_manifest = Path::new(benchmark);
725 
726         let mut filename = subargs.value_of("filename").unwrap().to_string();
727         if auto_filename {
728             let timestamp = chrono::Local::now().format("%Y-%m-%d-%H-%M-%S");
729             filename.push_str(
730                 &format!("/wrench-perf-{}.{}",
731                             timestamp,
732                             if as_csv { "csv" } else { "json" }));
733         }
734         harness.run(base_manifest, &filename, as_csv);
735         return;
736     } else if args.subcommand_matches("test_invalidation").is_some() {
737         let harness = test_invalidation::TestHarness::new(
738             &mut wrench,
739             &mut window,
740             rx.unwrap(),
741         );
742 
743         harness.run();
744     } else if let Some(subargs) = args.subcommand_matches("compare_perf") {
745         let first_filename = subargs.value_of("first_filename").unwrap();
746         let second_filename = subargs.value_of("second_filename").unwrap();
747         perf::compare(first_filename, second_filename);
748         return;
749     } else if args.subcommand_matches("test_init").is_some() {
750         // Wrench::new() unwraps the Renderer initialization, so if
751         // we reach this point then we have initialized successfully.
752         println!("Initialization successful");
753     } else if args.subcommand_matches("test_shaders").is_some() {
754         test_shaders::test_shaders();
755     } else {
756         panic!("Should never have gotten here! {:?}", args);
757     };
758 
759     wrench.renderer.deinit();
760 
761     // On android force-exit the process otherwise it stays running forever.
762     #[cfg(target_os = "android")]
763     process::exit(0);
764 }
765 
render<'a>( wrench: &mut Wrench, window: &mut WindowWrapper, size: DeviceIntSize, events_loop: &mut Option<winit::EventsLoop>, subargs: &clap::ArgMatches<'a>, no_block: bool, no_batch: bool, )766 fn render<'a>(
767     wrench: &mut Wrench,
768     window: &mut WindowWrapper,
769     size: DeviceIntSize,
770     events_loop: &mut Option<winit::EventsLoop>,
771     subargs: &clap::ArgMatches<'a>,
772     no_block: bool,
773     no_batch: bool,
774 ) {
775     let input_path = subargs.value_of("INPUT").map(PathBuf::from).unwrap();
776 
777     // If the input is a directory, we are looking at a capture.
778     let mut thing = if input_path.join("scenes").as_path().is_dir() {
779         let scene_id = subargs.value_of("scene-id").map(|z| z.parse::<u32>().unwrap());
780         let frame_id = subargs.value_of("frame-id").map(|z| z.parse::<u32>().unwrap());
781         Box::new(CapturedSequence::new(
782             input_path,
783             scene_id.unwrap_or(1),
784             frame_id.unwrap_or(1),
785         ))
786     } else if input_path.as_path().is_dir() {
787         let mut documents = wrench.api.load_capture(input_path, None);
788         println!("loaded {:?}", documents.iter().map(|cd| cd.document_id).collect::<Vec<_>>());
789         let captured = documents.swap_remove(0);
790         wrench.document_id = captured.document_id;
791         Box::new(captured) as Box<dyn WrenchThing>
792     } else {
793         match input_path.extension().and_then(std::ffi::OsStr::to_str) {
794             Some("yaml") => {
795                 Box::new(YamlFrameReader::new_from_args(subargs)) as Box<dyn WrenchThing>
796             }
797             _ => panic!("Tried to render with an unknown file type."),
798         }
799     };
800 
801     let mut show_help = false;
802     let mut do_loop = false;
803     let mut cursor_position = WorldPoint::zero();
804 
805     window.update(wrench);
806     thing.do_frame(wrench);
807 
808     if let Some(fb_size) = wrench.renderer.device_size() {
809         window.resize(fb_size);
810     }
811 
812     let mut debug_flags = DebugFlags::empty();
813     debug_flags.set(DebugFlags::DISABLE_BATCHING, no_batch);
814 
815     // Default the profile overlay on for android.
816     if cfg!(target_os = "android") {
817         debug_flags.toggle(DebugFlags::PROFILER_DBG);
818         wrench.api.send_debug_cmd(DebugCommand::SetFlags(debug_flags));
819     }
820 
821     let mut body = |wrench: &mut Wrench, events: Vec<winit::Event>| {
822         let mut do_frame = false;
823         let mut do_render = !events.is_empty();
824 
825         for event in events {
826             match event {
827                 winit::Event::Awakened => {}
828                 winit::Event::WindowEvent { event, .. } => match event {
829                     winit::WindowEvent::CloseRequested => {
830                         return winit::ControlFlow::Break;
831                     }
832                     winit::WindowEvent::Refresh |
833                     winit::WindowEvent::Focused(..) => {}
834                     winit::WindowEvent::CursorMoved { position: LogicalPosition { x, y }, .. } => {
835                         cursor_position = WorldPoint::new(x as f32, y as f32);
836                         wrench.renderer.set_cursor_position(
837                             DeviceIntPoint::new(
838                                 cursor_position.x.round() as i32,
839                                 cursor_position.y.round() as i32,
840                             ),
841                         );
842                     }
843                     winit::WindowEvent::KeyboardInput {
844                         input: winit::KeyboardInput {
845                             state: winit::ElementState::Pressed,
846                             virtual_keycode: Some(vk),
847                             ..
848                         },
849                         ..
850                     } => match vk {
851                         VirtualKeyCode::Escape => {
852                             return winit::ControlFlow::Break;
853                         }
854                         VirtualKeyCode::B => {
855                             debug_flags.toggle(DebugFlags::INVALIDATION_DBG);
856                             wrench.api.send_debug_cmd(DebugCommand::SetFlags(debug_flags));
857                         }
858                         VirtualKeyCode::P => {
859                             debug_flags.toggle(DebugFlags::PROFILER_DBG);
860                             wrench.api.send_debug_cmd(DebugCommand::SetFlags(debug_flags));
861                         }
862                         VirtualKeyCode::O => {
863                             debug_flags.toggle(DebugFlags::RENDER_TARGET_DBG);
864                             wrench.api.send_debug_cmd(DebugCommand::SetFlags(debug_flags));
865                         }
866                         VirtualKeyCode::I => {
867                             debug_flags.toggle(DebugFlags::TEXTURE_CACHE_DBG);
868                             wrench.api.send_debug_cmd(DebugCommand::SetFlags(debug_flags));
869                         }
870                         VirtualKeyCode::D => {
871                             debug_flags.toggle(DebugFlags::PICTURE_CACHING_DBG);
872                             wrench.api.send_debug_cmd(DebugCommand::SetFlags(debug_flags));
873                         }
874                         VirtualKeyCode::Q => {
875                             debug_flags.toggle(DebugFlags::GPU_TIME_QUERIES | DebugFlags::GPU_SAMPLE_QUERIES);
876                             wrench.api.send_debug_cmd(DebugCommand::SetFlags(debug_flags));
877                         }
878                         VirtualKeyCode::V => {
879                             debug_flags.toggle(DebugFlags::SHOW_OVERDRAW);
880                             wrench.api.send_debug_cmd(DebugCommand::SetFlags(debug_flags));
881                         }
882                         VirtualKeyCode::G => {
883                             debug_flags.toggle(DebugFlags::GPU_CACHE_DBG);
884                             wrench.api.send_debug_cmd(DebugCommand::SetFlags(debug_flags));
885 
886                             // force scene rebuild to see the full set of used GPU cache entries
887                             let mut txn = Transaction::new();
888                             txn.set_root_pipeline(wrench.root_pipeline_id);
889                             wrench.api.send_transaction(wrench.document_id, txn);
890 
891                             do_render = false;
892                             do_frame = true;
893                         }
894                         VirtualKeyCode::M => {
895                             wrench.api.notify_memory_pressure();
896                         }
897                         VirtualKeyCode::L => {
898                             do_loop = !do_loop;
899                         }
900                         VirtualKeyCode::Left => {
901                             thing.prev_frame();
902                             do_render = false;
903                             do_frame = true;
904                         }
905                         VirtualKeyCode::Right => {
906                             thing.next_frame();
907                             do_render = false;
908                             do_frame = true;
909                         }
910                         VirtualKeyCode::H => {
911                             show_help = !show_help;
912                         }
913                         VirtualKeyCode::C => {
914                             let path = PathBuf::from("../captures/wrench");
915                             wrench.api.save_capture(path, CaptureBits::all());
916                             do_render = false;
917                         }
918                         VirtualKeyCode::X => {
919                             let results = wrench.api.hit_test(
920                                 wrench.document_id,
921                                 cursor_position,
922                             );
923 
924                             println!("Hit test results:");
925                             for item in &results.items {
926                                 println!("  • {:?}", item);
927                             }
928                             println!();
929                             do_render = false;
930                         }
931                         VirtualKeyCode::Z => {
932                             debug_flags.toggle(DebugFlags::ZOOM_DBG);
933                             wrench.api.send_debug_cmd(DebugCommand::SetFlags(debug_flags));
934                         }
935                         VirtualKeyCode::Y => {
936                             println!("Clearing all caches...");
937                             wrench.api.send_debug_cmd(DebugCommand::ClearCaches(ClearCache::all()));
938                             do_frame = true;
939                         }
940                         _other_virtual_keycode => { do_render = false; }
941                     }
942                     _other_window_event => { do_render = false; }
943                 },
944                 _other_event => { do_render = false; }
945             }
946         }
947 
948         window.update(wrench);
949 
950         if do_frame {
951             let frame_num = thing.do_frame(wrench);
952             unsafe {
953                 CURRENT_FRAME_NUMBER = frame_num;
954             }
955         }
956 
957         if do_render {
958             if show_help {
959                 wrench.show_onscreen_help();
960             }
961 
962             wrench.render();
963             window.upload_software_to_native();
964             window.swap_buffers();
965 
966             if do_loop {
967                 thing.next_frame();
968             }
969         }
970 
971         winit::ControlFlow::Continue
972     };
973 
974     if let Some(events_loop) = events_loop.as_mut() {
975         // We want to ensure that we:
976         //
977         // (a) Block the thread when no events are present (for CPU/battery purposes)
978         // (b) Don't lag the input events by having the event queue back up.
979         loop {
980             let mut pending_events = Vec::new();
981 
982             // Block the thread until at least one event arrives
983             // On Android, we are generally profiling when running
984             // wrench, and don't want to block on UI events.
985             if !no_block && cfg!(not(target_os = "android")) {
986                 events_loop.run_forever(|event| {
987                     pending_events.push(event);
988                     winit::ControlFlow::Break
989                 });
990             }
991 
992             // Collect any other pending events that are also available
993             events_loop.poll_events(|event| {
994                 pending_events.push(event);
995             });
996 
997             // Ensure there is at least one event present so that the
998             // frame gets rendered.
999             if pending_events.is_empty() {
1000                 pending_events.push(winit::Event::Awakened);
1001             }
1002 
1003             // Process all of those pending events in the next vsync period
1004             if body(wrench, pending_events) == winit::ControlFlow::Break {
1005                 break;
1006             }
1007         }
1008     } else {
1009         while body(wrench, vec![winit::Event::Awakened]) == winit::ControlFlow::Continue {}
1010         let fb_rect = FramebufferIntSize::new(size.width, size.height).into();
1011         let pixels = wrench.renderer.read_pixels_rgba8(fb_rect);
1012         save_flipped("screenshot.png", pixels, size);
1013     }
1014 }
1015