tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }