Bug 1582624 - Add partial present API for webrender. r=nical
authorGlenn Watson <git@intuitionlibrary.com>
Sun, 06 Oct 2019 20:59:36 +0000
changeset 496457 79e9381b90c80d14dad1de04f2f339cef64d7ee2
parent 496456 ea10c9b6a81d795ab96a16e07ea2c64f0a0de247
child 496458 41c6b7e917fdd6fceb19f69ca0f8d93f118aaffe
push id114143
push userrgurzau@mozilla.com
push dateMon, 07 Oct 2019 09:35:08 +0000
treeherdermozilla-inbound@3955e0a93047 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnical
bugs1582624
milestone71.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 1582624 - Add partial present API for webrender. r=nical Differential Revision: https://phabricator.services.mozilla.com/D47732
gfx/wr/webrender/src/batch.rs
gfx/wr/webrender/src/composite.rs
gfx/wr/webrender/src/frame_builder.rs
gfx/wr/webrender/src/lib.rs
gfx/wr/webrender/src/picture.rs
gfx/wr/webrender/src/prim_store/mod.rs
gfx/wr/webrender/src/render_target.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/util.rs
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -2,17 +2,17 @@
  * 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, ClipMode, ExternalImageType, ImageRendering};
 use api::{YuvColorSpace, YuvFormat, ColorDepth, ColorRange, PremultipliedColorF, RasterSpace};
 use api::units::*;
 use crate::clip::{ClipDataStore, ClipNodeFlags, ClipNodeRange, ClipItemKind, ClipStore};
 use crate::clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex, CoordinateSystemId};
-use crate::composite::{CompositeConfig, CompositeTile, CompositeTileSurface};
+use crate::composite::{CompositeState, CompositeTile, CompositeTileSurface};
 use crate::glyph_rasterizer::GlyphFormat;
 use crate::gpu_cache::{GpuBlockData, GpuCache, GpuCacheHandle, GpuCacheAddress};
 use crate::gpu_types::{BrushFlags, BrushInstance, PrimitiveHeaders, ZBufferId, ZBufferIdGenerator};
 use crate::gpu_types::{ClipMaskInstance, SplitCompositeInstance};
 use crate::gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use crate::gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use crate::internal_types::{FastHashMap, SavedTargetIndex, Swizzle, TextureSource, Filter};
 use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, TileSurface};
@@ -640,34 +640,34 @@ impl BatchBuilder {
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskGraph,
         deferred_resolves: &mut Vec<DeferredResolve>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
         surface_spatial_node_index: SpatialNodeIndex,
         z_generator: &mut ZBufferIdGenerator,
-        composite_config: &mut CompositeConfig,
+        composite_state: &mut CompositeState,
     ) {
         for cluster in &pic.prim_list.clusters {
             // Add each run in this picture to the batch.
             for prim_instance in &cluster.prim_instances {
                 self.add_prim_to_batch(
                     prim_instance,
                     cluster.spatial_node_index,
                     ctx,
                     gpu_cache,
                     render_tasks,
                     deferred_resolves,
                     prim_headers,
                     transforms,
                     root_spatial_node_index,
                     surface_spatial_node_index,
                     z_generator,
-                    composite_config,
+                    composite_state,
                 );
             }
         }
     }
 
     // Adds a primitive to a batch.
     // It can recursively call itself in some situations, for
     // example if it encounters a picture where the items
@@ -680,17 +680,17 @@ impl BatchBuilder {
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskGraph,
         deferred_resolves: &mut Vec<DeferredResolve>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
         surface_spatial_node_index: SpatialNodeIndex,
         z_generator: &mut ZBufferIdGenerator,
-        composite_config: &mut CompositeConfig,
+        composite_state: &mut CompositeState,
     ) {
         if prim_instance.visibility_info == PrimitiveVisibilityIndex::INVALID {
             return;
         }
 
         #[cfg(debug_assertions)] //TODO: why is this needed?
         debug_assert_eq!(prim_instance.prepared_frame_id, render_tasks.frame_id());
 
@@ -1194,55 +1194,59 @@ impl BatchBuilder {
                                     None => {
                                         return;
                                     }
                                 };
                                 let world_clip_rect = map_local_to_world
                                     .map(&local_tile_clip_rect)
                                     .expect("bug: unable to map clip rect");
                                 let device_clip_rect = (world_clip_rect * ctx.global_device_pixel_scale).round();
-                                let z_id = composite_config.z_generator.next();
+                                let z_id = composite_state.z_generator.next();
                                 for key in &tile_cache.tiles_to_draw {
                                     let tile = &tile_cache.tiles[key];
                                     let device_rect = (tile.world_rect * ctx.global_device_pixel_scale).round();
+                                    let dirty_rect = (tile.world_dirty_rect * ctx.global_device_pixel_scale).round();
                                     let surface = tile.surface.as_ref().expect("no tile surface set!");
                                     match surface {
                                         TileSurface::Color { color } => {
-                                            composite_config.opaque_tiles.push(CompositeTile {
+                                            composite_state.opaque_tiles.push(CompositeTile {
                                                 surface: CompositeTileSurface::Color { color: *color },
                                                 rect: device_rect,
+                                                dirty_rect,
                                                 clip_rect: device_clip_rect,
                                                 z_id,
                                             });
                                         }
                                         TileSurface::Clear => {
-                                            composite_config.clear_tiles.push(CompositeTile {
+                                            composite_state.clear_tiles.push(CompositeTile {
                                                 surface: CompositeTileSurface::Clear,
                                                 rect: device_rect,
+                                                dirty_rect,
                                                 clip_rect: device_clip_rect,
                                                 z_id,
                                             });
                                         }
                                         TileSurface::Texture { handle, .. } => {
                                             let cache_item = ctx.resource_cache.texture_cache.get(handle);
 
                                             let composite_tile = CompositeTile {
                                                 surface: CompositeTileSurface::Texture {
                                                     texture_id: cache_item.texture_id,
                                                     texture_layer: cache_item.texture_layer,
                                                 },
                                                 rect: device_rect,
+                                                dirty_rect,
                                                 clip_rect: device_clip_rect,
                                                 z_id,
                                             };
 
                                             if tile.is_opaque || tile_cache.is_opaque() {
-                                                composite_config.opaque_tiles.push(composite_tile);
+                                                composite_state.opaque_tiles.push(composite_tile);
                                             } else {
-                                                composite_config.alpha_tiles.push(composite_tile);
+                                                composite_state.alpha_tiles.push(composite_tile);
                                             }
                                         }
                                     }
                                 }
                             }
                             PictureCompositeMode::Filter(ref filter) => {
                                 assert!(filter.is_visible());
                                 match filter {
@@ -1705,17 +1709,17 @@ impl BatchBuilder {
                             gpu_cache,
                             render_tasks,
                             deferred_resolves,
                             prim_headers,
                             transforms,
                             root_spatial_node_index,
                             surface_spatial_node_index,
                             z_generator,
-                            composite_config,
+                            composite_state,
                         );
                     }
                 }
             }
             PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
                 let prim_data = &ctx.data_stores.image_border[data_handle];
                 let common_data = &prim_data.common;
                 let border_data = &prim_data.kind;
--- a/gfx/wr/webrender/src/composite.rs
+++ b/gfx/wr/webrender/src/composite.rs
@@ -28,31 +28,40 @@ pub enum CompositeTileSurface {
 
 /// Describes the geometry and surface of a tile to be composited
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CompositeTile {
     pub surface: CompositeTileSurface,
     pub rect: DeviceRect,
     pub clip_rect: DeviceRect,
+    pub dirty_rect: DeviceRect,
     pub z_id: ZBufferId,
 }
 
 /// The list of tiles to be drawn this frame
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct CompositeConfig {
+pub struct CompositeState {
     pub opaque_tiles: Vec<CompositeTile>,
     pub alpha_tiles: Vec<CompositeTile>,
     pub clear_tiles: Vec<CompositeTile>,
     pub z_generator: ZBufferIdGenerator,
+    // If false, we can't rely on the dirty rects in the CompositeTile
+    // instances. This currently occurs during a scroll event, as a
+    // signal to refresh the whole screen. This is only a temporary
+    // measure until we integrate with OS compositors. In the meantime
+    // it gives us the ability to partial present for any non-scroll
+    // case as a simple win (e.g. video, animation etc).
+    pub dirty_rects_are_valid: bool,
 }
 
-impl CompositeConfig {
+impl CompositeState {
     pub fn new() -> Self {
-        CompositeConfig {
+        CompositeState {
             opaque_tiles: Vec::new(),
             alpha_tiles: Vec::new(),
             clear_tiles: Vec::new(),
             z_generator: ZBufferIdGenerator::new(0),
+            dirty_rects_are_valid: true,
         }
     }
 }
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ColorF, DebugFlags, DocumentLayer, FontRenderMode, PremultipliedColorF};
 use api::{PipelineId};
 use api::units::*;
 use crate::batch::{BatchBuilder, AlphaBatchBuilder, AlphaBatchContainer};
 use crate::clip::{ClipStore, ClipChainStack};
 use crate::clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
-use crate::composite::CompositeConfig;
+use crate::composite::CompositeState;
 use crate::debug_render::DebugItem;
 use crate::gpu_cache::{GpuCache, GpuCacheHandle};
 use crate::gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator};
 use crate::gpu_types::TransformData;
 use crate::internal_types::{FastHashMap, PlaneSplitter, SavedTargetIndex};
 use crate::picture::{PictureUpdateState, SurfaceInfo, ROOT_SURFACE_INDEX, SurfaceIndex, RecordedDirtyRegion};
 use crate::picture::{RetainedTiles, TileCacheInstance, DirtyRegion, SurfaceRenderTasks, SubpixelMode};
 use crate::prim_store::{SpaceMapper, PictureIndex, PrimitiveDebugId, PrimitiveScratchBuffer};
@@ -124,16 +124,17 @@ pub struct FrameVisibilityState<'a> {
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub scratch: &'a mut PrimitiveScratchBuffer,
     pub tile_cache: Option<Box<TileCacheInstance>>,
     pub retained_tiles: &'a mut RetainedTiles,
     pub data_stores: &'a mut DataStores,
     pub clip_chain_stack: ClipChainStack,
     pub render_tasks: &'a mut RenderTaskGraph,
+    pub composite_state: &'a mut CompositeState,
 }
 
 pub struct FrameBuildingContext<'a> {
     pub global_device_pixel_scale: DevicePixelScale,
     pub scene_properties: &'a SceneProperties,
     pub pipelines: &'a FastHashMap<PipelineId, Arc<ScenePipeline>>,
     pub global_screen_world_rect: WorldRect,
     pub clip_scroll_tree: &'a ClipScrollTree,
@@ -233,16 +234,17 @@ impl FrameBuilder {
         global_device_pixel_scale: DevicePixelScale,
         scene_properties: &SceneProperties,
         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,
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
         if scene.prim_store.pictures.is_empty() {
             return None
         }
 
         scratch.begin_frame();
@@ -331,16 +333,17 @@ impl FrameBuilder {
                 gpu_cache,
                 clip_store: &mut scene.clip_store,
                 scratch,
                 tile_cache: None,
                 retained_tiles: &mut retained_tiles,
                 data_stores,
                 clip_chain_stack: ClipChainStack::new(),
                 render_tasks,
+                composite_state,
             };
 
             scene.prim_store.update_visibility(
                 scene.root_pic_index,
                 ROOT_SURFACE_INDEX,
                 &global_screen_world_rect,
                 &visibility_context,
                 &mut visibility_state,
@@ -470,39 +473,40 @@ impl FrameBuilder {
         let mut render_tasks = RenderTaskGraph::new(
             stamp.frame_id(),
             render_task_counters,
         );
         let mut surfaces = Vec::new();
 
         let output_size = scene.output_rect.size.to_i32();
         let screen_world_rect = (scene.output_rect.to_f32() / global_device_pixel_scale).round_out();
+        let mut composite_state = CompositeState::new();
 
         let main_render_task_id = self.build_layer_screen_rects_and_cull_layers(
             scene,
             screen_world_rect,
             resource_cache,
             gpu_cache,
             &mut render_tasks,
             &mut profile_counters,
             global_device_pixel_scale,
             scene_properties,
             &mut transform_palette,
             data_stores,
             &mut surfaces,
             scratch,
             debug_flags,
             texture_cache_profile,
+            &mut composite_state,
         );
 
         let mut passes;
         let mut deferred_resolves = vec![];
         let mut has_texture_cache_tasks = false;
         let mut prim_headers = PrimitiveHeaders::new();
-        let mut composite_config = CompositeConfig::new();
 
         {
             profile_marker!("Batching");
 
             passes = render_tasks.generate_passes(
                 main_render_task_id,
                 output_size,
                 scene.config.gpu_supports_fast_clears,
@@ -535,17 +539,17 @@ impl FrameBuilder {
                     &mut ctx,
                     gpu_cache,
                     &mut render_tasks,
                     &mut deferred_resolves,
                     &scene.clip_store,
                     &mut transform_palette,
                     &mut prim_headers,
                     &mut z_generator,
-                    &mut composite_config,
+                    &mut composite_state,
                 );
 
                 match pass.kind {
                     RenderPassKind::MainFramebuffer { .. } => {}
                     RenderPassKind::OffScreen {
                         ref texture_cache,
                         ref picture_cache,
                         ..
@@ -576,19 +580,18 @@ impl FrameBuilder {
             transform_palette: transform_palette.finish(),
             render_tasks,
             deferred_resolves,
             gpu_cache_frame_id,
             has_been_rendered: false,
             has_texture_cache_tasks,
             prim_headers,
             recorded_dirty_regions: mem::replace(&mut scratch.recorded_dirty_regions, Vec::new()),
-            dirty_rects: mem::replace(&mut scratch.dirty_rects, Vec::new()),
             debug_items: mem::replace(&mut scratch.debug_items, Vec::new()),
-            composite_config,
+            composite_state,
         }
     }
 }
 
 /// Processes this pass to prepare it for rendering.
 ///
 /// Among other things, this allocates output regions for each of our tasks
 /// (added via `add_render_task`) in a RenderTarget and assigns it into that
@@ -598,17 +601,17 @@ pub fn build_render_pass(
     ctx: &mut RenderTargetContext,
     gpu_cache: &mut GpuCache,
     render_tasks: &mut RenderTaskGraph,
     deferred_resolves: &mut Vec<DeferredResolve>,
     clip_store: &ClipStore,
     transforms: &mut TransformPalette,
     prim_headers: &mut PrimitiveHeaders,
     z_generator: &mut ZBufferIdGenerator,
-    composite_config: &mut CompositeConfig,
+    composite_state: &mut CompositeState,
 ) {
     profile_scope!("RenderPass::build");
 
     match pass.kind {
         RenderPassKind::MainFramebuffer { ref mut main_target, .. } => {
             for &task_id in &pass.tasks {
                 assert_eq!(render_tasks[task_id].target_kind(), RenderTargetKind::Color);
                 main_target.add_task(
@@ -624,17 +627,17 @@ pub fn build_render_pass(
             main_target.build(
                 ctx,
                 gpu_cache,
                 render_tasks,
                 deferred_resolves,
                 prim_headers,
                 transforms,
                 z_generator,
-                composite_config,
+                composite_state,
             );
         }
         RenderPassKind::OffScreen {
             ref mut color,
             ref mut alpha,
             ref mut texture_cache,
             ref mut picture_cache,
         } => {
@@ -822,17 +825,17 @@ pub fn build_render_pass(
                     gpu_cache,
                     render_tasks,
                     deferred_resolves,
                     prim_headers,
                     transforms,
                     root_spatial_node_index,
                     surface_spatial_node_index,
                     z_generator,
-                    composite_config,
+                    composite_state,
                 );
 
                 // Create picture cache targets, one per render task, and assign
                 // the correct batcher to them.
                 let batchers = batch_builder.finalize();
                 for (task_id, batcher) in task_ids.into_iter().zip(batchers.into_iter()) {
                     let task = &render_tasks[task_id];
                     let (target_rect, _) = task.get_target_rect();
@@ -877,28 +880,28 @@ pub fn build_render_pass(
                 ctx,
                 gpu_cache,
                 render_tasks,
                 deferred_resolves,
                 saved_color,
                 prim_headers,
                 transforms,
                 z_generator,
-                composite_config,
+                composite_state,
             );
             alpha.build(
                 ctx,
                 gpu_cache,
                 render_tasks,
                 deferred_resolves,
                 saved_alpha,
                 prim_headers,
                 transforms,
                 z_generator,
-                composite_config,
+                composite_state,
             );
         }
     }
 }
 
 /// A rendering-oriented representation of the frame built by the render backend
 /// and presented to the renderer.
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -935,27 +938,23 @@ pub struct Frame {
     /// renderer.
     pub has_been_rendered: bool,
 
     /// Dirty regions recorded when generating this frame. Empty when not in
     /// testing.
     #[cfg_attr(feature = "serde", serde(skip))]
     pub recorded_dirty_regions: Vec<RecordedDirtyRegion>,
 
-    /// Dirty rects calculated when generating this frame.
-    #[cfg_attr(feature = "serde", serde(skip))]
-    pub dirty_rects: Vec<DeviceIntRect>,
-
     /// Debugging information to overlay for this frame.
     pub debug_items: Vec<DebugItem>,
 
     /// Contains picture cache tiles, and associated information.
     /// Used by the renderer to composite tiles into the framebuffer,
     /// or hand them off to an OS compositor.
-    pub composite_config: CompositeConfig,
+    pub composite_state: CompositeState,
 }
 
 impl Frame {
     // This frame must be flushed if it writes to the
     // texture cache, and hasn't been drawn yet.
     pub fn must_be_drawn(&self) -> bool {
         self.has_texture_cache_tasks && !self.has_been_rendered
     }
--- a/gfx/wr/webrender/src/lib.rs
+++ b/gfx/wr/webrender/src/lib.rs
@@ -206,14 +206,14 @@ pub use crate::device::{ProgramBinary, P
 pub use crate::device::Device;
 pub use crate::frame_builder::ChasePrimitive;
 pub use crate::prim_store::PrimitiveDebugId;
 pub use crate::profiler::{ProfilerHooks, set_profiler_hooks};
 pub use crate::renderer::{
     AsyncPropertySampler, CpuProfile, DebugFlags, OutputImageHandler, RendererKind, ExternalImage,
     ExternalImageHandler, ExternalImageSource, GpuProfile, GraphicsApi, GraphicsApiInfo,
     PipelineInfo, Renderer, RendererOptions, RenderResults, RendererStats, SceneBuilderHooks,
-    ThreadListener, ShaderPrecacheFlags, MAX_VERTEX_TEXTURE_WIDTH,
+    ThreadListener, ShaderPrecacheFlags, MAX_VERTEX_TEXTURE_WIDTH, PresentConfig,
 };
 pub use crate::screen_capture::{AsyncScreenshotHandle, RecordedFrameHandle};
 pub use crate::shade::{Shaders, WrShaders};
 pub use api as webrender_api;
 pub use webrender_build::shader::ProgramSourceDigest;
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -32,17 +32,17 @@ use crate::render_task_graph::RenderTask
 use crate::render_target::RenderTargetKind;
 use crate::render_task::{RenderTask, RenderTaskLocation, BlurTaskCache, ClearMode};
 use crate::resource_cache::ResourceCache;
 use crate::scene::SceneProperties;
 use smallvec::SmallVec;
 use std::{mem, u8, marker};
 use std::sync::atomic::{AtomicUsize, Ordering};
 use crate::texture_cache::TextureCacheHandle;
-use crate::util::{TransformedRectKind, MatrixHelpers, MaxRect, scale_factors, VecHelper, subtract_rect};
+use crate::util::{TransformedRectKind, MatrixHelpers, MaxRect, scale_factors, VecHelper};
 use crate::filterdata::{FilterDataHandle};
 
 /*
  A picture represents a dynamically rendered image. It consists of:
 
  * A number of primitives that are drawn onto the picture.
  * A composite operation describing how to composite this
    picture into its parent.
@@ -421,17 +421,17 @@ pub struct Tile {
     pub is_opaque: bool,
     /// Root node of the quadtree dirty rect tracker.
     root: TileNode,
     /// The picture space dirty rect for this tile.
     dirty_rect: PictureRect,
     /// The world space dirty rect for this tile.
     /// TODO(gw): We have multiple dirty rects available due to the quadtree above. In future,
     ///           expose these as multiple dirty rects, which will help in some cases.
-    world_dirty_rect: WorldRect,
+    pub world_dirty_rect: WorldRect,
     /// The last rendered background color on this tile.
     background_color: Option<ColorF>,
 }
 
 impl Tile {
     /// Construct a new, invalid tile.
     fn new(
         id: TileId,
@@ -1748,20 +1748,18 @@ impl TileCacheInstance {
         true
     }
 
     /// Apply any updates after prim dependency updates. This applies
     /// any late tile invalidations, and sets up the dirty rect and
     /// set of tile blits.
     pub fn post_update(
         &mut self,
-        resource_cache: &mut ResourceCache,
-        gpu_cache: &mut GpuCache,
         frame_context: &FrameVisibilityContext,
-        scratch: &mut PrimitiveScratchBuffer,
+        frame_state: &mut FrameVisibilityState,
     ) {
         self.tiles_to_draw.clear();
         self.dirty_region.clear();
 
         // Detect if the picture cache was scrolled or scaled. In this case,
         // the device space dirty rects aren't applicable (until we properly
         // integrate with OS compositors that can handle scrolling slices).
         let root_transform = frame_context
@@ -1769,16 +1767,17 @@ impl TileCacheInstance {
             .get_relative_transform(
                 self.spatial_node_index,
                 ROOT_SPATIAL_NODE_INDEX,
             )
             .into();
         let root_transform_changed = root_transform != self.root_transform;
         if root_transform_changed {
             self.root_transform = root_transform;
+            frame_state.composite_state.dirty_rects_are_valid = false;
         }
 
         // Diff the state of the spatial nodes between last frame build and now.
         let mut old_spatial_nodes = mem::replace(&mut self.spatial_nodes, FastHashMap::default());
 
         // TODO(gw): Maybe remove the used_spatial_nodes set and just mutate / create these
         //           diffs inside add_prim_dependency?
         for spatial_node_index in self.used_spatial_nodes.drain() {
@@ -1823,79 +1822,36 @@ impl TileCacheInstance {
             backdrop: self.backdrop,
             spatial_nodes: &self.spatial_nodes,
             opacity_bindings: &self.opacity_bindings,
             pic_to_world_mapper,
             current_tile_size: self.current_tile_size,
         };
 
         let mut state = TilePostUpdateState {
-            resource_cache,
-            gpu_cache,
-            scratch,
+            resource_cache: frame_state.resource_cache,
+            gpu_cache: frame_state.gpu_cache,
+            scratch: frame_state.scratch,
             dirty_region: &mut self.dirty_region,
         };
 
         // Step through each tile and invalidate if the dependencies have changed.
         for (key, tile) in self.tiles.iter_mut() {
             if tile.post_update(
                 &ctx,
                 &mut state,
             ) {
-                // If we have dirty tiles and a scroll didn't occur (e.g. video
-                // playback or animation) we can produce a valid dirty rect for
-                // Gecko to use as partial present.
-                if !root_transform_changed && !tile.dirty_rect.is_empty() {
-                    state.scratch.dirty_rects.push(
-                        (tile.world_dirty_rect * frame_context.global_device_pixel_scale).to_i32()
-                    );
-                }
-
                 self.tiles_to_draw.push(*key);
             }
         }
 
-        // If the cache was scrolled / scaled, just push a full-screen dirty rect
-        // for now, to ensure correctness. This won't be required once we fully
-        // support OS compositors, where we can pass the transform of the cache slice.
-        if root_transform_changed {
-            scratch.dirty_rects.push(
-                (frame_context.global_screen_world_rect * frame_context.global_device_pixel_scale).to_i32()
-            );
-        } else {
-            // TODO(gw): This is a total hack! While experimenting with dirty rects and
-            //           partial present, we need to include a dirty rect for the area
-            //           of the screen that is _not_ picture cached. Since we know there
-            //           is only a single picture cache right now, we can derive this
-            //           area by subtracting the picture cache rect from the screen rect.
-            //           This only works reliably while we can assume that (a) there is
-            //           only a single picture cache slice and (b) it's opaque. In future,
-            //           once we enable multiple picture cache slices, we won't need to
-            //           do this at all, since all content will be in a cache slice that
-            //           can provide valid dirty rects.
-            let world_clip_rect = ctx.pic_to_world_mapper
-                .map(&self.local_clip_rect)
-                .expect("bug - unable to map picture clip rect to world");
-            let mut non_cached_rects = Vec::new();
-            subtract_rect(
-                &ctx.global_screen_world_rect,
-                &world_clip_rect,
-                &mut non_cached_rects,
-            );
-            for rect in non_cached_rects {
-                scratch.dirty_rects.push(
-                    (rect * frame_context.global_device_pixel_scale).to_i32()
-                );
-            }
-        }
-
         // When under test, record a copy of the dirty region to support
         // invalidation testing in wrench.
         if frame_context.config.testing {
-            scratch.recorded_dirty_regions.push(self.dirty_region.record());
+            frame_state.scratch.recorded_dirty_regions.push(self.dirty_region.record());
         }
     }
 }
 
 /// Maintains a stack of picture and surface information, that
 /// is used during the initial picture traversal.
 pub struct PictureUpdateState<'a> {
     surfaces: &'a mut Vec<SurfaceInfo>,
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -1687,34 +1687,30 @@ pub struct PrimitiveScratchBuffer {
 
     /// List of the visibility information for currently visible primitives.
     pub prim_info: Vec<PrimitiveVisibility>,
 
     /// List of dirty regions for the cached pictures in this document, used to
     /// verify invalidation in wrench reftests. Only collected in testing.
     pub recorded_dirty_regions: Vec<RecordedDirtyRegion>,
 
-    /// List of dirty rects for the cached pictures in this document.
-    pub dirty_rects: Vec<DeviceIntRect>,
-
     /// List of debug display items for rendering.
     pub debug_items: Vec<DebugItem>,
 }
 
 impl PrimitiveScratchBuffer {
     pub fn new() -> Self {
         PrimitiveScratchBuffer {
             clip_mask_instances: Vec::new(),
             glyph_keys: GlyphKeyStorage::new(0),
             border_cache_handles: BorderHandleStorage::new(0),
             segments: SegmentStorage::new(0),
             segment_instances: SegmentInstanceStorage::new(0),
             gradient_tiles: GradientTileStorage::new(0),
             recorded_dirty_regions: Vec::new(),
-            dirty_rects: Vec::new(),
             debug_items: Vec::new(),
             prim_info: Vec::new(),
         }
     }
 
     pub fn recycle(&mut self, recycler: &mut Recycler) {
         recycler.recycle_vec(&mut self.clip_mask_instances);
         recycler.recycle_vec(&mut self.prim_info);
@@ -1741,17 +1737,16 @@ impl PrimitiveScratchBuffer {
         //           should fix this in the future to retain handles.
         self.gradient_tiles.clear();
 
         self.prim_info.clear();
 
         self.debug_items.clear();
 
         assert!(self.recorded_dirty_regions.is_empty(), "Should have sent to Renderer");
-        assert!(self.dirty_rects.is_empty(), "Should have sent to Renderer");
     }
 
     #[allow(dead_code)]
     pub fn push_debug_rect(
         &mut self,
         rect: DeviceRect,
         outer_color: ColorF,
         inner_color: ColorF,
@@ -2310,20 +2305,18 @@ impl PrimitiveStore {
                 pic.precise_local_rect = pic_local_rect;
             }
 
             if let PictureCompositeMode::TileCache { .. } = rc.composite_mode {
                 let mut tile_cache = frame_state.tile_cache.take().unwrap();
 
                 // Build the dirty region(s) for this tile cache.
                 tile_cache.post_update(
-                    frame_state.resource_cache,
-                    frame_state.gpu_cache,
                     frame_context,
-                    frame_state.scratch,
+                    frame_state,
                 );
 
                 pic.tile_cache = Some(tile_cache);
             }
 
             None
         } else {
             let parent_surface = &frame_context.surfaces[parent_surface_index.0 as usize];
--- a/gfx/wr/webrender/src/render_target.rs
+++ b/gfx/wr/webrender/src/render_target.rs
@@ -4,17 +4,17 @@
 
 
 use api::units::*;
 use api::{ColorF, PremultipliedColorF, ImageFormat, LineOrientation, BorderStyle, PipelineId};
 use crate::batch::{AlphaBatchBuilder, AlphaBatchContainer, BatchTextures, resolve_image};
 use crate::batch::{ClipBatcher, BatchBuilder};
 use crate::clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX};
 use crate::clip::ClipStore;
-use crate::composite::CompositeConfig;
+use crate::composite::CompositeState;
 use crate::device::Texture;
 use crate::frame_builder::{FrameGlobalResources};
 use crate::gpu_cache::{GpuCache, GpuCacheAddress};
 use crate::gpu_types::{BorderInstance, SvgFilterInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
 use crate::gpu_types::{TransformPalette, ZBufferIdGenerator};
 use crate::internal_types::{FastHashMap, TextureSource, LayerIndex, Swizzle, SavedTargetIndex};
 use crate::picture::SurfaceInfo;
 use crate::prim_store::{PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer, PrimitiveVisibilityMask};
@@ -97,17 +97,17 @@ pub trait RenderTarget {
         &mut self,
         _ctx: &mut RenderTargetContext,
         _gpu_cache: &mut GpuCache,
         _render_tasks: &mut RenderTaskGraph,
         _deferred_resolves: &mut Vec<DeferredResolve>,
         _prim_headers: &mut PrimitiveHeaders,
         _transforms: &mut TransformPalette,
         _z_generator: &mut ZBufferIdGenerator,
-        _composite_config: &mut CompositeConfig,
+        _composite_state: &mut CompositeState,
     ) {
     }
 
     /// Associates a `RenderTask` with this target. That task must be assigned
     /// to a region returned by invoking `allocate()` on this target.
     ///
     /// TODO(gw): It's a bit odd that we need the deferred resolves and mutable
     /// GPU cache here. They are typically used by the build step above. They
@@ -198,31 +198,31 @@ impl<T: RenderTarget> RenderTargetList<T
         ctx: &mut RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskGraph,
         deferred_resolves: &mut Vec<DeferredResolve>,
         saved_index: Option<SavedTargetIndex>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         z_generator: &mut ZBufferIdGenerator,
-        composite_config: &mut CompositeConfig,
+        composite_state: &mut CompositeState,
     ) {
         debug_assert_eq!(None, self.saved_index);
         self.saved_index = saved_index;
 
         for target in &mut self.targets {
             target.build(
                 ctx,
                 gpu_cache,
                 render_tasks,
                 deferred_resolves,
                 prim_headers,
                 transforms,
                 z_generator,
-                composite_config,
+                composite_state,
             );
         }
     }
 
     pub fn allocate(
         &mut self,
         alloc_size: DeviceIntSize,
     ) -> (RenderTargetIndex, DeviceIntPoint) {
@@ -331,17 +331,17 @@ impl RenderTarget for ColorRenderTarget 
         &mut self,
         ctx: &mut RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskGraph,
         deferred_resolves: &mut Vec<DeferredResolve>,
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         z_generator: &mut ZBufferIdGenerator,
-        composite_config: &mut CompositeConfig,
+        composite_state: &mut CompositeState,
     ) {
         let mut merged_batches = AlphaBatchContainer::new(None);
 
         for task_id in &self.alpha_tasks {
             let task = &render_tasks[*task_id];
 
             match task.clear_mode {
                 ClearMode::One |
@@ -399,17 +399,17 @@ impl RenderTarget for ColorRenderTarget 
                         gpu_cache,
                         render_tasks,
                         deferred_resolves,
                         prim_headers,
                         transforms,
                         raster_spatial_node_index,
                         pic_task.surface_spatial_node_index,
                         z_generator,
-                        composite_config,
+                        composite_state,
                     );
 
                     let alpha_batch_builders = batch_builder.finalize();
 
                     for batcher in alpha_batch_builders {
                         batcher.build(
                             &mut self.alpha_batch_containers,
                             &mut merged_batches,
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -42,17 +42,17 @@ use api::{DebugCommand, MemoryReport, Vo
 use api::{RenderApiSender, RenderNotifier, TextureTarget};
 use api::channel;
 use api::units::*;
 pub use api::DebugFlags;
 use api::channel::{MsgSender, PayloadReceiverHelperMethods};
 use crate::batch::{AlphaBatchContainer, BatchKind, BatchFeatures, BatchTextures, BrushBatchKind, ClipBatchList};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use crate::capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
-use crate::composite::{CompositeConfig, CompositeTileSurface, CompositeTile};
+use crate::composite::{CompositeState, CompositeTileSurface, CompositeTile};
 use crate::debug_colors;
 use crate::debug_render::{DebugItem, DebugRenderer};
 use crate::device::{DepthFunction, Device, GpuFrameId, Program, UploadMethod, Texture, PBO};
 use crate::device::{DrawTarget, ExternalTexture, FBOId, ReadTarget, TextureSlot};
 use crate::device::{ShaderError, TextureFilter, TextureFlags,
              VertexUsageHint, VAO, VBO, CustomVAO};
 use crate::device::ProgramCache;
 use crate::device::query::GpuTimer;
@@ -935,16 +935,44 @@ impl CpuProfile {
             frame_id,
             backend_time_ns,
             composite_time_ns,
             draw_calls,
         }
     }
 }
 
+/// Defines the configuration that the client is using to present results
+/// to the underlying device.
+#[derive(Debug, Copy, Clone)]
+pub enum PresentConfig {
+    /// In this mode, the device supports updating some number of dirty
+    /// regions since the last frame was drawn, which can provide significant
+    /// power savings.
+    PartialPresent {
+        /// The maximum number of dirty rects that are supported by the device.
+        max_dirty_rects: usize,
+    },
+}
+
+/// The selected partial present mode for a given frame.
+#[derive(Debug, Copy, Clone)]
+enum PartialPresentMode {
+    /// The device supports fewer dirty rects than the number of dirty rects
+    /// that WR produced. In this case, the WR dirty rects are union'ed into
+    /// a single dirty rect, that is provided to the caller.
+    Single {
+        dirty_rect: DeviceRect,
+    },
+    /// The device supports at least the same number of dirty rects as the
+    /// number of dirty rects produced by WR this frame. In this case, the
+    /// tile dirty rect is applied to the clip for each tile as it's drawn.
+    Multi,
+}
+
 /// A Texture that has been initialized by the `device` module and is ready to
 /// be used.
 struct ActiveTexture {
     texture: Texture,
     saved_index: Option<SavedTargetIndex>,
 }
 
 /// Helper struct for resolving device Textures for use during rendering passes.
@@ -1834,16 +1862,24 @@ pub struct Renderer {
 
     /// The set of documents which we've seen a publish for since last render.
     documents_seen: FastHashSet<DocumentId>,
 
     #[cfg(feature = "capture")]
     read_fbo: FBOId,
     #[cfg(feature = "replay")]
     owned_external_images: FastHashMap<(ExternalImageId, u8), ExternalTexture>,
+
+    /// The current presentation config, affecting how WR composites into the
+    /// final scene.
+    present_config: Option<PresentConfig>,
+
+    /// If true, partial present state has been reset and everything needs to
+    /// be drawn on the next render.
+    force_redraw: bool,
 }
 
 #[derive(Debug)]
 pub enum RendererError {
     Shader(ShaderError),
     Thread(std::io::Error),
     Resource(ResourceCacheError),
     MaxTextureSize,
@@ -2105,16 +2141,17 @@ impl Renderer {
             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,
         };
         info!("WR {:?}", config);
 
+        let present_config = options.present_config.take();
         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;
         let make_size_of_ops =
             move || size_of_op.map(|o| MallocSizeOfOps::new(o, enclosing_size_of_op));
         let recorder = options.recorder;
@@ -2337,16 +2374,18 @@ impl Renderer {
             #[cfg(feature = "replay")]
             owned_external_images: FastHashMap::default(),
             notifications: Vec::new(),
             device_size: None,
             zoom_debug_texture: None,
             cursor_position: DeviceIntPoint::zero(),
             shared_texture_cache_cleared: false,
             documents_seen: FastHashSet::default(),
+            present_config,
+            force_redraw: true,
         };
 
         // We initially set the flags to default and then now call set_debug_flags
         // to ensure any potential transition when enabling a flag is run.
         renderer.set_debug_flags(debug_flags);
 
         let sender = RenderApiSender::new(api_tx, payload_tx);
         Ok((renderer, sender))
@@ -2807,16 +2846,22 @@ impl Renderer {
         let gpu_profiles = self.gpu_profiles.drain(..).collect();
         (cpu_profiles, gpu_profiles)
     }
 
     pub fn notify_slow_frame(&mut self) {
         self.slow_frame_indicator.changed();
     }
 
+    /// Reset the current partial present state. This forces the entire framebuffer
+    /// to be refreshed next time `render` is called.
+    pub fn force_redraw(&mut self) {
+        self.force_redraw = true;
+    }
+
     /// Renders the current frame.
     ///
     /// A Frame is supplied by calling [`generate_frame()`][webrender_api::Transaction::generate_frame].
     pub fn render(
         &mut self,
         device_size: DeviceIntSize,
     ) -> Result<RenderResults, Vec<RendererError>> {
         self.device_size = Some(device_size);
@@ -2919,32 +2964,30 @@ impl Renderer {
                 assert!(frame.gpu_cache_frame_id <= self.gpu_cache_frame_id,
                     "Received frame depends on a later GPU cache epoch ({:?}) than one we received last via `UpdateGpuCache` ({:?})",
                     frame.gpu_cache_frame_id, self.gpu_cache_frame_id);
 
                 self.draw_frame(
                     frame,
                     device_size,
                     cpu_frame_id,
-                    &mut results.stats,
+                    &mut results,
                     doc_index == 0,
                 );
 
                 if let Some(_) = device_size {
                     self.draw_frame_debug_items(&frame.debug_items);
                 }
                 if self.debug_flags.contains(DebugFlags::PROFILER_DBG) {
                     frame_profiles.push(frame.profile_counters.clone());
                 }
 
                 let dirty_regions =
                     mem::replace(&mut frame.recorded_dirty_regions, Vec::new());
                 results.recorded_dirty_regions.extend(dirty_regions);
-                let dirty_rects = mem::replace(&mut frame.dirty_rects, Vec::new());
-                results.dirty_rects.extend(dirty_rects);
 
                 // If we're the last document, don't call end_pass here, because we'll
                 // be moving on to drawing the debug overlays. See the comment above
                 // the end_pass call in draw_frame about debug draw overlays
                 // for a bit more context.
                 if doc_index != last_document_index {
                     self.texture_resolver.end_pass(&mut self.device, None, None);
                 }
@@ -3833,16 +3876,17 @@ impl Renderer {
             self.device.disable_scissor();
         }
     }
 
     /// Draw a list of tiles to the framebuffer
     fn draw_tile_list<'a, I: Iterator<Item = &'a CompositeTile>>(
         &mut self,
         tiles_iter: I,
+        partial_present_mode: Option<PartialPresentMode>,
         stats: &mut RendererStats,
     ) {
         let mut current_textures = BatchTextures::no_texture();
         let mut instances = Vec::new();
 
         for tile in tiles_iter {
             // Work out the draw params based on the tile surface
             let (texture, layer, color) = match tile.surface {
@@ -3853,32 +3897,45 @@ impl Renderer {
                     (TextureSource::Dummy, 0.0, ColorF::BLACK)
                 }
                 CompositeTileSurface::Texture { texture_id, texture_layer } => {
                     (texture_id, texture_layer as f32, ColorF::WHITE)
                 }
             };
             let textures = BatchTextures::color(texture);
 
+            // Determine a clip rect to apply to this tile, depending on what
+            // the partial present mode is.
+            let partial_clip_rect = match partial_present_mode {
+                Some(PartialPresentMode::Single { dirty_rect }) => dirty_rect,
+                Some(PartialPresentMode::Multi) => tile.dirty_rect,
+                None => tile.rect,
+            };
+
+            let clip_rect = match partial_clip_rect.intersection(&tile.clip_rect) {
+                Some(rect) => rect,
+                None => continue,
+            };
+
             // Flush this batch if the textures aren't compatible
             if !current_textures.is_compatible_with(&textures) {
                 self.draw_instanced_batch(
                     &instances,
                     VertexArrayKind::Composite,
                     &current_textures,
                     stats,
                 );
                 instances.clear();
             }
             current_textures = textures;
 
             // Create the instance and add to current batch
             let instance = CompositeInstance::new(
                 tile.rect,
-                tile.clip_rect,
+                clip_rect,
                 color.premultiplied(),
                 layer,
                 tile.z_id,
             );
             instances.push(instance);
         }
 
         // Flush the last batch
@@ -3893,67 +3950,150 @@ impl Renderer {
     }
 
     /// Composite picture cache tiles into the framebuffer. This is currently
     /// the only way that picture cache tiles get drawn. In future, the tiles
     /// will often be handed to the OS compositor, and this method will be
     /// rarely used.
     fn composite(
         &mut self,
-        composite_config: &CompositeConfig,
+        composite_state: &CompositeState,
+        clear_framebuffer: bool,
         draw_target: DrawTarget,
         projection: &default::Transform3D<f32>,
-        stats: &mut RendererStats,
+        results: &mut RenderResults,
     ) {
         let _gm = self.gpu_profile.start_marker("framebuffer");
         let _timer = self.gpu_profile.start_timer(GPU_TAG_COMPOSITE);
 
         self.device.bind_draw_target(draw_target);
         self.device.enable_depth();
+        self.device.enable_depth_write();
+
+        // Determine the partial present mode for this frame, which is used during
+        // framebuffer clears and calculating the clip rect for each tile that is drawn.
+        let mut partial_present_mode = None;
+
+        if let Some(PresentConfig::PartialPresent { max_dirty_rects }) = self.present_config {
+            // We can only use partial present if we have valid dirty rects and the
+            // client hasn't reset partial present state since last frame.
+            if composite_state.dirty_rects_are_valid && !self.force_redraw {
+                let mut dirty_rect_count = 0;
+                let mut combined_dirty_rect = DeviceRect::zero();
+
+                // Work out how many dirty rects WR produced, and if that's more than
+                // what the device supports.
+                for tile in composite_state.opaque_tiles.iter().chain(composite_state.alpha_tiles.iter()) {
+                    if !tile.dirty_rect.is_empty() {
+                        dirty_rect_count += 1;
+                        results.dirty_rects.push(tile.dirty_rect.to_i32());
+                        combined_dirty_rect = combined_dirty_rect.union(&tile.dirty_rect);
+                    }
+                }
+
+                let mode = if dirty_rect_count > max_dirty_rects {
+                    // In this case, WR produced more dirty rects than what the device supports.
+                    // As a simple way to handle this, just union all the dirty rects into a
+                    // single dirty rect.
+                    // TODO(gw): In future, maybe we can handle this case better by trying to
+                    //           coalesce dirty rects into a smaller number rather than just
+                    //           falling back to a single dirty rect.
+                    let combined_dirty_rect = combined_dirty_rect.round();
+                    results.dirty_rects.clear();
+                    results.dirty_rects.push(combined_dirty_rect.to_i32());
+                    PartialPresentMode::Single {
+                        dirty_rect: combined_dirty_rect,
+                    }
+                } else {
+                    PartialPresentMode::Multi
+                };
+
+                partial_present_mode = Some(mode);
+            }
+
+            self.force_redraw = false;
+        }
+
+        // Clear the framebuffer, if required
+        if clear_framebuffer {
+            let clear_color = self.clear_color.map(|color| color.to_array());
+
+            match partial_present_mode {
+                Some(PartialPresentMode::Single { dirty_rect }) => {
+                    // We have a single dirty rect, so clear only that
+                    self.device.clear_target(clear_color,
+                                             Some(1.0),
+                                             Some(draw_target.to_framebuffer_rect(dirty_rect.to_i32())));
+                }
+                Some(PartialPresentMode::Multi) => {
+                    // We have a dirty rect per tile, so clear each of them
+                    let opaque_iter = composite_state.opaque_tiles.iter();
+                    let clear_iter = composite_state.clear_tiles.iter();
+                    let alpha_iter = composite_state.alpha_tiles.iter();
+
+                    for tile in opaque_iter.chain(clear_iter.chain(alpha_iter)) {
+                        if !tile.dirty_rect.is_empty() {
+                            self.device.clear_target(clear_color,
+                                                     Some(1.0),
+                                                     Some(draw_target.to_framebuffer_rect(tile.dirty_rect.to_i32())));
+                        }
+                    }
+                }
+                None => {
+                    // Partial present is disabled, so clear the entire framebuffer
+                    self.device.clear_target(clear_color,
+                                             Some(1.0),
+                                             None);
+                }
+            }
+        }
 
         self.shaders.borrow_mut().composite.bind(
             &mut self.device,
             &projection,
             &mut self.renderer_errors
         );
 
         // Draw opaque tiles first, front-to-back to get maxmum
         // z-reject efficiency.
-        if !composite_config.opaque_tiles.is_empty() {
+        if !composite_state.opaque_tiles.is_empty() {
             let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE);
             self.device.enable_depth_write();
             self.set_blend(false, FramebufferKind::Main);
             self.draw_tile_list(
-                composite_config.opaque_tiles.iter().rev(),
-                stats,
+                composite_state.opaque_tiles.iter().rev(),
+                partial_present_mode,
+                &mut results.stats,
             );
             self.gpu_profile.finish_sampler(opaque_sampler);
         }
 
-        if !composite_config.clear_tiles.is_empty() {
+        if !composite_state.clear_tiles.is_empty() {
             let transparent_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT);
             self.device.disable_depth_write();
             self.set_blend(true, FramebufferKind::Main);
             self.device.set_blend_mode_premultiplied_dest_out();
             self.draw_tile_list(
-                composite_config.clear_tiles.iter(),
-                stats,
+                composite_state.clear_tiles.iter(),
+                partial_present_mode,
+                &mut results.stats,
             );
             self.gpu_profile.finish_sampler(transparent_sampler);
         }
 
         // Draw alpha tiles
-        if !composite_config.alpha_tiles.is_empty() {
+        if !composite_state.alpha_tiles.is_empty() {
             let transparent_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT);
             self.device.disable_depth_write();
             self.set_blend(true, FramebufferKind::Main);
             self.set_blend_mode_premultiplied_alpha(FramebufferKind::Main);
             self.draw_tile_list(
-                composite_config.alpha_tiles.iter(),
-                stats,
+                composite_state.alpha_tiles.iter(),
+                partial_present_mode,
+                &mut results.stats,
             );
             self.gpu_profile.finish_sampler(transparent_sampler);
         }
     }
 
     fn draw_color_target(
         &mut self,
         draw_target: DrawTarget,
@@ -4698,17 +4838,17 @@ impl Renderer {
         debug_assert!(self.texture_resolver.prev_pass_color.is_none());
     }
 
     fn draw_frame(
         &mut self,
         frame: &mut Frame,
         device_size: Option<DeviceIntSize>,
         frame_id: GpuFrameId,
-        stats: &mut RendererStats,
+        results: &mut RenderResults,
         clear_framebuffer: bool,
     ) {
         // These markers seem to crash a lot on Android, see bug 1559834
         #[cfg(not(target_os = "android"))]
         let _gm = self.gpu_profile.start_marker("draw frame");
 
         if frame.passes.is_empty() {
             frame.has_been_rendered = true;
@@ -4734,17 +4874,17 @@ impl Renderer {
                 &TextureSource::PrevPassColor,
                 TextureSampler::PrevPassColor,
                 &mut self.device,
             );
 
             match pass.kind {
                 RenderPassKind::MainFramebuffer { ref main_target, .. } => {
                     if let Some(device_size) = device_size {
-                        stats.color_target_count += 1;
+                        results.stats.color_target_count += 1;
 
                         let offset = frame.content_origin.to_f32();
                         let size = frame.device_rect.size.to_f32();
                         let projection = Transform3D::ortho(
                             offset.x,
                             offset.x + size.width,
                             offset.y + size.height,
                             offset.y,
@@ -4755,42 +4895,36 @@ impl Renderer {
                         let fb_scale = Scale::<_, _, FramebufferPixel>::new(1i32);
                         let mut fb_rect = frame.device_rect * fb_scale;
                         fb_rect.origin.y = device_size.height - fb_rect.origin.y - fb_rect.size.height;
 
                         let draw_target = DrawTarget::Default {
                             rect: fb_rect,
                             total_size: device_size * fb_scale,
                         };
-                        if clear_framebuffer {
-                            self.device.bind_draw_target(draw_target);
-                            self.device.enable_depth_write();
-                            self.device.clear_target(self.clear_color.map(|color| color.to_array()),
-                                                     Some(1.0),
-                                                     None);
-                        }
 
                         if self.enable_picture_caching {
                             self.composite(
-                                &frame.composite_config,
+                                &frame.composite_state,
+                                clear_framebuffer,
                                 draw_target,
                                 &projection,
-                                stats,
+                                results,
                             );
                         } else {
                             self.draw_color_target(
                                 draw_target,
                                 main_target,
                                 frame.content_origin,
                                 None,
                                 None,
                                 &frame.render_tasks,
                                 &projection,
                                 frame_id,
-                                stats,
+                                &mut results.stats,
                             );
                         }
                     }
                 }
                 RenderPassKind::OffScreen {
                     ref mut alpha,
                     ref mut color,
                     ref mut texture_cache,
@@ -4804,23 +4938,23 @@ impl Renderer {
                     // skipped this time.
                     if !frame.has_been_rendered {
                         for (&(texture_id, target_index), target) in texture_cache {
                             self.draw_texture_cache_target(
                                 &texture_id,
                                 target_index,
                                 target,
                                 &frame.render_tasks,
-                                stats,
+                                &mut results.stats,
                             );
                         }
 
                         // Draw picture caching tiles for this pass.
                         for picture_target in picture_cache {
-                            stats.color_target_count += 1;
+                            results.stats.color_target_count += 1;
 
                             let (texture, _) = self.texture_resolver
                                 .resolve(&picture_target.texture)
                                 .expect("bug");
                             let draw_target = DrawTarget::from_texture(
                                 texture,
                                 picture_target.layer,
                                 true,
@@ -4836,23 +4970,23 @@ impl Renderer {
                             );
 
                             self.draw_picture_cache_target(
                                 picture_target,
                                 draw_target,
                                 frame.content_origin,
                                 &projection,
                                 &frame.render_tasks,
-                                stats,
+                                &mut results.stats,
                             );
                         }
                     }
 
                     for (target_index, target) in alpha.targets.iter().enumerate() {
-                        stats.alpha_target_count += 1;
+                        results.stats.alpha_target_count += 1;
                         let draw_target = DrawTarget::from_texture(
                             &alpha_tex.as_ref().unwrap().texture,
                             target_index,
                             false,
                         );
 
                         let projection = Transform3D::ortho(
                             0.0,
@@ -4863,22 +4997,22 @@ impl Renderer {
                             ORTHO_FAR_PLANE,
                         );
 
                         self.draw_alpha_target(
                             draw_target,
                             target,
                             &projection,
                             &frame.render_tasks,
-                            stats,
+                            &mut results.stats,
                         );
                     }
 
                     for (target_index, target) in color.targets.iter().enumerate() {
-                        stats.color_target_count += 1;
+                        results.stats.color_target_count += 1;
                         let draw_target = DrawTarget::from_texture(
                             &color_tex.as_ref().unwrap().texture,
                             target_index,
                             target.needs_depth(),
                         );
 
                         let projection = Transform3D::ortho(
                             0.0,
@@ -4899,17 +5033,17 @@ impl Renderer {
                             draw_target,
                             target,
                             frame.content_origin,
                             Some([0.0, 0.0, 0.0, 0.0]),
                             clear_depth,
                             &frame.render_tasks,
                             &projection,
                             frame_id,
-                            stats,
+                            &mut results.stats,
                         );
                     }
 
                     // Only end the pass here and invalidate previous textures for
                     // off-screen targets. Deferring return of the inputs to the
                     // frame buffer until the implicit end_pass in end_frame allows
                     // debug draw overlays to be added without triggering a copy
                     // resolve stage in mobile / tiled GPUs.
@@ -5730,16 +5864,18 @@ pub struct RendererOptions {
     pub allow_texture_swizzling: bool,
     /// Number of batches to look back in history for adding the current
     /// transparent instance into.
     pub batch_lookback_count: usize,
     /// Start the debug server for this renderer.
     pub start_debug_server: bool,
     /// Output the source of the shader with the given name.
     pub dump_shader_source: Option<String>,
+    /// An optional presentation config for compositor integration.
+    pub present_config: Option<PresentConfig>,
 }
 
 impl Default for RendererOptions {
     fn default() -> Self {
         RendererOptions {
             device_pixel_ratio: 1.0,
             resource_override_path: None,
             enable_aa: true,
@@ -5781,16 +5917,17 @@ impl Default for RendererOptions {
             allow_texture_swizzling: true,
             batch_lookback_count: DEFAULT_BATCH_LOOKBACK_COUNT,
             // For backwards compatibility we set this to true by default, so
             // that if the debugger feature is enabled, the debug server will
             // be started automatically. Users can explicitly disable this as
             // needed.
             start_debug_server: true,
             dump_shader_source: None,
+            present_config: None,
         }
     }
 }
 
 pub trait DebugServer {
     fn send(&mut self, _message: String);
 }
 
--- a/gfx/wr/webrender/src/util.rs
+++ b/gfx/wr/webrender/src/util.rs
@@ -1159,59 +1159,8 @@ pub fn clamp_to_scale_factor(val: f32, r
 /// Rounds a value up to the nearest multiple of mul
 pub fn round_up_to_multiple(val: usize, mul: NonZeroUsize) -> usize {
     match val % mul.get() {
         0 => val,
         rem => val - rem + mul.get(),
     }
 }
 
-/// A helper function to construct a rect from a (x0,y0) and (x1,y1) pair
-#[inline]
-fn rect_from_points_f<U>(x0: f32,
-                         y0: f32,
-                         x1: f32,
-                         y1: f32) -> Rect<f32, U> {
-    Rect::new(Point2D::new(x0, y0),
-              Size2D::new(x1 - x0, y1 - y0))
-}
-
-/// Subtract `other` from `rect`, and write the outputs into `results`.
-pub fn subtract_rect<U>(rect: &Rect<f32, U>,
-                        other: &Rect<f32, U>,
-                        results: &mut Vec<Rect<f32, U>>) {
-    results.clear();
-
-    let int = rect.intersection(other);
-    match int {
-        Some(int) => {
-            let rx0 = rect.origin.x;
-            let ry0 = rect.origin.y;
-            let rx1 = rx0 + rect.size.width;
-            let ry1 = ry0 + rect.size.height;
-
-            let ox0 = int.origin.x;
-            let oy0 = int.origin.y;
-            let ox1 = ox0 + int.size.width;
-            let oy1 = oy0 + int.size.height;
-
-            let r = rect_from_points_f(rx0, ry0, ox0, ry1);
-            if r.size.width > 0.0 && r.size.height > 0.0 {
-                results.push(r);
-            }
-            let r = rect_from_points_f(ox0, ry0, ox1, oy0);
-            if r.size.width > 0.0 && r.size.height > 0.0 {
-                results.push(r);
-            }
-            let r = rect_from_points_f(ox0, oy1, ox1, ry1);
-            if r.size.width > 0.0 && r.size.height > 0.0 {
-                results.push(r);
-            }
-            let r = rect_from_points_f(ox1, ry0, rx1, ry1);
-            if r.size.width > 0.0 && r.size.height > 0.0 {
-                results.push(r);
-            }
-        }
-        None => {
-            results.push(*rect);
-        }
-    }
-}