diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 44c5a060fe4b5..47d59fca4b1a0 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -282,6 +282,7 @@ ../../../flutter/shell/platform/android/external_view_embedder/surface_pool_unittests.cc ../../../flutter/shell/platform/android/flutter_shell_native_unittests.cc ../../../flutter/shell/platform/android/gradle.properties +../../../flutter/shell/platform/android/image_lru_unittests.cc ../../../flutter/shell/platform/android/jni/jni_mock_unittest.cc ../../../flutter/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc ../../../flutter/shell/platform/android/platform_view_android_unittests.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 9836b81f49751..ec69356c3241d 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -6347,6 +6347,8 @@ ORIGIN: ../../../flutter/shell/platform/android/image_external_texture.h + ../.. ORIGIN: ../../../flutter/shell/platform/android/image_external_texture_gl.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/image_external_texture_gl.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/image_external_texture_vk.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/android/image_lru.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/android/image_lru.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/BuildConfig.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/FlutterInjector.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/Log.java + ../../../flutter/LICENSE @@ -9203,6 +9205,8 @@ FILE: ../../../flutter/shell/platform/android/image_external_texture_gl.cc FILE: ../../../flutter/shell/platform/android/image_external_texture_gl.h FILE: ../../../flutter/shell/platform/android/image_external_texture_vk.cc FILE: ../../../flutter/shell/platform/android/image_external_texture_vk.h +FILE: ../../../flutter/shell/platform/android/image_lru.cc +FILE: ../../../flutter/shell/platform/android/image_lru.h FILE: ../../../flutter/shell/platform/android/io/flutter/BuildConfig.java FILE: ../../../flutter/shell/platform/android/io/flutter/FlutterInjector.java FILE: ../../../flutter/shell/platform/android/io/flutter/Log.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index f1fdf7c4ae75b..f850d83ab8f0b 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -47,6 +47,7 @@ executable("flutter_shell_native_unittests") { "android_shell_holder_unittests.cc", "apk_asset_provider_unittests.cc", "flutter_shell_native_unittests.cc", + "image_lru_unittests.cc", "platform_view_android_unittests.cc", ] public_configs = [ "//flutter:config" ] @@ -111,6 +112,8 @@ source_set("flutter_shell_native_src") { "image_external_texture_gl.h", "image_external_texture_vk.cc", "image_external_texture_vk.h", + "image_lru.cc", + "image_lru.h", "library_loader.cc", "ndk_helpers.cc", "ndk_helpers.h", diff --git a/shell/platform/android/image_external_texture.cc b/shell/platform/android/image_external_texture.cc index 87a8704c11134..da6f55832672e 100644 --- a/shell/platform/android/image_external_texture.cc +++ b/shell/platform/android/image_external_texture.cc @@ -25,6 +25,7 @@ void ImageExternalTexture::Paint(PaintContext& context, if (state_ == AttachmentState::kDetached) { return; } + latest_bounds_ = bounds; Attach(context); const bool should_process_frame = !freeze; if (should_process_frame) { @@ -61,6 +62,7 @@ void ImageExternalTexture::OnGrContextCreated() { void ImageExternalTexture::OnGrContextDestroyed() { if (state_ == AttachmentState::kAttached) { dl_image_.reset(); + image_lru_.Clear(); Detach(); } state_ = AttachmentState::kDetached; diff --git a/shell/platform/android/image_external_texture.h b/shell/platform/android/image_external_texture.h index 251310cf5371d..6193c7b86baea 100644 --- a/shell/platform/android/image_external_texture.h +++ b/shell/platform/android/image_external_texture.h @@ -7,6 +7,7 @@ #include "flutter/common/graphics/texture.h" #include "flutter/fml/logging.h" +#include "flutter/shell/platform/android/image_lru.h" #include "flutter/shell/platform/android/jni/platform_view_android_jni.h" #include "flutter/shell/platform/android/platform_view_android_jni_impl.h" @@ -58,11 +59,13 @@ class ImageExternalTexture : public flutter::Texture { fml::jni::ScopedJavaGlobalRef image_texture_entry_; std::shared_ptr jni_facade_; + SkRect latest_bounds_ = SkRect::MakeEmpty(); + fml::jni::ScopedJavaGlobalRef latest_android_image_; enum class AttachmentState { kUninitialized, kAttached, kDetached }; AttachmentState state_ = AttachmentState::kUninitialized; - sk_sp dl_image_; + ImageLRU image_lru_ = ImageLRU(); FML_DISALLOW_COPY_AND_ASSIGN(ImageExternalTexture); }; diff --git a/shell/platform/android/image_external_texture_gl.cc b/shell/platform/android/image_external_texture_gl.cc index fd966c8a10ee6..4acc3598be2a8 100644 --- a/shell/platform/android/image_external_texture_gl.cc +++ b/shell/platform/android/image_external_texture_gl.cc @@ -8,20 +8,13 @@ #include #include "flutter/common/graphics/texture.h" -#include "flutter/display_list/effects/dl_color_source.h" -#include "flutter/flow/layers/layer.h" #include "flutter/impeller/core/formats.h" #include "flutter/impeller/display_list/dl_image_impeller.h" -#include "flutter/impeller/renderer/backend/gles/texture_gles.h" #include "flutter/impeller/toolkit/egl/image.h" #include "flutter/impeller/toolkit/gles/texture.h" #include "flutter/shell/platform/android/ndk_helpers.h" #include "third_party/skia/include/core/SkAlphaType.h" -#include "third_party/skia/include/core/SkColorSpace.h" #include "third_party/skia/include/core/SkColorType.h" -#include "third_party/skia/include/core/SkImage.h" -#include "third_party/skia/include/gpu/GrBackendSurface.h" -#include "third_party/skia/include/gpu/GrDirectContext.h" #include "third_party/skia/include/gpu/ganesh/SkImageGanesh.h" #include "third_party/skia/include/gpu/ganesh/gl/GrGLBackendSurface.h" #include "third_party/skia/include/gpu/gl/GrGLTypes.h" @@ -36,40 +29,61 @@ ImageExternalTextureGL::ImageExternalTextureGL( void ImageExternalTextureGL::Attach(PaintContext& context) { if (state_ == AttachmentState::kUninitialized) { - if (!android_image_.is_null()) { - JavaLocalRef hardware_buffer = HardwareBufferFor(android_image_); - AHardwareBuffer* hardware_buffer_ahw = - AHardwareBufferFor(hardware_buffer); - egl_image_ = CreateEGLImage(hardware_buffer_ahw); + if (!latest_android_image_.is_null() && !latest_bounds_.isEmpty()) { + // After detach the cache of textures will have been cleared. If + // there is an android image we must populate it now so that the + // first frame isn't blank. + JavaLocalRef hardware_buffer = HardwareBufferFor(latest_android_image_); + UpdateImage(hardware_buffer, context); CloseHardwareBuffer(hardware_buffer); } state_ = AttachmentState::kAttached; } } -void ImageExternalTextureGL::Detach() { - egl_image_.reset(); +void ImageExternalTextureGL::UpdateImage(JavaLocalRef& hardware_buffer, + PaintContext& context) { + AHardwareBuffer* latest_hardware_buffer = AHardwareBufferFor(hardware_buffer); + HardwareBufferKey key = + flutter::NDKHelpers::AHardwareBuffer_getId(latest_hardware_buffer); + auto existing_image = image_lru_.FindImage(key); + if (existing_image != nullptr) { + dl_image_ = existing_image; + return; + } + + auto egl_image = CreateEGLImage(latest_hardware_buffer); + if (!egl_image.is_valid()) { + return; + } + + dl_image_ = CreateDlImage(context, latest_bounds_, key, std::move(egl_image)); + gl_entries_.erase(image_lru_.AddImage(dl_image_, key)); } -bool ImageExternalTextureGL::MaybeSwapImages() { +void ImageExternalTextureGL::ProcessFrame(PaintContext& context, + const SkRect& bounds) { JavaLocalRef image = AcquireLatestImage(); if (image.is_null()) { - return false; + return; } + JavaLocalRef hardware_buffer = HardwareBufferFor(image); + UpdateImage(hardware_buffer, context); + CloseHardwareBuffer(hardware_buffer); + // 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 // an image is closed the underlying HardwareBuffer may be recycled and used // for a future frame. - JavaLocalRef old_android_image(android_image_); - android_image_.Reset(image); - JavaLocalRef hardware_buffer = HardwareBufferFor(image); - egl_image_ = CreateEGLImage(AHardwareBufferFor(hardware_buffer)); - CloseHardwareBuffer(hardware_buffer); - // IMPORTANT: We only close the old image after egl_image_ stops referencing - // it. + JavaLocalRef old_android_image(latest_android_image_); + latest_android_image_.Reset(image); CloseImage(old_android_image); - return true; +} + +void ImageExternalTextureGL::Detach() { + image_lru_.Clear(); + gl_entries_.clear(); } impeller::UniqueEGLImageKHR ImageExternalTextureGL::CreateEGLImage( @@ -110,26 +124,11 @@ void ImageExternalTextureGLSkia::Attach(PaintContext& context) { // After this call state_ will be AttachmentState::kAttached and egl_image_ // will have been created if we still have an Image associated with us. ImageExternalTextureGL::Attach(context); - GLuint texture_name; - glGenTextures(1, &texture_name); - texture_.reset(impeller::GLTexture{texture_name}); } } void ImageExternalTextureGLSkia::Detach() { ImageExternalTextureGL::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( @@ -145,11 +144,22 @@ void ImageExternalTextureGLSkia::BindImageToTexture( sk_sp ImageExternalTextureGLSkia::CreateDlImage( PaintContext& context, - const SkRect& bounds) { - GrGLTextureInfo textureInfo = {GL_TEXTURE_EXTERNAL_OES, - texture_.get().texture_name, GL_RGBA8_OES}; + const SkRect& bounds, + HardwareBufferKey id, + impeller::UniqueEGLImageKHR&& egl_image) { + GLuint texture_name; + glGenTextures(1, &texture_name); + auto gl_texture = impeller::GLTexture{texture_name}; + impeller::UniqueGLTexture unique_texture; + unique_texture.reset(gl_texture); + + BindImageToTexture(egl_image, unique_texture.get().texture_name); + GrGLTextureInfo textureInfo = { + GL_TEXTURE_EXTERNAL_OES, unique_texture.get().texture_name, GL_RGBA8_OES}; auto backendTexture = GrBackendTextures::MakeGL(1, 1, skgpu::Mipmapped::kNo, textureInfo); + gl_entries_[id] = GlEntry{.egl_image = std::move(egl_image), + .texture = std::move(unique_texture)}; return DlImage::Make(SkImages::BorrowTextureFrom( context.gr_context, backendTexture, kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr)); @@ -171,19 +181,11 @@ 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 ImageExternalTextureGLImpeller::CreateDlImage( PaintContext& context, - const SkRect& bounds) { + const SkRect& bounds, + HardwareBufferKey id, + impeller::UniqueEGLImageKHR&& egl_image) { impeller::TextureDescriptor desc; desc.type = impeller::TextureType::kTextureExternalOES; desc.storage_mode = impeller::StorageMode::kDevicePrivate; @@ -201,7 +203,10 @@ sk_sp ImageExternalTextureGLImpeller::CreateDlImage( } // Associate the hardware buffer image with the texture. glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, - (GLeglImageOES)egl_image_.get().image); + (GLeglImageOES)egl_image.get().image); + gl_entries_[id] = GlEntry{ + .egl_image = std::move(egl_image), + }; return impeller::DlImageImpeller::Make(texture); } diff --git a/shell/platform/android/image_external_texture_gl.h b/shell/platform/android/image_external_texture_gl.h index bdaa50ac605df..656f790f10434 100644 --- a/shell/platform/android/image_external_texture_gl.h +++ b/shell/platform/android/image_external_texture_gl.h @@ -5,6 +5,8 @@ #ifndef FLUTTER_SHELL_PLATFORM_ANDROID_IMAGE_EXTERNAL_TEXTURE_GL_H_ #define FLUTTER_SHELL_PLATFORM_ANDROID_IMAGE_EXTERNAL_TEXTURE_GL_H_ +#include + #include "flutter/fml/platform/android/scoped_java_ref.h" #include "flutter/shell/platform/android/image_external_texture.h" @@ -30,14 +32,26 @@ class ImageExternalTextureGL : public ImageExternalTexture { protected: void Attach(PaintContext& context) override; void Detach() override; + void ProcessFrame(PaintContext& context, const SkRect& bounds) override; + void UpdateImage(JavaLocalRef& hardware_buffer, PaintContext& context); + + virtual sk_sp CreateDlImage( + PaintContext& context, + const SkRect& bounds, + HardwareBufferKey id, + impeller::UniqueEGLImageKHR&& egl_image) = 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 android_image_; - impeller::UniqueEGLImageKHR egl_image_; + struct GlEntry { + impeller::UniqueEGLImageKHR egl_image; + impeller::UniqueGLTexture texture; + }; + + // Each GL entry is keyed off of the currently active + // hardware buffers and evicted when the hardware buffer + // is removed from the LRU cache. + std::unordered_map gl_entries_; FML_DISALLOW_COPY_AND_ASSIGN(ImageExternalTextureGL); }; @@ -53,13 +67,14 @@ 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 CreateDlImage(PaintContext& context, - const SkRect& bounds); - impeller::UniqueGLTexture texture_; + sk_sp CreateDlImage( + PaintContext& context, + const SkRect& bounds, + HardwareBufferKey id, + impeller::UniqueEGLImageKHR&& egl_image) override; FML_DISALLOW_COPY_AND_ASSIGN(ImageExternalTextureGLSkia); }; @@ -75,11 +90,13 @@ class ImageExternalTextureGLImpeller : public ImageExternalTextureGL { private: void Attach(PaintContext& context) override; - void ProcessFrame(PaintContext& context, const SkRect& bounds) override; void Detach() override; - sk_sp CreateDlImage(PaintContext& context, - const SkRect& bounds); + sk_sp CreateDlImage( + PaintContext& context, + const SkRect& bounds, + HardwareBufferKey id, + impeller::UniqueEGLImageKHR&& egl_image) override; const std::shared_ptr impeller_context_; diff --git a/shell/platform/android/image_external_texture_vk.cc b/shell/platform/android/image_external_texture_vk.cc index 714b9929def27..660e69192a4a3 100644 --- a/shell/platform/android/image_external_texture_vk.cc +++ b/shell/platform/android/image_external_texture_vk.cc @@ -1,5 +1,6 @@ #include "flutter/shell/platform/android/image_external_texture_vk.h" +#include #include "flutter/impeller/core/formats.h" #include "flutter/impeller/core/texture_descriptor.h" @@ -37,14 +38,26 @@ void ImageExternalTextureVK::ProcessFrame(PaintContext& context, if (image.is_null()) { return; } - JavaLocalRef old_android_image(android_image_); - android_image_.Reset(image); - JavaLocalRef hardware_buffer = HardwareBufferFor(android_image_); + JavaLocalRef old_android_image(latest_android_image_); + latest_android_image_.Reset(image); + JavaLocalRef hardware_buffer = HardwareBufferFor(latest_android_image_); AHardwareBuffer* latest_hardware_buffer = AHardwareBufferFor(hardware_buffer); AHardwareBuffer_Desc hb_desc = {}; flutter::NDKHelpers::AHardwareBuffer_describe(latest_hardware_buffer, &hb_desc); + HardwareBufferKey key = + flutter::NDKHelpers::AHardwareBuffer_getId(latest_hardware_buffer); + auto existing_image = image_lru_.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; @@ -88,9 +101,10 @@ void ImageExternalTextureVK::ProcessFrame(PaintContext& context, } dl_image_ = impeller::DlImageImpeller::Make(texture); + image_lru_.AddImage(dl_image_, key); CloseHardwareBuffer(hardware_buffer); - // 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); } diff --git a/shell/platform/android/image_external_texture_vk.h b/shell/platform/android/image_external_texture_vk.h index a6c531e07945e..e3d240145aacc 100644 --- a/shell/platform/android/image_external_texture_vk.h +++ b/shell/platform/android/image_external_texture_vk.h @@ -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 +#include #include "flutter/shell/platform/android/image_external_texture.h" #include "flutter/impeller/renderer/backend/vulkan/android_hardware_buffer_texture_source_vk.h" @@ -31,8 +33,6 @@ class ImageExternalTextureVK : public ImageExternalTexture { void Detach() override; const std::shared_ptr impeller_context_; - - fml::jni::ScopedJavaGlobalRef android_image_; }; } // namespace flutter diff --git a/shell/platform/android/image_lru.cc b/shell/platform/android/image_lru.cc new file mode 100644 index 0000000000000..2d0317d8cfd0a --- /dev/null +++ b/shell/platform/android/image_lru.cc @@ -0,0 +1,61 @@ +// 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 "flutter/shell/platform/android/image_lru.h" + +namespace flutter { + +sk_sp ImageLRU::FindImage(HardwareBufferKey key) { + for (size_t i = 0u; i < kImageReaderSwapchainSize; i++) { + if (images_[i].key == key) { + auto result = images_[i].value; + UpdateKey(result, key); + return result; + } + } + return nullptr; +} + +void ImageLRU::UpdateKey(const sk_sp& image, + HardwareBufferKey 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]; + } + images_[0] = Data{.key = key, .value = image}; +} + +HardwareBufferKey ImageLRU::AddImage(const sk_sp& image, + HardwareBufferKey key) { + HardwareBufferKey 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] = Data{.key = key, .value = image}; + break; + } + } + if (!updated_image) { + images_[0] = Data{.key = key, .value = image}; + } + UpdateKey(image, key); + return lru_key; +} + +void ImageLRU::Clear() { + for (size_t i = 0u; i < kImageReaderSwapchainSize; i++) { + images_[i] = Data{.key = 0u, .value = nullptr}; + } +} + +} // namespace flutter diff --git a/shell/platform/android/image_lru.h b/shell/platform/android/image_lru.h new file mode 100644 index 0000000000000..ac9391b0406e0 --- /dev/null +++ b/shell/platform/android/image_lru.h @@ -0,0 +1,56 @@ +// 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_SHELL_PLATFORM_ANDROID_IMAGE_LRU_H_ +#define FLUTTER_SHELL_PLATFORM_ANDROID_IMAGE_LRU_H_ + +#include +#include + +#include "display_list/image/dl_image.h" +#include "shell/platform/android/ndk_helpers.h" + +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; + +class ImageLRU { + public: + ImageLRU() = default; + + ~ImageLRU() = default; + + /// @brief Retrieve the image associated with the given [key], or nullptr. + sk_sp FindImage(HardwareBufferKey key); + + /// @brief Add a new image to the cache with a key, returning the key of the + /// LRU entry that was removed. + /// + /// The value may be `0`, in which case nothing was removed. + HardwareBufferKey AddImage(const sk_sp& image, + HardwareBufferKey key); + + /// @brief Remove all entires from the image cache. + void Clear(); + + private: + /// @brief Marks [key] as the most recently used. + void UpdateKey(const sk_sp& image, HardwareBufferKey key); + + struct Data { + HardwareBufferKey key = 0u; + sk_sp value; + }; + + std::array images_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_ANDROID_IMAGE_LRU_H_ diff --git a/shell/platform/android/image_lru_unittests.cc b/shell/platform/android/image_lru_unittests.cc new file mode 100644 index 0000000000000..d97edfdffc5f7 --- /dev/null +++ b/shell/platform/android/image_lru_unittests.cc @@ -0,0 +1,57 @@ +// 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 "flutter/shell/platform/android/image_lru.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +TEST(ImageLRU, CanStoreSingleImage) { + auto image = DlImage::Make(nullptr); + ImageLRU image_lru; + + EXPECT_EQ(image_lru.FindImage(1), nullptr); + + image_lru.AddImage(image, 1); + + EXPECT_EQ(image_lru.FindImage(1), image); +} + +TEST(ImageLRU, EvictsLRU) { + auto image = DlImage::Make(nullptr); + ImageLRU image_lru; + + // Fill up the cache, nothing is removed + for (auto i = 0u; i < kImageReaderSwapchainSize; i++) { + EXPECT_EQ(image_lru.AddImage(image, i + 1), 0u); + } + // Confirm each image is in the cache. This should keep the LRU + // order the same. + for (auto i = 0u; i < kImageReaderSwapchainSize; i++) { + EXPECT_EQ(image_lru.FindImage(i + 1), image); + } + + // Insert new image and verify least recently used was removed. + EXPECT_EQ(image_lru.AddImage(image, 100), 1u); +} + +TEST(ImageLRU, CanClear) { + auto image = DlImage::Make(nullptr); + ImageLRU image_lru; + + // Fill up the cache, nothing is removed + for (auto i = 0u; i < kImageReaderSwapchainSize; i++) { + EXPECT_EQ(image_lru.AddImage(image, i + 1), 0u); + } + image_lru.Clear(); + + // Expect no cache entries. + for (auto i = 0u; i < kImageReaderSwapchainSize; i++) { + EXPECT_EQ(image_lru.FindImage(i + 1), nullptr); + } +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/android/ndk_helpers.cc b/shell/platform/android/ndk_helpers.cc index 9ff5d4ea3b6d1..2c7db68ccf8b0 100644 --- a/shell/platform/android/ndk_helpers.cc +++ b/shell/platform/android/ndk_helpers.cc @@ -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, + uint64_t* outId); + typedef EGLClientBuffer (*fp_eglGetNativeClientBufferANDROID)( AHardwareBuffer* buffer); @@ -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; @@ -61,6 +66,10 @@ void InitOnceCallback() { ->ResolveFunction( "AHardwareBuffer_release") .value_or(nullptr); + _AHardwareBuffer_getId = + android + ->ResolveFunction("AHardwareBuffer_getId") + .value_or(nullptr); _AHardwareBuffer_describe = android ->ResolveFunction( @@ -107,6 +116,14 @@ void NDKHelpers::AHardwareBuffer_describe(AHardwareBuffer* buffer, _AHardwareBuffer_describe(buffer, desc); } +HardwareBufferKey NDKHelpers::AHardwareBuffer_getId(AHardwareBuffer* buffer) { + NDKHelpers::Init(); + FML_CHECK(_AHardwareBuffer_getId != nullptr); + HardwareBufferKey outId; + _AHardwareBuffer_getId(buffer, &outId); + return outId; +} + EGLClientBuffer NDKHelpers::eglGetNativeClientBufferANDROID( AHardwareBuffer* buffer) { NDKHelpers::Init(); diff --git a/shell/platform/android/ndk_helpers.h b/shell/platform/android/ndk_helpers.h index f1c2592467dea..56db0e478c0b2 100644 --- a/shell/platform/android/ndk_helpers.h +++ b/shell/platform/android/ndk_helpers.h @@ -14,6 +14,8 @@ namespace flutter { +using HardwareBufferKey = uint64_t; + // A collection of NDK functions that are available depending on the version of // the Android SDK we are linked with at runtime. class NDKHelpers { @@ -27,6 +29,7 @@ class NDKHelpers { static void AHardwareBuffer_release(AHardwareBuffer* buffer); static void AHardwareBuffer_describe(AHardwareBuffer* buffer, AHardwareBuffer_Desc* desc); + static HardwareBufferKey AHardwareBuffer_getId(AHardwareBuffer* buffer); static EGLClientBuffer eglGetNativeClientBufferANDROID( AHardwareBuffer* buffer);