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