Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Cache GPU resources using HardwareBuffer's id as key #50028

Merged
merged 18 commits into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions shell/platform/android/image_external_texture.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,61 @@ void ImageExternalTexture::OnGrContextCreated() {
state_ = AttachmentState::kUninitialized;
}

sk_sp<flutter::DlImage> ImageExternalTexture::FindImage(uint64_t key) {
for (size_t i = 0u; i < kImageReaderSwapchainSize; i++) {
if (images_[i].key == key) {
auto result = images_[i].image;
UpdateKey(result, key);
return result;
}
}
return nullptr;
}

void ImageExternalTexture::UpdateKey(const sk_sp<flutter::DlImage>& image,
uint64_t key) {
if (images_[0].key == key) {
return;
}
size_t i = 1u;
for (; i < kImageReaderSwapchainSize; i++) {
if (images_[i].key == key) {
break;
}
}
for (auto j = i; j > 0; j--) {
images_[j] = images_[j - 1];
}
}

void ImageExternalTexture::AddImage(const sk_sp<flutter::DlImage>& image,
uint64_t key) {
uint64_t lru_key = images_[kImageReaderSwapchainSize - 1].key;
bool updated_image = false;
for (size_t i = 0u; i < kImageReaderSwapchainSize; i++) {
if (images_[i].key == lru_key) {
updated_image = true;
images_[i] = LRUImage{.key = key, .image = image};
break;
}
}
if (!updated_image) {
images_[0] = LRUImage{.key = key, .image = image};
}
UpdateKey(image, key);
}

void ImageExternalTexture::ResetCache() {
for (size_t i = 0u; i < kImageReaderSwapchainSize; i++) {
images_[i] = {.key = 0u, .image = nullptr};
}
}

// Implementing flutter::ContextListener.
void ImageExternalTexture::OnGrContextDestroyed() {
if (state_ == AttachmentState::kAttached) {
dl_image_.reset();
ResetCache();
Detach();
}
state_ = AttachmentState::kDetached;
Expand Down
22 changes: 22 additions & 0 deletions shell/platform/android/image_external_texture.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@

namespace flutter {

// This value needs to be larger than the number of swapchain images
// that a typical image reader will produce to ensure that we effectively
// cache. If the value is too small, we will unnecessarily churn through
// images, while if it is too large we may retain images longer than
// necessary.
static constexpr size_t kImageReaderSwapchainSize = 6u;

// External texture peered to a sequence of android.hardware.HardwareBuffers.
//
class ImageExternalTexture : public flutter::Texture {
Expand Down Expand Up @@ -49,6 +56,16 @@ class ImageExternalTexture : public flutter::Texture {
virtual void Detach() = 0;
virtual void ProcessFrame(PaintContext& context, const SkRect& bounds) = 0;

sk_sp<flutter::DlImage> FindImage(uint64_t key);
void UpdateKey(const sk_sp<flutter::DlImage>& image, uint64_t key);
void AddImage(const sk_sp<flutter::DlImage>& image, uint64_t key);
void ResetCache();

struct LRUImage {
uint64_t key;
johnmccutchan marked this conversation as resolved.
Show resolved Hide resolved
sk_sp<flutter::DlImage> image;
};

JavaLocalRef AcquireLatestImage();
void CloseImage(const fml::jni::JavaRef<jobject>& image);
JavaLocalRef HardwareBufferFor(const fml::jni::JavaRef<jobject>& image);
Expand All @@ -62,6 +79,11 @@ class ImageExternalTexture : public flutter::Texture {
enum class AttachmentState { kUninitialized, kAttached, kDetached };
AttachmentState state_ = AttachmentState::kUninitialized;
bool new_frame_ready_ = false;
std::array<LRUImage, kImageReaderSwapchainSize> images_ = {
LRUImage{.key = 0, .image = nullptr},
johnmccutchan marked this conversation as resolved.
Show resolved Hide resolved
LRUImage{.key = 0, .image = nullptr},
LRUImage{.key = 0, .image = nullptr},
};

sk_sp<flutter::DlImage> dl_image_;

Expand Down
63 changes: 32 additions & 31 deletions shell/platform/android/image_external_texture_gl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,13 @@ void ImageExternalTextureGL::Attach(PaintContext& context) {
}
}

void ImageExternalTextureGL::Detach() {
egl_image_.reset();
}

bool ImageExternalTextureGL::MaybeSwapImages() {
void ImageExternalTextureGL::ProcessFrame(PaintContext& context,
const SkRect& bounds) {
JavaLocalRef image = AcquireLatestImage();
if (image.is_null()) {
return false;
return;
}

// NOTE: In the following code it is important that old_android_image is
// not closed until after the update of egl_image_ otherwise the image might
// be closed before the old EGLImage referencing it has been deleted. After
Expand All @@ -64,12 +62,35 @@ bool ImageExternalTextureGL::MaybeSwapImages() {
JavaLocalRef old_android_image(android_image_);
android_image_.Reset(image);
JavaLocalRef hardware_buffer = HardwareBufferFor(image);
egl_image_ = CreateEGLImage(AHardwareBufferFor(hardware_buffer));
AHardwareBuffer* latest_hardware_buffer = AHardwareBufferFor(hardware_buffer);
auto key = flutter::NDKHelpers::AHardwareBuffer_getId(latest_hardware_buffer);
auto existing_image = FindImage(key);
if (existing_image != nullptr) {
dl_image_ = existing_image;

CloseHardwareBuffer(hardware_buffer);
// IMPORTANT: We have just received a new frame to display so close the
// previous Java Image so that it is recycled and used for a future frame.
CloseImage(old_android_image);
return;
}

egl_image_ = CreateEGLImage(latest_hardware_buffer);
johnmccutchan marked this conversation as resolved.
Show resolved Hide resolved
CloseHardwareBuffer(hardware_buffer);
// IMPORTANT: We only close the old image after egl_image_ stops referencing
// it.
// IMPORTANT: We have just received a new frame to display so close the
// previous Java Image so that it is recycled and used for a future frame.
CloseImage(old_android_image);
return true;

if (!egl_image_.is_valid()) {
return;
}

dl_image_ = CreateDlImage(context, bounds);
AddImage(dl_image_, key);
}

void ImageExternalTextureGL::Detach() {
egl_image_.reset();
}

impeller::UniqueEGLImageKHR ImageExternalTextureGL::CreateEGLImage(
Expand Down Expand Up @@ -121,17 +142,6 @@ void ImageExternalTextureGLSkia::Detach() {
texture_.reset();
}

void ImageExternalTextureGLSkia::ProcessFrame(PaintContext& context,
const SkRect& bounds) {
const bool swapped = MaybeSwapImages();
if (!swapped && !egl_image_.is_valid()) {
// Nothing to do.
return;
}
BindImageToTexture(egl_image_, texture_.get().texture_name);
dl_image_ = CreateDlImage(context, bounds);
}

void ImageExternalTextureGLSkia::BindImageToTexture(
const impeller::UniqueEGLImageKHR& image,
GLuint tex) {
Expand All @@ -146,6 +156,7 @@ void ImageExternalTextureGLSkia::BindImageToTexture(
sk_sp<flutter::DlImage> ImageExternalTextureGLSkia::CreateDlImage(
PaintContext& context,
const SkRect& bounds) {
BindImageToTexture(egl_image_, texture_.get().texture_name);
GrGLTextureInfo textureInfo = {GL_TEXTURE_EXTERNAL_OES,
texture_.get().texture_name, GL_RGBA8_OES};
auto backendTexture =
Expand All @@ -171,16 +182,6 @@ void ImageExternalTextureGLImpeller::Attach(PaintContext& context) {
}
}

void ImageExternalTextureGLImpeller::ProcessFrame(PaintContext& context,
const SkRect& bounds) {
const bool swapped = MaybeSwapImages();
if (!swapped && !egl_image_.is_valid()) {
// Nothing to do.
return;
}
dl_image_ = CreateDlImage(context, bounds);
}

sk_sp<flutter::DlImage> ImageExternalTextureGLImpeller::CreateDlImage(
PaintContext& context,
const SkRect& bounds) {
Expand Down
13 changes: 6 additions & 7 deletions shell/platform/android/image_external_texture_gl.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ class ImageExternalTextureGL : public ImageExternalTexture {
protected:
void Attach(PaintContext& context) override;
void Detach() override;
void ProcessFrame(PaintContext& context, const SkRect& bounds) override;

virtual sk_sp<flutter::DlImage> CreateDlImage(PaintContext& context,
const SkRect& bounds) = 0;

// Returns true if a new image was acquired and android_image_ and egl_image_
// were updated.
bool MaybeSwapImages();
impeller::UniqueEGLImageKHR CreateEGLImage(AHardwareBuffer* buffer);

fml::jni::ScopedJavaGlobalRef<jobject> android_image_;
Expand All @@ -53,11 +54,10 @@ class ImageExternalTextureGLSkia : public ImageExternalTextureGL {
private:
void Attach(PaintContext& context) override;
void Detach() override;
void ProcessFrame(PaintContext& context, const SkRect& bounds) override;

void BindImageToTexture(const impeller::UniqueEGLImageKHR& image, GLuint tex);
sk_sp<flutter::DlImage> CreateDlImage(PaintContext& context,
const SkRect& bounds);
const SkRect& bounds) override;

impeller::UniqueGLTexture texture_;

Expand All @@ -75,11 +75,10 @@ class ImageExternalTextureGLImpeller : public ImageExternalTextureGL {

private:
void Attach(PaintContext& context) override;
void ProcessFrame(PaintContext& context, const SkRect& bounds) override;
void Detach() override;

sk_sp<flutter::DlImage> CreateDlImage(PaintContext& context,
const SkRect& bounds);
const SkRect& bounds) override;

const std::shared_ptr<impeller::ContextGLES> impeller_context_;

Expand Down
17 changes: 15 additions & 2 deletions shell/platform/android/image_external_texture_vk.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

#include "flutter/shell/platform/android/image_external_texture_vk.h"
#include <cstdint>

#include "flutter/impeller/core/formats.h"
#include "flutter/impeller/core/texture_descriptor.h"
Expand Down Expand Up @@ -45,6 +46,17 @@ void ImageExternalTextureVK::ProcessFrame(PaintContext& context,
AHardwareBuffer_Desc hb_desc = {};
flutter::NDKHelpers::AHardwareBuffer_describe(latest_hardware_buffer,
&hb_desc);
auto key = flutter::NDKHelpers::AHardwareBuffer_getId(latest_hardware_buffer);
johnmccutchan marked this conversation as resolved.
Show resolved Hide resolved
auto existing_image = FindImage(key);
if (existing_image != nullptr) {
dl_image_ = existing_image;

CloseHardwareBuffer(hardware_buffer);
// IMPORTANT: We have just received a new frame to display so close the
// previous Java Image so that it is recycled and used for a future frame.
CloseImage(old_android_image);
return;
}

impeller::TextureDescriptor desc;
desc.storage_mode = impeller::StorageMode::kDevicePrivate;
Expand Down Expand Up @@ -88,9 +100,10 @@ void ImageExternalTextureVK::ProcessFrame(PaintContext& context,
}

dl_image_ = impeller::DlImageImpeller::Make(texture);
AddImage(dl_image_, key);
CloseHardwareBuffer(hardware_buffer);
johnmccutchan marked this conversation as resolved.
Show resolved Hide resolved
// IMPORTANT: We only close the old image after texture stops referencing
// it.
// IMPORTANT: We have just received a new frame to display so close the
// previous Java Image so that it is recycled and used for a future frame.
CloseImage(old_android_image);
}

Expand Down
2 changes: 2 additions & 0 deletions shell/platform/android/image_external_texture_vk.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#ifndef FLUTTER_SHELL_PLATFORM_ANDROID_IMAGE_EXTERNAL_TEXTURE_VK_H_
#define FLUTTER_SHELL_PLATFORM_ANDROID_IMAGE_EXTERNAL_TEXTURE_VK_H_

#include <cstdint>
#include <utility>
#include "flutter/shell/platform/android/image_external_texture.h"

#include "flutter/impeller/renderer/backend/vulkan/android_hardware_buffer_texture_source_vk.h"
Expand Down
17 changes: 17 additions & 0 deletions shell/platform/android/ndk_helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ typedef void (*fp_AHardwareBuffer_acquire)(AHardwareBuffer* buffer);
typedef void (*fp_AHardwareBuffer_release)(AHardwareBuffer* buffer);
typedef void (*fp_AHardwareBuffer_describe)(AHardwareBuffer* buffer,
AHardwareBuffer_Desc* desc);
typedef void (*fp_AHardwareBuffer_getId)(AHardwareBuffer* buffer,
johnmccutchan marked this conversation as resolved.
Show resolved Hide resolved
uint64_t* outId);

typedef EGLClientBuffer (*fp_eglGetNativeClientBufferANDROID)(
AHardwareBuffer* buffer);

Expand All @@ -32,6 +35,8 @@ void (*_AHardwareBuffer_acquire)(AHardwareBuffer* buffer) = nullptr;
void (*_AHardwareBuffer_release)(AHardwareBuffer* buffer) = nullptr;
void (*_AHardwareBuffer_describe)(AHardwareBuffer* buffer,
AHardwareBuffer_Desc* desc) = nullptr;
void (*_AHardwareBuffer_getId)(AHardwareBuffer* buffer,
uint64_t* outId) = nullptr;
EGLClientBuffer (*_eglGetNativeClientBufferANDROID)(AHardwareBuffer* buffer) =
nullptr;

Expand Down Expand Up @@ -61,6 +66,10 @@ void InitOnceCallback() {
->ResolveFunction<fp_AHardwareBuffer_release>(
"AHardwareBuffer_release")
.value_or(nullptr);
_AHardwareBuffer_getId =
android
->ResolveFunction<fp_AHardwareBuffer_getId>("AHardwareBuffer_getId")
.value_or(nullptr);
_AHardwareBuffer_describe =
android
->ResolveFunction<fp_AHardwareBuffer_describe>(
Expand Down Expand Up @@ -107,6 +116,14 @@ void NDKHelpers::AHardwareBuffer_describe(AHardwareBuffer* buffer,
_AHardwareBuffer_describe(buffer, desc);
}

uint64_t NDKHelpers::AHardwareBuffer_getId(AHardwareBuffer* buffer) {
NDKHelpers::Init();
FML_CHECK(_AHardwareBuffer_getId != nullptr);
uint64_t outId;
_AHardwareBuffer_getId(buffer, &outId);
return outId;
}

EGLClientBuffer NDKHelpers::eglGetNativeClientBufferANDROID(
AHardwareBuffer* buffer) {
NDKHelpers::Init();
Expand Down
1 change: 1 addition & 0 deletions shell/platform/android/ndk_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class NDKHelpers {
static void AHardwareBuffer_release(AHardwareBuffer* buffer);
static void AHardwareBuffer_describe(AHardwareBuffer* buffer,
AHardwareBuffer_Desc* desc);
static uint64_t AHardwareBuffer_getId(AHardwareBuffer* buffer);
static EGLClientBuffer eglGetNativeClientBufferANDROID(
AHardwareBuffer* buffer);

Expand Down