Bug 1589718: Add a RenderedFrameId to RenderCompositor and use it to control release of textures. r=sotaro
authorBob Owen <bobowencode@gmail.com>
Mon, 04 Nov 2019 16:15:20 +0000
changeset 500403 fbe264cd2d8269c636924b4b036abd8804ea3164
parent 500402 c1d881c710970ec0dbeda9cc24bcf2a3a23ab435
child 500404 2fa5d33ca68a18ea49f6dea6a3611b5c719e4b48
push id36763
push userrmaries@mozilla.com
push dateMon, 04 Nov 2019 21:44:06 +0000
treeherdermozilla-central@75a7a3400888 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssotaro
bugs1589718
milestone72.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 1589718: Add a RenderedFrameId to RenderCompositor and use it to control release of textures. r=sotaro This replaces mUpdatesCount in AsyncImagePipelineManager, which was really how many times NotifyPipelinesUpdated was called with aRender == true. I think this makes the release logic clearer as it is more explicit. It also changes things for RenderCompositorANGLE, so that we check to see if any other frames have completed even if we don't want to wait for them. Differential Revision: https://phabricator.services.mozilla.com/D51064
gfx/layers/wr/AsyncImagePipelineManager.cpp
gfx/layers/wr/AsyncImagePipelineManager.h
gfx/webrender_bindings/RenderCompositor.h
gfx/webrender_bindings/RenderCompositorANGLE.cpp
gfx/webrender_bindings/RenderCompositorANGLE.h
gfx/webrender_bindings/RenderCompositorEGL.cpp
gfx/webrender_bindings/RenderCompositorEGL.h
gfx/webrender_bindings/RenderCompositorOGL.cpp
gfx/webrender_bindings/RenderCompositorOGL.h
gfx/webrender_bindings/RenderThread.cpp
gfx/webrender_bindings/RendererOGL.cpp
gfx/webrender_bindings/RendererOGL.h
gfx/webrender_bindings/WebRenderTypes.h
--- a/gfx/layers/wr/AsyncImagePipelineManager.cpp
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -1,16 +1,19 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "AsyncImagePipelineManager.h"
 
+#include <algorithm>
+#include <iterator>
+
 #include "CompositableHost.h"
 #include "gfxEnv.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/SharedSurfacesParent.h"
 #include "mozilla/layers/WebRenderImageHost.h"
 #include "mozilla/layers/WebRenderTextureHost.h"
 #include "mozilla/webrender/RenderThread.h"
@@ -28,35 +31,28 @@ AsyncImagePipelineManager::ForwardingExt
 AsyncImagePipelineManager::AsyncImagePipeline::AsyncImagePipeline()
     : mInitialised(false),
       mRenderRoot(wr::RenderRoot::Default),
       mIsChanged(false),
       mUseExternalImage(false),
       mFilter(wr::ImageRendering::Auto),
       mMixBlendMode(wr::MixBlendMode::Normal) {}
 
-AsyncImagePipelineManager::PipelineUpdates::PipelineUpdates(
-    RefPtr<wr::WebRenderPipelineInfo> aPipelineInfo,
-    const uint64_t aUpdatesCount, const bool aRendered)
-    : mPipelineInfo(aPipelineInfo),
-      mUpdatesCount(aUpdatesCount),
-      mRendered(aRendered) {}
-
 AsyncImagePipelineManager::AsyncImagePipelineManager(
     nsTArray<RefPtr<wr::WebRenderAPI>>&& aApis, bool aUseCompositorWnd)
     : mApis(aApis),
       mUseCompositorWnd(aUseCompositorWnd),
       mIdNamespace(mApis[0]->GetNamespace()),
       mUseTripleBuffering(mApis[0]->GetUseTripleBuffering()),
       mResourceId(0),
       mAsyncImageEpoch{0},
       mWillGenerateFrame{},
       mDestroyed(false),
-      mUpdatesLock("UpdatesLock"),
-      mUpdatesCount(0) {
+      mRenderSubmittedUpdatesLock("SubmittedUpdatesLock"),
+      mLastCompletedFrameId(0) {
   MOZ_COUNT_CTOR(AsyncImagePipelineManager);
 }
 
 AsyncImagePipelineManager::~AsyncImagePipelineManager() {
   MOZ_COUNT_DTOR(AsyncImagePipelineManager);
 }
 
 void AsyncImagePipelineManager::Destroy() {
@@ -492,18 +488,25 @@ void AsyncImagePipelineManager::HoldExte
   MOZ_ASSERT(aTexture);
 
   PipelineTexturesHolder* holder =
       mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
   MOZ_ASSERT(holder);
   if (!holder) {
     return;
   }
-  // Hold WebRenderTextureHost until end of its usage on RenderThread
-  holder->mTextureHosts.push(ForwardingTextureHost(aEpoch, aTexture));
+  if (aTexture->HasIntermediateBuffer()) {
+    // Hold WebRenderTextureHost until submitted for rendering if it has an
+    // intermediate buffer.
+    holder->mTextureHostsUntilRenderSubmitted.emplace_back(aEpoch, aTexture);
+  } else {
+    // Hold WebRenderTextureHost until rendering completed if not.
+    holder->mTextureHostsUntilRenderCompleted.emplace_back(
+        MakeUnique<ForwardingTextureHost>(aEpoch, aTexture));
+  }
 }
 
 void AsyncImagePipelineManager::HoldExternalImage(
     const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch,
     const wr::ExternalImageId& aImageId) {
   if (mDestroyed) {
     SharedSurfacesParent::Release(aImageId);
     return;
@@ -512,165 +515,180 @@ void AsyncImagePipelineManager::HoldExte
   PipelineTexturesHolder* holder =
       mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
   MOZ_ASSERT(holder);
   if (!holder) {
     SharedSurfacesParent::Release(aImageId);
     return;
   }
 
-  auto image = MakeUnique<ForwardingExternalImage>(aEpoch, aImageId);
-  holder->mExternalImages.push(std::move(image));
+  holder->mExternalImages.emplace_back(
+      MakeUnique<ForwardingExternalImage>(aEpoch, aImageId));
 }
 
 void AsyncImagePipelineManager::NotifyPipelinesUpdated(
-    RefPtr<wr::WebRenderPipelineInfo> aInfo, bool aRender) {
-  // This is called on the render thread, so we just stash the data into
-  // UpdatesQueue and process it later on the compositor thread.
+    RefPtr<wr::WebRenderPipelineInfo> aInfo, wr::RenderedFrameId aLatestFrameId,
+    wr::RenderedFrameId aLastCompletedFrameId) {
   MOZ_ASSERT(wr::RenderThread::IsInRenderThread());
+  MOZ_ASSERT(mLastCompletedFrameId <= aLastCompletedFrameId.mId);
 
-  // Increment the count when render happens.
-  uint64_t currCount = aRender ? ++mUpdatesCount : mUpdatesCount;
-  auto updates = MakeUnique<PipelineUpdates>(aInfo, currCount, aRender);
+  // This is called on the render thread, so we just stash the data into
+  // mPendingUpdates and process it later on the compositor thread.
+  mPendingUpdates.push_back(aInfo);
+
+  // If aLatestFrameId is valid then a render has been submitted.
+  if (aLatestFrameId.IsValid()) {
+    mLastCompletedFrameId = aLastCompletedFrameId.mId;
+
+    {
+      // We need to lock for mRenderSubmittedUpdates because it can be accessed
+      // on the compositor thread.
+      MutexAutoLock lock(mRenderSubmittedUpdatesLock);
 
-  {
-    // Scope lock to push UpdatesQueue to mUpdatesQueues.
-    MutexAutoLock lock(mUpdatesLock);
-    mUpdatesQueues.push(std::move(updates));
-  }
+      // Move the pending updates into the submitted ones. Note that this clears
+      // mPendingUpdates.
+      mRenderSubmittedUpdates.emplace_back(aLatestFrameId,
+                                           std::move(mPendingUpdates));
+    }
 
-  if (!aRender) {
-    // Do not post ProcessPipelineUpdate when rendering did not happen.
-    return;
+    // Queue a runnable on the compositor thread to process the updates.
+    // This will also call CheckForTextureHostsNotUsedByGPU.
+    layers::CompositorThreadHolder::Loop()->PostTask(
+        NewRunnableMethod("ProcessPipelineUpdates", this,
+                          &AsyncImagePipelineManager::ProcessPipelineUpdates));
+  } else if (mLastCompletedFrameId < aLastCompletedFrameId.mId) {
+    // We're not running ProcessPipelineUpdates but a later frame has completed,
+    // so queue a runnable on the compositor thread to check if any TextureHosts
+    // can be released.
+    mLastCompletedFrameId = aLastCompletedFrameId.mId;
+    layers::CompositorThreadHolder::Loop()->PostTask(NewRunnableMethod(
+        "CheckForTextureHostsNotUsedByGPU", this,
+        &AsyncImagePipelineManager::CheckForTextureHostsNotUsedByGPU));
   }
-
-  // Queue a runnable on the compositor thread to process the queue
-  layers::CompositorThreadHolder::Loop()->PostTask(
-      NewRunnableMethod("ProcessPipelineUpdates", this,
-                        &AsyncImagePipelineManager::ProcessPipelineUpdates));
 }
 
 void AsyncImagePipelineManager::ProcessPipelineUpdates() {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
 
   if (mDestroyed) {
     return;
   }
 
-  UniquePtr<PipelineUpdates> updates;
+  std::vector<std::pair<wr::RenderedFrameId, PipelineInfoVector>>
+      submittedUpdates;
+  {
+    // We need to lock for mRenderSubmittedUpdates because it can be accessed on
+    // the compositor thread.
+    MutexAutoLock lock(mRenderSubmittedUpdatesLock);
+    mRenderSubmittedUpdates.swap(submittedUpdates);
+  }
 
-  while (true) {
-    {
-      // Scope lock to extract UpdatesQueue from mUpdatesQueues.
-      MutexAutoLock lock(mUpdatesLock);
-      if (mUpdatesQueues.empty()) {
-        // No more PipelineUpdates to process for now.
-        break;
-      }
-      // Check if PipelineUpdates is ready to process.
-      uint64_t currCount = mUpdatesCount;
-      if (mUpdatesQueues.front()->NeedsToWait(currCount)) {
-        // PipelineUpdates is not ready for processing for now.
-        break;
+  // submittedUpdates is a vector of RenderedFrameIds paired with vectors of
+  // WebRenderPipelineInfo.
+  for (auto update : submittedUpdates) {
+    for (auto pipelineInfo : update.second) {
+      auto& info = pipelineInfo->Raw();
+
+      for (uintptr_t i = 0; i < info.epochs.length; i++) {
+        ProcessPipelineRendered(info.epochs.data[i].pipeline_id,
+                                info.epochs.data[i].epoch, update.first);
       }
-      updates = std::move(mUpdatesQueues.front());
-      mUpdatesQueues.pop();
-    }
-    MOZ_ASSERT(updates);
-
-    auto& info = updates->mPipelineInfo->Raw();
-
-    for (uintptr_t i = 0; i < info.epochs.length; i++) {
-      ProcessPipelineRendered(info.epochs.data[i].pipeline_id,
-                              info.epochs.data[i].epoch,
-                              updates->mUpdatesCount);
-    }
-    for (uintptr_t i = 0; i < info.removed_pipelines.length; i++) {
-      ProcessPipelineRemoved(info.removed_pipelines.data[i],
-                             updates->mUpdatesCount);
+      for (uintptr_t i = 0; i < info.removed_pipelines.length; i++) {
+        ProcessPipelineRemoved(info.removed_pipelines.data[i], update.first);
+      }
     }
   }
   CheckForTextureHostsNotUsedByGPU();
 }
 
 void AsyncImagePipelineManager::ProcessPipelineRendered(
     const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch,
-    const uint64_t aUpdatesCount) {
+    wr::RenderedFrameId aRenderedFrameId) {
   if (auto entry = mPipelineTexturesHolders.Lookup(wr::AsUint64(aPipelineId))) {
     PipelineTexturesHolder* holder = entry.Data();
-    // Release TextureHosts based on Epoch
-    while (!holder->mTextureHosts.empty()) {
-      if (aEpoch <= holder->mTextureHosts.front().mEpoch) {
-        break;
-      }
-      // Need to extend holding TextureHost if it is direct bounded texture.
-      HoldUntilNotUsedByGPU(holder->mTextureHosts.front().mTexture,
-                            aUpdatesCount);
-      holder->mTextureHosts.pop();
+    // For TextureHosts that can be released on render submission, using aEpoch
+    // find the first that we can't release and then release all prior to that.
+    auto firstSubmittedHostToKeep = std::find_if(
+        holder->mTextureHostsUntilRenderSubmitted.begin(),
+        holder->mTextureHostsUntilRenderSubmitted.end(),
+        [&aEpoch](const auto& entry) { return aEpoch <= entry.mEpoch; });
+    holder->mTextureHostsUntilRenderSubmitted.erase(
+        holder->mTextureHostsUntilRenderSubmitted.begin(),
+        firstSubmittedHostToKeep);
+
+    // For TextureHosts that need to wait until render completed, find the first
+    // that is later than aEpoch and then move all prior to that to
+    // mTexturesInUseByGPU paired with aRenderedFrameId.  These will be released
+    // once rendering has completed for aRenderedFrameId.
+    auto firstCompletedHostToKeep = std::find_if(
+        holder->mTextureHostsUntilRenderCompleted.begin(),
+        holder->mTextureHostsUntilRenderCompleted.end(),
+        [&aEpoch](const auto& entry) { return aEpoch <= entry->mEpoch; });
+    if (firstCompletedHostToKeep !=
+        holder->mTextureHostsUntilRenderCompleted.begin()) {
+      std::vector<UniquePtr<ForwardingTextureHost>> hostsUntilCompleted(
+          std::make_move_iterator(
+              holder->mTextureHostsUntilRenderCompleted.begin()),
+          std::make_move_iterator(firstCompletedHostToKeep));
+      mTexturesInUseByGPU.emplace_back(aRenderedFrameId,
+                                       std::move(hostsUntilCompleted));
+      holder->mTextureHostsUntilRenderCompleted.erase(
+          holder->mTextureHostsUntilRenderCompleted.begin(),
+          firstCompletedHostToKeep);
     }
-    while (!holder->mExternalImages.empty()) {
-      if (aEpoch <= holder->mExternalImages.front()->mEpoch) {
-        break;
-      }
-      holder->mExternalImages.pop();
-    }
+
+    // Using aEpoch, find the first external image that we can't release and
+    // then release all prior to that.
+    auto firstImageToKeep = std::find_if(
+        holder->mExternalImages.begin(), holder->mExternalImages.end(),
+        [&aEpoch](const auto& entry) { return aEpoch <= entry->mEpoch; });
+    holder->mExternalImages.erase(holder->mExternalImages.begin(),
+                                  firstImageToKeep);
   }
 }
 
 void AsyncImagePipelineManager::ProcessPipelineRemoved(
-    const wr::RemovedPipeline& aRemovedPipeline, const uint64_t aUpdatesCount) {
+    const wr::RemovedPipeline& aRemovedPipeline,
+    wr::RenderedFrameId aRenderedFrameId) {
   if (mDestroyed) {
     return;
   }
   if (auto entry = mPipelineTexturesHolders.Lookup(
           wr::AsUint64(aRemovedPipeline.pipeline_id))) {
     PipelineTexturesHolder* holder = entry.Data();
     if (holder->mDestroyedEpoch.isSome()) {
-      while (!holder->mTextureHosts.empty()) {
-        // Need to extend holding TextureHost if it is direct bounded texture.
-        HoldUntilNotUsedByGPU(holder->mTextureHosts.front().mTexture,
-                              aUpdatesCount);
-        holder->mTextureHosts.pop();
+      if (!holder->mTextureHostsUntilRenderCompleted.empty()) {
+        // Move all TextureHosts that must be held until render completed to
+        // mTexturesInUseByGPU paired with aRenderedFrameId.
+        mTexturesInUseByGPU.emplace_back(
+            aRenderedFrameId,
+            std::move(holder->mTextureHostsUntilRenderCompleted));
       }
-      // Remove Pipeline
+
+      // Remove Pipeline releasing all remaining TextureHosts and external
+      // images.
       entry.Remove();
     }
+
     // If mDestroyedEpoch contains nothing it means we reused the same pipeline
     // id (probably because we moved the tab to another window). In this case we
     // need to keep the holder.
   }
 }
 
-void AsyncImagePipelineManager::HoldUntilNotUsedByGPU(
-    const CompositableTextureHostRef& aTextureHost, uint64_t aUpdatesCount) {
-  MOZ_ASSERT(aTextureHost);
-
-  if (aTextureHost->HasIntermediateBuffer()) {
-    // If texutre is not direct binding texture, gpu has already finished using
-    // it. We could release it now.
-    return;
-  }
+void AsyncImagePipelineManager::CheckForTextureHostsNotUsedByGPU() {
+  uint64_t lastCompletedFrameId = mLastCompletedFrameId;
 
-  // When Triple buffer is used, we need wait one more WebRender rendering,
-  if (mUseTripleBuffering) {
-    ++aUpdatesCount;
-  }
-
-  mTexturesInUseByGPU.emplace(std::make_pair(aUpdatesCount, aTextureHost));
-}
-
-void AsyncImagePipelineManager::CheckForTextureHostsNotUsedByGPU() {
-  uint64_t currCount = mUpdatesCount;
-
-  while (!mTexturesInUseByGPU.empty()) {
-    if (currCount <= mTexturesInUseByGPU.front().first) {
-      break;
-    }
-    mTexturesInUseByGPU.pop();
-  }
+  // Find first entry after mLastCompletedFrameId and release all prior ones.
+  auto firstTexturesToKeep =
+      std::find_if(mTexturesInUseByGPU.begin(), mTexturesInUseByGPU.end(),
+                   [lastCompletedFrameId](const auto& entry) {
+                     return lastCompletedFrameId < entry.first.mId;
+                   });
+  mTexturesInUseByGPU.erase(mTexturesInUseByGPU.begin(), firstTexturesToKeep);
 }
 
 wr::Epoch AsyncImagePipelineManager::GetNextImageEpoch() {
   mAsyncImageEpoch.mHandle++;
   return mAsyncImageEpoch;
 }
 
 }  // namespace layers
--- a/gfx/layers/wr/AsyncImagePipelineManager.h
+++ b/gfx/layers/wr/AsyncImagePipelineManager.h
@@ -2,17 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H
 #define MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H
 
-#include <queue>
+#include <vector>
 
 #include "CompositableHost.h"
 #include "mozilla/gfx/Point.h"
 #include "mozilla/layers/TextureHost.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "nsClassHashtable.h"
@@ -55,23 +55,27 @@ class AsyncImagePipelineManager final {
 
   void HoldExternalImage(const wr::PipelineId& aPipelineId,
                          const wr::Epoch& aEpoch, TextureHost* aTexture);
   void HoldExternalImage(const wr::PipelineId& aPipelineId,
                          const wr::Epoch& aEpoch,
                          const wr::ExternalImageId& aImageId);
 
   // This is called from the Renderer thread to notify this class about the
-  // pipelines in the most recently completed render. A copy of the update
-  // information is put into mUpdatesQueue.
+  // pipelines in the most recently completed update.
+  // @param aInfo PipelineInfo for the update
+  // @param aLatestFrameId RenderedFrameId if a frame has been submitted for
+  //                       rendering, invalid if not
+  // @param aLastCompletedFrameId RenderedFrameId for the last completed frame
   void NotifyPipelinesUpdated(RefPtr<wr::WebRenderPipelineInfo> aInfo,
-                              bool aRender);
+                              wr::RenderedFrameId aLatestFrameId,
+                              wr::RenderedFrameId aLastCompletedFrameId);
 
-  // This is run on the compositor thread to process mUpdatesQueue. We make
-  // this a public entry point because we need to invoke it from other places.
+  // This is run on the compositor thread to process mRenderSubmittedUpdates. We
+  // make this public because we need to invoke it from other places.
   void ProcessPipelineUpdates();
 
   TimeStamp GetCompositionTime() const { return mCompositionTime; }
   void SetCompositionTime(TimeStamp aTimeStamp) {
     mCompositionTime = aTimeStamp;
     if (!mCompositionTime.IsNull() && !mCompositeUntilTime.IsNull() &&
         mCompositionTime >= mCompositeUntilTime) {
       mCompositeUntilTime = TimeStamp();
@@ -120,19 +124,19 @@ class AsyncImagePipelineManager final {
 
   void SetWillGenerateFrameAllRenderRoots();
   void SetWillGenerateFrame(wr::RenderRoot aRenderRoot);
   bool GetAndResetWillGenerateFrame(wr::RenderRoot aRenderRoot);
 
  private:
   void ProcessPipelineRendered(const wr::PipelineId& aPipelineId,
                                const wr::Epoch& aEpoch,
-                               const uint64_t aUpdatesCount);
+                               wr::RenderedFrameId aRenderedFrameId);
   void ProcessPipelineRemoved(const wr::RemovedPipeline& aRemovedPipeline,
-                              const uint64_t aUpdatesCount);
+                              wr::RenderedFrameId aRenderedFrameId);
 
   wr::Epoch GetNextImageEpoch();
   uint32_t GetNextResourceId() { return ++mResourceId; }
   wr::IdNamespace GetNamespace() { return mIdNamespace; }
   wr::ImageKey GenerateImageKey() {
     wr::ImageKey key;
     key.mNamespace = GetNamespace();
     key.mHandle = GetNextResourceId();
@@ -152,18 +156,23 @@ class AsyncImagePipelineManager final {
         : mEpoch(aEpoch), mImageId(aImageId) {}
     ~ForwardingExternalImage();
     wr::Epoch mEpoch;
     wr::ExternalImageId mImageId;
   };
 
   struct PipelineTexturesHolder {
     // Holds forwarding WebRenderTextureHosts.
-    std::queue<ForwardingTextureHost> mTextureHosts;
-    std::queue<UniquePtr<ForwardingExternalImage>> mExternalImages;
+    std::vector<ForwardingTextureHost> mTextureHostsUntilRenderSubmitted;
+    // TextureHosts that must be held until rendering has completed. UniquePtr
+    // is used to make the entries movable, ideally ForwardingTextureHost would
+    // be fully movable.
+    std::vector<UniquePtr<ForwardingTextureHost>>
+        mTextureHostsUntilRenderCompleted;
+    std::vector<UniquePtr<ForwardingExternalImage>> mExternalImages;
     Maybe<wr::Epoch> mDestroyedEpoch;
     WebRenderBridgeParent* MOZ_NON_OWNING_REF mWrBridge = nullptr;
   };
 
   struct AsyncImagePipeline {
     AsyncImagePipeline();
     void Update(const LayoutDeviceRect& aScBounds,
                 const gfx::Matrix4x4& aScTransform,
@@ -204,19 +213,16 @@ class AsyncImagePipelineManager final {
       const wr::Epoch& aEpoch, const wr::PipelineId& aPipelineId,
       AsyncImagePipeline* aPipeline, nsTArray<wr::ImageKey>& aKeys,
       wr::TransactionBuilder& aSceneBuilderTxn,
       wr::TransactionBuilder& aMaybeFastTxn);
   Maybe<TextureHost::ResourceUpdateOp> UpdateWithoutExternalImage(
       TextureHost* aTexture, wr::ImageKey aKey, TextureHost::ResourceUpdateOp,
       wr::TransactionBuilder& aTxn);
 
-  // If texture is direct binding texture, keep it until it is not used by GPU.
-  void HoldUntilNotUsedByGPU(const CompositableTextureHostRef& aTextureHost,
-                             uint64_t aUpdatesCount);
   void CheckForTextureHostsNotUsedByGPU();
 
   nsTArray<RefPtr<wr::WebRenderAPI>> mApis;
   bool mUseCompositorWnd;
 
   const wr::IdNamespace mIdNamespace;
   const bool mUseTripleBuffering;
   uint32_t mResourceId;
@@ -233,38 +239,29 @@ class AsyncImagePipelineManager final {
 
   // When nonnull, during rendering, some compositable indicated that it will
   // change its rendering at this time. In order not to miss it, we composite
   // on every vsync until this time occurs (this is the latest such time).
   TimeStamp mCompositeUntilTime;
 
   nsTArray<ImageCompositeNotificationInfo> mImageCompositeNotifications;
 
-  // The lock that protects mUpdatesQueue
-  Mutex mUpdatesLock;
-  // Used for checking if PipelineUpdates could be processed.
-  Atomic<uint64_t> mUpdatesCount;
-  struct PipelineUpdates {
-    PipelineUpdates(RefPtr<wr::WebRenderPipelineInfo> aPipelineInfo,
-                    const uint64_t aUpdatesCount, const bool aRendered);
-    bool NeedsToWait(const uint64_t aUpdatesCount) {
-      MOZ_ASSERT(mUpdatesCount <= aUpdatesCount);
-      if (mUpdatesCount == aUpdatesCount && !mRendered) {
-        // RenderTextureHosts related to this might be still used by GPU.
-        return true;
-      }
-      return false;
-    }
-    RefPtr<wr::WebRenderPipelineInfo> mPipelineInfo;
-    const uint64_t mUpdatesCount;
-    const bool mRendered;
-  };
-  std::queue<UniquePtr<PipelineUpdates>> mUpdatesQueues;
+  typedef std::vector<RefPtr<wr::WebRenderPipelineInfo>> PipelineInfoVector;
 
-  // Queue to store TextureHosts that might still be used by GPU.
-  std::queue<std::pair<uint64_t, CompositableTextureHostRef>>
+  // PipelineInfo updates to be processed once a render has been submitted.
+  // This is only accessed on the render thread, so does not need a lock.
+  PipelineInfoVector mPendingUpdates;
+  // PipelineInfo updates that have been submitted for rendering. This is
+  // accessed on render and compositor threads, so requires a Lock.
+  std::vector<std::pair<wr::RenderedFrameId, PipelineInfoVector>>
+      mRenderSubmittedUpdates;
+  Mutex mRenderSubmittedUpdatesLock;
+
+  Atomic<uint64_t> mLastCompletedFrameId;
+  std::vector<std::pair<wr::RenderedFrameId,
+                        std::vector<UniquePtr<ForwardingTextureHost>>>>
       mTexturesInUseByGPU;
 };
 
 }  // namespace layers
 }  // namespace mozilla
 
 #endif /* MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H */
--- a/gfx/webrender_bindings/RenderCompositor.h
+++ b/gfx/webrender_bindings/RenderCompositor.h
@@ -32,20 +32,32 @@ class RenderCompositor {
  public:
   static UniquePtr<RenderCompositor> Create(
       RefPtr<widget::CompositorWidget>&& aWidget);
 
   RenderCompositor(RefPtr<widget::CompositorWidget>&& aWidget);
   virtual ~RenderCompositor();
 
   virtual bool BeginFrame() = 0;
-  virtual void EndFrame(const FfiVec<DeviceIntRect>& aDirtyRects) = 0;
+
+  // Called to notify the RenderCompositor that all of the commands for a frame
+  // have been pushed to the queue.
+  // @return a RenderedFrameId for the frame
+  virtual RenderedFrameId EndFrame(
+      const FfiVec<DeviceIntRect>& aDirtyRects) = 0;
   // Returns false when waiting gpu tasks is failed.
   // It might happen when rendering context is lost.
   virtual bool WaitForGPU() { return true; }
+
+  // Check for and return the last completed frame.
+  // @return the last (highest) completed RenderedFrameId
+  virtual RenderedFrameId GetLastCompletedFrameId() {
+    return mLatestRenderFrameId.Prev();
+  }
+
   virtual void Pause() = 0;
   virtual bool Resume() = 0;
   // Called when WR rendering is skipped
   virtual void Update() {}
 
   virtual gl::GLContext* gl() const { return nullptr; }
 
   virtual bool MakeCurrent();
@@ -81,16 +93,23 @@ class RenderCompositor {
   // Interface for partial present
   virtual bool RequestFullRender() { return false; }
   virtual uint32_t GetMaxPartialPresentRects() { return 0; }
 
   // Whether the surface contents are flipped vertically
   virtual bool SurfaceIsYFlipped() { return false; }
 
  protected:
+  // We default this to 2, so that mLatestRenderFrameId.Prev() is always valid.
+  RenderedFrameId mLatestRenderFrameId = RenderedFrameId{2};
+  RenderedFrameId GetNextRenderFrameId() {
+    mLatestRenderFrameId = mLatestRenderFrameId.Next();
+    return mLatestRenderFrameId;
+  }
+
   RefPtr<widget::CompositorWidget> mWidget;
   RefPtr<layers::SyncObjectHost> mSyncObject;
 };
 
 }  // namespace wr
 }  // namespace mozilla
 
 #endif
--- a/gfx/webrender_bindings/RenderCompositorANGLE.cpp
+++ b/gfx/webrender_bindings/RenderCompositorANGLE.cpp
@@ -410,18 +410,20 @@ bool RenderCompositorANGLE::BeginFrame()
       // It's timeout or other error. Handle the device-reset here.
       RenderThread::Get()->HandleDeviceReset("SyncObject", /* aNotify */ true);
       return false;
     }
   }
   return true;
 }
 
-void RenderCompositorANGLE::EndFrame(const FfiVec<DeviceIntRect>& aDirtyRects) {
-  InsertPresentWaitQuery();
+RenderedFrameId RenderCompositorANGLE::EndFrame(
+    const FfiVec<DeviceIntRect>& aDirtyRects) {
+  RenderedFrameId frameId = GetNextRenderFrameId();
+  InsertPresentWaitQuery(frameId);
 
   if (!UseCompositor()) {
     if (mWidget->AsWindows()->HasFxrOutputHandler()) {
       // There is a Firefox Reality handler for this swapchain. Update this
       // window's contents to the VR window.
       FxROutputHandler* fxrHandler =
           mWidget->AsWindows()->GetFxrOutputHandler();
       if (fxrHandler->TryInitialize(mSwapChain, mDevice)) {
@@ -465,16 +467,18 @@ void RenderCompositorANGLE::EndFrame(con
     } else {
       mSwapChain->Present(0, 0);
     }
   }
 
   if (mDCLayerTree) {
     mDCLayerTree->MaybeUpdateDebug();
   }
+
+  return frameId;
 }
 
 bool RenderCompositorANGLE::WaitForGPU() {
   // Note: this waits on the query we inserted in the previous frame,
   // not the one we just inserted now. Example:
   //   Insert query #1
   //   Present #1
   //   (first frame, no wait)
@@ -642,45 +646,64 @@ RefPtr<ID3D11Query> RenderCompositorANGL
   HRESULT hr = mDevice->CreateQuery(&desc, getter_AddRefs(query));
   if (FAILED(hr) || !query) {
     gfxWarning() << "Could not create D3D11_QUERY_EVENT: " << gfx::hexa(hr);
     return nullptr;
   }
   return query;
 }
 
-void RenderCompositorANGLE::InsertPresentWaitQuery() {
+void RenderCompositorANGLE::InsertPresentWaitQuery(RenderedFrameId aFrameId) {
   RefPtr<ID3D11Query> query;
   query = GetD3D11Query();
   if (!query) {
     return;
   }
 
   mCtx->End(query);
-  mWaitForPresentQueries.emplace(query);
+  mWaitForPresentQueries.emplace(aFrameId, query);
 }
 
 bool RenderCompositorANGLE::WaitForPreviousPresentQuery() {
   size_t waitLatency = mUseTripleBuffering ? 3 : 2;
 
   while (mWaitForPresentQueries.size() >= waitLatency) {
-    RefPtr<ID3D11Query>& query = mWaitForPresentQueries.front();
+    auto queryPair = mWaitForPresentQueries.front();
     BOOL result;
-    bool ret = layers::WaitForFrameGPUQuery(mDevice, mCtx, query, &result);
+    bool ret =
+        layers::WaitForFrameGPUQuery(mDevice, mCtx, queryPair.second, &result);
+
+    if (!ret) {
+      mWaitForPresentQueries.pop();
+      return false;
+    }
 
     // Recycle query for later use.
-    mRecycledQuery = query;
+    mRecycledQuery = queryPair.second;
+    mLastCompletedFrameId = queryPair.first;
     mWaitForPresentQueries.pop();
-    if (!ret) {
-      return false;
-    }
   }
   return true;
 }
 
+RenderedFrameId RenderCompositorANGLE::GetLastCompletedFrameId() {
+  while (!mWaitForPresentQueries.empty()) {
+    auto queryPair = mWaitForPresentQueries.front();
+    if (mCtx->GetData(queryPair.second, nullptr, 0, 0) != S_OK) {
+      break;
+    }
+
+    mRecycledQuery = queryPair.second;
+    mLastCompletedFrameId = queryPair.first;
+    mWaitForPresentQueries.pop();
+  }
+
+  return mLastCompletedFrameId;
+}
+
 bool RenderCompositorANGLE::IsContextLost() {
   // XXX glGetGraphicsResetStatus sometimes did not work for detecting TDR.
   // Then this function just uses GetDeviceRemovedReason().
   if (mDevice->GetDeviceRemovedReason() != S_OK) {
     return true;
   }
   return false;
 }
--- a/gfx/webrender_bindings/RenderCompositorANGLE.h
+++ b/gfx/webrender_bindings/RenderCompositorANGLE.h
@@ -35,18 +35,19 @@ class RenderCompositorANGLE : public Ren
   static UniquePtr<RenderCompositor> Create(
       RefPtr<widget::CompositorWidget>&& aWidget);
 
   explicit RenderCompositorANGLE(RefPtr<widget::CompositorWidget>&& aWidget);
   virtual ~RenderCompositorANGLE();
   bool Initialize();
 
   bool BeginFrame() override;
-  void EndFrame(const FfiVec<DeviceIntRect>& aDirtyRects) override;
+  RenderedFrameId EndFrame(const FfiVec<DeviceIntRect>& aDirtyRects) final;
   bool WaitForGPU() override;
+  RenderedFrameId GetLastCompletedFrameId() final;
   void Pause() override;
   bool Resume() override;
   void Update() override;
 
   gl::GLContext* gl() const override { return RenderThread::Get()->SharedGL(); }
 
   bool MakeCurrent() override;
 
@@ -78,17 +79,17 @@ class RenderCompositorANGLE : public Ren
 
   // Interface for partial present
   bool RequestFullRender() override;
   uint32_t GetMaxPartialPresentRects() override;
 
  protected:
   bool UseCompositor();
   void InitializeUsePartialPresent();
-  void InsertPresentWaitQuery();
+  void InsertPresentWaitQuery(RenderedFrameId aRenderedFrameId);
   bool WaitForPreviousPresentQuery();
   bool ResizeBufferIfNeeded();
   bool CreateEGLSurface();
   void DestroyEGLSurface();
   ID3D11Device* GetDeviceOfEGLDisplay();
   void CreateSwapChainForDCompIfPossible(IDXGIFactory2* aDXGIFactory2);
   RefPtr<IDXGISwapChain1> CreateSwapChainForDComp(bool aUseTripleBuffering,
                                                   bool aUseAlpha);
@@ -103,18 +104,20 @@ class RenderCompositorANGLE : public Ren
 
   RefPtr<ID3D11Device> mDevice;
   RefPtr<ID3D11DeviceContext> mCtx;
   RefPtr<IDXGISwapChain> mSwapChain;
   RefPtr<IDXGISwapChain1> mSwapChain1;
 
   UniquePtr<DCLayerTree> mDCLayerTree;
 
-  std::queue<RefPtr<ID3D11Query>> mWaitForPresentQueries;
+  std::queue<std::pair<RenderedFrameId, RefPtr<ID3D11Query>>>
+      mWaitForPresentQueries;
   RefPtr<ID3D11Query> mRecycledQuery;
+  RenderedFrameId mLastCompletedFrameId;
 
   Maybe<LayoutDeviceIntSize> mBufferSize;
   bool mUsePartialPresent;
   bool mFullRender;
 };
 
 }  // namespace wr
 }  // namespace mozilla
--- a/gfx/webrender_bindings/RenderCompositorEGL.cpp
+++ b/gfx/webrender_bindings/RenderCompositorEGL.cpp
@@ -106,20 +106,23 @@ bool RenderCompositorEGL::BeginFrame() {
 #ifdef MOZ_WIDGET_ANDROID
   java::GeckoSurfaceTexture::DestroyUnused((int64_t)gl());
   gl()->MakeCurrent();  // DestroyUnused can change the current context!
 #endif
 
   return true;
 }
 
-void RenderCompositorEGL::EndFrame(const FfiVec<DeviceIntRect>& aDirtyRects) {
+RenderedFrameId RenderCompositorEGL::EndFrame(
+    const FfiVec<DeviceIntRect>& aDirtyRects) {
+  RenderedFrameId frameId = GetNextRenderFrameId();
   if (mEGLSurface != EGL_NO_SURFACE) {
     gl()->SwapBuffers();
   }
+  return frameId;
 }
 
 void RenderCompositorEGL::Pause() {
 #ifdef MOZ_WIDGET_ANDROID
   java::GeckoSurfaceTexture::DestroyUnused((int64_t)gl());
   java::GeckoSurfaceTexture::DetachAllFromGLContext((int64_t)gl());
   DestroyEGLSurface();
 #endif
--- a/gfx/webrender_bindings/RenderCompositorEGL.h
+++ b/gfx/webrender_bindings/RenderCompositorEGL.h
@@ -18,17 +18,17 @@ class RenderCompositorEGL : public Rende
  public:
   static UniquePtr<RenderCompositor> Create(
       RefPtr<widget::CompositorWidget> aWidget);
 
   explicit RenderCompositorEGL(RefPtr<widget::CompositorWidget> aWidget);
   virtual ~RenderCompositorEGL();
 
   bool BeginFrame() override;
-  void EndFrame(const FfiVec<DeviceIntRect>& aDirtyRects) override;
+  RenderedFrameId EndFrame(const FfiVec<DeviceIntRect>& aDirtyRects) final;
   void Pause() override;
   bool Resume() override;
 
   gl::GLContext* gl() const override;
 
   bool MakeCurrent() override;
 
   bool UseANGLE() const override { return false; }
--- a/gfx/webrender_bindings/RenderCompositorOGL.cpp
+++ b/gfx/webrender_bindings/RenderCompositorOGL.cpp
@@ -86,23 +86,27 @@ bool RenderCompositorOGL::BeginFrame() {
     mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, *fbo);
   } else {
     mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGL->GetDefaultFramebuffer());
   }
 
   return true;
 }
 
-void RenderCompositorOGL::EndFrame(const FfiVec<DeviceIntRect>& aDirtyRects) {
+RenderedFrameId RenderCompositorOGL::EndFrame(
+    const FfiVec<DeviceIntRect>& aDirtyRects) {
+  RenderedFrameId frameId = GetNextRenderFrameId();
   InsertFrameDoneSync();
   mGL->SwapBuffers();
 
   if (mNativeLayerForEntireWindow) {
     mNativeLayerForEntireWindow->NotifySurfaceReady();
   }
+
+  return frameId;
 }
 
 void RenderCompositorOGL::InsertFrameDoneSync() {
 #ifdef XP_MACOSX
   // Only do this on macOS.
   // On other platforms, SwapBuffers automatically applies back-pressure.
   if (StaticPrefs::gfx_core_animation_enabled_AtStartup()) {
     if (mThisFrameDoneSync) {
--- a/gfx/webrender_bindings/RenderCompositorOGL.h
+++ b/gfx/webrender_bindings/RenderCompositorOGL.h
@@ -23,17 +23,17 @@ class RenderCompositorOGL : public Rende
   static UniquePtr<RenderCompositor> Create(
       RefPtr<widget::CompositorWidget>&& aWidget);
 
   RenderCompositorOGL(RefPtr<gl::GLContext>&& aGL,
                       RefPtr<widget::CompositorWidget>&& aWidget);
   virtual ~RenderCompositorOGL();
 
   bool BeginFrame() override;
-  void EndFrame(const FfiVec<DeviceIntRect>& aDirtyRects) override;
+  RenderedFrameId EndFrame(const FfiVec<DeviceIntRect>& aDirtyRects) final;
   bool WaitForGPU() override;
   void Pause() override;
   bool Resume() override;
 
   gl::GLContext* gl() const override { return mGL; }
 
   bool UseANGLE() const override { return false; }
 
--- a/gfx/webrender_bindings/RenderThread.cpp
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -431,60 +431,63 @@ void RenderThread::UpdateAndRender(
   TimeStamp start = TimeStamp::Now();
 
   auto& renderer = it->second;
 
   layers::CompositorThreadHolder::Loop()->PostTask(
       NewRunnableFunction("NotifyDidStartRenderRunnable", &NotifyDidStartRender,
                           renderer->GetCompositorBridge()));
 
-  bool rendered = false;
+  wr::RenderedFrameId latestFrameId;
   RendererStats stats = {0};
   if (aRender) {
-    rendered = renderer->UpdateAndRender(
+    latestFrameId = renderer->UpdateAndRender(
         aReadbackSize, aReadbackFormat, aReadbackBuffer, aHadSlowFrame, &stats);
   } else {
     renderer->Update();
   }
   // Check graphics reset status even when rendering is skipped.
   renderer->CheckGraphicsResetStatus();
 
   TimeStamp end = TimeStamp::Now();
   RefPtr<WebRenderPipelineInfo> info = renderer->FlushPipelineInfo();
 
   layers::CompositorThreadHolder::Loop()->PostTask(
       NewRunnableFunction("NotifyDidRenderRunnable", &NotifyDidRender,
                           renderer->GetCompositorBridge(), info, aStartId,
                           aStartTime, start, end, aRender, stats));
 
-  if (rendered) {
+  if (latestFrameId.IsValid()) {
     auto recorderIt = mCompositionRecorders.find(aWindowId);
     if (recorderIt != mCompositionRecorders.end()) {
       recorderIt->second->MaybeRecordFrame(renderer->GetRenderer(), info.get());
     }
   }
 
-  if (rendered) {
+  if (latestFrameId.IsValid()) {
     // Wait for GPU after posting NotifyDidRender, since the wait is not
     // necessary for the NotifyDidRender.
     // The wait is necessary for Textures recycling of AsyncImagePipelineManager
     // and for avoiding GPU queue is filled with too much tasks.
     // WaitForGPU's implementation is different for each platform.
     renderer->WaitForGPU();
   }
 
+  RenderedFrameId lastCompletedFrameId = renderer->GetLastCompletedFrameId();
+
   RefPtr<layers::AsyncImagePipelineManager> pipelineMgr =
       renderer->GetCompositorBridge()->GetAsyncImagePipelineManager();
   // pipelineMgr should always be non-null here because it is only nulled out
   // after the WebRenderAPI instance for the CompositorBridgeParent is
   // destroyed, and that destruction blocks until the renderer thread has
   // removed the relevant renderer. And after that happens we should never reach
   // this code at all; it would bail out at the mRenderers.find check above.
   MOZ_ASSERT(pipelineMgr);
-  pipelineMgr->NotifyPipelinesUpdated(info, aRender);
+  pipelineMgr->NotifyPipelinesUpdated(info, latestFrameId,
+                                      lastCompletedFrameId);
 }
 
 void RenderThread::Pause(wr::WindowId aWindowId) {
   MOZ_ASSERT(IsInRenderThread());
 
   auto it = mRenderers.find(aWindowId);
   MOZ_ASSERT(it != mRenderers.end());
   if (it == mRenderers.end()) {
--- a/gfx/webrender_bindings/RendererOGL.cpp
+++ b/gfx/webrender_bindings/RendererOGL.cpp
@@ -101,73 +101,75 @@ void RendererOGL::Update() {
   }
 }
 
 static void DoNotifyWebRenderContextPurge(
     layers::CompositorBridgeParent* aBridge) {
   aBridge->NotifyWebRenderContextPurge();
 }
 
-bool RendererOGL::UpdateAndRender(const Maybe<gfx::IntSize>& aReadbackSize,
-                                  const Maybe<wr::ImageFormat>& aReadbackFormat,
-                                  const Maybe<Range<uint8_t>>& aReadbackBuffer,
-                                  bool aHadSlowFrame,
-                                  RendererStats* aOutStats) {
+RenderedFrameId RendererOGL::UpdateAndRender(
+    const Maybe<gfx::IntSize>& aReadbackSize,
+    const Maybe<wr::ImageFormat>& aReadbackFormat,
+    const Maybe<Range<uint8_t>>& aReadbackBuffer, bool aHadSlowFrame,
+    RendererStats* aOutStats) {
   mozilla::widget::WidgetRenderingContext widgetContext;
 
 #if defined(XP_MACOSX)
   widgetContext.mGL = mCompositor->gl();
 // TODO: we don't have a notion of compositor here.
 //#elif defined(MOZ_WIDGET_ANDROID)
 //  widgetContext.mCompositor = mCompositor;
 #endif
 
   if (!mCompositor->GetWidget()->PreRender(&widgetContext)) {
     // XXX This could cause oom in webrender since pending_texture_updates is
     // not handled. It needs to be addressed.
-    return false;
+    return RenderedFrameId();
+    ;
   }
   // XXX set clear color if MOZ_WIDGET_ANDROID is defined.
 
   if (!mCompositor->BeginFrame()) {
     if (mCompositor->IsContextLost()) {
       RenderThread::Get()->HandleDeviceReset("BeginFrame", /* aNotify */ true);
     }
     mCompositor->GetWidget()->PostRender(&widgetContext);
-    return false;
+    return RenderedFrameId();
+    ;
   }
 
   wr_renderer_update(mRenderer);
 
   if (mCompositor->RequestFullRender()) {
     wr_renderer_force_redraw(mRenderer);
   }
 
   auto size = mCompositor->GetBufferSize();
 
   AutoWrRenderResult result(wr_renderer_render(
       mRenderer, size.width, size.height, aHadSlowFrame, aOutStats));
   if (!result.Result()) {
     RenderThread::Get()->HandleWebRenderError(WebRenderError::RENDER);
     mCompositor->GetWidget()->PostRender(&widgetContext);
-    return false;
+    return RenderedFrameId();
   }
 
   if (aReadbackBuffer.isSome()) {
     MOZ_ASSERT(aReadbackSize.isSome());
     MOZ_ASSERT(aReadbackFormat.isSome());
     wr_renderer_readback(mRenderer, aReadbackSize.ref().width,
                          aReadbackSize.ref().height, aReadbackFormat.ref(),
                          &aReadbackBuffer.ref()[0],
                          aReadbackBuffer.ref().length());
   }
 
   mScreenshotGrabber.MaybeGrabScreenshot(mRenderer, size.ToUnknownSize());
 
-  mCompositor->EndFrame(result.DirtyRects());
+  RenderedFrameId frameId = mCompositor->EndFrame(result.DirtyRects());
 
   mCompositor->GetWidget()->PostRender(&widgetContext);
 
 #if defined(ENABLE_FRAME_LATENCY_LOG)
   if (mFrameStartTime) {
     uint32_t latencyMs =
         round((TimeStamp::Now() - mFrameStartTime).ToMilliseconds());
     printf_stderr("generate frame latencyMs latencyMs %d\n", latencyMs);
@@ -176,17 +178,17 @@ bool RendererOGL::UpdateAndRender(const 
   mFrameStartTime = TimeStamp();
 #endif
 
   mScreenshotGrabber.MaybeProcessQueue(mRenderer);
 
   // TODO: Flush pending actions such as texture deletions/unlocks and
   //       textureHosts recycling.
 
-  return true;
+  return frameId;
 }
 
 void RendererOGL::CheckGraphicsResetStatus() {
   if (!mCompositor || !mCompositor->gl()) {
     return;
   }
 
   gl::GLContext* gl = mCompositor->gl();
@@ -203,16 +205,20 @@ void RendererOGL::CheckGraphicsResetStat
 void RendererOGL::WaitForGPU() {
   if (!mCompositor->WaitForGPU()) {
     if (mCompositor->IsContextLost()) {
       RenderThread::Get()->HandleDeviceReset("WaitForGPU", /* aNotify */ true);
     }
   }
 }
 
+RenderedFrameId RendererOGL::GetLastCompletedFrameId() {
+  return mCompositor->GetLastCompletedFrameId();
+}
+
 void RendererOGL::Pause() { mCompositor->Pause(); }
 
 bool RendererOGL::Resume() { return mCompositor->Resume(); }
 
 layers::SyncObjectHost* RendererOGL::GetSyncObject() const {
   return mCompositor->GetSyncObject();
 }
 
--- a/gfx/webrender_bindings/RendererOGL.h
+++ b/gfx/webrender_bindings/RendererOGL.h
@@ -53,25 +53,28 @@ class RendererOGL {
 
  public:
   wr::WrExternalImageHandler GetExternalImageHandler();
 
   /// This can be called on the render thread only.
   void Update();
 
   /// This can be called on the render thread only.
-  bool UpdateAndRender(const Maybe<gfx::IntSize>& aReadbackSize,
-                       const Maybe<wr::ImageFormat>& aReadbackFormat,
-                       const Maybe<Range<uint8_t>>& aReadbackBuffer,
-                       bool aHadSlowFrame, RendererStats* aOutStats);
+  RenderedFrameId UpdateAndRender(const Maybe<gfx::IntSize>& aReadbackSize,
+                                  const Maybe<wr::ImageFormat>& aReadbackFormat,
+                                  const Maybe<Range<uint8_t>>& aReadbackBuffer,
+                                  bool aHadSlowFrame, RendererStats* aOutStats);
 
   /// This can be called on the render thread only.
   void WaitForGPU();
 
   /// This can be called on the render thread only.
+  RenderedFrameId GetLastCompletedFrameId();
+
+  /// This can be called on the render thread only.
   void SetProfilerEnabled(bool aEnabled);
 
   /// This can be called on the render thread only.
   void SetFrameStartTime(const TimeStamp& aTime);
 
   /// This can be called on the render thread only.
   ~RendererOGL();
 
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -40,16 +40,19 @@ typedef wr::WrWindowId WindowId;
 typedef wr::WrPipelineId PipelineId;
 typedef wr::WrDocumentId DocumentId;
 typedef wr::WrRemovedPipeline RemovedPipeline;
 typedef wr::WrImageKey ImageKey;
 typedef wr::WrFontKey FontKey;
 typedef wr::WrFontInstanceKey FontInstanceKey;
 typedef wr::WrEpoch Epoch;
 
+class RenderedFrameIdType {};
+typedef layers::BaseTransactionId<RenderedFrameIdType> RenderedFrameId;
+
 typedef mozilla::Maybe<mozilla::wr::IdNamespace> MaybeIdNamespace;
 typedef mozilla::Maybe<mozilla::wr::ImageMask> MaybeImageMask;
 typedef Maybe<ExternalImageId> MaybeExternalImageId;
 
 typedef Maybe<FontInstanceOptions> MaybeFontInstanceOptions;
 typedef Maybe<FontInstancePlatformOptions> MaybeFontInstancePlatformOptions;
 
 struct ExternalImageKeyPair {