Bug 1449982 - Hook up epoch-based task processing on the updater thread. r?botond,nical draft
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 10 Apr 2018 12:30:01 -0400
changeset 779791 e7842d8b554bdd2e9d1a37e8d8c5679d31099098
parent 779790 ec150cdf65042878ad83d4acd082e4fd1ec13de0
child 779792 678d8879488d996057af4549272df130fd6101f7
push id105878
push userkgupta@mozilla.com
push dateTue, 10 Apr 2018 16:32:25 +0000
reviewersbotond, nical
bugs1449982
milestone61.0a1
Bug 1449982 - Hook up epoch-based task processing on the updater thread. r?botond,nical This makes it so that we only process a UpdateHitTestingTree task (and any tasks queued after it) once we know that the corresponding scene has been built and swapped in by WebRender. This ensures that processing of APZ data stays in sync with the active scene in WR. The way we do this is by tracking the "epoch" (which is updated per WR/layers transaction) that the APZ messages are based on. Only once the scene for that transaction is swapped in do we process those messages. MozReview-Commit-ID: 2qGTSTeSqve
gfx/layers/apz/public/APZUpdater.h
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/APZCTreeManager.h
gfx/layers/apz/src/APZUpdater.cpp
gfx/layers/wr/WebRenderBridgeParent.cpp
gfx/layers/wr/WebRenderBridgeParent.h
--- a/gfx/layers/apz/public/APZUpdater.h
+++ b/gfx/layers/apz/public/APZUpdater.h
@@ -10,16 +10,17 @@
 #include <deque>
 #include <unordered_map>
 
 #include "base/platform_thread.h"   // for PlatformThreadId
 #include "LayersTypes.h"
 #include "mozilla/layers/APZTestData.h"
 #include "mozilla/layers/WebRenderScrollData.h"
 #include "mozilla/StaticMutex.h"
+#include "mozilla/webrender/WebRenderTypes.h"
 #include "nsThreadUtils.h"
 #include "Units.h"
 
 namespace mozilla {
 
 namespace wr {
 struct WrWindowId;
 } // namespace wr
@@ -49,35 +50,41 @@ public:
 
   /**
    * This function is invoked from rust on the scene builder thread when it
    * is created. It effectively tells the APZUpdater "the current thread is
    * the updater thread for this window id" and allows APZUpdater to remember
    * which thread it is.
    */
   static void SetUpdaterThread(const wr::WrWindowId& aWindowId);
+  static void PrepareForSceneSwap(const wr::WrWindowId& aWindowId);
+  static void CompleteSceneSwap(const wr::WrWindowId& aWindowId,
+                                wr::WrPipelineInfo* aInfo);
   static void ProcessPendingTasks(const wr::WrWindowId& aWindowId);
 
   void ClearTree();
   void UpdateFocusState(LayersId aRootLayerTreeId,
                         LayersId aOriginatingLayersId,
                         const FocusTarget& aFocusTarget);
   void UpdateHitTestingTree(LayersId aRootLayerTreeId,
                             Layer* aRoot,
                             bool aIsFirstPaint,
                             LayersId aOriginatingLayersId,
                             uint32_t aPaintSequenceNumber);
   /**
    * This should be called (in the WR-enabled case) when the compositor receives
    * a new WebRenderScrollData for a layers id. The |aScrollData| parameter is
-   * the scroll data for |aOriginatingLayersId|. This function will store
-   * the new scroll data and update the focus state and hit-testing tree.
+   * the scroll data for |aOriginatingLayersId| and |aEpoch| is the corresponding
+   * epoch for the transaction that transferred the scroll data. This function
+   * will store the new scroll data and update the focus state and hit-testing
+   * tree.
    */
   void UpdateScrollDataAndTreeState(LayersId aRootLayerTreeId,
                                     LayersId aOriginatingLayersId,
+                                    const wr::Epoch& aEpoch,
                                     WebRenderScrollData&& aScrollData);
 
   void NotifyLayerTreeAdopted(LayersId aLayersId,
                               const RefPtr<APZUpdater>& aOldUpdater);
   void NotifyLayerTreeRemoved(LayersId aLayersId);
 
   bool GetAPZTestData(LayersId aLayersId, APZTestData* aOutData);
 
@@ -121,25 +128,62 @@ public:
   void RunOnControllerThread(already_AddRefed<Runnable> aTask);
 
 protected:
   virtual ~APZUpdater();
 
   bool UsingWebRenderUpdaterThread() const;
   static already_AddRefed<APZUpdater> GetUpdater(const wr::WrWindowId& aWindowId);
 
+  bool IsQueueBlocked() const;
+  void ProcessQueue();
+
 private:
   RefPtr<APZCTreeManager> mApz;
 
   // Map from layers id to WebRenderScrollData. This can only be touched on
   // the updater thread.
   std::unordered_map<LayersId,
                      WebRenderScrollData,
                      LayersId::HashFn> mScrollData;
 
+  // Stores epoch state for a particular layers id. This structure is only
+  // accessed on the updater thread.
+  struct EpochState {
+    // The epoch for the most recent scroll data sent from the content side.
+    wr::Epoch mRequired;
+    // The epoch for the most recent scene built and swapped in on the WR side.
+    Maybe<wr::Epoch> mBuilt;
+    // True if and only if the layers id is the root layers id for the compositor
+    bool mIsRoot;
+
+    EpochState();
+
+    // Whether or not the state for this layers id is such that it blocks
+    // processing of queued tasks. This happens if the root layers id or any
+    // "visible" layers id has scroll data for an epoch newer than what has
+    // been built. A "visible" layers id is one that is attached to the full
+    // layer tree (i.e. there is a chain of reflayer items from the root layer
+    // tree to the relevant layer subtree. This is not always the case; for
+    // instance a content process may send the compositor layers for a document
+    // before the chrome has attached the remote iframe to the root document.
+    // Since WR only builds pipelines for "visible" layers ids, |mBuilt| being
+    // populated means that the layers id is "visible".
+    bool IsBlockingQueue() const;
+  };
+
+  // Map from layers id to epoch state. If any of the epoch states returns true
+  // for IsBlockingQueue(), that means we cannot yet process content-side data
+  // still in the task queue, because otherwise we would apply it before the
+  // scene swap for that scene has occurred.
+  // This data structure can only be touched on the updater thread.
+  std::unordered_map<LayersId,
+                     EpochState,
+                     LayersId::HashFn> mEpochData;
+
   // Used to manage the mapping from a WR window id to APZUpdater. These are only
   // used if WebRender is enabled. Both sWindowIdMap and mWindowId should only
   // be used while holding the sWindowIdLock.
   static StaticMutex sWindowIdLock;
   static std::unordered_map<uint64_t, APZUpdater*> sWindowIdMap;
   Maybe<wr::WrWindowId> mWindowId;
 
   // If WebRender and async scene building are enabled, this holds the thread id
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -3236,16 +3236,30 @@ APZCTreeManager::GetUpdater() const
 
 void
 APZCTreeManager::AssertOnUpdaterThread()
 {
   GetUpdater()->AssertOnUpdaterThread();
 }
 
 void
+APZCTreeManager::LockTree()
+{
+  AssertOnUpdaterThread();
+  mTreeLock.Lock();
+}
+
+void
+APZCTreeManager::UnlockTree()
+{
+  AssertOnUpdaterThread();
+  mTreeLock.Unlock();
+}
+
+void
 APZCTreeManager::SetDPI(float aDpiValue)
 {
   APZThreadUtils::AssertOnControllerThread();
   mDPI = aDpiValue;
 }
 
 float
 APZCTreeManager::GetDPI() const
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -539,16 +539,24 @@ public:
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~APZCTreeManager();
 
   APZSampler* GetSampler() const;
   APZUpdater* GetUpdater() const;
 
+  // We need to allow APZUpdater to lock and unlock this tree during a WR
+  // scene swap. We do this using private helpers to avoid exposing these
+  // functions to the world.
+private:
+  friend class APZUpdater;
+  void LockTree();
+  void UnlockTree();
+
   // Protected hooks for gtests subclass
   virtual AsyncPanZoomController* NewAPZCInstance(LayersId aLayersId,
                                                   GeckoContentController* aController);
 public:
   // Public hooks for gtests subclass
   virtual TimeStamp GetFrameTime();
 
 public:
--- a/gfx/layers/apz/src/APZUpdater.cpp
+++ b/gfx/layers/apz/src/APZUpdater.cpp
@@ -9,17 +9,16 @@
 #include "APZCTreeManager.h"
 #include "AsyncPanZoomController.h"
 #include "base/task.h"
 #include "mozilla/layers/APZThreadUtils.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/SynchronousTask.h"
 #include "mozilla/layers/WebRenderScrollDataWrapper.h"
 #include "mozilla/webrender/WebRenderAPI.h"
-#include "mozilla/webrender/WebRenderTypes.h"
 
 namespace mozilla {
 namespace layers {
 
 StaticMutex APZUpdater::sWindowIdLock;
 std::unordered_map<uint64_t, APZUpdater*> APZUpdater::sWindowIdMap;
 
 
@@ -66,24 +65,77 @@ APZUpdater::SetUpdaterThread(const wr::W
   if (RefPtr<APZUpdater> updater = GetUpdater(aWindowId)) {
     // Ensure nobody tried to use the updater thread before this point.
     MOZ_ASSERT(!updater->mUpdaterThreadQueried);
     updater->mUpdaterThreadId = Some(PlatformThread::CurrentId());
   }
 }
 
 /*static*/ void
+APZUpdater::PrepareForSceneSwap(const wr::WrWindowId& aWindowId)
+{
+  if (RefPtr<APZUpdater> updater = GetUpdater(aWindowId)) {
+    updater->mApz->LockTree();
+  }
+}
+
+/*static*/ void
+APZUpdater::CompleteSceneSwap(const wr::WrWindowId& aWindowId,
+                              wr::WrPipelineInfo* aInfo)
+{
+  RefPtr<APZUpdater> updater = GetUpdater(aWindowId);
+  if (!updater) {
+    // This should only happen in cases where PrepareForSceneSwap also got a
+    // null updater. No updater-thread tasks get run between PrepareForSceneSwap
+    // and this function, so there is no opportunity for the updater mapping
+    // to have gotten removed from sWindowIdMap in between the two calls.
+    return;
+  }
+
+  wr::WrPipelineId pipeline;
+  wr::WrEpoch epoch;
+  while (wr_pipeline_info_next_removed_pipeline(aInfo, &pipeline)) {
+    LayersId layersId = wr::AsLayersId(pipeline);
+    updater->mEpochData.erase(layersId);
+  }
+  // Reset the built info for all pipelines, then put it back for the ones
+  // that got built in this scene swap.
+  for (auto& i : updater->mEpochData) {
+    i.second.mBuilt = Nothing();
+  }
+  while (wr_pipeline_info_next_epoch(aInfo, &pipeline, &epoch)) {
+    LayersId layersId = wr::AsLayersId(pipeline);
+    updater->mEpochData[layersId].mBuilt = Some(epoch);
+  }
+  wr_pipeline_info_delete(aInfo);
+
+  // Run any tasks that got unblocked, then unlock the tree. The order is
+  // important because we want to run all the tasks up to and including the
+  // UpdateHitTestingTree calls corresponding to the built epochs, and we
+  // want to run those before we release the lock (i.e. atomically with the
+  // scene swap). This ensures that any hit-tests always encounter a consistent
+  // state between the APZ tree and the built scene in WR.
+  //
+  // While we could add additional information to the queued tasks to figure
+  // out the minimal set of tasks we want to run here, it's easier and harmless
+  // to just run all the queued and now-unblocked tasks inside the lock.
+  //
+  // Note that the ProcessQueue here might remove the window id -> APZUpdater
+  // mapping from sWindowIdMap, but we still unlock the tree successfully to
+  // leave things in a good state.
+  updater->ProcessQueue();
+
+  updater->mApz->UnlockTree();
+}
+
+/*static*/ void
 APZUpdater::ProcessPendingTasks(const wr::WrWindowId& aWindowId)
 {
   if (RefPtr<APZUpdater> updater = GetUpdater(aWindowId)) {
-    MutexAutoLock lock(updater->mQueueLock);
-    for (auto task : updater->mUpdaterQueue) {
-      task->Run();
-    }
-    updater->mUpdaterQueue.clear();
+    updater->ProcessQueue();
   }
 }
 
 void
 APZUpdater::ClearTree()
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   RefPtr<APZUpdater> self = this;
@@ -130,20 +182,35 @@ APZUpdater::UpdateHitTestingTree(LayersI
   AssertOnUpdaterThread();
   mApz->UpdateHitTestingTree(aRootLayerTreeId, aRoot, aIsFirstPaint,
       aOriginatingLayersId, aPaintSequenceNumber);
 }
 
 void
 APZUpdater::UpdateScrollDataAndTreeState(LayersId aRootLayerTreeId,
                                          LayersId aOriginatingLayersId,
+                                         const wr::Epoch& aEpoch,
                                          WebRenderScrollData&& aScrollData)
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   RefPtr<APZUpdater> self = this;
+  // Insert an epoch requirement update into the queue, so that
+  // tasks inserted into the queue after this point only get executed
+  // once the epoch requirement is satisfied. In particular, the
+  // UpdateHitTestingTree call below needs to wait until the epoch requirement
+  // is satisfied, which is why it is a separate task in the queue.
+  RunOnUpdaterThread(NS_NewRunnableFunction(
+    "APZUpdater::UpdateEpochRequirement",
+    [=]() {
+      if (aRootLayerTreeId == aOriginatingLayersId) {
+        self->mEpochData[aOriginatingLayersId].mIsRoot = true;
+      }
+      self->mEpochData[aOriginatingLayersId].mRequired = aEpoch;
+    }
+  ));
   RunOnUpdaterThread(NS_NewRunnableFunction(
     "APZUpdater::UpdateHitTestingTree",
     [=,aScrollData=Move(aScrollData)]() {
       self->mApz->UpdateFocusState(aRootLayerTreeId,
           aOriginatingLayersId, aScrollData.GetFocusTarget());
 
       self->mScrollData[aOriginatingLayersId] = aScrollData;
       auto root = self->mScrollData.find(aRootLayerTreeId);
@@ -174,16 +241,17 @@ APZUpdater::NotifyLayerTreeAdopted(Layer
 void
 APZUpdater::NotifyLayerTreeRemoved(LayersId aLayersId)
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   RefPtr<APZUpdater> self = this;
   RunOnUpdaterThread(NS_NewRunnableFunction(
     "APZUpdater::NotifyLayerTreeRemoved",
     [=]() {
+      self->mEpochData.erase(aLayersId);
       self->mScrollData.erase(aLayersId);
       self->mApz->NotifyLayerTreeRemoved(aLayersId);
     }
   ));
 }
 
 bool
 APZUpdater::GetAPZTestData(LayersId aLayersId,
@@ -353,36 +421,116 @@ APZUpdater::GetUpdater(const wr::WrWindo
   StaticMutexAutoLock lock(sWindowIdLock);
   auto it = sWindowIdMap.find(wr::AsUint64(aWindowId));
   if (it != sWindowIdMap.end()) {
     updater = it->second;
   }
   return updater.forget();
 }
 
+bool
+APZUpdater::IsQueueBlocked() const
+{
+  AssertOnUpdaterThread();
+
+  for (const auto& i : mEpochData) {
+    if (i.second.IsBlockingQueue()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void
+APZUpdater::ProcessQueue()
+{
+  { // scope lock to check for emptiness
+    MutexAutoLock lock(mQueueLock);
+    if (mUpdaterQueue.empty()) {
+      return;
+    }
+  }
+
+  // Note that running a task might update mEpochData, so we can't
+  // cache the result of IsQueueBlocked().
+  while (!IsQueueBlocked()) {
+    RefPtr<Runnable> task;
+
+    { // scope lock to extract a task
+      MutexAutoLock lock(mQueueLock);
+      if (mUpdaterQueue.empty()) {
+        break;
+      }
+      task = mUpdaterQueue.front();
+      mUpdaterQueue.pop_front();
+    }
+
+    task->Run();
+  }
+}
+
+APZUpdater::EpochState::EpochState()
+  : mRequired{0}
+  , mIsRoot(false)
+{
+}
+
+bool
+APZUpdater::EpochState::IsBlockingQueue() const
+{
+  // The root is a special case because we basically assume it is "visible"
+  // even before it is built for the first time. This is because building the
+  // scene automatically makes it visible, and we need to make sure the APZ
+  // scroll data gets applied atomically with that happening.
+  //
+  // Layer subtrees on the other hand do not automatically become visible upon
+  // being built, because there must be a another layer tree update to change
+  // the visibility (i.e. an ancestor layer tree update that adds the necessary
+  // reflayer to complete the chain of reflayers).
+  //
+  // So in the case of non-visible subtrees, we know that no hit-test will
+  // actually end up hitting that subtree either before or after the scene swap,
+  // because the subtree will remain non-visible. That in turns means that we
+  // can apply the APZ scroll data for that subtree epoch before the scene is
+  // built, because it's not going to get used anyway. And that means we don't
+  // need to block the queue for non-visible subtrees. Which is a good thing,
+  // because in practice it seems like we often have non-visible subtrees sent
+  // to the compositor from content.
+  if (mIsRoot && !mBuilt) {
+    return true;
+  }
+  return mBuilt && (*mBuilt < mRequired);
+}
+
 } // namespace layers
 } // namespace mozilla
 
 // Rust callback implementations
 
 void
 apz_register_updater(mozilla::wr::WrWindowId aWindowId)
 {
   mozilla::layers::APZUpdater::SetUpdaterThread(aWindowId);
 }
 
 void
 apz_pre_scene_swap(mozilla::wr::WrWindowId aWindowId)
 {
+  // This should never get called unless async scene building is enabled.
+  MOZ_ASSERT(gfxPrefs::WebRenderAsyncSceneBuild());
+  mozilla::layers::APZUpdater::PrepareForSceneSwap(aWindowId);
 }
 
 void
 apz_post_scene_swap(mozilla::wr::WrWindowId aWindowId,
                     mozilla::wr::WrPipelineInfo* aInfo)
 {
+  // This should never get called unless async scene building is enabled.
+  MOZ_ASSERT(gfxPrefs::WebRenderAsyncSceneBuild());
+  mozilla::layers::APZUpdater::CompleteSceneSwap(aWindowId, aInfo);
 }
 
 void
 apz_run_updater(mozilla::wr::WrWindowId aWindowId)
 {
   // This should never get called unless async scene building is enabled.
   MOZ_ASSERT(gfxPrefs::WebRenderAsyncSceneBuild());
   mozilla::layers::APZUpdater::ProcessPendingTasks(aWindowId);
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -514,25 +514,26 @@ WebRenderBridgeParent::UpdateAPZFocusSta
   }
   LayersId rootLayersId = cbp->RootLayerTreeId();
   if (RefPtr<APZUpdater> apz = cbp->GetAPZUpdater()) {
     apz->UpdateFocusState(rootLayersId, GetLayersId(), aFocus);
   }
 }
 
 void
-WebRenderBridgeParent::UpdateAPZScrollData(WebRenderScrollData&& aData)
+WebRenderBridgeParent::UpdateAPZScrollData(const wr::Epoch& aEpoch,
+                                           WebRenderScrollData&& aData)
 {
   CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
   if (!cbp) {
     return;
   }
   LayersId rootLayersId = cbp->RootLayerTreeId();
   if (RefPtr<APZUpdater> apz = cbp->GetAPZUpdater()) {
-    apz->UpdateScrollDataAndTreeState(rootLayersId, GetLayersId(), Move(aData));
+    apz->UpdateScrollDataAndTreeState(rootLayersId, GetLayersId(), aEpoch, Move(aData));
   }
 }
 
 bool
 WebRenderBridgeParent::PushAPZStateToWR(wr::TransactionBuilder& aTxn,
                                         nsTArray<wr::WrTransformProperty>& aTransformArray)
 {
   CompositorBridgeParent* cbp = GetRootCompositorBridgeParent();
@@ -592,16 +593,25 @@ WebRenderBridgeParent::RecvSetDisplayLis
 
   wr::TransactionBuilder txn;
   if (!UpdateResources(aResourceUpdates, aSmallShmems, aLargeShmems, txn)) {
     return IPC_FAIL(this, "Failed to deserialize resource updates");
   }
 
   mReceivedDisplayList = true;
 
+  // aScrollData is moved into this function but that is not reflected by the
+  // function signature due to the way the IPDL generator works. We remove the
+  // const so that we can move this structure all the way to the desired
+  // destination.
+  // Also note that this needs to happen before the display list transaction is
+  // sent to WebRender, so that the UpdateHitTestingTree call is guaranteed to
+  // be in the updater queue at the time that the scene swap completes.
+  UpdateAPZScrollData(wrEpoch, Move(const_cast<WebRenderScrollData&>(aScrollData)));
+
   wr::Vec<uint8_t> dlData(Move(dl));
 
   // If id namespaces do not match, it means the command is obsolete, probably
   // because the tab just moved to a new window.
   // In that case do not send the commands to webrender.
   if (mIdNamespace == aIdNamespace) {
     if (mWidget) {
       LayoutDeviceIntSize widgetSize = mWidget->GetClientSize();
@@ -619,22 +629,16 @@ WebRenderBridgeParent::RecvSetDisplayLis
 
     if (ShouldParentObserveEpoch()) {
       mCompositorBridge->ObserveLayerUpdate(GetLayersId(), GetChildLayerObserverEpoch(), true);
     }
   }
 
   HoldPendingTransactionId(wrEpoch, aTransactionId, aTxnStartTime, aFwdTime);
 
-  // aScrollData is moved into this function but that is not reflected by the
-  // function signature due to the way the IPDL generator works. We remove the
-  // const so that we can move this structure all the way to the desired
-  // destination.
-  UpdateAPZScrollData(Move(const_cast<WebRenderScrollData&>(aScrollData)));
-
   if (mIdNamespace != aIdNamespace) {
     // Pretend we composited since someone is wating for this event,
     // though DisplayList was not pushed to webrender.
     TimeStamp now = TimeStamp::Now();
     mCompositorBridge->DidComposite(GetLayersId(), now, now);
   }
 
   wr::IpcResourceUpdateQueue::ReleaseShmems(this, aSmallShmems);
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -165,19 +165,16 @@ public:
 
   void ExtractImageCompositeNotifications(nsTArray<ImageCompositeNotificationInfo>* aNotifications);
 
   wr::IdNamespace GetIdNamespace()
   {
     return mIdNamespace;
   }
 
-  void UpdateAPZFocusState(const FocusTarget& aFocus);
-  void UpdateAPZScrollData(WebRenderScrollData&& aData);
-
   void FlushRendering();
   void FlushRenderingAsync();
 
   /**
    * Schedule generating WebRender frame definitely at next composite timing.
    *
    * WebRenderBridgeParent uses composite timing to check if there is an update
    * to AsyncImagePipelines. If there is no update, WebRenderBridgeParent skips
@@ -193,16 +190,19 @@ public:
                        wr::WebRenderAPI* aApi,
                        AsyncImagePipelineManager* aImageMgr,
                        CompositorAnimationStorage* aAnimStorage);
 
 private:
   explicit WebRenderBridgeParent(const wr::PipelineId& aPipelineId);
   virtual ~WebRenderBridgeParent();
 
+  void UpdateAPZFocusState(const FocusTarget& aFocus);
+  void UpdateAPZScrollData(const wr::Epoch& aEpoch, WebRenderScrollData&& aData);
+
   bool UpdateResources(const nsTArray<OpUpdateResource>& aResourceUpdates,
                        const nsTArray<RefCountedShmem>& aSmallShmems,
                        const nsTArray<ipc::Shmem>& aLargeShmems,
                        wr::TransactionBuilder& aUpdates);
   bool AddExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey,
                         wr::TransactionBuilder& aResources);
 
   LayersId GetLayersId() const;