Bug 1604383 - Refactor the Compositor trait to allow support for DC virtual surface API. r=mstange,sotaro
authorGlenn Watson <git@intuitionlibrary.com>
Tue, 17 Dec 2019 21:44:03 +0000
changeset 507464 c81cf0c409bcaea6a6e6299cb2f421e4528e023b
parent 507463 35af0b925215124a619bc054aeeb8d01e99b4e63
child 507465 666ed2796ff392bb355d71f9f139d9ea5f0aadf7
push id36928
push useropoprus@mozilla.com
push dateWed, 18 Dec 2019 09:16:14 +0000
treeherdermozilla-central@e928d6001344 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange, sotaro
bugs1604383
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 1604383 - Refactor the Compositor trait to allow support for DC virtual surface API. r=mstange,sotaro Differential Revision: https://phabricator.services.mozilla.com/D57415
gfx/webrender_bindings/DCLayerTree.cpp
gfx/webrender_bindings/DCLayerTree.h
gfx/webrender_bindings/RenderCompositor.cpp
gfx/webrender_bindings/RenderCompositor.h
gfx/webrender_bindings/RenderCompositorANGLE.cpp
gfx/webrender_bindings/RenderCompositorANGLE.h
gfx/webrender_bindings/RenderCompositorOGL.cpp
gfx/webrender_bindings/RenderCompositorOGL.h
gfx/webrender_bindings/src/bindings.rs
gfx/wr/example-compositor/compositor-windows/src/lib.cpp
gfx/wr/example-compositor/compositor-windows/src/lib.rs
gfx/wr/example-compositor/compositor/src/main.rs
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/render_backend.rs
gfx/wr/webrender/src/render_task.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/resource_cache.rs
--- a/gfx/webrender_bindings/DCLayerTree.cpp
+++ b/gfx/webrender_bindings/DCLayerTree.cpp
@@ -99,16 +99,22 @@ bool DCLayerTree::Initialize(HWND aHwnd)
   // By default, a visual inherits the interpolation mode of the parent visual.
   // If no visuals set the interpolation mode, the default for the entire visual
   // tree is nearest neighbor interpolation.
   mRootVisual->SetBitmapInterpolationMode(
       DCOMPOSITION_BITMAP_INTERPOLATION_MODE_LINEAR);
   return true;
 }
 
+DCSurface* DCLayerTree::GetSurface(wr::NativeSurfaceId aId) const {
+  auto surface_it = mDCSurfaces.find(aId);
+  MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
+  return surface_it->second.get();
+}
+
 void DCLayerTree::SetDefaultSwapChain(IDXGISwapChain1* aSwapChain) {
   mRootVisual->AddVisual(mDefaultSwapChainVisual, TRUE, nullptr);
   mDefaultSwapChainVisual->SetContent(aSwapChain);
   // Default SwapChain's visual does not need linear interporation.
   mDefaultSwapChainVisual->SetBitmapInterpolationMode(
       DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
   mCompositionDevice->Commit();
 }
@@ -171,122 +177,124 @@ bool DCLayerTree::MaybeUpdateDebugVisual
 
   mDebugVisualRedrawRegions = debugVisualRedrawRegions;
   return true;
 }
 
 void DCLayerTree::CompositorBeginFrame() {}
 
 void DCLayerTree::CompositorEndFrame() {
+  // Check if the visual tree of surfaces is the same as last frame.
   bool same = mPrevLayers == mCurrentLayers;
 
   if (!same) {
+    // If not, we need to rebuild the visual tree. Note that addition or
+    // removal of tiles no longer needs to rebuild the main visual tree
+    // here, since they are added as children of the surface visual.
     mRootVisual->RemoveAllVisuals();
 
+    // Add surfaces in z-order they were added to the scene.
     for (auto it = mCurrentLayers.begin(); it != mCurrentLayers.end(); ++it) {
-      auto layer_it = mDCLayers.find(*it);
-      MOZ_ASSERT(layer_it != mDCLayers.end());
-      const auto layer = layer_it->second.get();
-      const auto visual = layer->GetVisual();
+      auto surface_it = mDCSurfaces.find(*it);
+      MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
+      const auto surface = surface_it->second.get();
+      const auto visual = surface->GetVisual();
       mRootVisual->AddVisual(visual, FALSE, nullptr);
     }
   }
 
   mPrevLayers.swap(mCurrentLayers);
   mCurrentLayers.clear();
 
   mCompositionDevice->Commit();
 }
 
-void DCLayerTree::Bind(wr::NativeSurfaceId aId, wr::DeviceIntPoint* aOffset,
+void DCLayerTree::Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset,
                        uint32_t* aFboId, wr::DeviceIntRect aDirtyRect) {
-  auto it = mDCLayers.find(wr::AsUint64(aId));
-  MOZ_ASSERT(it != mDCLayers.end());
-  if (it == mDCLayers.end()) {
-    gfxCriticalNote << "Failed to get DCLayer for bind: " << wr::AsUint64(aId);
-    return;
-  }
-
-  const auto layer = it->second.get();
+  auto surface = GetSurface(aId.surface_id);
+  auto layer = surface->GetLayer(aId.x, aId.y);
 
   *aFboId = layer->CreateEGLSurfaceForCompositionSurface(aDirtyRect, aOffset);
-
   mCurrentId = Some(aId);
 }
 
 void DCLayerTree::Unbind() {
   if (mCurrentId.isNothing()) {
     return;
   }
 
-  auto it = mDCLayers.find(wr::AsUint64(mCurrentId.ref()));
-  MOZ_RELEASE_ASSERT(it != mDCLayers.end());
-
-  const auto layer = it->second.get();
+  const auto id = mCurrentId.ref();
+  auto surface = GetSurface(id.surface_id);
+  auto layer = surface->GetLayer(id.x, id.y);
 
   layer->EndDraw();
   mCurrentId = Nothing();
 }
 
 void DCLayerTree::CreateSurface(wr::NativeSurfaceId aId,
-                                wr::DeviceIntSize aSize, bool aIsOpaque) {
-  auto it = mDCLayers.find(wr::AsUint64(aId));
-  MOZ_RELEASE_ASSERT(it == mDCLayers.end());
-  MOZ_ASSERT(it == mDCLayers.end());
-  if (it != mDCLayers.end()) {
-    // DCLayer already exists.
+                                wr::DeviceIntSize aTileSize) {
+  auto it = mDCSurfaces.find(aId);
+  MOZ_RELEASE_ASSERT(it == mDCSurfaces.end());
+  if (it != mDCSurfaces.end()) {
+    // DCSurface already exists.
+    return;
+  }
+
+  auto surface = MakeUnique<DCSurface>(aTileSize, this);
+  if (!surface->Initialize()) {
+    gfxCriticalNote << "Failed to initialize DCSurface: " << wr::AsUint64(aId);
     return;
   }
 
-  auto layer = MakeUnique<DCLayer>(this);
-  if (!layer->Initialize(aSize, aIsOpaque)) {
-    gfxCriticalNote << "Failed to initialize DCLayer: " << wr::AsUint64(aId);
-    return;
-  }
-
-  mDCLayers[wr::AsUint64(aId)] = std::move(layer);
+  mDCSurfaces[aId] = std::move(surface);
 }
 
 void DCLayerTree::DestroySurface(NativeSurfaceId aId) {
-  auto it = mDCLayers.find(wr::AsUint64(aId));
-  MOZ_ASSERT(it != mDCLayers.end());
-  if (it == mDCLayers.end()) {
-    return;
-  }
-  mDCLayers.erase(it);
+  auto surface_it = mDCSurfaces.find(aId);
+  MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
+  auto surface = surface_it->second.get();
+
+  mRootVisual->RemoveVisual(surface->GetVisual());
+  mDCSurfaces.erase(surface_it);
+}
+
+void DCLayerTree::CreateTile(wr::NativeSurfaceId aId, int aX, int aY,
+                             bool aIsOpaque) {
+  auto surface = GetSurface(aId);
+  surface->CreateTile(aX, aY, aIsOpaque);
+}
+
+void DCLayerTree::DestroyTile(wr::NativeSurfaceId aId, int aX, int aY) {
+  auto surface = GetSurface(aId);
+  surface->DestroyTile(aX, aY);
 }
 
 void DCLayerTree::AddSurface(wr::NativeSurfaceId aId,
                              wr::DeviceIntPoint aPosition,
                              wr::DeviceIntRect aClipRect) {
-  auto it = mDCLayers.find(wr::AsUint64(aId));
-  MOZ_ASSERT(it != mDCLayers.end());
-  if (it == mDCLayers.end()) {
-    return;
-  }
+  auto it = mDCSurfaces.find(aId);
+  MOZ_RELEASE_ASSERT(it != mDCSurfaces.end());
   const auto layer = it->second.get();
   const auto visual = layer->GetVisual();
 
   // Place the visual - this changes frame to frame based on scroll position
   // of the slice.
-  int offset_x = aPosition.x;
-  int offset_y = aPosition.y;
-  visual->SetOffsetX(offset_x);
-  visual->SetOffsetY(offset_y);
+  visual->SetOffsetX(aPosition.x);
+  visual->SetOffsetY(aPosition.y);
 
   // Set the clip rect - converting from world space to the pre-offset space
   // that DC requires for rectangle clips.
   D2D_RECT_F clip_rect;
-  clip_rect.left = aClipRect.origin.x - offset_x;
-  clip_rect.top = aClipRect.origin.y - offset_y;
+  clip_rect.left = aClipRect.origin.x - aPosition.x;
+  clip_rect.top = aClipRect.origin.y - aPosition.y;
   clip_rect.right = clip_rect.left + aClipRect.size.width;
   clip_rect.bottom = clip_rect.top + aClipRect.size.height;
   visual->SetClip(clip_rect);
 
-  mCurrentLayers.push_back(wr::AsUint64(aId));
+  mCurrentLayers.push_back(aId);
 }
 
 GLuint DCLayerTree::GetOrCreateFbo(int aWidth, int aHeight) {
   const auto gl = GetGLContext();
   GLuint fboId = 0;
 
   // Check if we have a cached FBO with matching dimensions
   for (auto it = mFrameBuffers.begin(); it != mFrameBuffers.end(); ++it) {
@@ -322,22 +330,68 @@ GLuint DCLayerTree::GetOrCreateFbo(int a
     frame_buffer_info.fboId = fboId;
     frame_buffer_info.depthRboId = depthRboId;
     mFrameBuffers.push_back(frame_buffer_info);
   }
 
   return fboId;
 }
 
+DCSurface::DCSurface(wr::DeviceIntSize aTileSize, DCLayerTree* aDCLayerTree)
+    : mDCLayerTree(aDCLayerTree), mTileSize(aTileSize) {}
+
+DCSurface::~DCSurface() {}
+
+bool DCSurface::Initialize() {
+  HRESULT hr;
+  const auto dCompDevice = mDCLayerTree->GetCompositionDevice();
+  hr = dCompDevice->CreateVisual(getter_AddRefs(mVisual));
+  if (FAILED(hr)) {
+    gfxCriticalNote << "Failed to create DCompositionVisual: " << gfx::hexa(hr);
+    return false;
+  }
+
+  return true;
+}
+
+void DCSurface::CreateTile(int aX, int aY, bool aIsOpaque) {
+  TileKey key(aX, aY);
+  MOZ_RELEASE_ASSERT(mDCLayers.find(key) == mDCLayers.end());
+
+  auto layer = MakeUnique<DCLayer>(mDCLayerTree);
+  if (!layer->Initialize(aX, aY, mTileSize, aIsOpaque)) {
+    gfxCriticalNote << "Failed to initialize DCLayer: " << aX << aY;
+    return;
+  }
+
+  mVisual->AddVisual(layer->GetVisual(), FALSE, NULL);
+  mDCLayers[key] = std::move(layer);
+}
+
+void DCSurface::DestroyTile(int aX, int aY) {
+  TileKey key(aX, aY);
+  auto layer = GetLayer(aX, aY);
+  mVisual->RemoveVisual(layer->GetVisual());
+  mDCLayers.erase(key);
+}
+
+DCLayer* DCSurface::GetLayer(int aX, int aY) const {
+  TileKey key(aX, aY);
+  auto layer_it = mDCLayers.find(key);
+  MOZ_RELEASE_ASSERT(layer_it != mDCLayers.end());
+  return layer_it->second.get();
+}
+
 DCLayer::DCLayer(DCLayerTree* aDCLayerTree)
     : mDCLayerTree(aDCLayerTree), mEGLImage(EGL_NO_IMAGE), mColorRBO(0) {}
 
 DCLayer::~DCLayer() { DestroyEGLSurface(); }
 
-bool DCLayer::Initialize(wr::DeviceIntSize aSize, bool aIsOpaque) {
+bool DCLayer::Initialize(int aX, int aY, wr::DeviceIntSize aSize,
+                         bool aIsOpaque) {
   if (aSize.width <= 0 || aSize.height <= 0) {
     return false;
   }
   mBufferSize = LayoutDeviceIntSize(aSize.width, aSize.height);
 
   HRESULT hr;
   const auto dCompDevice = mDCLayerTree->GetCompositionDevice();
   hr = dCompDevice->CreateVisual(getter_AddRefs(mVisual));
@@ -352,16 +406,21 @@ bool DCLayer::Initialize(wr::DeviceIntSi
   }
 
   hr = mVisual->SetContent(mCompositionSurface);
   if (FAILED(hr)) {
     gfxCriticalNote << "SetContent failed: " << gfx::hexa(hr);
     return false;
   }
 
+  // Position this tile at a local space offset within the parent visual
+  // Scroll offsets get applied to the parent visual only.
+  mVisual->SetOffsetX(aX * aSize.width);
+  mVisual->SetOffsetY(aY * aSize.height);
+
   return true;
 }
 
 RefPtr<IDCompositionSurface> DCLayer::CreateCompositionSurface(
     wr::DeviceIntSize aSize, bool aIsOpaque) {
   HRESULT hr;
   const auto dCompDevice = mDCLayerTree->GetCompositionDevice();
   const auto alphaMode =
--- a/gfx/webrender_bindings/DCLayerTree.h
+++ b/gfx/webrender_bindings/DCLayerTree.h
@@ -28,16 +28,17 @@ namespace mozilla {
 
 namespace gl {
 class GLContext;
 }
 
 namespace wr {
 
 class DCLayer;
+class DCSurface;
 
 /**
  * DCLayerTree manages direct composition layers.
  * It does not manage gecko's layers::Layer.
  */
 class DCLayerTree {
  public:
   static UniquePtr<DCLayerTree> Create(gl::GLContext* aGL, EGLConfig aEGLConfig,
@@ -49,31 +50,34 @@ class DCLayerTree {
 
   void SetDefaultSwapChain(IDXGISwapChain1* aSwapChain);
   void MaybeUpdateDebug();
   void WaitForCommitCompletion();
 
   // Interface for wr::Compositor
   void CompositorBeginFrame();
   void CompositorEndFrame();
-  void Bind(wr::NativeSurfaceId aId, wr::DeviceIntPoint* aOffset,
-            uint32_t* aFboId, wr::DeviceIntRect aDirtyRect);
+  void Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset, uint32_t* aFboId,
+            wr::DeviceIntRect aDirtyRect);
   void Unbind();
-  void CreateSurface(wr::NativeSurfaceId aId, wr::DeviceIntSize aSize,
-                     bool aIsOpaque);
+  void CreateSurface(wr::NativeSurfaceId aId, wr::DeviceIntSize aTileSize);
   void DestroySurface(NativeSurfaceId aId);
+  void CreateTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY,
+                  bool aIsOpaque);
+  void DestroyTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY);
   void AddSurface(wr::NativeSurfaceId aId, wr::DeviceIntPoint aPosition,
                   wr::DeviceIntRect aClipRect);
 
   gl::GLContext* GetGLContext() const { return mGL; }
   EGLConfig GetEGLConfig() const { return mEGLConfig; }
   ID3D11Device* GetDevice() const { return mDevice; }
   IDCompositionDevice2* GetCompositionDevice() const {
     return mCompositionDevice;
   }
+  DCSurface* GetSurface(wr::NativeSurfaceId aId) const;
 
   // Get or create an FBO with depth buffer suitable for specified dimensions
   GLuint GetOrCreateFbo(int aWidth, int aHeight);
 
  protected:
   bool Initialize(HWND aHwnd);
   bool MaybeUpdateDebugCounter();
   bool MaybeUpdateDebugVisualRedrawRegions();
@@ -86,60 +90,115 @@ class DCLayerTree {
   RefPtr<IDCompositionDevice2> mCompositionDevice;
   RefPtr<IDCompositionTarget> mCompositionTarget;
   RefPtr<IDCompositionVisual2> mRootVisual;
   RefPtr<IDCompositionVisual2> mDefaultSwapChainVisual;
 
   bool mDebugCounter;
   bool mDebugVisualRedrawRegions;
 
-  Maybe<wr::NativeSurfaceId> mCurrentId;
+  Maybe<wr::NativeTileId> mCurrentId;
 
-  std::unordered_map<uint64_t, UniquePtr<DCLayer>> mDCLayers;
+  struct SurfaceIdHashFn {
+    std::size_t operator()(const wr::NativeSurfaceId& aId) const {
+      return HashGeneric(wr::AsUint64(aId));
+    }
+  };
+
+  std::unordered_map<wr::NativeSurfaceId, UniquePtr<DCSurface>, SurfaceIdHashFn>
+      mDCSurfaces;
 
   // A list of layer IDs as they are added to the visual tree this frame.
-  std::vector<uint64_t> mCurrentLayers;
+  std::vector<wr::NativeSurfaceId> mCurrentLayers;
 
   // The previous frame's list of layer IDs in visual order.
-  std::vector<uint64_t> mPrevLayers;
+  std::vector<wr::NativeSurfaceId> mPrevLayers;
 
   // Information about a cached FBO that is retained between frames.
   struct CachedFrameBuffer {
     int width;
     int height;
     GLuint fboId;
     GLuint depthRboId;
   };
 
   // A cache of FBOs, containing a depth buffer allocated to a specific size.
   // TODO(gw): Might be faster as a hashmap? The length is typically much less
   // than 10.
   std::vector<CachedFrameBuffer> mFrameBuffers;
 };
 
+/**
+ Represents a single picture cache slice. Each surface contains some
+ number of tiles. An implementation may choose to allocate individual
+ tiles to render in to (as the current impl does), or allocate a large
+ single virtual surface to draw into (e.g. the DirectComposition virtual
+ surface API in future).
+ */
+class DCSurface {
+ public:
+  explicit DCSurface(wr::DeviceIntSize aTileSize, DCLayerTree* aDCLayerTree);
+  ~DCSurface();
+
+  bool Initialize();
+  void CreateTile(int32_t aX, int32_t aY, bool aIsOpaque);
+  void DestroyTile(int32_t aX, int32_t aY);
+
+  IDCompositionVisual2* GetVisual() const { return mVisual; }
+  DCLayer* GetLayer(int32_t aX, int32_t aY) const;
+
+  struct TileKey {
+    TileKey(int32_t aX, int32_t aY) : mX(aX), mY(aY) {}
+
+    int32_t mX;
+    int32_t mY;
+  };
+
+ protected:
+  DCLayerTree* mDCLayerTree;
+
+  struct TileKeyHashFn {
+    std::size_t operator()(const TileKey& aId) const {
+      return HashGeneric(aId.mX, aId.mY);
+    }
+  };
+
+  // The visual for this surface. No content is attached to here, but tiles
+  // that belong to this surface are added as children. In this way, we can
+  // set the clip and scroll offset once, on this visual, to affect all
+  // children.
+  RefPtr<IDCompositionVisual2> mVisual;
+
+  wr::DeviceIntSize mTileSize;
+  std::unordered_map<TileKey, UniquePtr<DCLayer>, TileKeyHashFn> mDCLayers;
+};
+
+/**
+ Represents a tile within a surface.
+ TODO(gw): We should probably rename this to DCTile as a follow up.
+ */
 class DCLayer {
  public:
   explicit DCLayer(DCLayerTree* aDCLayerTree);
   ~DCLayer();
-  bool Initialize(wr::DeviceIntSize aSize, bool aIsOpaque);
+  bool Initialize(int aX, int aY, wr::DeviceIntSize aSize, bool aIsOpaque);
   GLuint CreateEGLSurfaceForCompositionSurface(wr::DeviceIntRect aDirtyRect,
                                                wr::DeviceIntPoint* aOffset);
   void EndDraw();
 
   IDCompositionSurface* GetCompositionSurface() const {
     return mCompositionSurface;
   }
   IDCompositionVisual2* GetVisual() const { return mVisual; }
 
  protected:
   RefPtr<IDCompositionSurface> CreateCompositionSurface(wr::DeviceIntSize aSize,
                                                         bool aIsOpaque);
   void DestroyEGLSurface();
 
- protected:
   DCLayerTree* mDCLayerTree;
 
   RefPtr<IDCompositionSurface> mCompositionSurface;
 
   // The EGL image that is bound to the D3D texture provided by
   // DirectComposition.
   EGLImage mEGLImage;
 
@@ -147,12 +206,17 @@ class DCLayer {
   // an FBO.
   GLuint mColorRBO;
 
   LayoutDeviceIntSize mBufferSize;
 
   RefPtr<IDCompositionVisual2> mVisual;
 };
 
+static inline bool operator==(const DCSurface::TileKey& a0,
+                              const DCSurface::TileKey& a1) {
+  return a0.mX == a1.mX && a0.mY == a1.mY;
+}
+
 }  // namespace wr
 }  // namespace mozilla
 
 #endif
--- a/gfx/webrender_bindings/RenderCompositor.cpp
+++ b/gfx/webrender_bindings/RenderCompositor.cpp
@@ -30,27 +30,39 @@ void wr_compositor_add_surface(void* aCo
   compositor->AddSurface(aId, aPosition, aClipRect);
 }
 
 void wr_compositor_begin_frame(void* aCompositor) {
   RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
   compositor->CompositorBeginFrame();
 }
 
-void wr_compositor_bind(void* aCompositor, wr::NativeSurfaceId aId,
+void wr_compositor_bind(void* aCompositor, wr::NativeTileId aId,
                         wr::DeviceIntPoint* aOffset, uint32_t* aFboId,
                         wr::DeviceIntRect aDirtyRect) {
   RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
   compositor->Bind(aId, aOffset, aFboId, aDirtyRect);
 }
 
 void wr_compositor_create_surface(void* aCompositor, wr::NativeSurfaceId aId,
-                                  wr::DeviceIntSize aSize, bool aIsOpaque) {
+                                  wr::DeviceIntSize aTileSize) {
+  RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+  compositor->CreateSurface(aId, aTileSize);
+}
+
+void wr_compositor_create_tile(void* aCompositor, wr::NativeSurfaceId aId,
+                               int32_t aX, int32_t aY, bool aIsOpaque) {
   RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
-  compositor->CreateSurface(aId, aSize, aIsOpaque);
+  compositor->CreateTile(aId, aX, aY, aIsOpaque);
+}
+
+void wr_compositor_destroy_tile(void* aCompositor, wr::NativeSurfaceId aId,
+                                int32_t aX, int32_t aY) {
+  RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+  compositor->DestroyTile(aId, aX, aY);
 }
 
 void wr_compositor_destroy_surface(void* aCompositor, NativeSurfaceId aId) {
   RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
   compositor->DestroySurface(aId);
 }
 
 void wr_compositor_end_frame(void* aCompositor) {
--- a/gfx/webrender_bindings/RenderCompositor.h
+++ b/gfx/webrender_bindings/RenderCompositor.h
@@ -80,22 +80,25 @@ class RenderCompositor {
   virtual bool IsContextLost();
 
   virtual bool ShouldUseNativeCompositor() { return false; }
   virtual uint32_t GetMaxUpdateRects() { return 0; }
 
   // Interface for wr::Compositor
   virtual void CompositorBeginFrame() {}
   virtual void CompositorEndFrame() {}
-  virtual void Bind(wr::NativeSurfaceId aId, wr::DeviceIntPoint* aOffset,
+  virtual void Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset,
                     uint32_t* aFboId, wr::DeviceIntRect aDirtyRect) {}
   virtual void Unbind() {}
-  virtual void CreateSurface(wr::NativeSurfaceId aId, wr::DeviceIntSize aSize,
-                             bool aIsOpaque) {}
+  virtual void CreateSurface(wr::NativeSurfaceId aId,
+                             wr::DeviceIntSize aTileSize) {}
   virtual void DestroySurface(NativeSurfaceId aId) {}
+  virtual void CreateTile(wr::NativeSurfaceId, int32_t aX, int32_t aY,
+                          bool aIsOpaque) {}
+  virtual void DestroyTile(wr::NativeSurfaceId, int32_t aX, int32_t aY) {}
   virtual void AddSurface(wr::NativeSurfaceId aId, wr::DeviceIntPoint aPosition,
                           wr::DeviceIntRect aClipRect) {}
 
   // Interface for partial present
   virtual bool UsePartialPresent() { return false; }
   virtual bool RequestFullRender() { return false; }
   virtual uint32_t GetMaxPartialPresentRects() { return 0; }
 
--- a/gfx/webrender_bindings/RenderCompositorANGLE.cpp
+++ b/gfx/webrender_bindings/RenderCompositorANGLE.cpp
@@ -745,34 +745,43 @@ uint32_t RenderCompositorANGLE::GetMaxUp
 void RenderCompositorANGLE::CompositorBeginFrame() {
   mDCLayerTree->CompositorBeginFrame();
 }
 
 void RenderCompositorANGLE::CompositorEndFrame() {
   mDCLayerTree->CompositorEndFrame();
 }
 
-void RenderCompositorANGLE::Bind(wr::NativeSurfaceId aId,
+void RenderCompositorANGLE::Bind(wr::NativeTileId aId,
                                  wr::DeviceIntPoint* aOffset, uint32_t* aFboId,
                                  wr::DeviceIntRect aDirtyRect) {
   mDCLayerTree->Bind(aId, aOffset, aFboId, aDirtyRect);
 }
 
 void RenderCompositorANGLE::Unbind() { mDCLayerTree->Unbind(); }
 
 void RenderCompositorANGLE::CreateSurface(wr::NativeSurfaceId aId,
-                                          wr::DeviceIntSize aSize,
-                                          bool aIsOpaque) {
-  mDCLayerTree->CreateSurface(aId, aSize, aIsOpaque);
+                                          wr::DeviceIntSize aTileSize) {
+  mDCLayerTree->CreateSurface(aId, aTileSize);
 }
 
 void RenderCompositorANGLE::DestroySurface(NativeSurfaceId aId) {
   mDCLayerTree->DestroySurface(aId);
 }
 
+void RenderCompositorANGLE::CreateTile(wr::NativeSurfaceId aId, int aX, int aY,
+                                       bool aIsOpaque) {
+  mDCLayerTree->CreateTile(aId, aX, aY, aIsOpaque);
+}
+
+void RenderCompositorANGLE::DestroyTile(wr::NativeSurfaceId aId, int aX,
+                                        int aY) {
+  mDCLayerTree->DestroyTile(aId, aX, aY);
+}
+
 void RenderCompositorANGLE::AddSurface(wr::NativeSurfaceId aId,
                                        wr::DeviceIntPoint aPosition,
                                        wr::DeviceIntRect aClipRect) {
   mDCLayerTree->AddSurface(aId, aPosition, aClipRect);
 }
 
 void RenderCompositorANGLE::InitializeUsePartialPresent() {
   if (UseCompositor() || !mSwapChain1 ||
--- a/gfx/webrender_bindings/RenderCompositorANGLE.h
+++ b/gfx/webrender_bindings/RenderCompositorANGLE.h
@@ -65,22 +65,25 @@ class RenderCompositorANGLE : public Ren
   bool SurfaceOriginIsTopLeft() override { return true; }
 
   bool ShouldUseNativeCompositor() override;
   uint32_t GetMaxUpdateRects() override;
 
   // Interface for wr::Compositor
   void CompositorBeginFrame() override;
   void CompositorEndFrame() override;
-  void Bind(wr::NativeSurfaceId aId, wr::DeviceIntPoint* aOffset,
-            uint32_t* aFboId, wr::DeviceIntRect aDirtyRect) override;
+  void Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset, uint32_t* aFboId,
+            wr::DeviceIntRect aDirtyRect) override;
   void Unbind() override;
-  void CreateSurface(wr::NativeSurfaceId aId, wr::DeviceIntSize aSize,
-                     bool aIsOpaque) override;
+  void CreateSurface(wr::NativeSurfaceId aId,
+                     wr::DeviceIntSize aTileSize) override;
   void DestroySurface(NativeSurfaceId aId) override;
+  void CreateTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY,
+                  bool aIsOpaque) override;
+  void DestroyTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY) override;
   void AddSurface(wr::NativeSurfaceId aId, wr::DeviceIntPoint aPosition,
                   wr::DeviceIntRect aClipRect) override;
 
   // Interface for partial present
   bool UsePartialPresent() override;
   bool RequestFullRender() override;
   uint32_t GetMaxPartialPresentRects() override;
 
--- a/gfx/webrender_bindings/RenderCompositorOGL.cpp
+++ b/gfx/webrender_bindings/RenderCompositorOGL.cpp
@@ -171,43 +171,51 @@ void RenderCompositorOGL::CompositorBegi
   mBeginFrameTimeStamp = TimeStamp::NowUnfuzzed();
 }
 
 void RenderCompositorOGL::CompositorEndFrame() {
 #ifdef MOZ_GECKO_PROFILER
   if (profiler_thread_is_being_profiled()) {
     auto bufferSize = GetBufferSize();
     uint64_t windowPixelCount = uint64_t(bufferSize.width) * bufferSize.height;
+    int nativeLayerCount = 0;
+    for (const auto& it : mSurfaces) {
+      nativeLayerCount += int(it.second.mNativeLayers.size());
+    }
     profiler_add_text_marker(
         "WR OS Compositor frame",
         nsPrintfCString("%d%% painting, %d%% overdraw, %d used "
                         "layers (%d%% memory) + %d unused layers (%d%% memory)",
                         int(mDrawnPixelCount * 100 / windowPixelCount),
                         int(mAddedClippedPixelCount * 100 / windowPixelCount),
                         int(mAddedLayers.Length()),
                         int(mAddedPixelCount * 100 / windowPixelCount),
-                        int(mNativeLayers.size() - mAddedLayers.Length()),
+                        int(nativeLayerCount - mAddedLayers.Length()),
                         int((mTotalPixelCount - mAddedPixelCount) * 100 /
                             windowPixelCount)),
         JS::ProfilingCategoryPair::GRAPHICS, mBeginFrameTimeStamp,
         TimeStamp::NowUnfuzzed());
   }
 #endif
   mDrawnPixelCount = 0;
 
   mNativeLayerRoot->SetLayers(mAddedLayers);
 }
 
-void RenderCompositorOGL::Bind(wr::NativeSurfaceId aId,
+void RenderCompositorOGL::Bind(wr::NativeTileId aId,
                                wr::DeviceIntPoint* aOffset, uint32_t* aFboId,
                                wr::DeviceIntRect aDirtyRect) {
   MOZ_RELEASE_ASSERT(!mCurrentlyBoundNativeLayer);
 
-  auto layerCursor = mNativeLayers.find(wr::AsUint64(aId));
-  MOZ_RELEASE_ASSERT(layerCursor != mNativeLayers.end());
+  auto surfaceCursor = mSurfaces.find(aId.surface_id);
+  MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+  Surface& surface = surfaceCursor->second;
+
+  auto layerCursor = surface.mNativeLayers.find(TileKey(aId.x, aId.y));
+  MOZ_RELEASE_ASSERT(layerCursor != surface.mNativeLayers.end());
   RefPtr<layers::NativeLayer> layer = layerCursor->second;
 
   gfx::IntRect dirtyRect(aDirtyRect.origin.x, aDirtyRect.origin.y,
                          aDirtyRect.size.width, aDirtyRect.size.height);
 
   Maybe<GLuint> fbo = layer->NextSurfaceAsFramebuffer(dirtyRect, true);
   MOZ_RELEASE_ASSERT(fbo);  // TODO: make fallible
   mCurrentlyBoundNativeLayer = layer;
@@ -222,52 +230,75 @@ void RenderCompositorOGL::Unbind() {
   MOZ_RELEASE_ASSERT(mCurrentlyBoundNativeLayer);
 
   mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
   mCurrentlyBoundNativeLayer->NotifySurfaceReady();
   mCurrentlyBoundNativeLayer = nullptr;
 }
 
 void RenderCompositorOGL::CreateSurface(wr::NativeSurfaceId aId,
-                                        wr::DeviceIntSize aSize,
-                                        bool aIsOpaque) {
+                                        wr::DeviceIntSize aTileSize) {
+  Surface surface(aTileSize);
+  mSurfaces.insert({aId, surface});
+}
+
+void RenderCompositorOGL::DestroySurface(NativeSurfaceId aId) {
+  mSurfaces.erase(aId);
+}
+
+void RenderCompositorOGL::CreateTile(wr::NativeSurfaceId aId, int aX, int aY,
+                                     bool aIsOpaque) {
+  auto surfaceCursor = mSurfaces.find(aId);
+  MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+  Surface& surface = surfaceCursor->second;
+
   RefPtr<layers::NativeLayer> layer = mNativeLayerRoot->CreateLayer(
-      IntSize(aSize.width, aSize.height), aIsOpaque);
+      IntSize(surface.mTileSize.width, surface.mTileSize.height), aIsOpaque);
   layer->SetGLContext(mGL);
-  mNativeLayers.insert({wr::AsUint64(aId), layer});
+  surface.mNativeLayers.insert({TileKey(aX, aY), layer});
   mTotalPixelCount += gfx::IntRect({}, layer->GetSize()).Area();
 }
 
-void RenderCompositorOGL::DestroySurface(NativeSurfaceId aId) {
-  auto layerCursor = mNativeLayers.find(wr::AsUint64(aId));
-  MOZ_RELEASE_ASSERT(layerCursor != mNativeLayers.end());
+void RenderCompositorOGL::DestroyTile(wr::NativeSurfaceId aId, int aX, int aY) {
+  auto surfaceCursor = mSurfaces.find(aId);
+  MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+  Surface& surface = surfaceCursor->second;
+
+  auto layerCursor = surface.mNativeLayers.find(TileKey(aX, aY));
+  MOZ_RELEASE_ASSERT(layerCursor != surface.mNativeLayers.end());
   RefPtr<layers::NativeLayer> layer = std::move(layerCursor->second);
-  mNativeLayers.erase(layerCursor);
+  surface.mNativeLayers.erase(layerCursor);
   mTotalPixelCount -= gfx::IntRect({}, layer->GetSize()).Area();
   // If the layer is currently present in mNativeLayerRoot, it will be destroyed
   // once CompositorEndFrame() replaces mNativeLayerRoot's layers and drops that
   // reference.
 }
 
 void RenderCompositorOGL::AddSurface(wr::NativeSurfaceId aId,
                                      wr::DeviceIntPoint aPosition,
                                      wr::DeviceIntRect aClipRect) {
   MOZ_RELEASE_ASSERT(!mCurrentlyBoundNativeLayer);
 
-  auto layerCursor = mNativeLayers.find(wr::AsUint64(aId));
-  MOZ_RELEASE_ASSERT(layerCursor != mNativeLayers.end());
-  RefPtr<layers::NativeLayer> layer = layerCursor->second;
+  auto surfaceCursor = mSurfaces.find(aId);
+  MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+  const Surface& surface = surfaceCursor->second;
 
-  gfx::IntSize layerSize = layer->GetSize();
-  gfx::IntRect layerRect(aPosition.x, aPosition.y, layerSize.width,
-                         layerSize.height);
-  gfx::IntRect clipRect(aClipRect.origin.x, aClipRect.origin.y,
-                        aClipRect.size.width, aClipRect.size.height);
-  layer->SetPosition(layerRect.TopLeft());
-  layer->SetClipRect(Some(clipRect));
-  mAddedLayers.AppendElement(layer);
+  for (auto it = surface.mNativeLayers.begin();
+       it != surface.mNativeLayers.end(); ++it) {
+    RefPtr<layers::NativeLayer> layer = it->second;
+    gfx::IntSize layerSize = layer->GetSize();
+    gfx::IntRect layerRect(
+        aPosition.x + surface.mTileSize.width * it->first.mX,
+        aPosition.y + surface.mTileSize.height * it->first.mY, layerSize.width,
+        layerSize.height);
+    gfx::IntRect clipRect(aClipRect.origin.x, aClipRect.origin.y,
+                          aClipRect.size.width, aClipRect.size.height);
+    layer->SetPosition(layerRect.TopLeft());
+    layer->SetClipRect(Some(clipRect));
+    mAddedLayers.AppendElement(layer);
 
-  mAddedPixelCount += layerRect.Area();
-  mAddedClippedPixelCount += clipRect.Intersect(layerRect).Area();
+    mAddedPixelCount += layerRect.Area();
+    mAddedClippedPixelCount += clipRect.Intersect(layerRect).Area();
+  }
 }
 
 }  // namespace wr
 }  // namespace mozilla
--- a/gfx/webrender_bindings/RenderCompositorOGL.h
+++ b/gfx/webrender_bindings/RenderCompositorOGL.h
@@ -41,46 +41,82 @@ class RenderCompositorOGL : public Rende
   LayoutDeviceIntSize GetBufferSize() override;
 
   bool ShouldUseNativeCompositor() override;
   uint32_t GetMaxUpdateRects() override;
 
   // Interface for wr::Compositor
   void CompositorBeginFrame() override;
   void CompositorEndFrame() override;
-  void Bind(wr::NativeSurfaceId aId, wr::DeviceIntPoint* aOffset,
-            uint32_t* aFboId, wr::DeviceIntRect aDirtyRect) override;
+  void Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset, uint32_t* aFboId,
+            wr::DeviceIntRect aDirtyRect) override;
   void Unbind() override;
-  void CreateSurface(wr::NativeSurfaceId aId, wr::DeviceIntSize aSize,
-                     bool aIsOpaque) override;
+  void CreateSurface(wr::NativeSurfaceId aId,
+                     wr::DeviceIntSize aTileSize) override;
   void DestroySurface(NativeSurfaceId aId) override;
+  void CreateTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY,
+                  bool aIsOpaque) override;
+  void DestroyTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY) override;
   void AddSurface(wr::NativeSurfaceId aId, wr::DeviceIntPoint aPosition,
                   wr::DeviceIntRect aClipRect) override;
 
+  struct TileKey {
+    TileKey(int32_t aX, int32_t aY) : mX(aX), mY(aY) {}
+
+    int32_t mX;
+    int32_t mY;
+  };
+
  protected:
   void InsertFrameDoneSync();
 
   RefPtr<gl::GLContext> mGL;
 
   // Can be null.
   RefPtr<layers::NativeLayerRoot> mNativeLayerRoot;
   RefPtr<layers::NativeLayer> mNativeLayerForEntireWindow;
 
+  struct TileKeyHashFn {
+    std::size_t operator()(const TileKey& aId) const {
+      return HashGeneric(aId.mX, aId.mY);
+    }
+  };
+
+  class Surface {
+   public:
+    explicit Surface(wr::DeviceIntSize aTileSize) : mTileSize(aTileSize) {}
+
+    wr::DeviceIntSize mTileSize;
+    std::unordered_map<TileKey, RefPtr<layers::NativeLayer>, TileKeyHashFn>
+        mNativeLayers;
+  };
+
+  struct SurfaceIdHashFn {
+    std::size_t operator()(const wr::NativeSurfaceId& aId) const {
+      return HashGeneric(wr::AsUint64(aId));
+    }
+  };
+
   // Used in native compositor mode:
   RefPtr<layers::NativeLayer> mCurrentlyBoundNativeLayer;
   nsTArray<RefPtr<layers::NativeLayer>> mAddedLayers;
   uint64_t mTotalPixelCount = 0;
   uint64_t mAddedPixelCount = 0;
   uint64_t mAddedClippedPixelCount = 0;
   uint64_t mDrawnPixelCount = 0;
   gfx::IntRect mVisibleBounds;
-  std::unordered_map<uint64_t, RefPtr<layers::NativeLayer>> mNativeLayers;
+  std::unordered_map<wr::NativeSurfaceId, Surface, SurfaceIdHashFn> mSurfaces;
   TimeStamp mBeginFrameTimeStamp;
 
   // Used to apply back-pressure in WaitForGPU().
   GLsync mPreviousFrameDoneSync;
   GLsync mThisFrameDoneSync;
 };
 
+static inline bool operator==(const RenderCompositorOGL::TileKey& a0,
+                              const RenderCompositorOGL::TileKey& a1) {
+  return a0.mX == a1.mX && a0.mY == a1.mY;
+}
+
 }  // namespace wr
 }  // namespace mozilla
 
 #endif
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -28,17 +28,17 @@ use std::os::raw::{c_int};
 use std::time::Duration;
 use gleam::gl;
 
 use webrender::{
     api::*, api::units::*, ApiRecordingReceiver, AsyncPropertySampler, AsyncScreenshotHandle,
     BinaryRecorder, Compositor, DebugFlags, Device,
     NativeSurfaceId, PipelineInfo, ProfilerHooks, RecordedFrameHandle, Renderer, RendererOptions, RendererStats,
     SceneBuilderHooks, ShaderPrecacheFlags, Shaders, ThreadListener, UploadMethod, VertexUsageHint,
-    WrShaders, set_profiler_hooks, CompositorConfig, NativeSurfaceInfo
+    WrShaders, set_profiler_hooks, CompositorConfig, NativeSurfaceInfo, NativeTileId
 };
 use thread_profiler::register_thread_with_profiler;
 use moz2d_renderer::Moz2dBlobImageHandler;
 use program_cache::{WrProgramCache, remove_disk_cache};
 use rayon;
 use num_cpus;
 use euclid::SideOffsets2D;
 use nsstring::nsAString;
@@ -1231,26 +1231,38 @@ fn wr_device_new(gl_context: *mut c_void
         false,
     )
 }
 
 extern "C" {
     fn wr_compositor_create_surface(
         compositor: *mut c_void,
         id: NativeSurfaceId,
-        size: DeviceIntSize,
-        is_opaque: bool,
+        tile_size: DeviceIntSize,
     );
     fn wr_compositor_destroy_surface(
         compositor: *mut c_void,
         id: NativeSurfaceId,
     );
+    fn wr_compositor_create_tile(
+        compositor: *mut c_void,
+        id: NativeSurfaceId,
+        x: i32,
+        y: i32,
+        is_opaque: bool,
+    );
+    fn wr_compositor_destroy_tile(
+        compositor: *mut c_void,
+        id: NativeSurfaceId,
+        x: i32,
+        y: i32,
+    );
     fn wr_compositor_bind(
         compositor: *mut c_void,
-        id: NativeSurfaceId,
+        id: NativeTileId,
         offset: &mut DeviceIntPoint,
         fbo_id: &mut u32,
         dirty_rect: DeviceIntRect,
     );
     fn wr_compositor_unbind(compositor: *mut c_void);
     fn wr_compositor_begin_frame(compositor: *mut c_void);
     fn wr_compositor_add_surface(
         compositor: *mut c_void,
@@ -1262,44 +1274,72 @@ extern "C" {
 }
 
 pub struct WrCompositor(*mut c_void);
 
 impl Compositor for WrCompositor {
     fn create_surface(
         &mut self,
         id: NativeSurfaceId,
-        size: DeviceIntSize,
-        is_opaque: bool,
+        tile_size: DeviceIntSize,
     ) {
         unsafe {
             wr_compositor_create_surface(
                 self.0,
                 id,
-                size,
-                is_opaque,
+                tile_size,
             );
         }
     }
 
     fn destroy_surface(
         &mut self,
         id: NativeSurfaceId,
     ) {
         unsafe {
             wr_compositor_destroy_surface(
                 self.0,
                 id,
             );
         }
     }
 
+    fn create_tile(
+        &mut self,
+        id: NativeTileId,
+        is_opaque: bool,
+    ) {
+        unsafe {
+            wr_compositor_create_tile(
+                self.0,
+                id.surface_id,
+                id.x,
+                id.y,
+                is_opaque,
+            );
+        }
+    }
+
+    fn destroy_tile(
+        &mut self,
+        id: NativeTileId,
+    ) {
+        unsafe {
+            wr_compositor_destroy_tile(
+                self.0,
+                id.surface_id,
+                id.x,
+                id.y,
+            );
+        }
+    }
+
     fn bind(
         &mut self,
-        id: NativeSurfaceId,
+        id: NativeTileId,
         dirty_rect: DeviceIntRect,
     ) -> NativeSurfaceInfo {
         let mut surface_info = NativeSurfaceInfo {
             origin: DeviceIntPoint::zero(),
             fbo_id: 0,
         };
 
         unsafe {
--- a/gfx/wr/example-compositor/compositor-windows/src/lib.cpp
+++ b/gfx/wr/example-compositor/compositor-windows/src/lib.cpp
@@ -7,16 +7,17 @@
 #include <windows.h>
 #include <math.h>
 #include <dcomp.h>
 #include <d3d11.h>
 #include <assert.h>
 #include <map>
 #include <vector>
 #include <dwmapi.h>
+#include <unordered_map>
 
 #define EGL_EGL_PROTOTYPES 1
 #define EGL_EGLEXT_PROTOTYPES 1
 #define GL_GLEXT_PROTOTYPES 1
 #include "EGL/egl.h"
 #include "EGL/eglext.h"
 #include "EGL/eglext_angle.h"
 #include "GL/gl.h"
@@ -37,16 +38,40 @@ enum SyncMode {
 // The OS compositor representation of a picture cache tile.
 struct Tile {
     // Represents the underlying DirectComposition surface texture that gets drawn into.
     IDCompositionSurface *pSurface;
     // Represents the node in the visual tree that defines the properties of this tile (clip, position etc).
     IDCompositionVisual2 *pVisual;
 };
 
+struct TileKey {
+    int x;
+    int y;
+
+    TileKey(int ax, int ay) : x(ax), y(ay) {}
+};
+
+bool operator==(const TileKey &k0, const TileKey &k1) {
+    return k0.x == k1.x && k0.y == k1.y;
+}
+
+struct TileKeyHasher {
+    size_t operator()(const TileKey &key) const {
+        return key.x ^ key.y;
+    }
+};
+
+struct Surface {
+    int tile_width;
+    int tile_height;
+    std::unordered_map<TileKey, Tile, TileKeyHasher> tiles;
+    IDCompositionVisual2 *pVisual;
+};
+
 struct CachedFrameBuffer {
     int width;
     int height;
     GLuint fboId;
     GLuint depthRboId;
 };
 
 struct Window {
@@ -77,23 +102,24 @@ struct Window {
     IDCompositionSurface *pCurrentSurface;
     EGLImage mEGLImage;
     GLuint mColorRBO;
 
     // The root of the DC visual tree. Nothing is drawn on this, but
     // all child tiles are parented to here.
     IDCompositionVisual2 *pRoot;
     IDCompositionVisualDebug *pVisualDebug;
-    // Maps the WR surface IDs to the DC representation of each tile.
-    std::map<uint64_t, Tile> tiles;
     std::vector<CachedFrameBuffer> mFrameBuffers;
 
     // Maintain list of layer state between frames to avoid visual tree rebuild.
     std::vector<uint64_t> mCurrentLayers;
     std::vector<uint64_t> mPrevLayers;
+
+    // Maps WR surface IDs to each OS surface
+    std::unordered_map<uint64_t, Surface> surfaces;
 };
 
 static const wchar_t *CLASS_NAME = L"WR DirectComposite";
 
 static GLuint GetOrCreateFbo(Window *window, int aWidth, int aHeight) {
     GLuint fboId = 0;
 
     // Check if we have a cached FBO with matching dimensions
@@ -344,19 +370,25 @@ extern "C" {
             window->EGLContext
         );
         assert(ok);
 
         return window;
     }
 
     void com_dc_destroy_window(Window *window) {
-        for (auto it=window->tiles.begin() ; it != window->tiles.end() ; ++it) {
-            it->second.pSurface->Release();
-            it->second.pVisual->Release();
+        for (auto surface_it=window->surfaces.begin() ; surface_it != window->surfaces.end() ; ++surface_it) {
+            Surface &surface = surface_it->second;
+
+            for (auto tile_it=surface.tiles.begin() ; tile_it != surface.tiles.end() ; ++tile_it) {
+                tile_it->second.pSurface->Release();
+                tile_it->second.pVisual->Release();
+            }
+
+            surface.pVisual->Release();
         }
 
         if (window->fb_surface != EGL_NO_SURFACE) {
             eglDestroySurface(window->EGLDisplay, window->fb_surface);
         }
         eglDestroyContext(window->EGLDisplay, window->EGLContext);
         eglTerminate(window->EGLDisplay);
         eglReleaseDeviceANGLE(window->EGLDevice);
@@ -427,74 +459,140 @@ extern "C" {
             }
         }
     }
 
     // Create a new DC surface
     void com_dc_create_surface(
         Window *window,
         uint64_t id,
-        int width,
-        int height,
+        int tile_width,
+        int tile_height
+    ) {
+        assert(window->surfaces.count(id) == 0);
+
+        Surface surface;
+        surface.tile_width = tile_width;
+        surface.tile_height = tile_height;
+
+        // Create the visual node in the DC tree that stores properties
+        HRESULT hr = window->pDCompDevice->CreateVisual(&surface.pVisual);
+        assert(SUCCEEDED(hr));
+
+        window->surfaces[id] = surface;
+    }
+
+    void com_dc_create_tile(
+        Window *window,
+        uint64_t id,
+        int x,
+        int y,
         bool is_opaque
     ) {
-        assert(window->tiles.count(id) == 0);
+        assert(window->surfaces.count(id) == 1);
+        Surface &surface = window->surfaces[id];
+
+        TileKey key(x, y);
+        assert(surface.tiles.count(key) == 0);
 
         Tile tile;
 
+        // Create the video memory surface.
         DXGI_ALPHA_MODE alpha_mode = is_opaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
-
-        // Create the video memory surface.
         HRESULT hr = window->pDCompDevice->CreateSurface(
-            width,
-            height,
+            surface.tile_width,
+            surface.tile_height,
             DXGI_FORMAT_B8G8R8A8_UNORM,
             alpha_mode,
             &tile.pSurface
         );
         assert(SUCCEEDED(hr));
 
         // Create the visual node in the DC tree that stores properties
         hr = window->pDCompDevice->CreateVisual(&tile.pVisual);
         assert(SUCCEEDED(hr));
 
         // Bind the surface memory to this visual
         hr = tile.pVisual->SetContent(tile.pSurface);
         assert(SUCCEEDED(hr));
 
-        window->tiles[id] = tile;
+        // Place the visual in local-space of this surface
+        float offset_x = (float) (x * surface.tile_width);
+        float offset_y = (float) (y * surface.tile_height);
+        tile.pVisual->SetOffsetX(offset_x);
+        tile.pVisual->SetOffsetY(offset_y);
+
+        surface.pVisual->AddVisual(
+            tile.pVisual,
+            FALSE,
+            NULL
+        );
+
+        surface.tiles[key] = tile;
+    }
+
+    void com_dc_destroy_tile(
+        Window *window,
+        uint64_t id,
+        int x,
+        int y
+    ) {
+        assert(window->surfaces.count(id) == 1);
+        Surface &surface = window->surfaces[id];
+
+        TileKey key(x, y);
+        assert(surface.tiles.count(key) == 1);
+        Tile &tile = surface.tiles[key];
+
+        surface.pVisual->RemoveVisual(tile.pVisual);
+
+        tile.pVisual->Release();
+        tile.pSurface->Release();
+
+        surface.tiles.erase(key);
     }
 
     void com_dc_destroy_surface(
         Window *window,
         uint64_t id
     ) {
-        assert(window->tiles.count(id) == 1);
+        assert(window->surfaces.count(id) == 1);
+        Surface &surface = window->surfaces[id];
+
+        window->pRoot->RemoveVisual(surface.pVisual);
 
         // Release the video memory and visual in the tree
-        Tile &tile = window->tiles[id];
-        tile.pVisual->Release();
-        tile.pSurface->Release();
+        for (auto tile_it=surface.tiles.begin() ; tile_it != surface.tiles.end() ; ++tile_it) {
+            tile_it->second.pSurface->Release();
+            tile_it->second.pVisual->Release();
+        }
 
-        window->tiles.erase(id);
+        surface.pVisual->Release();
+        window->surfaces.erase(id);
     }
 
     // Bind a DC surface to allow issuing GL commands to it
     GLuint com_dc_bind_surface(
         Window *window,
-        uint64_t id,
+        uint64_t surface_id,
+        int tile_x,
+        int tile_y,
         int *x_offset,
         int *y_offset,
         int dirty_x0,
         int dirty_y0,
         int dirty_width,
         int dirty_height
     ) {
-        assert(window->tiles.count(id) == 1);
-        Tile &tile = window->tiles[id];
+        assert(window->surfaces.count(surface_id) == 1);
+        Surface &surface = window->surfaces[surface_id];
+
+        TileKey key(tile_x, tile_y);
+        assert(surface.tiles.count(key) == 1);
+        Tile &tile = surface.tiles[key];
 
         // Store the current surface for unbinding later
         window->pCurrentSurface = tile.pSurface;
 
         // Inform DC that we want to draw on this surface. DC uses texture
         // atlases when the tiles are small. It returns an offset where the
         // client code must draw into this surface when this happens.
         RECT update_rect;
@@ -578,52 +676,51 @@ extern "C" {
         uint64_t id,
         int x,
         int y,
         int clip_x,
         int clip_y,
         int clip_w,
         int clip_h
     ) {
-        Tile &tile = window->tiles[id];
-
+        Surface surface = window->surfaces[id];
         window->mCurrentLayers.push_back(id);
 
         // Place the visual - this changes frame to frame based on scroll position
         // of the slice.
         float offset_x = (float) (x + window->client_rect.left);
         float offset_y = (float) (y + window->client_rect.top);
-        tile.pVisual->SetOffsetX(offset_x);
-        tile.pVisual->SetOffsetY(offset_y);
+        surface.pVisual->SetOffsetX(offset_x);
+        surface.pVisual->SetOffsetY(offset_y);
 
         // Set the clip rect - converting from world space to the pre-offset space
         // that DC requires for rectangle clips.
         D2D_RECT_F clip_rect;
         clip_rect.left = clip_x - offset_x;
         clip_rect.top = clip_y - offset_y;
         clip_rect.right = clip_rect.left + clip_w;
         clip_rect.bottom = clip_rect.top + clip_h;
-        tile.pVisual->SetClip(clip_rect);
+        surface.pVisual->SetClip(clip_rect);
     }
 
     // Finish the composition transaction, telling DC to composite
     void com_dc_end_transaction(Window *window) {
         bool same = window->mPrevLayers == window->mCurrentLayers;
 
         if (!same) {
             HRESULT hr = window->pRoot->RemoveAllVisuals();
             assert(SUCCEEDED(hr));
 
             for (auto it = window->mCurrentLayers.begin(); it != window->mCurrentLayers.end(); ++it) {
-                Tile &tile = window->tiles[*it];
+                Surface &surface = window->surfaces[*it];
 
                 // Add this visual as the last element in the visual tree (z-order is implicit,
                 // based on the order tiles are added).
-                HRESULT hr = window->pRoot->AddVisual(
-                    tile.pVisual,
+                hr = window->pRoot->AddVisual(
+                    surface.pVisual,
                     FALSE,
                     NULL
                 );
                 assert(SUCCEEDED(hr));
             }
         }
 
         window->mPrevLayers.swap(window->mCurrentLayers);
--- a/gfx/wr/example-compositor/compositor-windows/src/lib.rs
+++ b/gfx/wr/example-compositor/compositor-windows/src/lib.rs
@@ -31,29 +31,45 @@ extern {
     fn com_dc_destroy_window(window: *mut Window);
     fn com_dc_tick(window: *mut Window) -> bool;
     fn com_dc_get_proc_address(name: *const c_char) -> *const c_void;
     fn com_dc_swap_buffers(window: *mut Window);
 
     fn com_dc_create_surface(
         window: *mut Window,
         id: u64,
-        width: i32,
-        height: i32,
+        tile_width: i32,
+        tile_height: i32,
+    );
+
+    fn com_dc_create_tile(
+        window: *mut Window,
+        id: u64,
+        x: i32,
+        y: i32,
         is_opaque: bool,
     );
 
+    fn com_dc_destroy_tile(
+        window: *mut Window,
+        id: u64,
+        x: i32,
+        y: i32,
+    );
+
     fn com_dc_destroy_surface(
         window: *mut Window,
         id: u64,
     );
 
     fn com_dc_bind_surface(
         window: *mut Window,
-        id: u64,
+        surface_id: u64,
+        tile_x: i32,
+        tile_y: i32,
         x_offset: &mut i32,
         y_offset: &mut i32,
         dirty_x0: i32,
         dirty_y0: i32,
         dirty_width: i32,
         dirty_height: i32,
     ) -> u32;
     fn com_dc_unbind_surface(window: *mut Window);
@@ -101,27 +117,59 @@ pub fn get_proc_address(name: *const c_c
     unsafe {
         com_dc_get_proc_address(name)
     }
 }
 
 pub fn create_surface(
     window: *mut Window,
     id: u64,
-    width: i32,
-    height: i32,
-    is_opaque: bool,
+    tile_width: i32,
+    tile_height: i32,
 ) {
     unsafe {
         com_dc_create_surface(
             window,
             id,
-            width,
-            height,
-            is_opaque
+            tile_width,
+            tile_height,
+        )
+    }
+}
+
+pub fn create_tile(
+    window: *mut Window,
+    id: u64,
+    x: i32,
+    y: i32,
+    is_opaque: bool,
+) {
+    unsafe {
+        com_dc_create_tile(
+            window,
+            id,
+            x,
+            y,
+            is_opaque,
+        )
+    }
+}
+
+pub fn destroy_tile(
+    window: *mut Window,
+    id: u64,
+    x: i32,
+    y: i32,
+) {
+    unsafe {
+        com_dc_destroy_tile(
+            window,
+            id,
+            x,
+            y,
         )
     }
 }
 
 pub fn destroy_surface(
     window: *mut Window,
     id: u64,
 ) {
@@ -130,29 +178,33 @@ pub fn destroy_surface(
             window,
             id,
         )
     }
 }
 
 pub fn bind_surface(
     window: *mut Window,
-    id: u64,
+    surface_id: u64,
+    tile_x: i32,
+    tile_y: i32,
     dirty_x0: i32,
     dirty_y0: i32,
     dirty_width: i32,
     dirty_height: i32,
 ) -> (u32, i32, i32) {
     unsafe {
         let mut x_offset = 0;
         let mut y_offset = 0;
 
         let fbo_id = com_dc_bind_surface(
             window,
-            id,
+            surface_id,
+            tile_x,
+            tile_y,
             &mut x_offset,
             &mut y_offset,
             dirty_x0,
             dirty_y0,
             dirty_width,
             dirty_height,
         );
 
--- a/gfx/wr/example-compositor/compositor/src/main.rs
+++ b/gfx/wr/example-compositor/compositor/src/main.rs
@@ -37,43 +37,69 @@ impl DirectCompositeInterface {
         }
     }
 }
 
 impl webrender::Compositor for DirectCompositeInterface {
     fn create_surface(
         &mut self,
         id: webrender::NativeSurfaceId,
-        size: DeviceIntSize,
-        is_opaque: bool,
+        tile_size: DeviceIntSize,
     ) {
         compositor::create_surface(
             self.window,
             id.0,
-            size.width,
-            size.height,
-            is_opaque,
+            tile_size.width,
+            tile_size.height,
         );
     }
 
     fn destroy_surface(
         &mut self,
         id: webrender::NativeSurfaceId,
     ) {
         compositor::destroy_surface(self.window, id.0);
     }
 
+    fn create_tile(
+        &mut self,
+        id: webrender::NativeTileId,
+        is_opaque: bool,
+    ) {
+        compositor::create_tile(
+            self.window,
+            id.surface_id.0,
+            id.x,
+            id.y,
+            is_opaque,
+        );
+    }
+
+    fn destroy_tile(
+        &mut self,
+        id: webrender::NativeTileId,
+    ) {
+        compositor::destroy_tile(
+            self.window,
+            id.surface_id.0,
+            id.x,
+            id.y,
+        );
+    }
+
     fn bind(
         &mut self,
-        id: webrender::NativeSurfaceId,
+        id: webrender::NativeTileId,
         dirty_rect: DeviceIntRect,
     ) -> webrender::NativeSurfaceInfo {
         let (fbo_id, x, y) = compositor::bind_surface(
             self.window,
-            id.0,
+            id.surface_id.0,
+            id.x,
+            id.y,
             dirty_rect.origin.x,
             dirty_rect.origin.y,
             dirty_rect.size.width,
             dirty_rect.size.height,
         );
 
         webrender::NativeSurfaceInfo {
             origin: DeviceIntPoint::new(x, y),
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -2,25 +2,25 @@
  * 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};
 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::{CompositeState, CompositeTile, CompositeTileSurface};
+use crate::composite::{CompositeState};
 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, BrushShaderKind};
 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};
+use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive};
 use crate::prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind, PrimitiveVisibilityIndex, PrimitiveVisibilityMask};
 use crate::prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
 use crate::prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, VECS_PER_SEGMENT, SpaceMapper};
 use crate::prim_store::image::ImageSource;
 use crate::render_target::RenderTargetContext;
 use crate::render_task_graph::{RenderTaskId, RenderTaskGraph};
 use crate::render_task::RenderTaskAddress;
 use crate::renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
@@ -1217,53 +1217,24 @@ impl BatchBuilder {
                                         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_state.z_generator.next();
-                                for key in &tile_cache.tiles_to_draw {
-                                    let tile = &tile_cache.tiles[key];
-                                    if !tile.is_visible {
-                                        // This can occur when a tile is found to be occluded during frame building.
-                                        continue;
-                                    }
-                                    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!");
 
-                                    let (surface, is_opaque) = match surface {
-                                        TileSurface::Color { color } => {
-                                            (CompositeTileSurface::Color { color: *color }, true)
-                                        }
-                                        TileSurface::Clear => {
-                                            (CompositeTileSurface::Clear, false)
-                                        }
-                                        TileSurface::Texture { descriptor, .. } => {
-                                            let surface = descriptor.resolve(ctx.resource_cache);
-                                            (
-                                                CompositeTileSurface::Texture { surface },
-                                                tile.is_opaque || tile_cache.is_opaque(),
-                                            )
-                                        }
-                                    };
-
-                                    let tile = CompositeTile {
-                                        surface,
-                                        rect: device_rect,
-                                        dirty_rect,
-                                        clip_rect: device_clip_rect,
-                                        z_id,
-                                        tile_id: tile.id,
-                                    };
-
-                                    composite_state.push_tile(tile, is_opaque);
-                                }
+                                composite_state.push_surface(
+                                    tile_cache,
+                                    device_clip_rect,
+                                    z_id,
+                                    ctx.global_device_pixel_scale,
+                                    ctx.resource_cache,
+                                );
                             }
                             PictureCompositeMode::Filter(ref filter) => {
                                 assert!(filter.is_visible());
                                 match filter {
                                     Filter::Blur(..) => {
                                         let kind = BatchKind::Brush(
                                             BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                         );
--- a/gfx/wr/webrender/src/composite.rs
+++ b/gfx/wr/webrender/src/composite.rs
@@ -1,41 +1,50 @@
 /* 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::ColorF;
-use api::units::{DeviceRect, DeviceIntSize, DeviceIntRect, DeviceIntPoint, WorldRect, DevicePixelScale};
+use api::units::{DeviceRect, DeviceIntSize, DeviceIntRect, DeviceIntPoint, WorldRect, DevicePixelScale, DevicePoint};
 use crate::gpu_types::{ZBufferId, ZBufferIdGenerator};
-use crate::picture::{ResolvedSurfaceTexture, TileId};
+use crate::picture::{ResolvedSurfaceTexture, TileId, TileCacheInstance, TileSurface};
+use crate::resource_cache::ResourceCache;
 use std::{ops, u64};
 
 /*
  Types and definitions related to compositing picture cache tiles
  and/or OS compositor integration.
  */
 
 /// Describes details of an operation to apply to a native surface
 #[derive(Debug, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum NativeSurfaceOperationDetails {
     CreateSurface {
-        size: DeviceIntSize,
+        id: NativeSurfaceId,
+        tile_size: DeviceIntSize,
+    },
+    DestroySurface {
+        id: NativeSurfaceId,
+    },
+    CreateTile {
+        id: NativeTileId,
         is_opaque: bool,
     },
-    DestroySurface,
+    DestroyTile {
+        id: NativeTileId,
+    }
 }
 
 /// Describes an operation to apply to a native surface
 #[derive(Debug, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct NativeSurfaceOperation {
-    pub id: NativeSurfaceId,
     pub details: NativeSurfaceOperationDetails,
 }
 
 /// Describes the source surface information for a tile to be composited. This
 /// is the analog of the TileSurface type, with target surface information
 /// resolved such that it can be used by the renderer.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -126,49 +135,51 @@ impl Default for CompositorKind {
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct Occluder {
     slice: usize,
     device_rect: DeviceIntRect,
 }
 
 /// Describes the properties that identify a tile composition uniquely.
-#[derive(PartialEq)]
-pub struct CompositeTileDescriptor {
-    pub rect: DeviceRect,
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(PartialEq, Clone)]
+pub struct CompositeSurfaceDescriptor {
+    pub slice: usize,
+    pub surface_id: Option<NativeSurfaceId>,
+    pub offset: DevicePoint,
     pub clip_rect: DeviceRect,
-    pub tile_id: TileId,
 }
 
-/// Describes which tiles and properties were used to composite a frame. This
+/// Describes surface properties used to composite a frame. This
 /// is used to compare compositions between frames.
-#[derive(PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(PartialEq, Clone)]
 pub struct CompositeDescriptor {
-    tiles: Vec<CompositeTileDescriptor>,
+    pub surfaces: Vec<CompositeSurfaceDescriptor>,
 }
 
 impl CompositeDescriptor {
     /// Construct an empty descriptor.
     pub fn empty() -> Self {
         CompositeDescriptor {
-            tiles: Vec::new(),
+            surfaces: Vec::new(),
         }
     }
 }
 
 /// The list of tiles to be drawn this frame
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CompositeState {
     // TODO(gw): Consider splitting up CompositeState into separate struct types depending
     //           on the selected compositing mode. Many of the fields in this state struct
     //           are only applicable to either Native or Draw compositing mode.
-    /// List of tiles to be drawn by the native compositor. These are added in draw order
-    /// and not separated by kind (opacity is a property of the native surface).
-    pub native_tiles: Vec<CompositeTile>,
     /// List of opaque tiles to be drawn by the Draw compositor.
     pub opaque_tiles: Vec<CompositeTile>,
     /// List of alpha tiles to be drawn by the Draw compositor.
     pub alpha_tiles: Vec<CompositeTile>,
     /// List of clear tiles to be drawn by the Draw compositor.
     pub clear_tiles: Vec<CompositeTile>,
     /// Used to generate z-id values for tiles in the Draw compositor mode.
     pub z_generator: ZBufferIdGenerator,
@@ -182,16 +193,18 @@ pub struct CompositeState {
     /// The kind of compositor for picture cache tiles (e.g. drawn by WR, or OS compositor)
     pub compositor_kind: CompositorKind,
     /// Picture caching may be disabled dynamically, based on debug flags, pinch zoom etc.
     pub picture_caching_is_enabled: bool,
     /// The overall device pixel scale, used for tile occlusion conversions.
     global_device_pixel_scale: DevicePixelScale,
     /// List of registered occluders
     occluders: Vec<Occluder>,
+    /// Description of the surfaces and properties that are being composited.
+    pub descriptor: CompositeDescriptor,
 }
 
 impl CompositeState {
     /// Construct a new state for compositing picture tiles. This is created
     /// during each frame construction and passed to the renderer.
     pub fn new(
         compositor_kind: CompositorKind,
         mut picture_caching_is_enabled: bool,
@@ -202,49 +215,26 @@ impl CompositeState {
         if let CompositorKind::Native { .. } = compositor_kind {
             if !picture_caching_is_enabled {
                 warn!("Picture caching cannot be disabled in native compositor config");
             }
             picture_caching_is_enabled = true;
         }
 
         CompositeState {
-            native_tiles: Vec::new(),
             opaque_tiles: Vec::new(),
             alpha_tiles: Vec::new(),
             clear_tiles: Vec::new(),
             z_generator: ZBufferIdGenerator::new(0),
             dirty_rects_are_valid: true,
             compositor_kind,
             picture_caching_is_enabled,
             global_device_pixel_scale,
             occluders: Vec::new(),
-        }
-    }
-
-    /// Construct a descriptor of this composition state. Used for comparing
-    /// composition states between frames.
-    pub fn create_descriptor(&self) -> CompositeDescriptor {
-        let mut tiles = Vec::new();
-
-        let native_iter = self.native_tiles.iter();
-        let opaque_iter = self.opaque_tiles.iter();
-        let clear_iter = self.clear_tiles.iter();
-        let alpha_iter = self.alpha_tiles.iter();
-
-        for tile in native_iter.chain(opaque_iter).chain(clear_iter).chain(alpha_iter) {
-            tiles.push(CompositeTileDescriptor {
-                rect: tile.rect,
-                clip_rect: tile.clip_rect,
-                tile_id: tile.tile_id,
-            });
-        }
-
-        CompositeDescriptor {
-            tiles,
+            descriptor: CompositeDescriptor::empty(),
         }
     }
 
     /// Register an occluder during picture cache updates that can be
     /// used during frame building to occlude tiles.
     pub fn register_occluder(
         &mut self,
         slice: usize,
@@ -284,71 +274,140 @@ impl CompositeState {
         // Calculate the non-overlapping area of the valid occluders.
         let cover_area = area_of_occluders(&self.occluders, slice, &device_rect);
         debug_assert!(cover_area <= ref_area);
 
         // Check if the tile area is completely covered
         ref_area == cover_area
     }
 
+    /// Add a picture cache to be composited
+    pub fn push_surface(
+        &mut self,
+        tile_cache: &TileCacheInstance,
+        device_clip_rect: DeviceRect,
+        z_id: ZBufferId,
+        global_device_pixel_scale: DevicePixelScale,
+        resource_cache: &ResourceCache,
+    ) {
+        let mut visible_tile_count = 0;
+
+        for key in &tile_cache.tiles_to_draw {
+            let tile = &tile_cache.tiles[key];
+            if !tile.is_visible {
+                // This can occur when a tile is found to be occluded during frame building.
+                continue;
+            }
+
+            visible_tile_count += 1;
+
+            let device_rect = (tile.world_rect * global_device_pixel_scale).round();
+            let dirty_rect = (tile.world_dirty_rect * global_device_pixel_scale).round();
+            let surface = tile.surface.as_ref().expect("no tile surface set!");
+
+            let (surface, is_opaque) = match surface {
+                TileSurface::Color { color } => {
+                    (CompositeTileSurface::Color { color: *color }, true)
+                }
+                TileSurface::Clear => {
+                    (CompositeTileSurface::Clear, false)
+                }
+                TileSurface::Texture { descriptor, .. } => {
+                    let surface = descriptor.resolve(resource_cache, tile_cache.current_tile_size);
+                    (
+                        CompositeTileSurface::Texture { surface },
+                        tile.is_opaque || tile_cache.is_opaque(),
+                    )
+                }
+            };
+
+            let tile = CompositeTile {
+                surface,
+                rect: device_rect,
+                dirty_rect,
+                clip_rect: device_clip_rect,
+                z_id,
+                tile_id: tile.id,
+            };
+
+            self.push_tile(tile, is_opaque);
+        }
+
+        if visible_tile_count > 0 {
+            self.descriptor.surfaces.push(
+                CompositeSurfaceDescriptor {
+                    slice: tile_cache.slice,
+                    surface_id: tile_cache.native_surface_id,
+                    offset: tile_cache.device_position,
+                    clip_rect: device_clip_rect,
+                }
+            );
+        }
+    }
+
     /// Add a tile to the appropriate array, depending on tile properties and compositor mode.
-    pub fn push_tile(
+    fn push_tile(
         &mut self,
         tile: CompositeTile,
         is_opaque: bool,
     ) {
-        match (self.compositor_kind, &tile.surface) {
-            (CompositorKind::Draw { .. }, CompositeTileSurface::Color { .. }) => {
+        match tile.surface {
+            CompositeTileSurface::Color { .. } => {
                 // Color tiles are, by definition, opaque. We might support non-opaque color
                 // tiles if we ever find pages that have a lot of these.
                 self.opaque_tiles.push(tile);
             }
-            (CompositorKind::Draw { .. }, CompositeTileSurface::Clear) => {
+            CompositeTileSurface::Clear => {
                 // Clear tiles have a special bucket
                 self.clear_tiles.push(tile);
             }
-            (CompositorKind::Draw { .. }, CompositeTileSurface::Texture { .. }) => {
+            CompositeTileSurface::Texture { .. } => {
                 // Texture surfaces get bucketed by opaque/alpha, for z-rejection
                 // on the Draw compositor mode.
                 if is_opaque {
                     self.opaque_tiles.push(tile);
                 } else {
                     self.alpha_tiles.push(tile);
                 }
             }
-            (CompositorKind::Native { .. }, CompositeTileSurface::Color { .. }) => {
-                // Native compositor doesn't (yet) support color surfaces.
-                unreachable!();
-            }
-            (CompositorKind::Native { .. }, CompositeTileSurface::Clear) => {
-                // Native compositor doesn't support color surfaces.
-                unreachable!();
-            }
-            (CompositorKind::Native { .. }, CompositeTileSurface::Texture { .. }) => {
-                // Native tiles are supplied to the OS compositor in draw order,
-                // since there is no z-buffer involved (opacity is supplied as part
-                // of the surface properties).
-                self.native_tiles.push(tile);
-            }
         }
     }
 }
 
 /// An arbitrary identifier for a native (OS compositor) surface
 #[repr(C)]
 #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct NativeSurfaceId(pub u64);
 
 impl NativeSurfaceId {
     /// A special id for the native surface that is used for debug / profiler overlays.
     pub const DEBUG_OVERLAY: NativeSurfaceId = NativeSurfaceId(u64::MAX);
 }
 
+#[repr(C)]
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct NativeTileId {
+    pub surface_id: NativeSurfaceId,
+    pub x: i32,
+    pub y: i32,
+}
+
+impl NativeTileId {
+    /// A special id for the native surface that is used for debug / profiler overlays.
+    pub const DEBUG_OVERLAY: NativeTileId = NativeTileId {
+        surface_id: NativeSurfaceId::DEBUG_OVERLAY,
+        x: 0,
+        y: 0,
+    };
+}
+
 /// Information about a bound surface that the native compositor
 /// returns to WR.
 #[repr(C)]
 #[derive(Copy, Clone)]
 pub struct NativeSurfaceInfo {
     /// An offset into the surface that WR should draw. Some compositing
     /// implementations (notably, DirectComposition) use texture atlases
     /// when the surface sizes are small. In this case, an offset can
@@ -365,49 +424,60 @@ pub struct NativeSurfaceInfo {
     pub fbo_id: u32,
 }
 
 /// Defines an interface to a native (OS level) compositor. If supplied
 /// by the client application, then picture cache slices will be
 /// composited by the OS compositor, rather than drawn via WR batches.
 pub trait Compositor {
     /// Create a new OS compositor surface with the given properties.
-    /// The surface is allocated but not placed in the visual tree.
     fn create_surface(
         &mut self,
         id: NativeSurfaceId,
-        size: DeviceIntSize,
-        is_opaque: bool,
+        tile_size: DeviceIntSize,
     );
 
     /// Destroy the surface with the specified id. WR may call this
     /// at any time the surface is no longer required (including during
     /// renderer deinit). It's the responsibility of the embedder
     /// to ensure that the surface is only freed once the GPU is
     /// no longer using the surface (if this isn't already handled
     /// by the operating system).
     fn destroy_surface(
         &mut self,
         id: NativeSurfaceId,
     );
 
+    /// Create a new OS compositor tile with the given properties.
+    fn create_tile(
+        &mut self,
+        id: NativeTileId,
+        is_opaque: bool,
+    );
+
+    /// Destroy an existing compositor tile.
+    fn destroy_tile(
+        &mut self,
+        id: NativeTileId,
+    );
+
     /// Bind this surface such that WR can issue OpenGL commands
     /// that will target the surface. Returns an (x, y) offset
     /// where WR should draw into the surface. This can be set
     /// to (0, 0) if the OS doesn't use texture atlases. The dirty
     /// rect is a local surface rect that specifies which part
     /// of the surface needs to be updated. If max_update_rects
     /// in CompositeConfig is 0, this will always be the size
     /// of the entire surface. The returned offset is only
     /// relevant to compositors that store surfaces in a texture
     /// atlas (that is, WR expects that the dirty rect doesn't
     /// affect the coordinates of the returned origin).
     fn bind(
         &mut self,
-        id: NativeSurfaceId,
+        id: NativeTileId,
         dirty_rect: DeviceIntRect,
     ) -> NativeSurfaceInfo;
 
     /// Unbind the surface. This is called by WR when it has
     /// finished issuing OpenGL commands on the current surface.
     fn unbind(
         &mut self,
     );
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -354,21 +354,20 @@ impl FrameBuilder {
             // allows the first frame of a new display list to reuse any existing tiles
             // and surfaces that match. Once the `update_visibility` call above is
             // complete, any tiles that are left remaining in the `retained_tiles`
             // map are not needed and will be dropped. For simple compositing mode,
             // this is fine, since texture cache handles are garbage collected at
             // the end of each frame. However, if we're in native compositor mode,
             // we need to manually clean up any native compositor surfaces that were
             // allocated by these tiles.
-            for (_, cache_state) in visibility_state.retained_tiles.caches.drain() {
-                visibility_state.composite_state.destroy_native_surfaces(
-                    cache_state.tiles.values(),
-                    visibility_state.resource_cache,
-                );
+            for (_, mut cache_state) in visibility_state.retained_tiles.caches.drain() {
+                if let Some(native_surface_id) = cache_state.native_surface_id.take() {
+                    visibility_state.resource_cache.destroy_compositor_surface(native_surface_id);
+                }
             }
         }
 
         let mut frame_state = FrameBuildingState {
             render_tasks,
             profile_counters,
             clip_store: &mut scene.clip_store,
             resource_cache,
--- a/gfx/wr/webrender/src/lib.rs
+++ b/gfx/wr/webrender/src/lib.rs
@@ -196,17 +196,17 @@ extern crate png;
 #[cfg(test)]
 extern crate rand;
 
 #[macro_use]
 pub extern crate api;
 extern crate webrender_build;
 
 #[doc(hidden)]
-pub use crate::composite::{CompositorConfig, Compositor, NativeSurfaceId, NativeSurfaceInfo};
+pub use crate::composite::{CompositorConfig, Compositor, NativeSurfaceId, NativeTileId, NativeSurfaceInfo};
 pub use crate::device::{build_shader_strings, UploadMethod, VertexUsageHint, get_gl_target};
 pub use crate::device::{ProgramBinary, ProgramCache, ProgramCacheObserver, FormatDesc};
 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, RendererKind, GpuProfile, GraphicsApi,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -73,17 +73,17 @@ use api::{MixBlendMode, PipelineId, Prem
 use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FontRenderMode};
 use api::{DebugFlags, RasterSpace, ImageKey, ColorF, PrimitiveFlags};
 use api::units::*;
 use crate::box_shadow::{BLUR_SAMPLE_SCALE};
 use crate::clip::{ClipStore, ClipChainInstance, ClipDataHandle, ClipChainId};
 use crate::clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX,
     ClipScrollTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace
 };
-use crate::composite::{CompositorKind, CompositeState, NativeSurfaceId};
+use crate::composite::{CompositorKind, CompositeState, NativeSurfaceId, NativeTileId};
 use crate::debug_colors;
 use euclid::{vec3, Point2D, Scale, Size2D, Vector2D, Rect};
 use euclid::approxeq::ApproxEq;
 use crate::filterdata::SFilterData;
 use crate::frame_builder::{FrameVisibilityContext, FrameVisibilityState};
 use crate::intern::ItemUid;
 use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, Filter, PlaneSplitAnchor, TextureSource};
 use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
@@ -197,16 +197,18 @@ pub struct PictureCacheState {
     /// State of opacity bindings from previous frame
     opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
     /// The current transform of the picture cache root spatial node
     root_transform: TransformKey,
     /// The current tile size in device pixels
     current_tile_size: DeviceIntSize,
     /// Various allocations we want to avoid re-doing.
     allocations: PictureCacheRecycledAllocations,
+    /// Currently allocated native compositor surface for this picture cache.
+    pub native_surface_id: Option<NativeSurfaceId>,
 }
 
 pub struct PictureCacheRecycledAllocations {
     old_tiles: FastHashMap<TileOffset, Tile>,
     old_opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
     compare_cache: FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
 }
 
@@ -441,64 +443,63 @@ pub struct TileId(pub usize);
 pub enum SurfaceTextureDescriptor {
     /// When using the WR compositor, the tile is drawn into an entry
     /// in the WR texture cache.
     TextureCache {
         handle: TextureCacheHandle
     },
     /// When using an OS compositor, the tile is drawn into a native
     /// surface identified by arbitrary id.
-    NativeSurface {
-        /// The arbitrary id of this surface.
-        id: Option<NativeSurfaceId>,
-        /// Size in device pixels of the native surface.
-        size: DeviceIntSize,
+    Native {
+        /// The arbitrary id of this tile.
+        id: Option<NativeTileId>,
     },
 }
 
 /// This is the same as a `SurfaceTextureDescriptor` but has been resolved
 /// into a texture cache handle (if appropriate) that can be used by the
 /// batching and compositing code in the renderer.
 #[derive(Clone, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum ResolvedSurfaceTexture {
     TextureCache {
         /// The texture ID to draw to.
         texture: TextureSource,
         /// Slice index in the texture array to draw to.
         layer: i32,
     },
-    NativeSurface {
-        /// The arbitrary id of this surface.
-        id: NativeSurfaceId,
-        /// Size in device pixels of the native surface.
+    Native {
+        /// The arbitrary id of this tile.
+        id: NativeTileId,
+        /// The size of the tile in device pixels.
         size: DeviceIntSize,
     }
 }
 
 impl SurfaceTextureDescriptor {
     /// Create a resolved surface texture for this descriptor
     pub fn resolve(
         &self,
         resource_cache: &ResourceCache,
+        size: DeviceIntSize,
     ) -> ResolvedSurfaceTexture {
         match self {
             SurfaceTextureDescriptor::TextureCache { handle } => {
                 let cache_item = resource_cache.texture_cache.get(handle);
 
                 ResolvedSurfaceTexture::TextureCache {
                     texture: cache_item.texture_id,
                     layer: cache_item.texture_layer,
                 }
             }
-            SurfaceTextureDescriptor::NativeSurface { id, size } => {
-                ResolvedSurfaceTexture::NativeSurface {
+            SurfaceTextureDescriptor::Native { id } => {
+                ResolvedSurfaceTexture::Native {
                     id: id.expect("bug: native surface not allocated"),
-                    size: *size,
+                    size,
                 }
             }
         }
     }
 }
 
 /// The backing surface for this tile.
 #[derive(Debug)]
@@ -968,25 +969,25 @@ impl Tile {
                 Some(TileSurface::Texture { mut descriptor, visibility_mask }) => {
                     // If opacity changed, and this is a native OS compositor surface,
                     // it needs to be recreated.
                     // TODO(gw): This is a limitation of the DirectComposite APIs. It might
                     //           make sense on other platforms to be able to change this as
                     //           a property on a surface, if we ever see pages where this
                     //           is changing frequently.
                     if opacity_changed {
-                        if let SurfaceTextureDescriptor::NativeSurface { ref mut id, .. } = descriptor {
+                        if let SurfaceTextureDescriptor::Native { ref mut id, .. } = descriptor {
                             // Reset the dirty rect and tile validity in this case, to
                             // force the new tile to be completely redrawn.
                             self.invalidate(None, InvalidationReason::SurfaceOpacityChanged);
 
                             // If this tile has a currently allocated native surface, destroy it. It
                             // will be re-allocated next time it's determined to be visible.
                             if let Some(id) = id.take() {
-                                state.resource_cache.destroy_compositor_surface(id);
+                                state.resource_cache.destroy_compositor_tile(id);
                             }
                         }
                     }
 
                     // Reuse the existing descriptor and vis mask
                     TileSurface::Texture {
                         descriptor,
                         visibility_mask,
@@ -1004,19 +1005,18 @@ impl Tile {
                             SurfaceTextureDescriptor::TextureCache {
                                 handle: TextureCacheHandle::invalid(),
                             }
                         }
                         CompositorKind::Native { .. } => {
                             // Create a native surface surface descriptor, but don't allocate
                             // a surface yet. The surface is allocated *after* occlusion
                             // culling occurs, so that only visible tiles allocate GPU memory.
-                            SurfaceTextureDescriptor::NativeSurface {
+                            SurfaceTextureDescriptor::Native {
                                 id: None,
-                                size: ctx.current_tile_size,
                             }
                         }
                     };
 
                     TileSurface::Texture {
                         descriptor,
                         visibility_mask: PrimitiveVisibilityMask::empty(),
                     }
@@ -1506,16 +1506,23 @@ pub struct TileCacheInstance {
     /// we don't want to constantly invalidate and reallocate different tile size
     /// configuration each frame.
     frames_until_size_eval: usize,
     /// The current fractional offset of the cached picture
     fract_offset: PictureVector2D,
     /// keep around the hash map used as compare_cache to avoid reallocating it each
     /// frame.
     compare_cache: FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
+    /// The allocated compositor surface for this picture cache. May be None if
+    /// 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,
 }
 
 impl TileCacheInstance {
     pub fn new(
         slice: usize,
         spatial_node_index: SpatialNodeIndex,
         background_color: Option<ColorF>,
         shared_clips: Vec<ClipDataHandle>,
@@ -1553,16 +1560,18 @@ impl TileCacheInstance {
             subpixel_mode: SubpixelMode::Allow,
             root_transform: TransformKey::Local,
             shared_clips,
             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(),
         }
     }
 
     /// 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 {
@@ -1673,16 +1682,17 @@ impl TileCacheInstance {
 
         // If there are pending retained state, retrieve it.
         if let Some(prev_state) = frame_state.retained_tiles.caches.remove(&self.slice) {
             self.tiles.extend(prev_state.tiles);
             self.root_transform = prev_state.root_transform;
             self.spatial_nodes = prev_state.spatial_nodes;
             self.opacity_bindings = prev_state.opacity_bindings;
             self.current_tile_size = prev_state.current_tile_size;
+            self.native_surface_id = prev_state.native_surface_id;
 
             fn recycle_map<K: std::cmp::Eq + std::hash::Hash, V>(
                 ideal_len: usize,
                 dest: &mut FastHashMap<K, V>,
                 src: FastHashMap<K, V>,
             ) {
                 if dest.capacity() < src.capacity() {
                     if src.capacity() < 3 * ideal_len {
@@ -1730,20 +1740,19 @@ impl TileCacheInstance {
                 desired_tile_size = 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.
-                frame_state.composite_state.destroy_native_surfaces(
-                    self.tiles.values(),
-                    frame_state.resource_cache,
-                );
+                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;
         }
@@ -1757,16 +1766,17 @@ impl TileCacheInstance {
         let world_origin = pic_to_world_mapper
             .map(&PictureRect::new(PicturePoint::zero(), PictureSize::new(1.0, 1.0)))
             .expect("bug: unable to map origin to world space")
             .origin;
 
         // Get the desired integer device coordinate
         let device_origin = world_origin * frame_context.global_device_pixel_scale;
         let desired_device_origin = device_origin.round();
+        self.device_position = desired_device_origin;
 
         // Unmap from device space to world space rect
         let ref_world_rect = WorldRect::new(
             desired_device_origin / frame_context.global_device_pixel_scale,
             WorldSize::new(1.0, 1.0),
         );
 
         // Unmap from world space to picture space
@@ -1906,17 +1916,17 @@ impl TileCacheInstance {
                 self.tiles.insert(key, tile);
             }
         }
 
         // Any old tiles that remain after the loop above are going to be dropped. For
         // simple composite mode, the texture cache handle will expire and be collected
         // by the texture cache. For native compositor mode, we need to explicitly
         // invoke a callback to the client to destroy that surface.
-        frame_state.composite_state.destroy_native_surfaces(
+        frame_state.composite_state.destroy_native_tiles(
             self.old_tiles.values(),
             frame_state.resource_cache,
         );
 
         world_culling_rect
     }
 
     /// Update the dependencies for each tile for a given primitive instance.
@@ -3145,26 +3155,27 @@ impl PicturePrimitive {
     /// Destroy an existing picture. This is called just before
     /// a frame builder is replaced with a newly built scene. It
     /// gives a picture a chance to retain any cached tiles that
     /// may be useful during the next scene build.
     pub fn destroy(
         &mut self,
         retained_tiles: &mut RetainedTiles,
     ) {
-        if let Some(tile_cache) = self.tile_cache.take() {
+        if let Some(mut tile_cache) = self.tile_cache.take() {
             if !tile_cache.tiles.is_empty() {
                 retained_tiles.caches.insert(
                     tile_cache.slice,
                     PictureCacheState {
                         tiles: tile_cache.tiles,
                         spatial_nodes: tile_cache.spatial_nodes,
                         opacity_bindings: tile_cache.opacity_bindings,
                         root_transform: tile_cache.root_transform,
                         current_tile_size: tile_cache.current_tile_size,
+                        native_surface_id: tile_cache.native_surface_id.take(),
                         allocations: PictureCacheRecycledAllocations {
                             old_tiles: tile_cache.old_tiles,
                             old_opacity_bindings: tile_cache.old_opacity_bindings,
                             compare_cache: tile_cache.compare_cache,
                         },
                     },
                 );
             }
@@ -3600,19 +3611,19 @@ impl PicturePrimitive {
                             // code below.
                             if frame_state.composite_state.is_tile_occluded(tile_cache.slice, tile_draw_rect) {
                                 // If this tile has an allocated native surface, free it, since it's completely
                                 // occluded. We will need to re-allocate this surface if it becomes visible,
                                 // but that's likely to be rare (e.g. when there is no content display list
                                 // for a frame or two during a tab switch).
                                 let surface = tile.surface.as_mut().expect("no tile surface set!");
 
-                                if let TileSurface::Texture { descriptor: SurfaceTextureDescriptor::NativeSurface { id, .. }, .. } = surface {
+                                if let TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { id, .. }, .. } = surface {
                                     if let Some(id) = id.take() {
-                                        frame_state.resource_cache.destroy_compositor_surface(id);
+                                        frame_state.resource_cache.destroy_compositor_tile(id);
                                     }
                                 }
 
                                 tile.is_visible = false;
                                 continue;
                             }
 
                             if frame_context.debug_flags.contains(DebugFlags::PICTURE_CACHING_DBG) {
@@ -3656,17 +3667,17 @@ impl PicturePrimitive {
                                             // TODO(gw): Consider switching to manual eviction policy?
                                             frame_state.resource_cache.texture_cache.request(handle, frame_state.gpu_cache);
                                         } else {
                                             // If the texture was evicted on a previous frame, we need to assume
                                             // that the entire tile rect is dirty.
                                             tile.invalidate(None, InvalidationReason::NoTexture);
                                         }
                                     }
-                                    SurfaceTextureDescriptor::NativeSurface { id, .. } => {
+                                    SurfaceTextureDescriptor::Native { id, .. } => {
                                         if id.is_none() {
                                             // There is no current surface allocation, so ensure the entire tile is invalidated
                                             tile.invalidate(None, InvalidationReason::NoSurface);
                                         }
                                     }
                                 }
                             }
 
@@ -3684,22 +3695,42 @@ impl PicturePrimitive {
                                         if !frame_state.resource_cache.texture_cache.is_allocated(handle) {
                                             frame_state.resource_cache.texture_cache.update_picture_cache(
                                                 tile_cache.current_tile_size,
                                                 handle,
                                                 frame_state.gpu_cache,
                                             );
                                         }
                                     }
-                                    SurfaceTextureDescriptor::NativeSurface { id, size } => {
+                                    SurfaceTextureDescriptor::Native { id } => {
                                         if id.is_none() {
-                                            *id = Some(frame_state.resource_cache.create_compositor_surface(
-                                                *size,
+                                            // Allocate a native surface id if we're in native compositing mode,
+                                            // and we don't have a surface yet (due to first frame, or destruction
+                                            // due to tile size changing etc).
+                                            if tile_cache.native_surface_id.is_none() {
+                                                let surface_id = frame_state
+                                                    .resource_cache
+                                                    .create_compositor_surface(tile_cache.current_tile_size);
+
+                                                tile_cache.native_surface_id = Some(surface_id);
+                                            }
+
+                                            // Create the tile identifier and allocate it.
+                                            let tile_id = NativeTileId {
+                                                surface_id: tile_cache.native_surface_id.unwrap(),
+                                                x: key.x,
+                                                y: key.y,
+                                            };
+
+                                            frame_state.resource_cache.create_compositor_tile(
+                                                tile_id,
                                                 tile.is_opaque,
-                                            ));
+                                            );
+
+                                            *id = Some(tile_id);
                                         }
                                     }
                                 }
 
                                 *visibility_mask = PrimitiveVisibilityMask::empty();
                                 let dirty_region_index = tile_cache.dirty_region.dirty_rects.len();
 
                                 // If we run out of dirty regions, then force the last dirty region to
@@ -3734,17 +3765,20 @@ impl PicturePrimitive {
                                     -tile.world_rect.origin.to_vector()
                                 );
                                 // The world rect is guaranteed to be device pixel aligned, by the tile
                                 // sizing code in tile::pre_update. However, there might be some
                                 // small floating point accuracy issues (these were observed on ARM
                                 // CPUs). Round the rect here before casting to integer device pixels
                                 // to ensure the scissor rect is correct.
                                 let scissor_rect = (scissor_rect * device_pixel_scale).round();
-                                let surface = descriptor.resolve(frame_state.resource_cache);
+                                let surface = descriptor.resolve(
+                                    frame_state.resource_cache,
+                                    tile_cache.current_tile_size,
+                                );
 
                                 let task = RenderTask::new_picture(
                                     RenderTaskLocation::PictureCache {
                                         size: tile_cache.current_tile_size,
                                         surface,
                                     },
                                     tile_cache.current_tile_size.to_f32(),
                                     pic_index,
@@ -5189,31 +5223,31 @@ impl TileNode {
                 }
             }
         }
     }
 }
 
 impl CompositeState {
     // A helper function to destroy all native surfaces for a given list of tiles
-    pub fn destroy_native_surfaces<'a, I: Iterator<Item = &'a Tile>>(
+    pub fn destroy_native_tiles<'a, I: Iterator<Item = &'a Tile>>(
         &mut self,
         tiles_iter: I,
         resource_cache: &mut ResourceCache,
     ) {
         // Any old tiles that remain after the loop above are going to be dropped. For
         // simple composite mode, the texture cache handle will expire and be collected
         // by the texture cache. For native compositor mode, we need to explicitly
         // invoke a callback to the client to destroy that surface.
         if let CompositorKind::Native { .. } = self.compositor_kind {
             for tile in tiles_iter {
                 // Only destroy native surfaces that have been allocated. It's
                 // possible for display port tiles to be created that never
                 // come on screen, and thus never get a native surface allocated.
-                if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::NativeSurface { id, .. }, .. }) = tile.surface {
+                if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { id, .. }, .. }) = tile.surface {
                     if let Some(id) = id {
-                        resource_cache.destroy_compositor_surface(id);
+                        resource_cache.destroy_compositor_tile(id);
                     }
                 }
             }
         }
     }
 }
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -1543,17 +1543,18 @@ impl RenderBackend {
                 let pending_update = self.resource_cache.pending_updates();
                 (pending_update, rendered_document)
             };
 
             // Build a small struct that represents the state of the tiles to be composited.
             let composite_descriptor = rendered_document
                 .frame
                 .composite_state
-                .create_descriptor();
+                .descriptor
+                .clone();
 
             // If there are no texture cache updates to apply, and if the produced
             // frame is a no-op, and the compositor state is equal, then we can skip
             // compositing this frame completely.
             if pending_update.is_nop() &&
                rendered_document.frame.is_nop() &&
                composite_descriptor == doc.prev_composite_descriptor {
                 doc.rendered_frame_is_valid = true;
--- a/gfx/wr/webrender/src/render_task.rs
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -1353,17 +1353,17 @@ impl RenderTask {
                 (DeviceIntRect::zero(), RenderTargetIndex(0))
             }
             RenderTaskLocation::TextureCache {layer, rect, .. } => {
                 (rect, RenderTargetIndex(layer as usize))
             }
             RenderTaskLocation::PictureCache { ref surface, size, .. } => {
                 let layer = match surface {
                     ResolvedSurfaceTexture::TextureCache { layer, .. } => *layer,
-                    ResolvedSurfaceTexture::NativeSurface { .. } => 0,
+                    ResolvedSurfaceTexture::Native { .. } => 0,
                 };
 
                 (
                     DeviceIntRect::new(
                         DeviceIntPoint::zero(),
                         size,
                     ),
                     RenderTargetIndex(layer as usize),
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -44,17 +44,17 @@ use api::{RenderApiSender, RenderNotifie
 use api::ExternalImage;
 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::{CompositeState, CompositeTileSurface, CompositeTile, CompositorKind, Compositor};
+use crate::composite::{CompositeState, CompositeTileSurface, CompositeTile, CompositorKind, Compositor, NativeTileId};
 use crate::composite::{CompositorConfig, NativeSurfaceOperationDetails, NativeSurfaceId, NativeSurfaceOperation};
 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;
@@ -2963,33 +2963,36 @@ impl Renderer {
                 }
             }
 
             // Allocate a new surface, if we need it and there isn't one.
             if self.debug_overlay_state.is_enabled && self.debug_overlay_state.current_size.is_none() {
                 compositor.create_surface(
                     NativeSurfaceId::DEBUG_OVERLAY,
                     framebuffer_size,
+                );
+                compositor.create_tile(
+                    NativeTileId::DEBUG_OVERLAY,
                     false,
                 );
                 self.debug_overlay_state.current_size = Some(framebuffer_size);
             }
         }
     }
 
     /// Bind a draw target for the debug / profiler overlays, if required.
     fn bind_debug_overlay(&mut self) {
         // Debug overlay setup are only required in native compositing mode
         if self.debug_overlay_state.is_enabled {
             if let CompositorConfig::Native { ref mut compositor, .. } = self.compositor_config {
                 let surface_size = self.debug_overlay_state.current_size.unwrap();
 
                 // Bind the native surface
                 let surface_info = compositor.bind(
-                    NativeSurfaceId::DEBUG_OVERLAY,
+                    NativeTileId::DEBUG_OVERLAY,
                     DeviceIntRect::new(
                         DeviceIntPoint::zero(),
                         surface_size,
                     ),
                 );
 
                 // Bind the native surface to current FBO target
                 let draw_target = DrawTarget::NativeSurface {
@@ -4114,17 +4117,17 @@ impl Renderer {
                     (TextureSource::Dummy, 0.0, color)
                 }
                 CompositeTileSurface::Clear => {
                     (TextureSource::Dummy, 0.0, ColorF::BLACK)
                 }
                 CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::TextureCache { texture, layer } } => {
                     (texture, layer as f32, ColorF::WHITE)
                 }
-                CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::NativeSurface { .. } } => {
+                CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::Native { .. } } => {
                     unreachable!("bug: found native surface in simple composite path");
                 }
             };
             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 {
@@ -5062,33 +5065,31 @@ impl Renderer {
         debug_assert!(self.texture_resolver.prev_pass_color.is_none());
     }
 
     fn update_native_surfaces(&mut self) {
         match self.compositor_config {
             CompositorConfig::Native { ref mut compositor, .. } => {
                 for op in self.pending_native_surface_updates.drain(..) {
                     match op.details {
-                        NativeSurfaceOperationDetails::CreateSurface { size, is_opaque } => {
-                            let _inserted = self.allocated_native_surfaces.insert(op.id);
+                        NativeSurfaceOperationDetails::CreateSurface { id, tile_size } => {
+                            let _inserted = self.allocated_native_surfaces.insert(id);
                             debug_assert!(_inserted, "bug: creating existing surface");
-
-                            compositor.create_surface(
-                                op.id,
-                                size,
-                                is_opaque,
-                            );
+                            compositor.create_surface(id, tile_size);
                         }
-                        NativeSurfaceOperationDetails::DestroySurface => {
-                            let _existed = self.allocated_native_surfaces.remove(&op.id);
+                        NativeSurfaceOperationDetails::DestroySurface { id } => {
+                            let _existed = self.allocated_native_surfaces.remove(&id);
                             debug_assert!(_existed, "bug: removing unknown surface");
-
-                            compositor.destroy_surface(
-                                op.id,
-                            );
+                            compositor.destroy_surface(id);
+                        }
+                        NativeSurfaceOperationDetails::CreateTile { id, is_opaque } => {
+                            compositor.create_tile(id, is_opaque);
+                        }
+                        NativeSurfaceOperationDetails::DestroyTile { id } => {
+                            compositor.destroy_tile(id);
                         }
                     }
                 }
             }
             CompositorConfig::Draw { .. } => {
                 // Ensure nothing is added in simple composite mode, since otherwise
                 // memory will leak as this doesn't get drained
                 debug_assert!(self.pending_native_surface_updates.is_empty());
@@ -5253,17 +5254,17 @@ impl Renderer {
                                         .expect("bug");
 
                                     DrawTarget::from_texture(
                                         texture,
                                         layer as usize,
                                         true,
                                     )
                                 }
-                                ResolvedSurfaceTexture::NativeSurface { id, size, .. } => {
+                                ResolvedSurfaceTexture::Native { id, size } => {
                                     let surface_info = match self.compositor_config {
                                         CompositorConfig::Native { ref mut compositor, .. } => {
                                             compositor.bind(id, picture_target.dirty_rect)
                                         }
                                         CompositorConfig::Draw { .. } => {
                                             unreachable!();
                                         }
                                     };
@@ -5290,17 +5291,17 @@ impl Renderer {
                                 draw_target,
                                 frame.content_origin,
                                 &projection,
                                 &frame.render_tasks,
                                 &mut results.stats,
                             );
 
                             // Native OS surfaces must be unbound at the end of drawing to them
-                            if let ResolvedSurfaceTexture::NativeSurface { .. } = picture_target.surface {
+                            if let ResolvedSurfaceTexture::Native { .. } = picture_target.surface {
                                 match self.compositor_config {
                                     CompositorConfig::Native { ref mut compositor, .. } => {
                                         compositor.unbind();
                                     }
                                     CompositorConfig::Draw { .. } => {
                                         unreachable!();
                                     }
                                 }
@@ -6769,27 +6770,20 @@ fn should_skip_batch(kind: &BatchKind, f
 
 impl CompositeState {
     /// Use the client provided native compositor interface to add all picture
     /// cache tiles to the OS compositor
     fn composite_native(
         &self,
         compositor: &mut dyn Compositor,
     ) {
-        // For each tile, update the properties with the native OS compositor,
-        // such as position and clip rect. z-order of the tiles are implicit based
-        // on the order they are added in this loop.
-        for tile in &self.native_tiles {
-            // Extract the native surface id. We should only ever encounter native surfaces here!
-            let id = match tile.surface {
-                CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::NativeSurface { id, .. }, .. } => id,
-                _ => unreachable!(),
-            };
-
-            // Add the tile to the OS compositor.
+        // Add each surface to the visual tree. z-order is implicit based on
+        // order added. Offset and clip rect apply to all tiles within this
+        // surface.
+        for surface in &self.descriptor.surfaces {
             compositor.add_surface(
-                id,
-                tile.rect.origin.to_i32(),
-                tile.clip_rect.to_i32(),
+                surface.surface_id.expect("bug: no native surface allocated"),
+                surface.offset.to_i32(),
+                surface.clip_rect.to_i32(),
             );
         }
     }
 }
--- a/gfx/wr/webrender/src/resource_cache.rs
+++ b/gfx/wr/webrender/src/resource_cache.rs
@@ -12,17 +12,17 @@ use api::{ImageData, ImageDescriptor, Im
 use api::{BlobImageData, BlobImageKey, MemoryReport, VoidPtrToSizeFn};
 use api::units::*;
 #[cfg(feature = "capture")]
 use crate::capture::ExternalCaptureImage;
 #[cfg(feature = "replay")]
 use crate::capture::PlainExternalImage;
 #[cfg(any(feature = "replay", feature = "png"))]
 use crate::capture::CaptureConfig;
-use crate::composite::{NativeSurfaceId, NativeSurfaceOperation, NativeSurfaceOperationDetails};
+use crate::composite::{NativeSurfaceId, NativeSurfaceOperation, NativeTileId, NativeSurfaceOperationDetails};
 use crate::device::TextureFilter;
 use euclid::{point2, size2};
 use crate::glyph_cache::GlyphCache;
 use crate::glyph_cache::GlyphCacheEntry;
 use crate::glyph_rasterizer::{GLYPH_FLASHING, BaseFontInstance, FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
 use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use crate::gpu_types::UvRectKind;
 use crate::image::{compute_tile_size, compute_tile_rect, compute_tile_range, for_each_tile_in_range};
@@ -1770,47 +1770,76 @@ impl ResourceCache {
                     UvRectKind::Rect,
                     eviction,
                 );
             }
         }
     }
 
     /// Queue up allocation of a new OS native compositor surface with the
-    /// specified id and dimensions.
+    /// specified tile size.
     pub fn create_compositor_surface(
         &mut self,
-        size: DeviceIntSize,
-        is_opaque: bool,
+        tile_size: DeviceIntSize,
     ) -> NativeSurfaceId {
         let id = NativeSurfaceId(NEXT_NATIVE_SURFACE_ID.fetch_add(1, Ordering::Relaxed));
 
         self.pending_native_surface_updates.push(
             NativeSurfaceOperation {
-                id,
                 details: NativeSurfaceOperationDetails::CreateSurface {
-                    size,
-                    is_opaque,
-                }
+                    id,
+                    tile_size,
+                },
             }
         );
 
         id
     }
 
     /// Queue up destruction of an existing native OS surface. This is used when
-    /// a picture cache tile is dropped or resized.
+    /// a picture cache surface is dropped or resized.
     pub fn destroy_compositor_surface(
         &mut self,
         id: NativeSurfaceId,
     ) {
         self.pending_native_surface_updates.push(
             NativeSurfaceOperation {
-                id,
-                details: NativeSurfaceOperationDetails::DestroySurface,
+                details: NativeSurfaceOperationDetails::DestroySurface {
+                    id,
+                }
+            }
+        );
+    }
+
+    /// Queue construction of a native compositor tile on a given surface.
+    pub fn create_compositor_tile(
+        &mut self,
+        id: NativeTileId,
+        is_opaque: bool,
+    ) {
+        self.pending_native_surface_updates.push(
+            NativeSurfaceOperation {
+                details: NativeSurfaceOperationDetails::CreateTile {
+                    id,
+                    is_opaque,
+                },
+            }
+        );
+    }
+
+    /// Queue destruction of a native compositor tile.
+    pub fn destroy_compositor_tile(
+        &mut self,
+        id: NativeTileId,
+    ) {
+        self.pending_native_surface_updates.push(
+            NativeSurfaceOperation {
+                details: NativeSurfaceOperationDetails::DestroyTile {
+                    id,
+                },
             }
         );
     }
 
     pub fn end_frame(&mut self, texture_cache_profile: &mut TextureCacheProfileCounters) {
         debug_assert_eq!(self.state, State::QueryResources);
         self.state = State::Idle;
         self.texture_cache.end_frame(texture_cache_profile);