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