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