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 use crate::{WindowWrapper, NotifierEvent}; 6 use base64; 7 use semver; 8 use image::load as load_piston_image; 9 use image::png::PNGEncoder; 10 use image::{ColorType, ImageFormat}; 11 use crate::parse_function::parse_function; 12 use crate::png::save_flipped; 13 use std::{cmp, env}; 14 use std::fmt::{Display, Error, Formatter}; 15 use std::fs::File; 16 use std::io::{BufRead, BufReader}; 17 use std::path::{Path, PathBuf}; 18 use std::process::Command; 19 use std::sync::mpsc::Receiver; 20 use webrender::RenderResults; 21 use webrender::api::*; 22 use webrender::render_api::*; 23 use webrender::api::units::*; 24 use crate::wrench::{Wrench, WrenchThing}; 25 use crate::yaml_frame_reader::YamlFrameReader; 26 27 28 const OPTION_DISABLE_SUBPX: &str = "disable-subpixel"; 29 const OPTION_DISABLE_AA: &str = "disable-aa"; 30 const OPTION_DISABLE_DUAL_SOURCE_BLENDING: &str = "disable-dual-source-blending"; 31 const OPTION_ALLOW_MIPMAPS: &str = "allow-mipmaps"; 32 33 pub struct ReftestOptions { 34 // These override values that are lower. 35 pub allow_max_difference: usize, 36 pub allow_num_differences: usize, 37 } 38 39 impl ReftestOptions { default() -> Self40 pub fn default() -> Self { 41 ReftestOptions { 42 allow_max_difference: 0, 43 allow_num_differences: 0, 44 } 45 } 46 } 47 48 #[derive(Debug, Copy, Clone)] 49 pub enum ReftestOp { 50 /// Expect that the images match the reference 51 Equal, 52 /// Expect that the images *don't* match the reference 53 NotEqual, 54 /// Expect that drawing the reference at different tiles sizes gives the same pixel exact result. 55 Accurate, 56 /// Expect that drawing the reference at different tiles sizes gives a *different* pixel exact result. 57 Inaccurate, 58 } 59 60 impl Display for ReftestOp { fmt(&self, f: &mut Formatter) -> Result<(), Error>61 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 62 write!( 63 f, 64 "{}", 65 match *self { 66 ReftestOp::Equal => "==".to_owned(), 67 ReftestOp::NotEqual => "!=".to_owned(), 68 ReftestOp::Accurate => "**".to_owned(), 69 ReftestOp::Inaccurate => "!*".to_owned(), 70 } 71 ) 72 } 73 } 74 75 #[derive(Debug)] 76 enum ExtraCheck { 77 DrawCalls(usize), 78 AlphaTargets(usize), 79 ColorTargets(usize), 80 } 81 82 impl ExtraCheck { run(&self, results: &[RenderResults]) -> bool83 fn run(&self, results: &[RenderResults]) -> bool { 84 match *self { 85 ExtraCheck::DrawCalls(x) => 86 x == results.last().unwrap().stats.total_draw_calls, 87 ExtraCheck::AlphaTargets(x) => 88 x == results.last().unwrap().stats.alpha_target_count, 89 ExtraCheck::ColorTargets(x) => 90 x == results.last().unwrap().stats.color_target_count, 91 } 92 } 93 } 94 95 pub struct RefTestFuzzy { 96 max_difference: usize, 97 num_differences: usize, 98 } 99 100 pub struct Reftest { 101 op: ReftestOp, 102 test: Vec<PathBuf>, 103 reference: PathBuf, 104 font_render_mode: Option<FontRenderMode>, 105 fuzziness: Vec<RefTestFuzzy>, 106 extra_checks: Vec<ExtraCheck>, 107 disable_dual_source_blending: bool, 108 allow_mipmaps: bool, 109 force_subpixel_aa_where_possible: Option<bool>, 110 } 111 112 impl Reftest { 113 /// Check the positive case (expecting equality) and report details if different check_and_report_equality_failure( &self, comparison: ReftestImageComparison, test: &ReftestImage, reference: &ReftestImage, ) -> bool114 fn check_and_report_equality_failure( 115 &self, 116 comparison: ReftestImageComparison, 117 test: &ReftestImage, 118 reference: &ReftestImage, 119 ) -> bool { 120 match comparison { 121 ReftestImageComparison::Equal => { 122 true 123 } 124 ReftestImageComparison::NotEqual { difference_histogram, max_difference, count_different } => { 125 // Each entry in the sorted self.fuzziness list represents a bucket which 126 // allows at most num_differences pixels with a difference of at most 127 // max_difference -- but with the caveat that a difference which is small 128 // enough to be less than a max_difference of an earlier bucket, must be 129 // counted against that bucket. 130 // 131 // Thus the test will fail if the number of pixels with a difference 132 // > fuzzy[j-1].max_difference and <= fuzzy[j].max_difference 133 // exceeds fuzzy[j].num_differences. 134 // 135 // (For the first entry, consider fuzzy[j-1] to allow zero pixels of zero 136 // difference). 137 // 138 // For example, say we have this histogram of differences: 139 // 140 // | [0] [1] [2] [3] [4] [5] [6] ... [255] 141 // ------+------------------------------------------ 142 // Hist. | 0 3 2 1 6 2 0 ... 0 143 // 144 // Ie. image comparison found 3 pixels that differ by 1, 2 that differ by 2, etc. 145 // (Note that entry 0 is always zero, we don't count matching pixels.) 146 // 147 // First we calculate an inclusive prefix sum: 148 // 149 // | [0] [1] [2] [3] [4] [5] [6] ... [255] 150 // ------+------------------------------------------ 151 // Hist. | 0 3 2 1 6 2 0 ... 0 152 // Sum | 0 3 5 6 12 14 14 ... 14 153 // 154 // Let's say the fuzzy statements are: 155 // Fuzzy( 2, 6 ) -- allow up to 6 pixels that differ by 2 or less 156 // Fuzzy( 4, 8 ) -- allow up to 8 pixels that differ by 4 or less _but_ 157 // also by more than 2 (= by 3 or 4). 158 // 159 // The first check is Sum[2] <= max 6 which passes: 5 <= 6. 160 // The second check is Sum[4] - Sum[2] <= max 8 which passes: 12-5 <= 8. 161 // Finally we check if there are any pixels that exceed the max difference (4) 162 // by checking Sum[255] - Sum[4] which shows there are 14-12 == 2 so we fail. 163 164 let prefix_sum = difference_histogram.iter() 165 .scan(0, |sum, i| { *sum += i; Some(*sum) }) 166 .collect::<Vec<_>>(); 167 168 // check each fuzzy statement for violations. 169 assert_eq!(0, difference_histogram[0]); 170 assert_eq!(0, prefix_sum[0]); 171 172 // loop invariant: this is the max_difference of the previous iteration's 'fuzzy' 173 let mut previous_max_diff = 0; 174 175 // loop invariant: this is the number of pixels to ignore as they have been counted 176 // against previous iterations' fuzzy statements. 177 let mut previous_sum_fail = 0; // == prefix_sum[previous_max_diff] 178 179 let mut is_failing = false; 180 let mut fail_text = String::new(); 181 182 for fuzzy in &self.fuzziness { 183 let fuzzy_max_difference = cmp::min(255, fuzzy.max_difference); 184 let num_differences = prefix_sum[fuzzy_max_difference] - previous_sum_fail; 185 if num_differences > fuzzy.num_differences { 186 fail_text.push_str( 187 &format!("{} differences > {} and <= {} (allowed {}); ", 188 num_differences, 189 previous_max_diff, fuzzy_max_difference, 190 fuzzy.num_differences)); 191 is_failing = true; 192 } 193 previous_max_diff = fuzzy_max_difference; 194 previous_sum_fail = prefix_sum[previous_max_diff]; 195 } 196 // do we have any pixels with a difference above the highest allowed 197 // max difference? if so, we fail the test: 198 let num_differences = prefix_sum[255] - previous_sum_fail; 199 if num_differences > 0 { 200 fail_text.push_str( 201 &format!("{} num_differences > {} and <= {} (allowed {}); ", 202 num_differences, 203 previous_max_diff, 255, 204 0)); 205 is_failing = true; 206 } 207 208 if is_failing { 209 println!( 210 "{} | {} | {}: {}, {}: {} | {}", 211 "REFTEST TEST-UNEXPECTED-FAIL", 212 self, 213 "image comparison, max difference", 214 max_difference, 215 "number of differing pixels", 216 count_different, 217 fail_text, 218 ); 219 println!("REFTEST IMAGE 1 (TEST): {}", test.clone().create_data_uri()); 220 println!( 221 "REFTEST IMAGE 2 (REFERENCE): {}", 222 reference.clone().create_data_uri() 223 ); 224 println!("REFTEST TEST-END | {}", self); 225 226 false 227 } else { 228 true 229 } 230 } 231 } 232 } 233 234 /// Report details of the negative case report_unexpected_equality(&self)235 fn report_unexpected_equality(&self) { 236 println!("REFTEST TEST-UNEXPECTED-FAIL | {} | image comparison", self); 237 println!("REFTEST TEST-END | {}", self); 238 } 239 } 240 241 impl Display for Reftest { fmt(&self, f: &mut Formatter) -> Result<(), Error>242 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 243 let paths: Vec<String> = self.test.iter().map(|t| t.display().to_string()).collect(); 244 write!( 245 f, 246 "{} {} {}", 247 paths.join(", "), 248 self.op, 249 self.reference.display() 250 ) 251 } 252 } 253 254 #[derive(Clone)] 255 pub struct ReftestImage { 256 pub data: Vec<u8>, 257 pub size: DeviceIntSize, 258 } 259 260 #[derive(Debug, Clone)] 261 pub enum ReftestImageComparison { 262 Equal, 263 NotEqual { 264 /// entry[j] = number of pixels with a difference of exactly j 265 difference_histogram: Vec<usize>, 266 max_difference: usize, 267 count_different: usize, 268 }, 269 } 270 271 impl ReftestImage { compare(&self, other: &ReftestImage) -> ReftestImageComparison272 pub fn compare(&self, other: &ReftestImage) -> ReftestImageComparison { 273 assert_eq!(self.size, other.size); 274 assert_eq!(self.data.len(), other.data.len()); 275 assert_eq!(self.data.len() % 4, 0); 276 277 let mut histogram = [0usize; 256]; 278 let mut count = 0; 279 let mut max = 0; 280 281 for (a, b) in self.data.chunks(4).zip(other.data.chunks(4)) { 282 if a != b { 283 let pixel_max = a.iter() 284 .zip(b.iter()) 285 .map(|(x, y)| (*x as isize - *y as isize).abs() as usize) 286 .max() 287 .unwrap(); 288 289 count += 1; 290 assert!(pixel_max < 256, "pixel values are not 8 bit, update the histogram binning code"); 291 // deliberately avoid counting pixels that match -- 292 // histogram[0] stays at zero. 293 // this helps our prefix sum later during analysis to 294 // only count actual differences. 295 histogram[pixel_max as usize] += 1; 296 max = cmp::max(max, pixel_max); 297 } 298 } 299 300 if count != 0 { 301 ReftestImageComparison::NotEqual { 302 difference_histogram: histogram.to_vec(), 303 max_difference: max, 304 count_different: count, 305 } 306 } else { 307 ReftestImageComparison::Equal 308 } 309 } 310 create_data_uri(mut self) -> String311 pub fn create_data_uri(mut self) -> String { 312 let width = self.size.width; 313 let height = self.size.height; 314 315 // flip image vertically (texture is upside down) 316 let orig_pixels = self.data.clone(); 317 let stride = width as usize * 4; 318 for y in 0 .. height as usize { 319 let dst_start = y * stride; 320 let src_start = (height as usize - y - 1) * stride; 321 let src_slice = &orig_pixels[src_start .. src_start + stride]; 322 (&mut self.data[dst_start .. dst_start + stride]) 323 .clone_from_slice(&src_slice[.. stride]); 324 } 325 326 let mut png: Vec<u8> = vec![]; 327 { 328 let encoder = PNGEncoder::new(&mut png); 329 encoder 330 .encode(&self.data[..], width as u32, height as u32, ColorType::Rgba8) 331 .expect("Unable to encode PNG!"); 332 } 333 let png_base64 = base64::encode(&png); 334 format!("data:image/png;base64,{}", png_base64) 335 } 336 } 337 338 struct ReftestManifest { 339 reftests: Vec<Reftest>, 340 } 341 impl ReftestManifest { new(manifest: &Path, environment: &ReftestEnvironment, options: &ReftestOptions) -> ReftestManifest342 fn new(manifest: &Path, environment: &ReftestEnvironment, options: &ReftestOptions) -> ReftestManifest { 343 let dir = manifest.parent().unwrap(); 344 let f = 345 File::open(manifest).expect(&format!("couldn't open manifest: {}", manifest.display())); 346 let file = BufReader::new(&f); 347 348 let mut reftests = Vec::new(); 349 350 for line in file.lines() { 351 let l = line.unwrap(); 352 353 // strip the comments 354 let s = &l[0 .. l.find('#').unwrap_or(l.len())]; 355 let s = s.trim(); 356 if s.is_empty() { 357 continue; 358 } 359 360 let tokens: Vec<&str> = s.split_whitespace().collect(); 361 362 let mut fuzziness = Vec::new(); 363 let mut op = None; 364 let mut font_render_mode = None; 365 let mut extra_checks = vec![]; 366 let mut disable_dual_source_blending = false; 367 let mut allow_mipmaps = false; 368 let mut force_subpixel_aa_where_possible = None; 369 370 let mut parse_command = |token: &str| -> bool { 371 match token { 372 function if function.starts_with("force_subpixel_aa_where_possible(") => { 373 let (_, args, _) = parse_function(function); 374 force_subpixel_aa_where_possible = Some(args[0].parse().unwrap()); 375 } 376 function if function.starts_with("fuzzy-range(") || 377 function.starts_with("fuzzy-range-if(") => { 378 let (_, mut args, _) = parse_function(function); 379 if function.starts_with("fuzzy-range-if(") { 380 if !environment.parse_condition(args.remove(0)).expect("unknown condition") { 381 return true; 382 } 383 fuzziness.clear(); 384 } 385 let num_range = args.len() / 2; 386 for range in 0..num_range { 387 let mut max = args[range * 2 + 0]; 388 let mut num = args[range * 2 + 1]; 389 if max.starts_with("<=") { // trim_start_matches would allow <=<=123 390 max = &max[2..]; 391 } 392 if num.starts_with("*") { 393 num = &num[1..]; 394 } 395 let max_difference = max.parse().unwrap(); 396 let num_differences = num.parse().unwrap(); 397 fuzziness.push(RefTestFuzzy { max_difference, num_differences }); 398 } 399 } 400 function if function.starts_with("fuzzy(") || 401 function.starts_with("fuzzy-if(") => { 402 let (_, mut args, _) = parse_function(function); 403 if function.starts_with("fuzzy-if(") { 404 if !environment.parse_condition(args.remove(0)).expect("unknown condition") { 405 return true; 406 } 407 fuzziness.clear(); 408 } 409 let max_difference = args[0].parse().unwrap(); 410 let num_differences = args[1].parse().unwrap(); 411 assert!(fuzziness.is_empty()); // if this fires, consider fuzzy-range instead 412 fuzziness.push(RefTestFuzzy { max_difference, num_differences }); 413 } 414 function if function.starts_with("draw_calls(") => { 415 let (_, args, _) = parse_function(function); 416 extra_checks.push(ExtraCheck::DrawCalls(args[0].parse().unwrap())); 417 } 418 function if function.starts_with("alpha_targets(") => { 419 let (_, args, _) = parse_function(function); 420 extra_checks.push(ExtraCheck::AlphaTargets(args[0].parse().unwrap())); 421 } 422 function if function.starts_with("color_targets(") => { 423 let (_, args, _) = parse_function(function); 424 extra_checks.push(ExtraCheck::ColorTargets(args[0].parse().unwrap())); 425 } 426 options if options.starts_with("options(") => { 427 let (_, args, _) = parse_function(options); 428 if args.iter().any(|arg| arg == &OPTION_DISABLE_SUBPX) { 429 font_render_mode = Some(FontRenderMode::Alpha); 430 } 431 if args.iter().any(|arg| arg == &OPTION_DISABLE_AA) { 432 font_render_mode = Some(FontRenderMode::Mono); 433 } 434 if args.iter().any(|arg| arg == &OPTION_DISABLE_DUAL_SOURCE_BLENDING) { 435 disable_dual_source_blending = true; 436 } 437 if args.iter().any(|arg| arg == &OPTION_ALLOW_MIPMAPS) { 438 allow_mipmaps = true; 439 } 440 } 441 _ => return false, 442 } 443 return true; 444 }; 445 446 let mut paths = vec![]; 447 for (i, token) in tokens.iter().enumerate() { 448 match *token { 449 "include" => { 450 assert!(i == 0, "include must be by itself"); 451 let include = dir.join(tokens[1]); 452 453 reftests.append( 454 &mut ReftestManifest::new(include.as_path(), environment, options).reftests, 455 ); 456 457 break; 458 } 459 "==" => { 460 op = Some(ReftestOp::Equal); 461 } 462 "!=" => { 463 op = Some(ReftestOp::NotEqual); 464 } 465 "**" => { 466 op = Some(ReftestOp::Accurate); 467 } 468 "!*" => { 469 op = Some(ReftestOp::Inaccurate); 470 } 471 cond if cond.starts_with("if(") => { 472 let (_, args, _) = parse_function(cond); 473 if environment.parse_condition(args[0]).expect("unknown condition") { 474 for command in &args[1..] { 475 parse_command(command); 476 } 477 } 478 } 479 command if parse_command(command) => {} 480 _ => { 481 match environment.parse_condition(*token) { 482 Some(true) => {} 483 Some(false) => break, 484 _ => paths.push(dir.join(*token)), 485 } 486 } 487 } 488 } 489 490 // Don't try to add tests for include lines. 491 let op = match op { 492 Some(op) => op, 493 None => { 494 assert!(paths.is_empty(), "paths = {:?}", paths); 495 continue; 496 } 497 }; 498 499 // The reference is the last path provided. If multiple paths are 500 // passed for the test, they render sequentially before being 501 // compared to the reference, which is useful for testing 502 // invalidation. 503 let reference = paths.pop().unwrap(); 504 let test = paths; 505 506 if environment.platform == "android" { 507 // Add some fuzz on mobile as we do for non-wrench reftests. 508 // First remove the ranges with difference <= 2, otherwise they might cause the 509 // test to fail before the new range is picked up. 510 fuzziness.retain(|fuzzy| fuzzy.max_difference > 2); 511 fuzziness.push(RefTestFuzzy { max_difference: 2, num_differences: std::usize::MAX }); 512 } 513 514 // to avoid changing the meaning of existing tests, the case of 515 // only a single (or no) 'fuzzy' keyword means we use the max 516 // of that fuzzy and options.allow_.. (we don't want that to 517 // turn into a test that allows fuzzy.allow_ *plus* options.allow_): 518 match fuzziness.len() { 519 0 => fuzziness.push(RefTestFuzzy { 520 max_difference: options.allow_max_difference, 521 num_differences: options.allow_num_differences }), 522 1 => { 523 let mut fuzzy = &mut fuzziness[0]; 524 fuzzy.max_difference = cmp::max(fuzzy.max_difference, options.allow_max_difference); 525 fuzzy.num_differences = cmp::max(fuzzy.num_differences, options.allow_num_differences); 526 }, 527 _ => { 528 // ignore options, use multiple fuzzy keywords instead. make sure 529 // the list is sorted to speed up counting violations. 530 fuzziness.sort_by(|a, b| a.max_difference.cmp(&b.max_difference)); 531 for pair in fuzziness.windows(2) { 532 if pair[0].max_difference == pair[1].max_difference { 533 println!("Warning: repeated fuzzy of max_difference {} ignored.", 534 pair[1].max_difference); 535 } 536 } 537 } 538 } 539 540 reftests.push(Reftest { 541 op, 542 test, 543 reference, 544 font_render_mode, 545 fuzziness, 546 extra_checks, 547 disable_dual_source_blending, 548 allow_mipmaps, 549 force_subpixel_aa_where_possible, 550 }); 551 } 552 553 ReftestManifest { reftests: reftests } 554 } 555 find(&self, prefix: &Path) -> Vec<&Reftest>556 fn find(&self, prefix: &Path) -> Vec<&Reftest> { 557 self.reftests 558 .iter() 559 .filter(|x| { 560 x.test.iter().any(|t| t.starts_with(prefix)) || x.reference.starts_with(prefix) 561 }) 562 .collect() 563 } 564 } 565 566 struct YamlRenderOutput { 567 image: ReftestImage, 568 results: RenderResults, 569 } 570 571 struct ReftestEnvironment { 572 pub platform: &'static str, 573 pub version: Option<semver::Version>, 574 pub mode: &'static str, 575 } 576 577 impl ReftestEnvironment { new(wrench: &Wrench, window: &WindowWrapper) -> Self578 fn new(wrench: &Wrench, window: &WindowWrapper) -> Self { 579 Self { 580 platform: Self::platform(wrench, window), 581 version: Self::version(wrench, window), 582 mode: Self::mode(), 583 } 584 } 585 has(&self, condition: &str) -> bool586 fn has(&self, condition: &str) -> bool { 587 if self.platform == condition || self.mode == condition { 588 return true; 589 } 590 match (&self.version, &semver::VersionReq::parse(condition)) { 591 (Some(v), Ok(r)) => { 592 if r.matches(v) { 593 return true; 594 } 595 }, 596 _ => (), 597 }; 598 let envkey = format!("WRENCH_REFTEST_CONDITION_{}", condition.to_uppercase()); 599 env::var(envkey).is_ok() 600 } 601 platform(_wrench: &Wrench, window: &WindowWrapper) -> &'static str602 fn platform(_wrench: &Wrench, window: &WindowWrapper) -> &'static str { 603 if window.is_software() { 604 "swgl" 605 } else if cfg!(target_os = "windows") { 606 "win" 607 } else if cfg!(target_os = "linux") { 608 "linux" 609 } else if cfg!(target_os = "macos") { 610 "mac" 611 } else if cfg!(target_os = "android") { 612 "android" 613 } else { 614 "other" 615 } 616 } 617 version(_wrench: &Wrench, window: &WindowWrapper) -> Option<semver::Version>618 fn version(_wrench: &Wrench, window: &WindowWrapper) -> Option<semver::Version> { 619 if window.is_software() { 620 None 621 } else if cfg!(target_os = "macos") { 622 use std::str; 623 let version_bytes = Command::new("defaults") 624 .arg("read") 625 .arg("loginwindow") 626 .arg("SystemVersionStampAsString") 627 .output() 628 .expect("Failed to get macOS version") 629 .stdout; 630 let mut version_string = str::from_utf8(&version_bytes) 631 .expect("Failed to read macOS version") 632 .trim() 633 .to_string(); 634 // On some machines this produces just the major.minor and on 635 // some machines this gives major.minor.patch. But semver requires 636 // the patch so we fake one if it's not there. 637 if version_string.chars().filter(|c| *c == '.').count() == 1 { 638 version_string.push_str(".0"); 639 } 640 Some(semver::Version::parse(&version_string) 641 .expect(&format!("Failed to parse macOS version {}", version_string))) 642 } else { 643 None 644 } 645 } 646 mode() -> &'static str647 fn mode() -> &'static str { 648 if cfg!(debug_assertions) { 649 "debug" 650 } else { 651 "release" 652 } 653 } 654 parse_condition(&self, token: &str) -> Option<bool>655 fn parse_condition(&self, token: &str) -> Option<bool> { 656 match token { 657 platform if platform.starts_with("skip_on(") => { 658 // e.g. skip_on(android,debug) will skip only when 659 // running on a debug android build. 660 let (_, args, _) = parse_function(platform); 661 Some(!args.iter().all(|arg| self.has(arg))) 662 } 663 platform if platform.starts_with("env(") => { 664 // non-negated version of skip_on for nested conditions 665 let (_, args, _) = parse_function(platform); 666 Some(args.iter().all(|arg| self.has(arg))) 667 } 668 platform if platform.starts_with("platform(") => { 669 let (_, args, _) = parse_function(platform); 670 // Skip due to platform not matching 671 Some(args.iter().any(|arg| arg == &self.platform)) 672 } 673 op if op.starts_with("not(") => { 674 let (_, args, _) = parse_function(op); 675 Some(!self.parse_condition(args[0]).expect("unknown condition")) 676 } 677 op if op.starts_with("or(") => { 678 let (_, args, _) = parse_function(op); 679 Some(args.iter().any(|arg| self.parse_condition(arg).expect("unknown condition"))) 680 } 681 op if op.starts_with("and(") => { 682 let (_, args, _) = parse_function(op); 683 Some(args.iter().all(|arg| self.parse_condition(arg).expect("unknown condition"))) 684 } 685 _ => None, 686 } 687 } 688 } 689 690 pub struct ReftestHarness<'a> { 691 wrench: &'a mut Wrench, 692 window: &'a mut WindowWrapper, 693 rx: &'a Receiver<NotifierEvent>, 694 environment: ReftestEnvironment, 695 } 696 impl<'a> ReftestHarness<'a> { new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: &'a Receiver<NotifierEvent>) -> Self697 pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: &'a Receiver<NotifierEvent>) -> Self { 698 let environment = ReftestEnvironment::new(wrench, window); 699 ReftestHarness { wrench, window, rx, environment } 700 } 701 run(mut self, base_manifest: &Path, reftests: Option<&Path>, options: &ReftestOptions) -> usize702 pub fn run(mut self, base_manifest: &Path, reftests: Option<&Path>, options: &ReftestOptions) -> usize { 703 let manifest = ReftestManifest::new(base_manifest, &self.environment, options); 704 let reftests = manifest.find(reftests.unwrap_or(&PathBuf::new())); 705 706 let mut total_passing = 0; 707 let mut failing = Vec::new(); 708 709 for t in reftests { 710 if self.run_reftest(t) { 711 total_passing += 1; 712 } else { 713 failing.push(t); 714 } 715 } 716 717 println!( 718 "REFTEST INFO | {} passing, {} failing", 719 total_passing, 720 failing.len() 721 ); 722 723 if !failing.is_empty() { 724 println!("\nReftests with unexpected results:"); 725 726 for reftest in &failing { 727 println!("\t{}", reftest); 728 } 729 } 730 731 failing.len() 732 } 733 run_reftest(&mut self, t: &Reftest) -> bool734 fn run_reftest(&mut self, t: &Reftest) -> bool { 735 let test_name = t.to_string(); 736 println!("REFTEST {}", test_name); 737 profile_scope!("wrench reftest", text: &test_name); 738 739 self.wrench 740 .api 741 .send_debug_cmd( 742 DebugCommand::ClearCaches(ClearCache::all()) 743 ); 744 745 let quality_settings = match t.force_subpixel_aa_where_possible { 746 Some(force_subpixel_aa_where_possible) => { 747 QualitySettings { 748 force_subpixel_aa_where_possible, 749 } 750 } 751 None => { 752 QualitySettings::default() 753 } 754 }; 755 756 self.wrench.set_quality_settings(quality_settings); 757 758 if t.disable_dual_source_blending { 759 self.wrench 760 .api 761 .send_debug_cmd( 762 DebugCommand::EnableDualSourceBlending(false) 763 ); 764 } 765 766 let window_size = self.window.get_inner_size(); 767 let reference_image = match t.reference.extension().unwrap().to_str().unwrap() { 768 "yaml" => None, 769 "png" => Some(self.load_image(t.reference.as_path(), ImageFormat::Png)), 770 other => panic!("Unknown reftest extension: {}", other), 771 }; 772 let test_size = reference_image.as_ref().map_or(window_size, |img| img.size); 773 774 // The reference can be smaller than the window size, in which case 775 // we only compare the intersection. 776 // 777 // Note also that, when we have multiple test scenes in sequence, we 778 // want to test the picture caching machinery. But since picture caching 779 // only takes effect after the result has been the same several frames in 780 // a row, we need to render the scene multiple times. 781 let mut images = vec![]; 782 let mut results = vec![]; 783 784 match t.op { 785 ReftestOp::Equal | ReftestOp::NotEqual => { 786 // For equality tests, render each test image and store result 787 for filename in t.test.iter() { 788 let output = self.render_yaml( 789 &filename, 790 test_size, 791 t.font_render_mode, 792 t.allow_mipmaps, 793 ); 794 images.push(output.image); 795 results.push(output.results); 796 } 797 } 798 ReftestOp::Accurate | ReftestOp::Inaccurate => { 799 // For accuracy tests, render the reference yaml at an arbitrary series 800 // of tile sizes, and compare to the reference drawn at normal tile size. 801 let tile_sizes = [ 802 DeviceIntSize::new(128, 128), 803 DeviceIntSize::new(256, 256), 804 DeviceIntSize::new(512, 512), 805 ]; 806 807 for tile_size in &tile_sizes { 808 self.wrench 809 .api 810 .send_debug_cmd( 811 DebugCommand::SetPictureTileSize(Some(*tile_size)) 812 ); 813 814 let output = self.render_yaml( 815 &t.reference, 816 test_size, 817 t.font_render_mode, 818 t.allow_mipmaps, 819 ); 820 images.push(output.image); 821 results.push(output.results); 822 } 823 824 self.wrench 825 .api 826 .send_debug_cmd( 827 DebugCommand::SetPictureTileSize(None) 828 ); 829 } 830 } 831 832 let reference = match reference_image { 833 Some(image) => { 834 let save_all_png = false; // flip to true to update all the tests! 835 if save_all_png { 836 let img = images.last().unwrap(); 837 save_flipped(&t.reference, img.data.clone(), img.size); 838 } 839 image 840 } 841 None => { 842 let output = self.render_yaml( 843 &t.reference, 844 test_size, 845 t.font_render_mode, 846 t.allow_mipmaps, 847 ); 848 output.image 849 } 850 }; 851 852 if t.disable_dual_source_blending { 853 self.wrench 854 .api 855 .send_debug_cmd( 856 DebugCommand::EnableDualSourceBlending(true) 857 ); 858 } 859 860 for extra_check in t.extra_checks.iter() { 861 if !extra_check.run(&results) { 862 println!( 863 "REFTEST TEST-UNEXPECTED-FAIL | {} | Failing Check: {:?} | Actual Results: {:?}", 864 t, 865 extra_check, 866 results, 867 ); 868 println!("REFTEST TEST-END | {}", t); 869 return false; 870 } 871 } 872 873 match t.op { 874 ReftestOp::Equal => { 875 // Ensure that the final image matches the reference 876 let test = images.pop().unwrap(); 877 let comparison = test.compare(&reference); 878 t.check_and_report_equality_failure( 879 comparison, 880 &test, 881 &reference, 882 ) 883 } 884 ReftestOp::NotEqual => { 885 // Ensure that the final image *doesn't* match the reference 886 let test = images.pop().unwrap(); 887 let comparison = test.compare(&reference); 888 match comparison { 889 ReftestImageComparison::Equal => { 890 t.report_unexpected_equality(); 891 false 892 } 893 ReftestImageComparison::NotEqual { .. } => { 894 true 895 } 896 } 897 } 898 ReftestOp::Accurate => { 899 // Ensure that *all* images match the reference 900 for test in images.drain(..) { 901 let comparison = test.compare(&reference); 902 903 if !t.check_and_report_equality_failure( 904 comparison, 905 &test, 906 &reference, 907 ) { 908 return false; 909 } 910 } 911 912 true 913 } 914 ReftestOp::Inaccurate => { 915 // Ensure that at least one of the images doesn't match the reference 916 let all_same = images.iter().all(|image| { 917 match image.compare(&reference) { 918 ReftestImageComparison::Equal => true, 919 ReftestImageComparison::NotEqual { .. } => false, 920 } 921 }); 922 923 if all_same { 924 t.report_unexpected_equality(); 925 } 926 927 !all_same 928 } 929 } 930 } 931 load_image(&mut self, filename: &Path, format: ImageFormat) -> ReftestImage932 fn load_image(&mut self, filename: &Path, format: ImageFormat) -> ReftestImage { 933 let file = BufReader::new(File::open(filename).unwrap()); 934 let img_raw = load_piston_image(file, format).unwrap(); 935 let img = img_raw.flipv().to_rgba(); 936 let size = img.dimensions(); 937 ReftestImage { 938 data: img.into_raw(), 939 size: DeviceIntSize::new(size.0 as i32, size.1 as i32), 940 } 941 } 942 render_yaml( &mut self, filename: &Path, size: DeviceIntSize, font_render_mode: Option<FontRenderMode>, allow_mipmaps: bool, ) -> YamlRenderOutput943 fn render_yaml( 944 &mut self, 945 filename: &Path, 946 size: DeviceIntSize, 947 font_render_mode: Option<FontRenderMode>, 948 allow_mipmaps: bool, 949 ) -> YamlRenderOutput { 950 let mut reader = YamlFrameReader::new(filename); 951 reader.set_font_render_mode(font_render_mode); 952 reader.allow_mipmaps(allow_mipmaps); 953 reader.do_frame(self.wrench); 954 955 self.wrench.api.flush_scene_builder(); 956 957 // wait for the frame 958 self.rx.recv().unwrap(); 959 let results = self.wrench.render(); 960 961 let window_size = self.window.get_inner_size(); 962 assert!( 963 size.width <= window_size.width && 964 size.height <= window_size.height, 965 "size={:?} ws={:?}", size, window_size 966 ); 967 968 // taking the bottom left sub-rectangle 969 let rect = FramebufferIntRect::from_origin_and_size( 970 FramebufferIntPoint::new(0, window_size.height - size.height), 971 FramebufferIntSize::new(size.width, size.height), 972 ); 973 let pixels = self.wrench.renderer.read_pixels_rgba8(rect); 974 self.window.swap_buffers(); 975 976 let write_debug_images = false; 977 if write_debug_images { 978 let debug_path = filename.with_extension("yaml.png"); 979 save_flipped(debug_path, pixels.clone(), size); 980 } 981 982 reader.deinit(self.wrench); 983 984 YamlRenderOutput { 985 image: ReftestImage { data: pixels, size }, 986 results, 987 } 988 } 989 } 990