Bug 1600472 - Part 1 - Add API for configuring performance vs. quality. r=mstange
authorGlenn Watson <git@intuitionlibrary.com>
Mon, 09 Dec 2019 03:27:53 +0000
changeset 568149 aa07114fb54ab1f7b819522f45563cb83f6b637e
parent 568148 7be7da6bb6455f9e5dbd8bf40d5db8394147e228
child 568150 8a285497c16992995f515e43166432b0c765ea72
push id12493
push userffxbld-merge
push dateMon, 06 Jan 2020 15:38:57 +0000
treeherdermozilla-beta@63ae456b848d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1600472
milestone73.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
Bug 1600472 - Part 1 - Add API for configuring performance vs. quality. r=mstange Differential Revision: https://phabricator.services.mozilla.com/D56316
gfx/wr/webrender/src/render_backend.rs
gfx/wr/webrender/src/scene_building.rs
gfx/wr/webrender_api/src/api.rs
gfx/wr/wrench/reftests/boxshadow/reftest.list
gfx/wr/wrench/reftests/text/reftest.list
gfx/wr/wrench/reftests/text/text-fixed-slice-fast.png
gfx/wr/wrench/reftests/text/text-fixed-slice-slow.png
gfx/wr/wrench/reftests/text/text-fixed-slice.yaml
gfx/wr/wrench/src/reftest.rs
gfx/wr/wrench/src/wrench.rs
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -7,17 +7,17 @@
 //!
 //! See the comment at the top of the `renderer` module for a description of
 //! how these two pieces interact.
 
 use api::{ApiMsg, BuiltDisplayList, ClearCache, DebugCommand, DebugFlags};
 use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestFlags, HitTestResult};
 use api::{IdNamespace, MemoryReport, PipelineId, RenderNotifier, SceneMsg, ScrollClamping};
 use api::{ScrollLocation, TransactionMsg, ResourceUpdate, BlobImageKey};
-use api::{NotificationRequest, Checkpoint};
+use api::{NotificationRequest, Checkpoint, QualitySettings};
 use api::{ClipIntern, FilterDataIntern, PrimitiveKeyKind};
 use api::units::*;
 use api::channel::{MsgReceiver, MsgSender, Payload};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
 use crate::clip_scroll_tree::SpatialNodeIndex;
@@ -70,16 +70,17 @@ use crate::util::{Recycler, VecHelper, d
 #[derive(Clone)]
 pub struct DocumentView {
     pub device_rect: DeviceIntRect,
     pub layer: DocumentLayer,
     pub pan: DeviceIntPoint,
     pub device_pixel_ratio: f32,
     pub page_zoom_factor: f32,
     pub pinch_zoom_factor: f32,
+    pub quality_settings: QualitySettings,
 }
 
 impl DocumentView {
     pub fn accumulated_scale_factor(&self) -> DevicePixelScale {
         DevicePixelScale::new(
             self.device_pixel_ratio *
             self.page_zoom_factor *
             self.pinch_zoom_factor
@@ -395,16 +396,17 @@ impl Document {
             removed_pipelines: Vec::new(),
             view: DocumentView {
                 device_rect: size.into(),
                 layer,
                 pan: DeviceIntPoint::zero(),
                 page_zoom_factor: 1.0,
                 pinch_zoom_factor: 1.0,
                 device_pixel_ratio: default_device_pixel_ratio,
+                quality_settings: QualitySettings::default(),
             },
             stamp: FrameStamp::first(id),
             scene: BuiltScene::empty(),
             frame_builder: FrameBuilder::new(),
             output_pipelines: FastHashSet::default(),
             hit_tester: None,
             dynamic_properties: SceneProperties::new(),
             frame_is_valid: false,
@@ -769,16 +771,19 @@ impl RenderBackend {
 
         match message {
             SceneMsg::UpdateEpoch(pipeline_id, epoch) => {
                 txn.epoch_updates.push((pipeline_id, epoch));
             }
             SceneMsg::SetPageZoom(factor) => {
                 doc.view.page_zoom_factor = factor.get();
             }
+            SceneMsg::SetQualitySettings { settings } => {
+                doc.view.quality_settings = settings;
+            }
             SceneMsg::SetDocumentView {
                 device_rect,
                 device_pixel_ratio,
             } => {
                 doc.view.device_rect = device_rect;
                 doc.view.device_pixel_ratio = device_pixel_ratio;
             }
             SceneMsg::SetDisplayList {
--- a/gfx/wr/webrender/src/scene_building.rs
+++ b/gfx/wr/webrender/src/scene_building.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, PrimitiveFlags};
 use api::{ClipId, ColorF, CommonItemProperties, ComplexClipRegion, ComponentTransferFuncType, RasterSpace};
 use api::{DisplayItem, DisplayItemRef, ExtendMode, ExternalScrollId, FilterData};
 use api::{FilterOp, FilterPrimitive, FontInstanceKey, GlyphInstance, GlyphOptions, GradientStop};
-use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, ColorDepth};
+use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, ColorDepth, QualitySettings};
 use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId, MixBlendMode};
 use api::{PropertyBinding, ReferenceFrame, ReferenceFrameKind, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpaceAndClipInfo, SpatialId, StackingContext, StickyFrameDisplayItem};
 use api::{ClipMode, PrimitiveKeyKind, TransformStyle, YuvColorSpace, ColorRange, YuvData, TempFilterData};
 use api::units::*;
 use crate::clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore, ClipItemKeyKind};
 use crate::clip::{ClipInternData, ClipDataHandle, ClipNodeKind};
 use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
@@ -374,16 +374,19 @@ pub struct SceneBuilder<'a> {
 
     /// A set of any spatial nodes that are attached to either a picture cache
     /// root, or a clip node on the picture cache primitive. These are used
     /// to detect cases where picture caching must be disabled. This is mostly
     /// a temporary workaround for some existing wrench tests. I don't think
     /// Gecko ever produces picture cache slices with complex transforms, so
     /// in future we should prevent this in the public API and remove this hack.
     picture_cache_spatial_nodes: FastHashSet<SpatialNodeIndex>,
+
+    /// The current quality / performance settings for this scene.
+    quality_settings: QualitySettings,
 }
 
 impl<'a> SceneBuilder<'a> {
     pub fn build(
         scene: &Scene,
         font_instances: FontInstanceMap,
         view: &DocumentView,
         output_pipelines: &FastHashSet<PipelineId>,
@@ -415,16 +418,17 @@ impl<'a> SceneBuilder<'a> {
             interners,
             root_pic_index: PictureIndex(0),
             rf_mapper: ReferenceFrameMapper::new(),
             external_scroll_mapper: ScrollOffsetMapper::new(),
             picture_caching_initialized: false,
             iframe_depth: 0,
             content_slice_count: 0,
             picture_cache_spatial_nodes: FastHashSet::default(),
+            quality_settings: view.quality_settings,
         };
 
         let device_pixel_scale = view.accumulated_scale_factor_for_snapping();
 
         builder.push_root(
             root_pipeline_id,
             &root_pipeline.viewport_size,
             &root_pipeline.content_size,
@@ -1905,16 +1909,17 @@ impl<'a> SceneBuilder<'a> {
                         // invalidations. There is ongoing work to add tags to primitives that
                         // are scrollbars. Once this lands, we can simplify this logic considerably
                         // (and add a separate picture cache slice / OS layer for scroll bars).
                         if parent_sc.pipeline_id != stacking_context.pipeline_id && self.iframe_depth == 1 {
                             self.content_slice_count = stacking_context.init_picture_caching(
                                 &self.clip_scroll_tree,
                                 &self.clip_store,
                                 &self.interners,
+                                &self.quality_settings,
                             );
 
                             // Mark that a user supplied tile cache was specified.
                             self.picture_caching_initialized = true;
                         }
 
                         // If the parent context primitives list is empty, it's faster
                         // to assign the storage of the popped context instead of paying
@@ -1937,16 +1942,17 @@ impl<'a> SceneBuilder<'a> {
             // If we didn't encounter a content iframe, then set up picture caching slice markers
             // on the root stacking context. This can happen in Gecko when the parent process
             // provides the content display list (e.g. about:support, about:config etc).
             if !self.picture_caching_initialized {
                 self.content_slice_count = stacking_context.init_picture_caching(
                     &self.clip_scroll_tree,
                     &self.clip_store,
                     &self.interners,
+                    &self.quality_settings,
                 );
                 self.picture_caching_initialized = true;
             }
 
             self.setup_picture_caching(
                 &mut stacking_context.prim_list,
             );
         }
@@ -3596,16 +3602,17 @@ impl FlattenedStackingContext {
     }
 
     /// Set up appropriate cluster flags for picture caching on this stacking context.
     fn init_picture_caching(
         &mut self,
         clip_scroll_tree: &ClipScrollTree,
         clip_store: &ClipStore,
         interners: &Interners,
+        quality_settings: &QualitySettings,
     ) -> usize {
         struct SliceInfo {
             cluster_index: usize,
             scroll_root: SpatialNodeIndex,
         }
 
         let mut content_slice_count = 0;
         let mut slices: Vec<SliceInfo> = Vec::new();
@@ -3628,16 +3635,22 @@ impl FlattenedStackingContext {
                             // Both current slice and this cluster are fixed position, no need to cut
                             false
                         }
                         (ROOT_SPATIAL_NODE_INDEX, _) => {
                             // A real scroll root is being established, so create a cache slice
                             true
                         }
                         (_, ROOT_SPATIAL_NODE_INDEX) => {
+                            // If quality settings prefer subpixel AA over performance, skip creating
+                            // a slice for the fixed position element(s) here.
+                            if !quality_settings.allow_sacrificing_subpixel_aa {
+                                return false;
+                            }
+
                             // A fixed position slice is encountered within a scroll root. Only create
                             // a slice in this case if all the clips referenced by this cluster are also
                             // fixed position. There's no real point in creating slices for these cases,
                             // since we'll have to rasterize them as the scrolling clip moves anyway. It
                             // also allows us to retain subpixel AA in these cases. For these types of
                             // slices, the intra-slice dirty rect handling typically works quite well
                             // (a common case is parallax scrolling effects).
                             for prim_instance in &cluster.prim_instances {
--- a/gfx/wr/webrender_api/src/api.rs
+++ b/gfx/wr/webrender_api/src/api.rs
@@ -23,16 +23,36 @@ use crate::image::{BlobImageData, BlobIm
 use crate::units::*;
 
 /// Width and height in device pixels of image tiles.
 pub type TileSize = u16;
 
 /// Documents are rendered in the ascending order of their associated layer values.
 pub type DocumentLayer = i8;
 
+/// Various settings that the caller can select based on desired tradeoffs
+/// between rendering quality and performance / power usage.
+#[derive(Copy, Clone, Deserialize, Serialize)]
+pub struct QualitySettings {
+    /// If true, allow picture cache slices to be created that may prevent
+    /// subpixel AA on text being used due to lack of opaque background. This
+    /// often allows a significant performance win on pages that interleave
+    /// scroll regions with fixed position elements.
+    pub allow_sacrificing_subpixel_aa: bool,
+}
+
+impl Default for QualitySettings {
+    fn default() -> Self {
+        QualitySettings {
+            // Preferring performance in this case retains the current behavior.
+            allow_sacrificing_subpixel_aa: true,
+        }
+    }
+}
+
 /// Update of a persistent resource in WebRender.
 ///
 /// ResourceUpdate changes keep theirs effect across display list changes.
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ResourceUpdate {
     /// See `AddImage`.
     AddImage(AddImage),
     /// See `UpdateImage`.
@@ -302,16 +322,21 @@ impl Transaction {
         &mut self,
         origin: LayoutPoint,
         id: di::ExternalScrollId,
         clamp: ScrollClamping,
     ) {
         self.frame_ops.push(FrameMsg::ScrollNodeWithId(origin, id, clamp));
     }
 
+    /// Set the current quality / performance settings for this document.
+    pub fn set_quality_settings(&mut self, settings: QualitySettings) {
+        self.scene_ops.push(SceneMsg::SetQualitySettings { settings });
+    }
+
     ///
     pub fn set_page_zoom(&mut self, page_zoom: ZoomFactor) {
         self.scene_ops.push(SceneMsg::SetPageZoom(page_zoom));
     }
 
     ///
     pub fn set_pinch_zoom(&mut self, pinch_zoom: ZoomFactor) {
         self.frame_ops.push(FrameMsg::SetPinchZoom(pinch_zoom));
@@ -807,16 +832,21 @@ pub enum SceneMsg {
     },
     ///
     SetDocumentView {
         ///
         device_rect: DeviceIntRect,
         ///
         device_pixel_ratio: f32,
     },
+    /// Set the current quality / performance configuration for this document.
+    SetQualitySettings {
+        /// The set of available quality / performance config values.
+        settings: QualitySettings,
+    },
 }
 
 /// Frame messages affect frame generation (applied after building the scene).
 #[derive(Clone, Deserialize, Serialize)]
 pub enum FrameMsg {
     ///
     UpdateEpoch(PipelineId, Epoch),
     ///
@@ -844,16 +874,17 @@ impl fmt::Debug for SceneMsg {
         f.write_str(match *self {
             SceneMsg::UpdateEpoch(..) => "SceneMsg::UpdateEpoch",
             SceneMsg::SetDisplayList { .. } => "SceneMsg::SetDisplayList",
             SceneMsg::SetPageZoom(..) => "SceneMsg::SetPageZoom",
             SceneMsg::RemovePipeline(..) => "SceneMsg::RemovePipeline",
             SceneMsg::EnableFrameOutput(..) => "SceneMsg::EnableFrameOutput",
             SceneMsg::SetDocumentView { .. } => "SceneMsg::SetDocumentView",
             SceneMsg::SetRootPipeline(..) => "SceneMsg::SetRootPipeline",
+            SceneMsg::SetQualitySettings { .. } => "SceneMsg::SetQualitySettings",
         })
     }
 }
 
 impl fmt::Debug for FrameMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             FrameMsg::UpdateEpoch(..) => "FrameMsg::UpdateEpoch",
--- a/gfx/wr/wrench/reftests/boxshadow/reftest.list
+++ b/gfx/wr/wrench/reftests/boxshadow/reftest.list
@@ -16,17 +16,17 @@ platform(linux,mac) == box-shadow-suite-
 fuzzy(1,6388) == rounding.yaml rounding-ref.yaml
 platform(linux,mac) == box-shadow-border-radii.yaml box-shadow-border-radii.png
 skip_on(android) == box-shadow-spread.yaml box-shadow-spread.png  # Too wide for Android
 == box-shadow-spread-radii.yaml box-shadow-spread-radii-ref.yaml
 == invalid.yaml invalid-ref.yaml
 == inset-empty.yaml blank.yaml
 platform(linux,mac) == inset-subpx.yaml inset-subpx.png
 platform(linux,mac) fuzzy(1,4) == inset-downscale.yaml inset-downscale.png
-platform(linux,mac) fuzzy(1,50) == box-shadow-cache.yaml box-shadow-cache.png
+platform(linux,mac) fuzzy(1,979) == box-shadow-cache.yaml box-shadow-cache.png
 platform(linux,mac) fuzzy(1,685) == overlap1.yaml overlap1.png
 fuzzy(2,691) == overlap2.yaml overlap2.png
 platform(linux,mac) fuzzy(1,48) == no-stretch.yaml no-stretch.png
 platform(linux,mac) fuzzy(1,9) == box-shadow-stretch-mode-x.yaml box-shadow-stretch-mode-x.png
 platform(linux,mac) fuzzy(1,41) == box-shadow-stretch-mode-y.yaml box-shadow-stretch-mode-y.png
 platform(linux,mac) fuzzy(1,14) == inset-mask-region.yaml inset-mask-region.png
 == box-shadow-blurred-overlapping-radii.yaml box-shadow-blurred-overlapping-radii-ref.yaml
 
--- a/gfx/wr/wrench/reftests/text/reftest.list
+++ b/gfx/wr/wrench/reftests/text/reftest.list
@@ -68,8 +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
new file mode 100644
index 0000000000000000000000000000000000000000..7f3ee0ec9d32ee54f212408d767153d754c36dd5
GIT binary patch
literal 5025
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
new file mode 100644
index 0000000000000000000000000000000000000000..4eaa7fc3ad5f3cf382b4e6bb4c70a9239cf49c20
GIT binary patch
literal 6024
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
Ci3}|O
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/text/text-fixed-slice.yaml
@@ -0,0 +1,20 @@
+# Verify that the option to configure performance / quality settings for
+# subpixel AA with picture caching is respected.
+root:
+  items:
+    - type: scroll-frame
+      bounds: [0, 0, 500, 100]
+      content-size: [500, 100]
+      id: 2
+      items:
+      - type: rect
+        bounds: [0, 0, 500, 100]
+        color: white
+    - type: rect
+      bounds: [0, 0, 500, 100]
+      color: white
+    - text: "The sun has frightened off the night!"
+      origin: 20 40
+      size: 20
+      font: "FreeSans.ttf"
+      clip-and-scroll: [2, 2]
--- a/gfx/wr/wrench/src/reftest.rs
+++ b/gfx/wr/wrench/src/reftest.rs
@@ -94,16 +94,17 @@ pub struct Reftest {
     reference: PathBuf,
     font_render_mode: Option<FontRenderMode>,
     max_difference: usize,
     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 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,
             "{} {} {}",
@@ -213,16 +214,17 @@ impl ReftestManifest {
             let mut max_count = 0;
             let mut op = ReftestOp::Equal;
             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;
 
             let mut paths = vec![];
             for (i, token) in tokens.iter().enumerate() {
                 match *token {
                     "include" => {
                         assert!(i == 0, "include must be by itself");
                         let include = dir.join(tokens[1]);
 
@@ -246,16 +248,20 @@ impl ReftestManifest {
                             // Skip due to platform not matching
                             break;
                         }
                     }
                     function if function.starts_with("zoom") => {
                         let (_, args, _) = parse_function(function);
                         zoom_factor = args[0].parse().unwrap();
                     }
+                    function if function.starts_with("allow_sacrificing_subpixel_aa") => {
+                        let (_, args, _) = parse_function(function);
+                        allow_sacrificing_subpixel_aa = Some(args[0].parse().unwrap());
+                    }
                     function if function.starts_with("fuzzy") => {
                         let (_, args, _) = parse_function(function);
                         max_difference = args[0].parse().unwrap();
                         max_count = args[1].parse().unwrap();
                     }
                     function if function.starts_with("draw_calls") => {
                         let (_, args, _) = parse_function(function);
                         extra_checks.push(ExtraCheck::DrawCalls(args[0].parse().unwrap()));
@@ -323,16 +329,17 @@ impl ReftestManifest {
                 reference,
                 font_render_mode,
                 max_difference: cmp::max(max_difference, options.allow_max_difference),
                 num_differences: cmp::max(max_count, options.allow_num_differences),
                 extra_checks,
                 disable_dual_source_blending,
                 allow_mipmaps,
                 zoom_factor,
+                allow_sacrificing_subpixel_aa,
             });
         }
 
         ReftestManifest { reftests: reftests }
     }
 
     fn find(&self, prefix: &Path) -> Vec<&Reftest> {
         self.reftests
@@ -478,16 +485,28 @@ impl<'a> ReftestHarness<'a> {
         println!("REFTEST {}", t);
 
         self.wrench
             .api
             .send_debug_cmd(
                 DebugCommand::ClearCaches(ClearCache::all())
             );
 
+        let quality_settings = match t.allow_sacrificing_subpixel_aa {
+            Some(allow_sacrificing_subpixel_aa) => {
+                QualitySettings {
+                    allow_sacrificing_subpixel_aa,
+                }
+            }
+            None => {
+                QualitySettings::default()
+            }
+        };
+
+        self.wrench.set_quality_settings(quality_settings);
         self.wrench.set_page_zoom(ZoomFactor::new(t.zoom_factor));
 
         if t.disable_dual_source_blending {
             self.wrench
                 .api
                 .send_debug_cmd(
                     DebugCommand::EnableDualSourceBlending(false)
                 );
--- a/gfx/wr/wrench/src/wrench.rs
+++ b/gfx/wr/wrench/src/wrench.rs
@@ -277,16 +277,22 @@ impl Wrench {
         wrench.set_title("start");
         let mut txn = Transaction::new();
         txn.set_root_pipeline(wrench.root_pipeline_id);
         wrench.api.send_transaction(wrench.document_id, txn);
 
         wrench
     }
 
+    pub fn set_quality_settings(&mut self, settings: QualitySettings) {
+        let mut txn = Transaction::new();
+        txn.set_quality_settings(settings);
+        self.api.send_transaction(self.document_id, txn);
+    }
+
     pub fn get_page_zoom(&self) -> ZoomFactor {
         self.page_zoom_factor
     }
 
     pub fn set_page_zoom(&mut self, zoom_factor: ZoomFactor) {
         if self.page_zoom_factor.get() != zoom_factor.get() {
             self.page_zoom_factor = zoom_factor;
             let mut txn = Transaction::new();