| author | Glenn Watson <gw@intuitionlibrary.com> |
| Sun, 19 Jan 2020 19:45:37 +0000 | |
| changeset 510675 | 918e156c75aa1ac1fbfdd0a81434c68eb9d11f18 |
| parent 510674 | 06b1d6b3800216a1d8ca31db900f89f702a70d78 |
| child 510676 | 2c45e23bf71f150468921cd8a96ed522a42116f8 |
| push id | 37033 |
| push user | aciure@mozilla.com |
| push date | Mon, 20 Jan 2020 09:42:16 +0000 |
| treeherder | mozilla-central@206cec28723a [default view] [failures only] |
| perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
| reviewers | nical, Bert |
| bugs | 1609805 |
| milestone | 74.0a1 |
| first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
| last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/gfx/wr/webrender/src/frame_builder.rs +++ b/gfx/wr/webrender/src/frame_builder.rs @@ -60,16 +60,17 @@ pub struct FrameBuilderConfig { /// True if we're running tests (i.e. via wrench). pub testing: bool, pub gpu_supports_fast_clears: bool, pub gpu_supports_advanced_blend: bool, pub advanced_blend_is_coherent: bool, pub batch_lookback_count: usize, pub background_color: Option<ColorF>, pub compositor_kind: CompositorKind, + pub tile_size_override: Option<DeviceIntSize>, } /// A set of common / global resources that are retained between /// new display lists, such that any GPU cache handles can be /// persisted even when a new display list arrives. #[cfg_attr(feature = "capture", derive(Serialize))] pub struct FrameGlobalResources { /// The image shader block for the most common / default @@ -112,17 +113,17 @@ pub struct FrameBuilder { pub struct FrameVisibilityContext<'a> { pub clip_scroll_tree: &'a ClipScrollTree, pub global_screen_world_rect: WorldRect, pub global_device_pixel_scale: DevicePixelScale, pub surfaces: &'a [SurfaceInfo], pub debug_flags: DebugFlags, pub scene_properties: &'a SceneProperties, - pub config: &'a FrameBuilderConfig, + pub config: FrameBuilderConfig, } pub struct FrameVisibilityState<'a> { pub clip_store: &'a mut ClipStore, pub resource_cache: &'a mut ResourceCache, pub gpu_cache: &'a mut GpuCache, pub scratch: &'a mut PrimitiveScratchBuffer, pub tile_cache: Option<Box<TileCacheInstance>>, @@ -237,16 +238,17 @@ impl FrameBuilder { transform_palette: &mut TransformPalette, data_stores: &mut DataStores, surfaces: &mut Vec<SurfaceInfo>, scratch: &mut PrimitiveScratchBuffer, debug_flags: DebugFlags, texture_cache_profile: &mut TextureCacheProfileCounters, composite_state: &mut CompositeState, tile_cache_logger: &mut TileCacheLogger, + config: FrameBuilderConfig, ) -> Option<RenderTaskId> { profile_scope!("cull"); if scene.prim_store.pictures.is_empty() { return None } scratch.begin_frame(); @@ -322,17 +324,17 @@ impl FrameBuilder { let visibility_context = FrameVisibilityContext { global_device_pixel_scale, clip_scroll_tree: &scene.clip_scroll_tree, global_screen_world_rect, surfaces, debug_flags, scene_properties, - config: &scene.config, + config, }; let mut visibility_state = FrameVisibilityState { resource_cache, gpu_cache, clip_store: &mut scene.clip_store, scratch, tile_cache: None, @@ -467,16 +469,17 @@ impl FrameBuilder { pan: WorldPoint, resource_profile: &mut ResourceProfileCounters, scene_properties: &SceneProperties, data_stores: &mut DataStores, scratch: &mut PrimitiveScratchBuffer, render_task_counters: &mut RenderTaskGraphCounters, debug_flags: DebugFlags, tile_cache_logger: &mut TileCacheLogger, + config: FrameBuilderConfig, ) -> Frame { profile_scope!("build"); profile_marker!("BuildFrame"); let mut profile_counters = FrameProfileCounters::new(); profile_counters .total_primitives .set(scene.prim_store.prim_count()); @@ -537,16 +540,17 @@ impl FrameBuilder { &mut transform_palette, data_stores, &mut surfaces, scratch, debug_flags, &mut resource_profile.texture_cache, &mut composite_state, tile_cache_logger, + config, ); let mut passes; let mut deferred_resolves = vec![]; let mut has_texture_cache_tasks = false; let mut prim_headers = PrimitiveHeaders::new(); {
--- a/gfx/wr/webrender/src/picture.rs +++ b/gfx/wr/webrender/src/picture.rs @@ -273,23 +273,48 @@ pub const TILE_SIZE_SCROLLBAR_HORIZONTAL /// The size in device pixels of a tile for vertical scroll bars pub const TILE_SIZE_SCROLLBAR_VERTICAL: DeviceIntSize = DeviceIntSize { width: 16, height: 512, _unit: marker::PhantomData, }; +const TILE_SIZE_FOR_TESTS: [DeviceIntSize; 6] = [ + DeviceIntSize { + width: 128, + height: 128, + _unit: marker::PhantomData, + }, + DeviceIntSize { + width: 256, + height: 256, + _unit: marker::PhantomData, + }, + DeviceIntSize { + width: 512, + height: 512, + _unit: marker::PhantomData, + }, + TILE_SIZE_DEFAULT, + TILE_SIZE_SCROLLBAR_VERTICAL, + TILE_SIZE_SCROLLBAR_HORIZONTAL, +]; + // Return the list of tile sizes for the renderer to allocate texture arrays for. -pub fn tile_cache_sizes() -> &'static [DeviceIntSize] { - &[ - TILE_SIZE_DEFAULT, - TILE_SIZE_SCROLLBAR_HORIZONTAL, - TILE_SIZE_SCROLLBAR_VERTICAL, - ] +pub fn tile_cache_sizes(testing: bool) -> &'static [DeviceIntSize] { + if testing { + &TILE_SIZE_FOR_TESTS + } else { + &[ + TILE_SIZE_DEFAULT, + TILE_SIZE_SCROLLBAR_HORIZONTAL, + TILE_SIZE_SCROLLBAR_VERTICAL, + ] + } } /// The maximum size per axis of a surface, /// in WorldPixel coordinates. const MAX_SURFACE_SIZE: f32 = 4096.0; /// The maximum number of sub-dependencies (e.g. clips, transforms) we can handle /// per-primitive. If a primitive has more than this, it will invalidate every frame. @@ -1649,16 +1674,19 @@ pub struct TileCacheInstance { /// not using native compositor, or if the surface was destroyed and needs /// to be reallocated next time this surface contains valid tiles. pub native_surface_id: Option<NativeSurfaceId>, /// The current device position of this cache. Used to set the compositor /// offset of the surface when building the visual tree. pub device_position: DevicePoint, /// True if the entire picture cache surface is opaque. is_opaque: bool, + /// The currently considered tile size override. Used to check if we should + /// re-evaluate tile size, even if the frame timer hasn't expired. + tile_size_override: Option<DeviceIntSize>, } impl TileCacheInstance { pub fn new( slice: usize, spatial_node_index: SpatialNodeIndex, background_color: Option<ColorF>, shared_clips: Vec<ClipDataHandle>, @@ -1699,16 +1727,17 @@ impl TileCacheInstance { shared_clip_chain, current_tile_size: DeviceIntSize::zero(), frames_until_size_eval: 0, fract_offset: PictureVector2D::zero(), compare_cache: FastHashMap::default(), native_surface_id: None, device_position: DevicePoint::zero(), is_opaque: true, + tile_size_override: None, } } /// Returns true if this tile cache is considered opaque. pub fn is_opaque(&self) -> bool { // If known opaque due to background clear color and being the first slice. // The background_color will only be Some(..) if this is the first slice. match self.background_color { @@ -1856,48 +1885,55 @@ impl TileCacheInstance { &mut self.compare_cache, prev_state.allocations.compare_cache, ); } // Only evaluate what tile size to use fairly infrequently, so that we don't end // up constantly invalidating and reallocating tiles if the picture rect size is // changing near a threshold value. - if self.frames_until_size_eval == 0 { + if self.frames_until_size_eval == 0 || + self.tile_size_override != frame_context.config.tile_size_override { const TILE_SIZE_TINY: f32 = 32.0; // Work out what size tile is appropriate for this picture cache. - let desired_tile_size; - - // There's no need to check the other dimension. If we encounter a picture - // that is small on one dimension, it's a reasonable choice to use a scrollbar - // sized tile configuration regardless of the other dimension. - if pic_rect.size.width <= TILE_SIZE_TINY { - desired_tile_size = TILE_SIZE_SCROLLBAR_VERTICAL; - } else if pic_rect.size.height <= TILE_SIZE_TINY { - desired_tile_size = TILE_SIZE_SCROLLBAR_HORIZONTAL; - } else { - desired_tile_size = TILE_SIZE_DEFAULT; - } + let desired_tile_size = match frame_context.config.tile_size_override { + Some(tile_size_override) => { + tile_size_override + } + None => { + // There's no need to check the other dimension. If we encounter a picture + // that is small on one dimension, it's a reasonable choice to use a scrollbar + // sized tile configuration regardless of the other dimension. + if pic_rect.size.width <= TILE_SIZE_TINY { + TILE_SIZE_SCROLLBAR_VERTICAL + } else if pic_rect.size.height <= TILE_SIZE_TINY { + TILE_SIZE_SCROLLBAR_HORIZONTAL + } else { + TILE_SIZE_DEFAULT + } + } + }; // If the desired tile size has changed, then invalidate and drop any // existing tiles. if desired_tile_size != self.current_tile_size { // Destroy any native surfaces on the tiles that will be dropped due // to resizing. if let Some(native_surface_id) = self.native_surface_id.take() { frame_state.resource_cache.destroy_compositor_surface(native_surface_id); } self.tiles.clear(); self.current_tile_size = desired_tile_size; } // Reset counter until next evaluating the desired tile size. This is an // arbitrary value. self.frames_until_size_eval = 120; + self.tile_size_override = frame_context.config.tile_size_override; } // Map an arbitrary point in picture space to world space, to work out // what the fractional translation is that's applied by this scroll root. // TODO(gw): I'm not 100% sure this is right. At least, in future, we should // make a specific API for this, and/or enforce that the picture // cache transform only includes scale and/or translation (we // already ensure it doesn't have perspective).
--- a/gfx/wr/webrender/src/render_backend.rs +++ b/gfx/wr/webrender/src/render_backend.rs @@ -536,16 +536,17 @@ impl Document { fn build_frame( &mut self, resource_cache: &mut ResourceCache, gpu_cache: &mut GpuCache, resource_profile: &mut ResourceProfileCounters, debug_flags: DebugFlags, tile_cache_logger: &mut TileCacheLogger, + config: FrameBuilderConfig, ) -> RenderedDocument { let accumulated_scale_factor = self.view.accumulated_scale_factor(); let pan = self.view.pan.to_f32() / accumulated_scale_factor; // Advance to the next frame. self.stamp.advance(); assert!(self.stamp.frame_id() != FrameId::INVALID, @@ -563,16 +564,17 @@ impl Document { pan, resource_profile, &self.dynamic_properties, &mut self.data_stores, &mut self.scratch, &mut self.render_task_counters, debug_flags, tile_cache_logger, + config, ); self.hit_tester = Some(self.scene.create_hit_tester(&self.data_stores.clip)); frame }; self.frame_is_valid = true; self.hit_tester_is_valid = true; @@ -1137,16 +1139,21 @@ impl RenderBackend { self.low_priority_scene_tx.send(SceneBuilderRequest::SetFrameBuilderConfig( self.frame_config.clone() )).unwrap(); // We don't want to forward this message to the renderer. return RenderBackendStatus::Continue; } + DebugCommand::SetPictureTileSize(tile_size) => { + self.frame_config.tile_size_override = tile_size; + + return RenderBackendStatus::Continue; + } DebugCommand::FetchDocuments => { // Ask SceneBuilderThread to send JSON presentation of the documents, // that will be forwarded to Renderer. self.scene_tx.send(SceneBuilderRequest::DocumentsForDebugger).unwrap(); return RenderBackendStatus::Continue; } DebugCommand::FetchClipScrollTree => { let json = self.get_clip_scroll_tree_for_debugger(); @@ -1529,16 +1536,17 @@ impl RenderBackend { let frame_build_start_time = precise_time_ns(); let rendered_document = doc.build_frame( &mut self.resource_cache, &mut self.gpu_cache, &mut profile_counters.resources, self.debug_flags, &mut self.tile_cache_logger, + self.frame_config, ); debug!("generated frame for document {:?} with {} passes", document_id, rendered_document.frame.passes.len()); let msg = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates()); self.result_tx.send(msg).unwrap(); @@ -1707,16 +1715,17 @@ impl RenderBackend { debug!("\tdocument {:?}", id); if config.bits.contains(CaptureBits::FRAME) { let rendered_document = doc.build_frame( &mut self.resource_cache, &mut self.gpu_cache, &mut profile_counters.resources, self.debug_flags, &mut self.tile_cache_logger, + self.frame_config, ); // After we rendered the frames, there are pending updates to both // GPU cache and resources. Instead of serializing them, we are going to make sure // they are applied on the `Renderer` side. let msg_update_gpu_cache = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates()); self.result_tx.send(msg_update_gpu_cache).unwrap(); //TODO: write down doc's pipeline info? // it has `pipeline_epoch_map`,
--- a/gfx/wr/webrender/src/renderer.rs +++ b/gfx/wr/webrender/src/renderer.rs @@ -2180,16 +2180,17 @@ impl Renderer { global_enable_picture_caching: options.enable_picture_caching, testing: options.testing, gpu_supports_fast_clears: options.gpu_supports_fast_clears, gpu_supports_advanced_blend: ext_blend_equation_advanced, advanced_blend_is_coherent: ext_blend_equation_advanced_coherent, batch_lookback_count: options.batch_lookback_count, background_color: options.clear_color, compositor_kind, + tile_size_override: None, }; info!("WR {:?}", config); let device_pixel_ratio = options.device_pixel_ratio; let debug_flags = options.debug_flags; let payload_rx_for_backend = payload_rx.to_mpsc_receiver(); let size_of_op = options.size_of_op; let enclosing_size_of_op = options.enclosing_size_of_op; @@ -2287,17 +2288,17 @@ impl Renderer { if let Some(ref thread_listener) = *thread_listener_for_render_backend { thread_listener.thread_started(&rb_thread_name); } let texture_cache = TextureCache::new( max_texture_size, max_texture_layers, if config.global_enable_picture_caching { - tile_cache_sizes() + tile_cache_sizes(config.testing) } else { &[] }, start_size, color_cache_formats, swizzle_settings, ); @@ -2840,17 +2841,18 @@ impl Renderer { } serde_json::to_string(&debug_root).unwrap() } fn handle_debug_command(&mut self, command: DebugCommand) { match command { DebugCommand::EnableDualSourceBlending(_) | - DebugCommand::SetTransactionLogging(_) => { + DebugCommand::SetTransactionLogging(_) | + DebugCommand::SetPictureTileSize(_) => { panic!("Should be handled by render backend"); } DebugCommand::FetchDocuments | DebugCommand::FetchClipScrollTree => {} DebugCommand::FetchRenderTasks => { let json = self.get_render_tasks_for_debugger(); self.debug_server.send(json); }
--- a/gfx/wr/webrender/src/scene.rs +++ b/gfx/wr/webrender/src/scene.rs @@ -253,16 +253,17 @@ impl BuiltScene { global_enable_picture_caching: false, testing: false, gpu_supports_fast_clears: false, gpu_supports_advanced_blend: false, advanced_blend_is_coherent: false, batch_lookback_count: 0, background_color: None, compositor_kind: CompositorKind::default(), + tile_size_override: None, }, } } /// Get the memory usage statistics to pre-allocate for the next scene. pub fn get_stats(&self) -> SceneStats { SceneStats { prim_store_stats: self.prim_store.get_stats(),
--- a/gfx/wr/webrender_api/src/api.rs +++ b/gfx/wr/webrender_api/src/api.rs @@ -972,16 +972,18 @@ pub enum DebugCommand { /// Causes the scene builder to pause for a given amount of milliseconds each time it /// processes a transaction. SimulateLongSceneBuild(u32), /// Causes the low priority scene builder to pause for a given amount of milliseconds /// each time it processes a transaction. SimulateLongLowPrioritySceneBuild(u32), /// Logs transactions to a file for debugging purposes SetTransactionLogging(bool), + /// Set an override tile size to use for picture caches + SetPictureTileSize(Option<DeviceIntSize>), } /// Message sent by the `RenderApi` to the render backend thread. #[derive(Clone, Deserialize, Serialize)] pub enum ApiMsg { /// Add/remove/update images and fonts. UpdateResources(Vec<ResourceUpdate>), /// Gets the glyph dimensions
--- a/gfx/wr/wrench/reftests/border/reftest.list +++ b/gfx/wr/wrench/reftests/border/reftest.list @@ -23,10 +23,10 @@ platform(linux,mac) == border-image.yaml platform(linux,mac) == dotted-corner-small-radius.yaml dotted-corner-small-radius.png == overlapping.yaml overlapping.png == zero-width.yaml blank.yaml platform(linux,mac) == small-dotted-border.yaml small-dotted-border.png == discontinued-dash.yaml discontinued-dash.png platform(linux,mac) == border-dashed-dotted-caching.yaml border-dashed-dotted-caching.png != small-inset-outset.yaml small-inset-outset-notref.yaml fuzzy(1,16) == no-aa.yaml green-square.yaml -skip_on(android,device) border-double-1px.yaml border-double-1px-ref.yaml # Fails on Pixel2 +skip_on(android,device) == border-double-1px.yaml border-double-1px-ref.yaml # Fails on Pixel2 == max-scale.yaml max-scale-ref.yaml
--- a/gfx/wr/wrench/reftests/reftest.list +++ b/gfx/wr/wrench/reftests/reftest.list @@ -10,8 +10,9 @@ include image/reftest.list include invalidation/reftest.list include mask/reftest.list include performance/reftest.list include scrolling/reftest.list include snap/reftest.list include split/reftest.list include text/reftest.list include transforms/reftest.list +include tiles/reftest.list
--- a/gfx/wr/wrench/reftests/text/reftest.list +++ b/gfx/wr/wrench/reftests/text/reftest.list @@ -68,10 +68,10 @@ skip_on(android,device) == bg-color.yaml != large-glyphs.yaml blank.yaml skip_on(android,device) == snap-text-offset.yaml snap-text-offset-ref.yaml fuzzy(5,4435) == shadow-border.yaml shadow-solid-ref.yaml fuzzy(5,4435) == shadow-image.yaml shadow-solid-ref.yaml options(disable-aa) == snap-clip.yaml snap-clip-ref.yaml platform(linux) == perspective-clip.yaml perspective-clip.png fuzzy(1,39) options(disable-subpixel) == raster-space-snap.yaml raster-space-snap-ref.yaml # == intermediate-transform.yaml intermediate-transform-ref.yaml # fails because of AA inavailable with an intermediate surface -platform(linux) allow_sacrificing_subpixel_aa(false) text-fixed-slice.yaml text-fixed-slice-slow.png -platform(linux) allow_sacrificing_subpixel_aa(true) text-fixed-slice.yaml text-fixed-slice-fast.png +platform(linux) allow_sacrificing_subpixel_aa(false) == text-fixed-slice.yaml text-fixed-slice-slow.png +platform(linux) allow_sacrificing_subpixel_aa(true) == text-fixed-slice.yaml text-fixed-slice-fast.png
new file mode 100644 --- /dev/null +++ b/gfx/wr/wrench/reftests/tiles/prim-suite.yaml @@ -0,0 +1,45 @@ +--- +root: + items: + - type: stacking-context + bounds: [50, 50, 100, 100] + transform: rotate(30) + items: + - type: rect + bounds: [ 10, 10, 80, 80 ] + color: [0, 255, 0] + - type: box-shadow + bounds: [ 10, 10, 80, 80 ] + blur-radius: 25 + clip-mode: inset + + - type: rect + bounds: [ 140, 10, 80, 80 ] + color: [0, 255, 0] + - type: box-shadow + bounds: [ 140, 10, 80, 80 ] + blur-radius: 25 + clip-mode: outset + + - type: border + bounds: [ 250, 10, 100, 100 ] + width: [ 10, 10, 10, 10 ] + border-type: normal + style: solid + color: [ red, green, blue, black ] + radius: { + top-left: [20, 20], + top-right: [10, 10], + bottom-left: [25, 25], + bottom-right: [0, 0], + } + + - bounds: [150, 150, 128, 128] + image: checkerboard(4, 15, 8) + stretch-size: 128 128 + + - type: radial-gradient + bounds: 300 150 100 100 + center: 50 50 + radius: 50 50 + stops: [0, red, 1, blue]
new file mode 100644 --- /dev/null +++ b/gfx/wr/wrench/reftests/tiles/rect.yaml @@ -0,0 +1,6 @@ +--- +root: + items: + - type: rect + bounds: 50 50 200 200 + color: red
new file mode 100644 --- /dev/null +++ b/gfx/wr/wrench/reftests/tiles/reftest.list @@ -0,0 +1,4 @@ +** rect.yaml +** simple-gradient.yaml +# TODO: Fix rasterizer inaccuracies so this is the same regardless of tile size! +!* prim-suite.yaml
new file mode 100644 --- /dev/null +++ b/gfx/wr/wrench/reftests/tiles/simple-gradient.yaml @@ -0,0 +1,9 @@ +--- +root: + items: + - type: gradient + bounds: [ 0, 0, 1980, 1080] + start: [ 0, -2000 ] + end: [ 0, 4000 ] + stops: [ 0.0, red, 1.0, green ] + repeat: false
--- a/gfx/wr/wrench/src/reftest.rs +++ b/gfx/wr/wrench/src/reftest.rs @@ -39,29 +39,38 @@ impl ReftestOptions { pub fn default() -> Self { ReftestOptions { allow_max_difference: 0, allow_num_differences: 0, } } } +#[derive(Debug, Copy, Clone)] pub enum ReftestOp { + /// Expect that the images match the reference Equal, + /// Expect that the images *don't* match the reference NotEqual, + /// Expect that drawing the reference at different tiles sizes gives the same pixel exact result. + Accurate, + /// Expect that drawing the reference at different tiles sizes gives a *different* pixel exact result. + Inaccurate, } impl Display for ReftestOp { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { write!( f, "{}", match *self { ReftestOp::Equal => "==".to_owned(), ReftestOp::NotEqual => "!=".to_owned(), + ReftestOp::Accurate => "**".to_owned(), + ReftestOp::Inaccurate => "!*".to_owned(), } ) } } #[derive(Debug)] enum ExtraCheck { DrawCalls(usize), @@ -97,33 +106,92 @@ pub struct Reftest { num_differences: usize, extra_checks: Vec<ExtraCheck>, disable_dual_source_blending: bool, allow_mipmaps: bool, zoom_factor: f32, allow_sacrificing_subpixel_aa: Option<bool>, } +impl Reftest { + /// Check the positive case (expecting equality) and report details if different + fn check_and_report_equality_failure( + &self, + comparison: ReftestImageComparison, + test: &ReftestImage, + reference: &ReftestImage, + ) -> bool { + match comparison { + ReftestImageComparison::Equal => { + true + } + ReftestImageComparison::NotEqual { max_difference, count_different } => { + if max_difference > self.max_difference || count_different > self.num_differences { + println!( + "{} | {} | {}: {}, {}: {}", + "REFTEST TEST-UNEXPECTED-FAIL", + self, + "image comparison, max difference", + max_difference, + "number of differing pixels", + count_different + ); + println!("REFTEST IMAGE 1 (TEST): {}", test.clone().create_data_uri()); + println!( + "REFTEST IMAGE 2 (REFERENCE): {}", + reference.clone().create_data_uri() + ); + println!("REFTEST TEST-END | {}", self); + + false + } else { + true + } + } + } + } + + /// Check the negative case (expecting inequality) and report details if same + fn check_and_report_inequality_failure( + &self, + comparison: ReftestImageComparison, + ) -> bool { + match comparison { + ReftestImageComparison::Equal => { + println!("REFTEST TEST-UNEXPECTED-FAIL | {} | image comparison", self); + println!("REFTEST TEST-END | {}", self); + false + } + ReftestImageComparison::NotEqual { .. } => { + true + } + } + } +} + impl Display for Reftest { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { let paths: Vec<String> = self.test.iter().map(|t| t.display().to_string()).collect(); write!( f, "{} {} {}", paths.join(", "), self.op, self.reference.display() ) } } +#[derive(Clone)] pub struct ReftestImage { pub data: Vec<u8>, pub size: DeviceIntSize, } + +#[derive(Debug, Copy, Clone)] pub enum ReftestImageComparison { Equal, NotEqual { max_difference: usize, count_different: usize, }, } @@ -207,17 +275,17 @@ impl ReftestManifest { if s.is_empty() { continue; } let tokens: Vec<&str> = s.split_whitespace().collect(); let mut max_difference = 0; let mut max_count = 0; - let mut op = ReftestOp::Equal; + let mut op = None; let mut font_render_mode = None; let mut extra_checks = vec![]; let mut disable_dual_source_blending = false; let mut zoom_factor = 1.0; let mut allow_mipmaps = false; let mut dirty_region_index = 0; let mut allow_sacrificing_subpixel_aa = None; @@ -294,32 +362,41 @@ impl ReftestManifest { if args.iter().any(|arg| arg == &OPTION_DISABLE_DUAL_SOURCE_BLENDING) { disable_dual_source_blending = true; } if args.iter().any(|arg| arg == &OPTION_ALLOW_MIPMAPS) { allow_mipmaps = true; } } "==" => { - op = ReftestOp::Equal; + op = Some(ReftestOp::Equal); } "!=" => { - op = ReftestOp::NotEqual; + op = Some(ReftestOp::NotEqual); + } + "**" => { + op = Some(ReftestOp::Accurate); + } + "!*" => { + op = Some(ReftestOp::Inaccurate); } _ => { paths.push(dir.join(*token)); } } } // Don't try to add tests for include lines. - if paths.len() < 2 { - assert_eq!(paths.len(), 0, "Only one path provided: {:?}", paths[0]); - continue; - } + let op = match op { + Some(op) => op, + None => { + assert!(paths.is_empty(), format!("paths = {:?}", paths)); + continue; + } + }; // The reference is the last path provided. If multiple paths are // passed for the test, they render sequentially before being // compared to the reference, which is useful for testing // invalidation. let reference = paths.pop().unwrap(); let test = paths; @@ -525,25 +602,62 @@ impl<'a> ReftestHarness<'a> { // // Note also that, when we have multiple test scenes in sequence, we // want to test the picture caching machinery. But since picture caching // only takes effect after the result has been the same several frames in // a row, we need to render the scene multiple times. let mut images = vec![]; let mut results = vec![]; - for filename in t.test.iter() { - let output = self.render_yaml( - &filename, - test_size, - t.font_render_mode, - t.allow_mipmaps, - ); - images.push(output.image); - results.push(output.results); + match t.op { + ReftestOp::Equal | ReftestOp::NotEqual => { + // For equality tests, render each test image and store result + for filename in t.test.iter() { + let output = self.render_yaml( + &filename, + test_size, + t.font_render_mode, + t.allow_mipmaps, + ); + images.push(output.image); + results.push(output.results); + } + } + ReftestOp::Accurate | ReftestOp::Inaccurate => { + // For accuracy tests, render the reference yaml at an arbitrary series + // of tile sizes, and compare to the reference drawn at normal tile size. + let tile_sizes = [ + DeviceIntSize::new(128, 128), + DeviceIntSize::new(256, 256), + DeviceIntSize::new(512, 512), + ]; + + for tile_size in &tile_sizes { + self.wrench + .api + .send_debug_cmd( + DebugCommand::SetPictureTileSize(Some(*tile_size)) + ); + + let output = self.render_yaml( + &t.reference, + test_size, + t.font_render_mode, + t.allow_mipmaps, + ); + images.push(output.image); + results.push(output.results); + } + + self.wrench + .api + .send_debug_cmd( + DebugCommand::SetPictureTileSize(None) + ); + } } let reference = match reference_image { Some(image) => image, None => { let output = self.render_yaml( &t.reference, test_size, @@ -570,54 +684,60 @@ impl<'a> ReftestHarness<'a> { extra_check, results, ); println!("REFTEST TEST-END | {}", t); return false; } } - let test = images.pop().unwrap(); - let comparison = test.compare(&reference); - match (&t.op, comparison) { - (&ReftestOp::Equal, ReftestImageComparison::Equal) => true, - ( - &ReftestOp::Equal, - ReftestImageComparison::NotEqual { - max_difference, - count_different, - }, - ) => if max_difference > t.max_difference || count_different > t.num_differences { - println!( - "{} | {} | {}: {}, {}: {}", - "REFTEST TEST-UNEXPECTED-FAIL", - t, - "image comparison, max difference", - max_difference, - "number of differing pixels", - count_different - ); - println!("REFTEST IMAGE 1 (TEST): {}", test.create_data_uri()); - println!( - "REFTEST IMAGE 2 (REFERENCE): {}", - reference.create_data_uri() - ); - println!("REFTEST TEST-END | {}", t); + match t.op { + ReftestOp::Equal => { + // Ensure that the final image matches the reference + let test = images.pop().unwrap(); + let comparison = test.compare(&reference); + t.check_and_report_equality_failure( + comparison, + &test, + &reference, + ) + } + ReftestOp::NotEqual => { + // Ensure that the final image *doesn't* match the reference + let test = images.pop().unwrap(); + let comparison = test.compare(&reference); + t.check_and_report_inequality_failure(comparison) + } + ReftestOp::Accurate => { + // Ensure that *all* images match the reference + for test in images.drain(..) { + let comparison = test.compare(&reference); - false - } else { + if !t.check_and_report_equality_failure( + comparison, + &test, + &reference, + ) { + return false; + } + } + true - }, - (&ReftestOp::NotEqual, ReftestImageComparison::Equal) => { - println!("REFTEST TEST-UNEXPECTED-FAIL | {} | image comparison", t); - println!("REFTEST TEST-END | {}", t); + } + ReftestOp::Inaccurate => { + // Ensure that at least one of the images doesn't match the reference + let mut found_mismatch = false; - false + for test in images.drain(..) { + let comparison = test.compare(&reference); + found_mismatch |= t.check_and_report_inequality_failure(comparison); + } + + found_mismatch } - (&ReftestOp::NotEqual, ReftestImageComparison::NotEqual { .. }) => true, } } fn load_image(&mut self, filename: &Path, format: ImageFormat) -> ReftestImage { let file = BufReader::new(File::open(filename).unwrap()); let img_raw = load_piston_image(file, format).unwrap(); let img = img_raw.flipv().to_rgba(); let size = img.dimensions();