[Impeller] Wait for the previous AHB texture to be fully recyclable. (#52588)

The Android surface transaction completion handler documentation states:

```
Buffers which are replaced or removed from the scene in the transaction
invoking this callback may be reused after this point.
```

However, this is NOT sufficient. One also needs to be obtain the previous release fence and perform a wait for the buffer to be safely reusable.

This patch adds a GPU side wait for this fence using available extensions.

Fixes https://github.com/flutter/flutter/issues/147758
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index 90536fb..ac492bd 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -40895,6 +40895,8 @@
 ORIGIN: ../../../flutter/impeller/toolkit/android/surface_control.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/toolkit/android/surface_transaction.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/toolkit/android/surface_transaction.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/toolkit/android/surface_transaction_stats.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/toolkit/android/surface_transaction_stats.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/toolkit/egl/config.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/toolkit/egl/config.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/toolkit/egl/context.cc + ../../../flutter/LICENSE
@@ -43771,6 +43773,8 @@
 FILE: ../../../flutter/impeller/toolkit/android/surface_control.h
 FILE: ../../../flutter/impeller/toolkit/android/surface_transaction.cc
 FILE: ../../../flutter/impeller/toolkit/android/surface_transaction.h
+FILE: ../../../flutter/impeller/toolkit/android/surface_transaction_stats.cc
+FILE: ../../../flutter/impeller/toolkit/android/surface_transaction_stats.h
 FILE: ../../../flutter/impeller/toolkit/egl/config.cc
 FILE: ../../../flutter/impeller/toolkit/egl/config.h
 FILE: ../../../flutter/impeller/toolkit/egl/context.cc
diff --git a/impeller/renderer/backend/vulkan/capabilities_vk.cc b/impeller/renderer/backend/vulkan/capabilities_vk.cc
index 0b79dba..a052954 100644
--- a/impeller/renderer/backend/vulkan/capabilities_vk.cc
+++ b/impeller/renderer/backend/vulkan/capabilities_vk.cc
@@ -183,6 +183,10 @@
       return VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME;
     case RequiredAndroidDeviceExtensionVK::kKHRExternalFence:
       return VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME;
+    case RequiredAndroidDeviceExtensionVK::kKHRExternalSemaphoreFd:
+      return VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME;
+    case RequiredAndroidDeviceExtensionVK::kKHRExternalSemaphore:
+      return VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME;
     case RequiredAndroidDeviceExtensionVK::kLast:
       return "Unknown";
   }
diff --git a/impeller/renderer/backend/vulkan/capabilities_vk.h b/impeller/renderer/backend/vulkan/capabilities_vk.h
index 0a38d7f..d1e327b6 100644
--- a/impeller/renderer/backend/vulkan/capabilities_vk.h
+++ b/impeller/renderer/backend/vulkan/capabilities_vk.h
@@ -92,6 +92,19 @@
   ///
   kKHRExternalFence,
 
+  //----------------------------------------------------------------------------
+  /// For importing sync file descriptors as semaphores so the GPU can wait for
+  /// semaphore to be signaled.
+  ///
+  /// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_external_semaphore_fd.html
+  kKHRExternalSemaphoreFd,
+
+  //----------------------------------------------------------------------------
+  /// Dependency of kKHRExternalSemaphoreFd
+  ///
+  /// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_external_semaphore.html
+  kKHRExternalSemaphore,
+
   kLast,
 };
 
diff --git a/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_swapchain_impl_vk.cc b/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_swapchain_impl_vk.cc
index 27e5b62..9cac3d9 100644
--- a/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_swapchain_impl_vk.cc
+++ b/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_swapchain_impl_vk.cc
@@ -9,10 +9,12 @@
 #include "impeller/renderer/backend/vulkan/barrier_vk.h"
 #include "impeller/renderer/backend/vulkan/command_buffer_vk.h"
 #include "impeller/renderer/backend/vulkan/command_encoder_vk.h"
+#include "impeller/renderer/backend/vulkan/fence_waiter_vk.h"
 #include "impeller/renderer/backend/vulkan/gpu_tracer_vk.h"
 #include "impeller/renderer/backend/vulkan/swapchain/ahb/ahb_formats.h"
 #include "impeller/renderer/backend/vulkan/swapchain/surface_vk.h"
 #include "impeller/toolkit/android/surface_transaction.h"
+#include "impeller/toolkit/android/surface_transaction_stats.h"
 
 namespace impeller {
 
@@ -96,9 +98,9 @@
     return nullptr;
   }
 
-  auto texture = pool_->Pop();
+  auto pool_entry = pool_->Pop();
 
-  if (!texture) {
+  if (!pool_entry.IsValid()) {
     VALIDATION_LOG << "Could not create AHB texture source.";
     return nullptr;
   }
@@ -108,9 +110,19 @@
     ContextVK::Cast(*context).GetGPUTracer()->MarkFrameStart();
   }
 
+  // Ask the GPU to wait for the render ready semaphore to be signaled before
+  // performing rendering operations.
+  if (!SubmitWaitForRenderReady(pool_entry.render_ready_fence,
+                                pool_entry.texture)) {
+    VALIDATION_LOG << "Could not submit a command to the GPU to wait on render "
+                      "readiness.";
+    return nullptr;
+  }
+
   auto surface = SurfaceVK::WrapSwapchainImage(
-      transients_, texture,
-      [signaler = auto_sema_signaler, weak = weak_from_this(), texture]() {
+      transients_, pool_entry.texture,
+      [signaler = auto_sema_signaler, weak = weak_from_this(),
+       texture = pool_entry.texture]() {
         auto thiz = weak.lock();
         if (!thiz) {
           VALIDATION_LOG << "Swapchain died before image could be presented.";
@@ -145,7 +157,7 @@
     return false;
   }
 
-  auto fence = SubmitCompletionSignal(texture);
+  auto fence = SubmitSignalForPresentReady(texture);
 
   if (!fence) {
     VALIDATION_LOG << "Could not submit completion signal.";
@@ -161,16 +173,18 @@
                       "control.";
     return false;
   }
-  return transaction.Apply([signaler, texture, weak = weak_from_this()]() {
+  return transaction.Apply([signaler, texture, weak = weak_from_this()](
+                               ASurfaceTransactionStats* stats) {
     auto thiz = weak.lock();
     if (!thiz) {
       return;
     }
-    thiz->OnTextureSetOnSurfaceControl(signaler, texture);
+    thiz->OnTextureUpdatedOnSurfaceControl(signaler, texture, stats);
   });
 }
 
-std::shared_ptr<ExternalFenceVK> AHBSwapchainImplVK::SubmitCompletionSignal(
+std::shared_ptr<ExternalFenceVK>
+AHBSwapchainImplVK::SubmitSignalForPresentReady(
     const std::shared_ptr<AHBTextureSourceVK>& texture) const {
   auto context = transients_->GetContext().lock();
   if (!context) {
@@ -185,7 +199,7 @@
   if (!command_buffer) {
     return nullptr;
   }
-  command_buffer->SetLabel("AHBPresentCommandBuffer");
+  command_buffer->SetLabel("AHBSubmitSignalForPresentReadyCB");
   const auto& encoder = CommandBufferVK::Cast(*command_buffer).GetEncoder();
 
   const auto command_encoder_vk = encoder->GetCommandBuffer();
@@ -219,17 +233,148 @@
   return fence;
 }
 
-void AHBSwapchainImplVK::OnTextureSetOnSurfaceControl(
+vk::UniqueSemaphore AHBSwapchainImplVK::CreateRenderReadySemaphore(
+    const std::shared_ptr<fml::UniqueFD>& fd) const {
+  if (!fd->is_valid()) {
+    return {};
+  }
+
+  auto context = transients_->GetContext().lock();
+  if (!context) {
+    return {};
+  }
+
+  const auto& context_vk = ContextVK::Cast(*context);
+
+  const auto& device = context_vk.GetDevice();
+
+  auto signal_wait = device.createSemaphoreUnique({});
+
+  if (signal_wait.result != vk::Result::eSuccess) {
+    return {};
+  }
+
+  context_vk.SetDebugName(*signal_wait.value, "AHBRenderReadySemaphore");
+
+  vk::ImportSemaphoreFdInfoKHR import_info;
+  import_info.semaphore = *signal_wait.value;
+  import_info.fd = fd->get();
+  import_info.handleType = vk::ExternalSemaphoreHandleTypeFlagBits::eSyncFd;
+  // From the spec: Sync FDs can only be imported temporarily.
+  import_info.flags = vk::SemaphoreImportFlagBitsKHR::eTemporary;
+
+  const auto import_result = device.importSemaphoreFdKHR(import_info);
+
+  if (import_result != vk::Result::eSuccess) {
+    VALIDATION_LOG << "Could not import semaphore FD: "
+                   << vk::to_string(import_result);
+    return {};
+  }
+
+  // From the spec: Importing a semaphore payload from a file descriptor
+  // transfers ownership of the file descriptor from the application to the
+  // Vulkan implementation. The application must not perform any operations on
+  // the file descriptor after a successful import.
+  [[maybe_unused]] auto released = fd->release();
+
+  return std::move(signal_wait.value);
+}
+
+bool AHBSwapchainImplVK::SubmitWaitForRenderReady(
+    const std::shared_ptr<fml::UniqueFD>& render_ready_fence,
+    const std::shared_ptr<AHBTextureSourceVK>& texture) const {
+  // If there is no render ready fence, we are already ready to render into
+  // the texture. There is nothing more to do.
+  if (!render_ready_fence || !render_ready_fence->is_valid()) {
+    return true;
+  }
+
+  auto context = transients_->GetContext().lock();
+  if (!context) {
+    return false;
+  }
+
+  auto completion_fence =
+      ContextVK::Cast(*context).GetDevice().createFenceUnique({}).value;
+  if (!completion_fence) {
+    return false;
+  }
+
+  auto command_buffer = context->CreateCommandBuffer();
+  if (!command_buffer) {
+    return false;
+  }
+  command_buffer->SetLabel("AHBSubmitWaitForRenderReadyCB");
+  const auto& encoder = CommandBufferVK::Cast(*command_buffer).GetEncoder();
+
+  const auto command_buffer_vk = encoder->GetCommandBuffer();
+
+  BarrierVK barrier;
+  barrier.cmd_buffer = command_buffer_vk;
+  barrier.new_layout = vk::ImageLayout::eColorAttachmentOptimal;
+  barrier.src_stage = vk::PipelineStageFlagBits::eBottomOfPipe;
+  barrier.src_access = {};
+  barrier.dst_stage = vk::PipelineStageFlagBits::eTopOfPipe;
+  barrier.dst_access = {};
+
+  if (!texture->SetLayout(barrier).ok()) {
+    return false;
+  }
+
+  auto render_ready_semaphore =
+      MakeSharedVK(CreateRenderReadySemaphore(render_ready_fence));
+  encoder->Track(render_ready_semaphore);
+
+  if (!encoder->EndCommandBuffer()) {
+    return false;
+  }
+
+  vk::SubmitInfo submit_info;
+
+  if (render_ready_semaphore) {
+    constexpr const auto kWaitStages =
+        vk::PipelineStageFlagBits::eColorAttachmentOutput |
+        vk::PipelineStageFlagBits::eFragmentShader |
+        vk::PipelineStageFlagBits::eTransfer;
+    submit_info.setWaitSemaphores(render_ready_semaphore->Get());
+    submit_info.setWaitDstStageMask(kWaitStages);
+  }
+
+  submit_info.setCommandBuffers(command_buffer_vk);
+
+  auto result = ContextVK::Cast(*context).GetGraphicsQueue()->Submit(
+      submit_info, *completion_fence);
+  if (result != vk::Result::eSuccess) {
+    return false;
+  }
+
+  ContextVK::Cast(*context).GetFenceWaiter()->AddFence(
+      std::move(completion_fence), [encoder]() {});
+
+  return true;
+}
+
+void AHBSwapchainImplVK::OnTextureUpdatedOnSurfaceControl(
     const AutoSemaSignaler& signaler,
-    std::shared_ptr<AHBTextureSourceVK> texture) {
-  signaler->Reset();
+    std::shared_ptr<AHBTextureSourceVK> texture,
+    ASurfaceTransactionStats* stats) {
+  auto control = surface_control_.lock();
+  if (!control) {
+    return;
+  }
+
+  // Ask for an FD that gets signaled when the previous buffer is released. This
+  // can be invalid if there is no wait necessary.
+  auto render_ready_fence =
+      android::CreatePreviousReleaseFence(*control, stats);
+
   // The transaction completion indicates that the surface control now
   // references the hardware buffer. We can recycle the previous set buffer
   // safely.
   Lock lock(currently_displayed_texture_mutex_);
   auto old_texture = currently_displayed_texture_;
   currently_displayed_texture_ = std::move(texture);
-  pool_->Push(std::move(old_texture));
+  pool_->Push(std::move(old_texture), std::move(render_ready_fence));
 }
 
 }  // namespace impeller
diff --git a/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_swapchain_impl_vk.h b/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_swapchain_impl_vk.h
index 82d0b96..f865efc 100644
--- a/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_swapchain_impl_vk.h
+++ b/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_swapchain_impl_vk.h
@@ -112,12 +112,20 @@
   bool Present(const AutoSemaSignaler& signaler,
                const std::shared_ptr<AHBTextureSourceVK>& texture);
 
-  std::shared_ptr<ExternalFenceVK> SubmitCompletionSignal(
+  vk::UniqueSemaphore CreateRenderReadySemaphore(
+      const std::shared_ptr<fml::UniqueFD>& fd) const;
+
+  bool SubmitWaitForRenderReady(
+      const std::shared_ptr<fml::UniqueFD>& render_ready_fence,
       const std::shared_ptr<AHBTextureSourceVK>& texture) const;
 
-  void OnTextureSetOnSurfaceControl(
+  std::shared_ptr<ExternalFenceVK> SubmitSignalForPresentReady(
+      const std::shared_ptr<AHBTextureSourceVK>& texture) const;
+
+  void OnTextureUpdatedOnSurfaceControl(
       const AutoSemaSignaler& signaler,
-      std::shared_ptr<AHBTextureSourceVK> texture);
+      std::shared_ptr<AHBTextureSourceVK> texture,
+      ASurfaceTransactionStats* stats);
 };
 
 }  // namespace impeller
diff --git a/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_texture_pool_vk.cc b/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_texture_pool_vk.cc
index 154b573..24199c2 100644
--- a/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_texture_pool_vk.cc
+++ b/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_texture_pool_vk.cc
@@ -25,24 +25,25 @@
 
 AHBTexturePoolVK::~AHBTexturePoolVK() = default;
 
-std::shared_ptr<AHBTextureSourceVK> AHBTexturePoolVK::Pop() {
+AHBTexturePoolVK::PoolEntry AHBTexturePoolVK::Pop() {
   {
     Lock lock(pool_mutex_);
     if (!pool_.empty()) {
-      auto texture = pool_.back().item;
+      auto entry = pool_.back();
       pool_.pop_back();
-      return texture;
+      return entry;
     }
   }
-  return CreateTexture();
+  return PoolEntry{CreateTexture()};
 }
 
-void AHBTexturePoolVK::Push(std::shared_ptr<AHBTextureSourceVK> texture) {
+void AHBTexturePoolVK::Push(std::shared_ptr<AHBTextureSourceVK> texture,
+                            fml::UniqueFD render_ready_fence) {
   if (!texture) {
     return;
   }
   Lock lock(pool_mutex_);
-  pool_.push_back(PoolEntry{std::move(texture)});
+  pool_.push_back(PoolEntry{std::move(texture), std::move(render_ready_fence)});
   PerformGCLocked();
 }
 
diff --git a/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_texture_pool_vk.h b/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_texture_pool_vk.h
index d40050f..9cc3beb 100644
--- a/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_texture_pool_vk.h
+++ b/impeller/renderer/backend/vulkan/swapchain/ahb/ahb_texture_pool_vk.h
@@ -7,6 +7,7 @@
 
 #include <deque>
 
+#include "flutter/fml/unique_fd.h"
 #include "impeller/base/thread.h"
 #include "impeller/base/timing.h"
 #include "impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.h"
@@ -29,6 +30,21 @@
 ///
 class AHBTexturePoolVK {
  public:
+  struct PoolEntry {
+    TimePoint last_access_time;
+    std::shared_ptr<AHBTextureSourceVK> texture;
+    std::shared_ptr<fml::UniqueFD> render_ready_fence;
+
+    explicit PoolEntry(std::shared_ptr<AHBTextureSourceVK> p_item,
+                       fml::UniqueFD p_render_ready_fence = {})
+        : last_access_time(Clock::now()),
+          texture(std::move(p_item)),
+          render_ready_fence(std::make_shared<fml::UniqueFD>(
+              std::move(p_render_ready_fence))) {}
+
+    constexpr bool IsValid() const { return !!texture; }
+  };
+
   //----------------------------------------------------------------------------
   /// @brief      Create a new (empty) texture pool.
   ///
@@ -72,7 +88,7 @@
   /// @return     A texture source that can be used as a swapchain image. This
   ///             can be nullptr in case of resource exhaustion.
   ///
-  std::shared_ptr<AHBTextureSourceVK> Pop();
+  PoolEntry Pop();
 
   //----------------------------------------------------------------------------
   /// @brief      Push a popped texture back into the pool. This also performs a
@@ -86,7 +102,8 @@
   ///
   /// @param[in]  texture  The texture to be returned to the pool.
   ///
-  void Push(std::shared_ptr<AHBTextureSourceVK> texture);
+  void Push(std::shared_ptr<AHBTextureSourceVK> texture,
+            fml::UniqueFD render_ready_fence);
 
   //----------------------------------------------------------------------------
   /// @brief      Perform an explicit GC of the pool items. This happens
@@ -97,14 +114,6 @@
   void PerformGC();
 
  private:
-  struct PoolEntry {
-    TimePoint last_access_time;
-    std::shared_ptr<AHBTextureSourceVK> item;
-
-    explicit PoolEntry(std::shared_ptr<AHBTextureSourceVK> p_item)
-        : last_access_time(Clock::now()), item(std::move(p_item)) {}
-  };
-
   const std::weak_ptr<Context> context_;
   const android::HardwareBufferDescriptor desc_;
   const size_t max_entries_;
diff --git a/impeller/toolkit/android/BUILD.gn b/impeller/toolkit/android/BUILD.gn
index 758a5a9..0e0b303 100644
--- a/impeller/toolkit/android/BUILD.gn
+++ b/impeller/toolkit/android/BUILD.gn
@@ -23,6 +23,8 @@
     "surface_control.h",
     "surface_transaction.cc",
     "surface_transaction.h",
+    "surface_transaction_stats.cc",
+    "surface_transaction_stats.h",
   ]
 
   public_deps = [
diff --git a/impeller/toolkit/android/proc_table.h b/impeller/toolkit/android/proc_table.h
index 964489e..9e63960 100644
--- a/impeller/toolkit/android/proc_table.h
+++ b/impeller/toolkit/android/proc_table.h
@@ -34,33 +34,34 @@
 ///             directly. Instead, rely on the handle wrappers (`Choreographer`,
 ///             `HardwareBuffer`, etc..).
 ///
-#define FOR_EACH_ANDROID_PROC(INVOKE)            \
-  INVOKE(AChoreographer_getInstance, 24)         \
-  INVOKE(AChoreographer_postFrameCallback, 24)   \
-  INVOKE(AChoreographer_postFrameCallback64, 29) \
-  INVOKE(AHardwareBuffer_acquire, 26)            \
-  INVOKE(AHardwareBuffer_allocate, 26)           \
-  INVOKE(AHardwareBuffer_describe, 26)           \
-  INVOKE(AHardwareBuffer_fromHardwareBuffer, 26) \
-  INVOKE(AHardwareBuffer_getId, 31)              \
-  INVOKE(AHardwareBuffer_isSupported, 29)        \
-  INVOKE(AHardwareBuffer_lock, 26)               \
-  INVOKE(AHardwareBuffer_release, 26)            \
-  INVOKE(AHardwareBuffer_unlock, 26)             \
-  INVOKE(ANativeWindow_acquire, 0)               \
-  INVOKE(ANativeWindow_getHeight, 0)             \
-  INVOKE(ANativeWindow_getWidth, 0)              \
-  INVOKE(ANativeWindow_release, 0)               \
-  INVOKE(ASurfaceControl_createFromWindow, 29)   \
-  INVOKE(ASurfaceControl_release, 29)            \
-  INVOKE(ASurfaceTransaction_apply, 29)          \
-  INVOKE(ASurfaceTransaction_create, 29)         \
-  INVOKE(ASurfaceTransaction_delete, 29)         \
-  INVOKE(ASurfaceTransaction_reparent, 29)       \
-  INVOKE(ASurfaceTransaction_setBuffer, 29)      \
-  INVOKE(ASurfaceTransaction_setColor, 29)       \
-  INVOKE(ASurfaceTransaction_setOnComplete, 29)  \
-  INVOKE(ATrace_isEnabled, 23)                   \
+#define FOR_EACH_ANDROID_PROC(INVOKE)                            \
+  INVOKE(AChoreographer_getInstance, 24)                         \
+  INVOKE(AChoreographer_postFrameCallback, 24)                   \
+  INVOKE(AChoreographer_postFrameCallback64, 29)                 \
+  INVOKE(AHardwareBuffer_acquire, 26)                            \
+  INVOKE(AHardwareBuffer_allocate, 26)                           \
+  INVOKE(AHardwareBuffer_describe, 26)                           \
+  INVOKE(AHardwareBuffer_fromHardwareBuffer, 26)                 \
+  INVOKE(AHardwareBuffer_getId, 31)                              \
+  INVOKE(AHardwareBuffer_isSupported, 29)                        \
+  INVOKE(AHardwareBuffer_lock, 26)                               \
+  INVOKE(AHardwareBuffer_release, 26)                            \
+  INVOKE(AHardwareBuffer_unlock, 26)                             \
+  INVOKE(ANativeWindow_acquire, 0)                               \
+  INVOKE(ANativeWindow_getHeight, 0)                             \
+  INVOKE(ANativeWindow_getWidth, 0)                              \
+  INVOKE(ANativeWindow_release, 0)                               \
+  INVOKE(ASurfaceControl_createFromWindow, 29)                   \
+  INVOKE(ASurfaceControl_release, 29)                            \
+  INVOKE(ASurfaceTransaction_apply, 29)                          \
+  INVOKE(ASurfaceTransaction_create, 29)                         \
+  INVOKE(ASurfaceTransaction_delete, 29)                         \
+  INVOKE(ASurfaceTransaction_reparent, 29)                       \
+  INVOKE(ASurfaceTransaction_setBuffer, 29)                      \
+  INVOKE(ASurfaceTransaction_setColor, 29)                       \
+  INVOKE(ASurfaceTransaction_setOnComplete, 29)                  \
+  INVOKE(ASurfaceTransactionStats_getPreviousReleaseFenceFd, 29) \
+  INVOKE(ATrace_isEnabled, 23)                                   \
   INVOKE(eglGetNativeClientBufferANDROID, 0)
 
 template <class T>
diff --git a/impeller/toolkit/android/surface_transaction.cc b/impeller/toolkit/android/surface_transaction.cc
index a69c6f0..557dedb 100644
--- a/impeller/toolkit/android/surface_transaction.cc
+++ b/impeller/toolkit/android/surface_transaction.cc
@@ -29,7 +29,7 @@
   }
 
   if (!callback) {
-    callback = []() {};
+    callback = [](auto) {};
   }
 
   const auto& proc_table = GetProcTable();
@@ -41,7 +41,7 @@
       data.release(),      //
       [](void* context, ASurfaceTransactionStats* stats) -> void {
         auto data = reinterpret_cast<TransactionInFlightData*>(context);
-        data->callback();
+        data->callback(stats);
         delete data;
       });
   proc_table.ASurfaceTransaction_apply(transaction_.get());
diff --git a/impeller/toolkit/android/surface_transaction.h b/impeller/toolkit/android/surface_transaction.h
index a2bca45..83bb09c 100644
--- a/impeller/toolkit/android/surface_transaction.h
+++ b/impeller/toolkit/android/surface_transaction.h
@@ -84,7 +84,7 @@
   [[nodiscard]] bool SetBackgroundColor(const SurfaceControl& control,
                                         const Color& color);
 
-  using OnCompleteCallback = std::function<void(void)>;
+  using OnCompleteCallback = std::function<void(ASurfaceTransactionStats*)>;
 
   //----------------------------------------------------------------------------
   /// @brief      Applies the updated encoded in the transaction and invokes the
diff --git a/impeller/toolkit/android/surface_transaction_stats.cc b/impeller/toolkit/android/surface_transaction_stats.cc
new file mode 100644
index 0000000..d2d4e3e
--- /dev/null
+++ b/impeller/toolkit/android/surface_transaction_stats.cc
@@ -0,0 +1,23 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "impeller/toolkit/android/surface_transaction_stats.h"
+
+#include "impeller/toolkit/android/proc_table.h"
+
+namespace impeller::android {
+
+fml::UniqueFD CreatePreviousReleaseFence(const SurfaceControl& control,
+                                         ASurfaceTransactionStats* stats) {
+  const auto fd =
+      GetProcTable().ASurfaceTransactionStats_getPreviousReleaseFenceFd(
+          stats, control.GetHandle());
+  if (fd == -1) {
+    // The previous buffer has already been released. This is not an error.
+    return {};
+  }
+  return fml::UniqueFD{fd};
+}
+
+}  // namespace impeller::android
diff --git a/impeller/toolkit/android/surface_transaction_stats.h b/impeller/toolkit/android/surface_transaction_stats.h
new file mode 100644
index 0000000..2ddda8f
--- /dev/null
+++ b/impeller/toolkit/android/surface_transaction_stats.h
@@ -0,0 +1,18 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_IMPELLER_TOOLKIT_ANDROID_SURFACE_TRANSACTION_STATS_H_
+#define FLUTTER_IMPELLER_TOOLKIT_ANDROID_SURFACE_TRANSACTION_STATS_H_
+
+#include "flutter/fml/unique_fd.h"
+#include "impeller/toolkit/android/surface_control.h"
+
+namespace impeller::android {
+
+fml::UniqueFD CreatePreviousReleaseFence(const SurfaceControl& control,
+                                         ASurfaceTransactionStats* stats);
+
+}  // namespace impeller::android
+
+#endif  // FLUTTER_IMPELLER_TOOLKIT_ANDROID_SURFACE_TRANSACTION_STATS_H_
diff --git a/impeller/toolkit/android/toolkit_android_unittests.cc b/impeller/toolkit/android/toolkit_android_unittests.cc
index c0086d4..9c6218f 100644
--- a/impeller/toolkit/android/toolkit_android_unittests.cc
+++ b/impeller/toolkit/android/toolkit_android_unittests.cc
@@ -89,7 +89,7 @@
   SurfaceTransaction transaction;
   ASSERT_TRUE(transaction.IsValid());
   fml::AutoResetWaitableEvent event;
-  ASSERT_TRUE(transaction.Apply([&event]() { event.Signal(); }));
+  ASSERT_TRUE(transaction.Apply([&event](auto) { event.Signal(); }));
   event.Wait();
 }