Skip to content
This repository has been archived by the owner on Feb 25, 2025. It is now read-only.

Commit

Permalink
[Impeller] Wait for the previous AHB texture to be fully recyclable. (#…
Browse files Browse the repository at this point in the history
…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 flutter/flutter#147758
  • Loading branch information
chinmaygarde authored May 7, 2024
1 parent 28bd141 commit 42898e0
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 63 deletions.
4 changes: 4 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -40895,6 +40895,8 @@ ORIGIN: ../../../flutter/impeller/toolkit/android/surface_control.cc + ../../../
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
Expand Down Expand Up @@ -43771,6 +43773,8 @@ FILE: ../../../flutter/impeller/toolkit/android/surface_control.cc
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
Expand Down
4 changes: 4 additions & 0 deletions impeller/renderer/backend/vulkan/capabilities_vk.cc
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ static const char* GetExtensionName(RequiredAndroidDeviceExtensionVK ext) {
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";
}
Expand Down
13 changes: 13 additions & 0 deletions impeller/renderer/backend/vulkan/capabilities_vk.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ enum class RequiredAndroidDeviceExtensionVK : uint32_t {
///
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,
};

Expand Down
171 changes: 158 additions & 13 deletions impeller/renderer/backend/vulkan/swapchain/ahb/ahb_swapchain_impl_vk.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -96,9 +98,9 @@ std::unique_ptr<Surface> AHBSwapchainImplVK::AcquireNextDrawable() {
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;
}
Expand All @@ -108,9 +110,19 @@ std::unique_ptr<Surface> AHBSwapchainImplVK::AcquireNextDrawable() {
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.";
Expand Down Expand Up @@ -145,7 +157,7 @@ bool AHBSwapchainImplVK::Present(
return false;
}

auto fence = SubmitCompletionSignal(texture);
auto fence = SubmitSignalForPresentReady(texture);

if (!fence) {
VALIDATION_LOG << "Could not submit completion signal.";
Expand All @@ -161,16 +173,18 @@ bool AHBSwapchainImplVK::Present(
"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) {
Expand All @@ -185,7 +199,7 @@ std::shared_ptr<ExternalFenceVK> AHBSwapchainImplVK::SubmitCompletionSignal(
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();
Expand Down Expand Up @@ -219,17 +233,148 @@ std::shared_ptr<ExternalFenceVK> AHBSwapchainImplVK::SubmitCompletionSignal(
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
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,20 @@ class AHBSwapchainImplVK final
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;

std::shared_ptr<ExternalFenceVK> SubmitSignalForPresentReady(
const std::shared_ptr<AHBTextureSourceVK>& texture) const;

void OnTextureSetOnSurfaceControl(
void OnTextureUpdatedOnSurfaceControl(
const AutoSemaSignaler& signaler,
std::shared_ptr<AHBTextureSourceVK> texture);
std::shared_ptr<AHBTextureSourceVK> texture,
ASurfaceTransactionStats* stats);
};

} // namespace impeller
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,25 @@ AHBTexturePoolVK::AHBTexturePoolVK(std::weak_ptr<Context> context,

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

Expand Down
Loading

0 comments on commit 42898e0

Please sign in to comment.