[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();
}