From dc8231f9db836ce3f968de9d6b08885a85952b2e Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Thu, 12 Oct 2023 10:32:35 -0700 Subject: [PATCH 01/11] [Impeller] implements a retry mechanism for dart:ui/Image.toByteData. --- impeller/renderer/backend/metal/BUILD.gn | 5 + .../backend/metal/app_state_notifier.h | 32 +++++++ .../backend/metal/app_state_notifier.mm | 94 +++++++++++++++++++ impeller/renderer/backend/metal/context_mtl.h | 8 ++ .../renderer/backend/metal/context_mtl.mm | 14 +++ impeller/renderer/context.h | 6 ++ lib/ui/painting/image_encoding_impeller.cc | 65 ++++++++++--- 7 files changed, 212 insertions(+), 12 deletions(-) create mode 100644 impeller/renderer/backend/metal/app_state_notifier.h create mode 100644 impeller/renderer/backend/metal/app_state_notifier.mm diff --git a/impeller/renderer/backend/metal/BUILD.gn b/impeller/renderer/backend/metal/BUILD.gn index 9a0f6a631e3d7..8e84084f055f4 100644 --- a/impeller/renderer/backend/metal/BUILD.gn +++ b/impeller/renderer/backend/metal/BUILD.gn @@ -8,6 +8,8 @@ impeller_component("metal") { sources = [ "allocator_mtl.h", "allocator_mtl.mm", + "app_state_notifier.h", + "app_state_notifier.mm", "blit_command_mtl.h", "blit_command_mtl.mm", "blit_pass_mtl.h", @@ -54,4 +56,7 @@ impeller_component("metal") { ] frameworks = [ "Metal.framework" ] + if (is_ios) { + frameworks += [ "UIKit.framework" ] + } } diff --git a/impeller/renderer/backend/metal/app_state_notifier.h b/impeller/renderer/backend/metal/app_state_notifier.h new file mode 100644 index 0000000000000..6c0038fde1a2b --- /dev/null +++ b/impeller/renderer/backend/metal/app_state_notifier.h @@ -0,0 +1,32 @@ +// 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. + +#pragma once + +#include +#include + +namespace impeller { + +class AppStateNotifier { + public: + struct AppStateNotifierImpl; + + AppStateNotifier(); + + /// Callback is executed on the platform thread. + void SetAppDidBecomeActiveCallback(std::function callback) { + app_did_become_active_callback_ = callback; + } + + void OnAppBecameActive(); + + private: + static void DeleteImpl(AppStateNotifierImpl* impl); + std::function app_did_become_active_callback_; + std::unique_ptr + impl_; +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/app_state_notifier.mm b/impeller/renderer/backend/metal/app_state_notifier.mm new file mode 100644 index 0000000000000..9acf7bda06a83 --- /dev/null +++ b/impeller/renderer/backend/metal/app_state_notifier.mm @@ -0,0 +1,94 @@ +// 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/renderer/backend/metal/app_state_notifier.h" + +#import + +#include "flutter/fml/logging.h" + +#if !__has_feature(objc_arc) +#error ARC must be enabled ! +#endif + +#ifdef FML_OS_IOS +#import +#endif + +@interface ImpellerAppStateNotifier : NSObject +@property(nonatomic, assign) impeller::AppStateNotifier* parent; +- (instancetype)initWithParent:(impeller::AppStateNotifier*)parent + NS_DESIGNATED_INITIALIZER; +- (void)onAppBecameActive:(NSNotification*)notification; +@end + +#ifdef FML_OS_IOS +@implementation ImpellerAppStateNotifier +- (instancetype)initWithParent:(impeller::AppStateNotifier*)parent { + self = [super init]; + if (self) { + _parent = parent; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(onAppBecameActive:) + name:UIApplicationDidBecomeActiveNotification + object:nil]; + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)onAppBecameActive:(NSNotification*)notification { + _parent->OnAppBecameActive(); +} + +@end +#else + +@implementation ImpellerAppStateNotifier + +- (instancetype)init { + self = [self initWithParent:nil]; + return self; +} + +- (instancetype)initWithParent:(impeller::AppStateNotifier*)parent { + // Note: Not supported on macOS. + return [super init]; +} + +- (void)onAppBecameActive:(NSNotification*)notification { +} +@end + +#endif // FML_OS_IOS + +namespace impeller { + +struct AppStateNotifier::AppStateNotifierImpl { + ImpellerAppStateNotifier* notifier_; +}; + +AppStateNotifier::AppStateNotifier() + : impl_( + new AppStateNotifierImpl{ + .notifier_ = + [[ImpellerAppStateNotifier alloc] initWithParent:this], + }, + &AppStateNotifier::DeleteImpl) {} + +void AppStateNotifier::OnAppBecameActive() { + if (app_did_become_active_callback_) { + app_did_become_active_callback_(); + } +} + +void AppStateNotifier::DeleteImpl(AppStateNotifierImpl* impl) { + delete impl; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/context_mtl.h b/impeller/renderer/backend/metal/context_mtl.h index c272c68769fe9..5947dd5c049e2 100644 --- a/impeller/renderer/backend/metal/context_mtl.h +++ b/impeller/renderer/backend/metal/context_mtl.h @@ -15,6 +15,7 @@ #include "impeller/base/backend_cast.h" #include "impeller/core/sampler.h" #include "impeller/renderer/backend/metal/allocator_mtl.h" +#include "impeller/renderer/backend/metal/app_state_notifier.h" #include "impeller/renderer/backend/metal/command_buffer_mtl.h" #include "impeller/renderer/backend/metal/pipeline_library_mtl.h" #include "impeller/renderer/backend/metal/shader_library_mtl.h" @@ -93,6 +94,9 @@ class ContextMTL final : public Context, std::shared_ptr GetIsGpuDisabledSyncSwitch() const; + // |Context| + void StoreTaskForGPU(std::function task) override; + private: id device_ = nullptr; id command_queue_ = nullptr; @@ -103,6 +107,8 @@ class ContextMTL final : public Context, std::shared_ptr device_capabilities_; std::shared_ptr raster_message_loop_; std::shared_ptr is_gpu_disabled_sync_switch_; + std::vector> tasks_awaiting_gpu_; + AppStateNotifier app_state_notifier_; bool is_valid_ = false; ContextMTL( @@ -114,6 +120,8 @@ class ContextMTL final : public Context, std::shared_ptr CreateCommandBufferInQueue( id queue) const; + void FlushTasksAwaitingGPU(); + FML_DISALLOW_COPY_AND_ASSIGN(ContextMTL); }; diff --git a/impeller/renderer/backend/metal/context_mtl.mm b/impeller/renderer/backend/metal/context_mtl.mm index 528c1854802a3..4cd424ad698d5 100644 --- a/impeller/renderer/backend/metal/context_mtl.mm +++ b/impeller/renderer/backend/metal/context_mtl.mm @@ -83,6 +83,9 @@ static bool DeviceSupportsComputeSubgroups(id device) { return; } + app_state_notifier_.SetAppDidBecomeActiveCallback( + [this] { this->FlushTasksAwaitingGPU(); }); + // Worker task runner. { raster_message_loop_ = fml::ConcurrentMessageLoop::Create( @@ -376,4 +379,15 @@ new ContextMTL(device, command_queue, return buffer; } +void ContextMTL::StoreTaskForGPU(std::function task) { + tasks_awaiting_gpu_.emplace_back(std::move(task)); +} + +void ContextMTL::FlushTasksAwaitingGPU() { + for (const auto& task : tasks_awaiting_gpu_) { + task(); + } + tasks_awaiting_gpu_.clear(); +} + } // namespace impeller diff --git a/impeller/renderer/context.h b/impeller/renderer/context.h index 3cc847235cd7d..03c66340cca07 100644 --- a/impeller/renderer/context.h +++ b/impeller/renderer/context.h @@ -176,6 +176,12 @@ class Context { CaptureContext capture; + /// Threadsafe. + /// `task` should be executed on the platform thread. + virtual void StoreTaskForGPU(std::function task) { + FML_CHECK(false && "not supported in this context"); + } + protected: Context(); diff --git a/lib/ui/painting/image_encoding_impeller.cc b/lib/ui/painting/image_encoding_impeller.cc index 4e403d5d3f2bf..1e02a08c4aedc 100644 --- a/lib/ui/painting/image_encoding_impeller.cc +++ b/lib/ui/painting/image_encoding_impeller.cc @@ -56,21 +56,23 @@ sk_sp ConvertBufferToSkImage( return raster_image; } -void DoConvertImageToRasterImpeller( +[[nodiscard]] fml::Status DoConvertImageToRasterImpeller( const sk_sp& dl_image, - std::function>)> encode_task, + const std::function>)>& encode_task, const std::shared_ptr& is_gpu_disabled_sync_switch, const std::shared_ptr& impeller_context) { + fml::Status result; is_gpu_disabled_sync_switch->Execute( fml::SyncSwitch::Handlers() - .SetIfTrue([&encode_task] { - encode_task( - fml::Status(fml::StatusCode::kUnavailable, "GPU unavailable.")); + .SetIfTrue([&result] { + result = + fml::Status(fml::StatusCode::kUnavailable, "GPU unavailable."); }) .SetIfFalse([&dl_image, &encode_task, &impeller_context] { ImageEncodingImpeller::ConvertDlImageToSkImage( dl_image, std::move(encode_task), impeller_context); })); + return result; } } // namespace @@ -153,18 +155,57 @@ void ImageEncodingImpeller::ConvertImageToRaster( }; if (dl_image->owning_context() != DlImage::OwningContext::kRaster) { - DoConvertImageToRasterImpeller(dl_image, std::move(encode_task), - is_gpu_disabled_sync_switch, - impeller_context); + fml::Status status = DoConvertImageToRasterImpeller( + dl_image, encode_task, is_gpu_disabled_sync_switch, impeller_context); + if (!status.ok()) { + if (status.code() == fml::StatusCode::kUnavailable) { + impeller_context->StoreTaskForGPU([dl_image = std::move(dl_image), + encode_task = std::move(encode_task), + is_gpu_disabled_sync_switch, + impeller_context] { + fml::Status retry_status = DoConvertImageToRasterImpeller( + dl_image, encode_task, is_gpu_disabled_sync_switch, + impeller_context); + if (!retry_status.ok()) { + encode_task(retry_status); + } + }); + } else { + encode_task(status); + } + } return; } raster_task_runner->PostTask([dl_image, encode_task = std::move(encode_task), io_task_runner, is_gpu_disabled_sync_switch, - impeller_context]() mutable { - DoConvertImageToRasterImpeller(dl_image, std::move(encode_task), - is_gpu_disabled_sync_switch, - impeller_context); + impeller_context, + raster_task_runner]() mutable { + fml::Status status = DoConvertImageToRasterImpeller( + dl_image, std::move(encode_task), is_gpu_disabled_sync_switch, + impeller_context); + if (!status.ok()) { + if (status.code() == fml::StatusCode::kUnavailable) { + impeller_context->StoreTaskForGPU( + [dl_image = std::move(dl_image), + encode_task = std::move(encode_task), is_gpu_disabled_sync_switch, + impeller_context, raster_task_runner] { + raster_task_runner->PostTask( + [dl_image = std::move(dl_image), + encode_task = std::move(encode_task), + is_gpu_disabled_sync_switch, impeller_context] { + fml::Status retry_status = DoConvertImageToRasterImpeller( + dl_image, encode_task, is_gpu_disabled_sync_switch, + impeller_context); + if (!retry_status.ok()) { + encode_task(retry_status); + } + }); + }); + } else { + encode_task(status); + } + } }); } From de678446ab5979c6837ea1a3bb8c634d6bde6908 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 13 Oct 2023 10:16:38 -0700 Subject: [PATCH 02/11] removed the app_state_notifier, opting for observers on the sync switch --- fml/synchronization/sync_switch.cc | 20 +++- fml/synchronization/sync_switch.h | 16 ++++ impeller/renderer/backend/metal/BUILD.gn | 5 - .../backend/metal/app_state_notifier.h | 32 ------- .../backend/metal/app_state_notifier.mm | 94 ------------------- impeller/renderer/backend/metal/context_mtl.h | 13 ++- .../renderer/backend/metal/context_mtl.mm | 17 +++- 7 files changed, 60 insertions(+), 137 deletions(-) delete mode 100644 impeller/renderer/backend/metal/app_state_notifier.h delete mode 100644 impeller/renderer/backend/metal/app_state_notifier.mm diff --git a/fml/synchronization/sync_switch.cc b/fml/synchronization/sync_switch.cc index ac951062267f8..7340086af95b3 100644 --- a/fml/synchronization/sync_switch.cc +++ b/fml/synchronization/sync_switch.cc @@ -32,8 +32,26 @@ void SyncSwitch::Execute(const SyncSwitch::Handlers& handlers) const { } void SyncSwitch::SetSwitch(bool value) { + { + fml::UniqueLock lock(*mutex_); + value_ = value; + } + for (Observer* observer : observers_) { + observer->OnSyncSwitchUpdate(value); + } +} + +void SyncSwitch::AddObserver(Observer* observer) const { fml::UniqueLock lock(*mutex_); - value_ = value; + if (std::find(observers_.begin(), observers_.end(), observer) == + observers_.end()) { + observers_.push_back(observer); + } } +void SyncSwitch::RemoveObserver(Observer* observer) const { + fml::UniqueLock lock(*mutex_); + observers_.erase(std::remove(observers_.begin(), observers_.end(), observer), + observers_.end()); +} } // namespace fml diff --git a/fml/synchronization/sync_switch.h b/fml/synchronization/sync_switch.h index 470072fe4f25d..388c260f3f864 100644 --- a/fml/synchronization/sync_switch.h +++ b/fml/synchronization/sync_switch.h @@ -21,6 +21,15 @@ namespace fml { /// at a time. class SyncSwitch { public: + /// Observes changes to the SyncSwitch. + class Observer { + public: + virtual ~Observer() = default; + /// `new_value` not guaranteed to be the value of the SyncSwitch during + /// execution, it should be checked with calls to SyncSwitch::Execute. + virtual void OnSyncSwitchUpdate(bool new_value) = 0; + }; + /// Represents the 2 code paths available when calling |SyncSwitch::Execute|. struct Handlers { /// Sets the handler that will be executed if the |SyncSwitch| is true. @@ -53,8 +62,15 @@ class SyncSwitch { /// @param[in] value New value for the |SyncSwitch|. void SetSwitch(bool value); + /// Threadsafe. + void AddObserver(Observer* observer) const; + + /// Threadsafe. + void RemoveObserver(Observer* observer) const; + private: mutable std::unique_ptr mutex_; + mutable std::vector observers_; bool value_; FML_DISALLOW_COPY_AND_ASSIGN(SyncSwitch); diff --git a/impeller/renderer/backend/metal/BUILD.gn b/impeller/renderer/backend/metal/BUILD.gn index 8e84084f055f4..9a0f6a631e3d7 100644 --- a/impeller/renderer/backend/metal/BUILD.gn +++ b/impeller/renderer/backend/metal/BUILD.gn @@ -8,8 +8,6 @@ impeller_component("metal") { sources = [ "allocator_mtl.h", "allocator_mtl.mm", - "app_state_notifier.h", - "app_state_notifier.mm", "blit_command_mtl.h", "blit_command_mtl.mm", "blit_pass_mtl.h", @@ -56,7 +54,4 @@ impeller_component("metal") { ] frameworks = [ "Metal.framework" ] - if (is_ios) { - frameworks += [ "UIKit.framework" ] - } } diff --git a/impeller/renderer/backend/metal/app_state_notifier.h b/impeller/renderer/backend/metal/app_state_notifier.h deleted file mode 100644 index 6c0038fde1a2b..0000000000000 --- a/impeller/renderer/backend/metal/app_state_notifier.h +++ /dev/null @@ -1,32 +0,0 @@ -// 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. - -#pragma once - -#include -#include - -namespace impeller { - -class AppStateNotifier { - public: - struct AppStateNotifierImpl; - - AppStateNotifier(); - - /// Callback is executed on the platform thread. - void SetAppDidBecomeActiveCallback(std::function callback) { - app_did_become_active_callback_ = callback; - } - - void OnAppBecameActive(); - - private: - static void DeleteImpl(AppStateNotifierImpl* impl); - std::function app_did_become_active_callback_; - std::unique_ptr - impl_; -}; - -} // namespace impeller diff --git a/impeller/renderer/backend/metal/app_state_notifier.mm b/impeller/renderer/backend/metal/app_state_notifier.mm deleted file mode 100644 index 9acf7bda06a83..0000000000000 --- a/impeller/renderer/backend/metal/app_state_notifier.mm +++ /dev/null @@ -1,94 +0,0 @@ -// 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/renderer/backend/metal/app_state_notifier.h" - -#import - -#include "flutter/fml/logging.h" - -#if !__has_feature(objc_arc) -#error ARC must be enabled ! -#endif - -#ifdef FML_OS_IOS -#import -#endif - -@interface ImpellerAppStateNotifier : NSObject -@property(nonatomic, assign) impeller::AppStateNotifier* parent; -- (instancetype)initWithParent:(impeller::AppStateNotifier*)parent - NS_DESIGNATED_INITIALIZER; -- (void)onAppBecameActive:(NSNotification*)notification; -@end - -#ifdef FML_OS_IOS -@implementation ImpellerAppStateNotifier -- (instancetype)initWithParent:(impeller::AppStateNotifier*)parent { - self = [super init]; - if (self) { - _parent = parent; - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(onAppBecameActive:) - name:UIApplicationDidBecomeActiveNotification - object:nil]; - } - return self; -} - -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)onAppBecameActive:(NSNotification*)notification { - _parent->OnAppBecameActive(); -} - -@end -#else - -@implementation ImpellerAppStateNotifier - -- (instancetype)init { - self = [self initWithParent:nil]; - return self; -} - -- (instancetype)initWithParent:(impeller::AppStateNotifier*)parent { - // Note: Not supported on macOS. - return [super init]; -} - -- (void)onAppBecameActive:(NSNotification*)notification { -} -@end - -#endif // FML_OS_IOS - -namespace impeller { - -struct AppStateNotifier::AppStateNotifierImpl { - ImpellerAppStateNotifier* notifier_; -}; - -AppStateNotifier::AppStateNotifier() - : impl_( - new AppStateNotifierImpl{ - .notifier_ = - [[ImpellerAppStateNotifier alloc] initWithParent:this], - }, - &AppStateNotifier::DeleteImpl) {} - -void AppStateNotifier::OnAppBecameActive() { - if (app_did_become_active_callback_) { - app_did_become_active_callback_(); - } -} - -void AppStateNotifier::DeleteImpl(AppStateNotifierImpl* impl) { - delete impl; -} - -} // namespace impeller diff --git a/impeller/renderer/backend/metal/context_mtl.h b/impeller/renderer/backend/metal/context_mtl.h index 5947dd5c049e2..a5a5725a7eab4 100644 --- a/impeller/renderer/backend/metal/context_mtl.h +++ b/impeller/renderer/backend/metal/context_mtl.h @@ -15,7 +15,6 @@ #include "impeller/base/backend_cast.h" #include "impeller/core/sampler.h" #include "impeller/renderer/backend/metal/allocator_mtl.h" -#include "impeller/renderer/backend/metal/app_state_notifier.h" #include "impeller/renderer/backend/metal/command_buffer_mtl.h" #include "impeller/renderer/backend/metal/pipeline_library_mtl.h" #include "impeller/renderer/backend/metal/shader_library_mtl.h" @@ -98,6 +97,16 @@ class ContextMTL final : public Context, void StoreTaskForGPU(std::function task) override; private: + class SyncSwitchObserver : public fml::SyncSwitch::Observer { + public: + SyncSwitchObserver(ContextMTL& parent); + virtual ~SyncSwitchObserver() = default; + void OnSyncSwitchUpdate(bool new_value) override; + + private: + ContextMTL& parent_; + }; + id device_ = nullptr; id command_queue_ = nullptr; std::shared_ptr shader_library_; @@ -108,7 +117,7 @@ class ContextMTL final : public Context, std::shared_ptr raster_message_loop_; std::shared_ptr is_gpu_disabled_sync_switch_; std::vector> tasks_awaiting_gpu_; - AppStateNotifier app_state_notifier_; + std::unique_ptr sync_switch_observer_; bool is_valid_ = false; ContextMTL( diff --git a/impeller/renderer/backend/metal/context_mtl.mm b/impeller/renderer/backend/metal/context_mtl.mm index 4cd424ad698d5..b484752637ce6 100644 --- a/impeller/renderer/backend/metal/context_mtl.mm +++ b/impeller/renderer/backend/metal/context_mtl.mm @@ -83,8 +83,8 @@ static bool DeviceSupportsComputeSubgroups(id device) { return; } - app_state_notifier_.SetAppDidBecomeActiveCallback( - [this] { this->FlushTasksAwaitingGPU(); }); + sync_switch_observer_.reset(new SyncSwitchObserver(*this)); + is_gpu_disabled_sync_switch_->AddObserver(sync_switch_observer_.get()); // Worker task runner. { @@ -287,7 +287,9 @@ new ContextMTL(device, command_queue, return context; } -ContextMTL::~ContextMTL() = default; +ContextMTL::~ContextMTL() { + is_gpu_disabled_sync_switch_->RemoveObserver(sync_switch_observer_.get()); +} Context::BackendType ContextMTL::GetBackendType() const { return Context::BackendType::kMetal; @@ -390,4 +392,13 @@ new ContextMTL(device, command_queue, tasks_awaiting_gpu_.clear(); } +ContextMTL::SyncSwitchObserver::SyncSwitchObserver(ContextMTL& parent) + : parent_(parent) {} + +void ContextMTL::SyncSwitchObserver::OnSyncSwitchUpdate(bool new_value) { + if (new_value) { + parent_.FlushTasksAwaitingGPU(); + } +} + } // namespace impeller From ecff4553e336227493af97ab8edbfa9a0fdee15a Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 13 Oct 2023 11:36:02 -0700 Subject: [PATCH 03/11] fixed the existing test --- impeller/renderer/backend/metal/context_mtl.h | 4 ++-- impeller/renderer/backend/metal/context_mtl.mm | 4 ++++ impeller/renderer/context.h | 4 ++++ lib/ui/fixtures/ui_test.dart | 11 +++++++++-- lib/ui/painting/image_encoding_unittests.cc | 15 +++++++++++++++ 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/impeller/renderer/backend/metal/context_mtl.h b/impeller/renderer/backend/metal/context_mtl.h index a5a5725a7eab4..165ff722e256a 100644 --- a/impeller/renderer/backend/metal/context_mtl.h +++ b/impeller/renderer/backend/metal/context_mtl.h @@ -6,8 +6,8 @@ #include +#include #include -#include #include "flutter/fml/concurrent_message_loop.h" #include "flutter/fml/macros.h" @@ -116,7 +116,7 @@ class ContextMTL final : public Context, std::shared_ptr device_capabilities_; std::shared_ptr raster_message_loop_; std::shared_ptr is_gpu_disabled_sync_switch_; - std::vector> tasks_awaiting_gpu_; + std::deque> tasks_awaiting_gpu_; std::unique_ptr sync_switch_observer_; bool is_valid_ = false; diff --git a/impeller/renderer/backend/metal/context_mtl.mm b/impeller/renderer/backend/metal/context_mtl.mm index b484752637ce6..d7ae85f2a808a 100644 --- a/impeller/renderer/backend/metal/context_mtl.mm +++ b/impeller/renderer/backend/metal/context_mtl.mm @@ -383,6 +383,10 @@ new ContextMTL(device, command_queue, void ContextMTL::StoreTaskForGPU(std::function task) { tasks_awaiting_gpu_.emplace_back(std::move(task)); + while (tasks_awaiting_gpu_.size() > kMaxTasksAwaitingGPU) { + tasks_awaiting_gpu_.front()(); + tasks_awaiting_gpu_.pop_front(); + } } void ContextMTL::FlushTasksAwaitingGPU() { diff --git a/impeller/renderer/context.h b/impeller/renderer/context.h index 03c66340cca07..882d7c29ff455 100644 --- a/impeller/renderer/context.h +++ b/impeller/renderer/context.h @@ -52,6 +52,10 @@ class Context { kVulkan, }; + /// The maximum number of tasks that should ever be stored for + /// `StoreTaskForGPU`. + static constexpr int32_t kMaxTasksAwaitingGPU = 10; + //---------------------------------------------------------------------------- /// @brief Destroys an Impeller context. /// diff --git a/lib/ui/fixtures/ui_test.dart b/lib/ui/fixtures/ui_test.dart index a4b8ada735b2c..92522c4300a5d 100644 --- a/lib/ui/fixtures/ui_test.dart +++ b/lib/ui/fixtures/ui_test.dart @@ -326,6 +326,8 @@ external void _validateExternal(Uint8List result); external void _validateError(String? error); @pragma('vm:external-name', 'TurnOffGPU') external void _turnOffGPU(); +@pragma('vm:external-name', 'FlushGpuAwaitingTasks') +external void _flushGpuAwaitingTasks(); @pragma('vm:entry-point') Future toByteDataWithoutGPU() async { @@ -339,11 +341,16 @@ Future toByteDataWithoutGPU() async { final Picture picture = pictureRecorder.endRecording(); final Image image = await picture.toImage(100, 100); _turnOffGPU(); + Timer flusher = Timer.periodic(Duration(milliseconds: 1), (timer) { + _flushGpuAwaitingTasks(); + }); try { ByteData? byteData = await image.toByteData(); _validateError(null); - } catch (ex) { - _validateError(ex.toString()); + } catch (error) { + _validateError(error.toString()); + } finally { + flusher.cancel(); } } diff --git a/lib/ui/painting/image_encoding_unittests.cc b/lib/ui/painting/image_encoding_unittests.cc index cf8abf76b2f4f..8d0df4a78f954 100644 --- a/lib/ui/painting/image_encoding_unittests.cc +++ b/lib/ui/painting/image_encoding_unittests.cc @@ -262,6 +262,21 @@ TEST_F(ShellTest, EncodeImageFailsWithoutGPUImpeller) { AddNativeCallback("TurnOffGPU", CREATE_NATIVE_ENTRY(turn_off_gpu)); + auto flush_awaiting_tasks = [&](Dart_NativeArguments args) { + task_runners.GetIOTaskRunner()->PostTask([&] { + std::shared_ptr impeller_context = + shell->GetIOManager()->GetImpellerContext(); + // This will cause the stored tasks to overflow and start throwing them + // away. + for (int i = 0; i < impeller::Context::kMaxTasksAwaitingGPU; ++i) { + impeller_context->StoreTaskForGPU([] {}); + } + }); + }; + + AddNativeCallback("FlushGpuAwaitingTasks", + CREATE_NATIVE_ENTRY(flush_awaiting_tasks)); + ASSERT_TRUE(shell->IsSetup()); auto configuration = RunConfiguration::InferFromSettings(settings); configuration.SetEntrypoint("toByteDataWithoutGPU"); From de82a23acd21d63cd1d2c8565f98ef5f66a1f46b Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 13 Oct 2023 12:13:57 -0700 Subject: [PATCH 04/11] added test for retrying --- .../renderer/backend/metal/context_mtl.mm | 4 +- lib/ui/fixtures/ui_test.dart | 29 +++++++++- lib/ui/painting/image_encoding_unittests.cc | 55 ++++++++++++++++++- shell/common/shell_test.cc | 4 +- shell/common/shell_test.h | 2 +- 5 files changed, 86 insertions(+), 8 deletions(-) diff --git a/impeller/renderer/backend/metal/context_mtl.mm b/impeller/renderer/backend/metal/context_mtl.mm index d7ae85f2a808a..5297ab987145b 100644 --- a/impeller/renderer/backend/metal/context_mtl.mm +++ b/impeller/renderer/backend/metal/context_mtl.mm @@ -399,8 +399,8 @@ new ContextMTL(device, command_queue, ContextMTL::SyncSwitchObserver::SyncSwitchObserver(ContextMTL& parent) : parent_(parent) {} -void ContextMTL::SyncSwitchObserver::OnSyncSwitchUpdate(bool new_value) { - if (new_value) { +void ContextMTL::SyncSwitchObserver::OnSyncSwitchUpdate(bool new_is_disabled) { + if (!new_is_disabled) { parent_.FlushTasksAwaitingGPU(); } } diff --git a/lib/ui/fixtures/ui_test.dart b/lib/ui/fixtures/ui_test.dart index 92522c4300a5d..ea74dd84650fa 100644 --- a/lib/ui/fixtures/ui_test.dart +++ b/lib/ui/fixtures/ui_test.dart @@ -325,9 +325,11 @@ external void _validateExternal(Uint8List result); @pragma('vm:external-name', 'ValidateError') external void _validateError(String? error); @pragma('vm:external-name', 'TurnOffGPU') -external void _turnOffGPU(); +external void _turnOffGPU(bool value); @pragma('vm:external-name', 'FlushGpuAwaitingTasks') external void _flushGpuAwaitingTasks(); +@pragma('vm:external-name', 'ValidateNotNull') +external void _validateNotNull(Object? object); @pragma('vm:entry-point') Future toByteDataWithoutGPU() async { @@ -340,7 +342,7 @@ Future toByteDataWithoutGPU() async { canvas.drawCircle(c, 25.0, paint); final Picture picture = pictureRecorder.endRecording(); final Image image = await picture.toImage(100, 100); - _turnOffGPU(); + _turnOffGPU(true); Timer flusher = Timer.periodic(Duration(milliseconds: 1), (timer) { _flushGpuAwaitingTasks(); }); @@ -354,6 +356,29 @@ Future toByteDataWithoutGPU() async { } } +@pragma('vm:entry-point') +Future toByteDataRetries() async { + final PictureRecorder pictureRecorder = PictureRecorder(); + final Canvas canvas = Canvas(pictureRecorder); + final Paint paint = Paint() + ..color = Color.fromRGBO(255, 255, 255, 1.0) + ..style = PaintingStyle.fill; + final Offset c = Offset(50.0, 50.0); + canvas.drawCircle(c, 25.0, paint); + final Picture picture = pictureRecorder.endRecording(); + final Image image = await picture.toImage(100, 100); + _turnOffGPU(true); + Future.delayed(Duration(milliseconds: 10), () { + _turnOffGPU(false); + }); + try { + ByteData? byteData = await image.toByteData(); + _validateNotNull(byteData); + } catch (error) { + _validateNotNull(null); + } +} + @pragma('vm:entry-point') Future pumpImage() async { const int width = 60; diff --git a/lib/ui/painting/image_encoding_unittests.cc b/lib/ui/painting/image_encoding_unittests.cc index 8d0df4a78f954..8de593ce4bb73 100644 --- a/lib/ui/painting/image_encoding_unittests.cc +++ b/lib/ui/painting/image_encoding_unittests.cc @@ -226,6 +226,55 @@ std::shared_ptr MakeConvertDlImageToSkImageContext( } } // namespace +TEST_F(ShellTest, EncodeImageRetries) { +#ifndef FML_OS_MACOSX + // Only works on macos currently. + GTEST_SKIP(); +#endif + Settings settings = CreateSettingsForFixture(); + settings.enable_impeller = true; + TaskRunners task_runners("test", // label + GetCurrentTaskRunner(), // platform + CreateNewThread(), // raster + CreateNewThread(), // ui + CreateNewThread() // io + ); + + std::unique_ptr shell = CreateShell({ + .settings = settings, + .task_runners = task_runners, + }); + + auto turn_off_gpu = [&](Dart_NativeArguments args) { + auto handle = Dart_GetNativeArgument(args, 0); + bool value = true; + ASSERT_TRUE(Dart_IsBoolean(handle)); + Dart_BooleanValue(handle, &value); + TurnOffGPU(shell.get(), value); + }; + + AddNativeCallback("TurnOffGPU", CREATE_NATIVE_ENTRY(turn_off_gpu)); + + auto validate_not_null = [&](Dart_NativeArguments args) { + auto handle = Dart_GetNativeArgument(args, 0); + EXPECT_FALSE(Dart_IsNull(handle)); + message_latch.Signal(); + }; + + AddNativeCallback("ValidateNotNull", CREATE_NATIVE_ENTRY(validate_not_null)); + + ASSERT_TRUE(shell->IsSetup()); + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("toByteDataRetries"); + + shell->RunEngine(std::move(configuration), [&](auto result) { + ASSERT_EQ(result, Engine::RunStatus::Success); + }); + + message_latch.Wait(); + DestroyShell(std::move(shell), task_runners); +} + TEST_F(ShellTest, EncodeImageFailsWithoutGPUImpeller) { #ifndef FML_OS_MACOSX // Only works on macos currently. @@ -257,7 +306,11 @@ TEST_F(ShellTest, EncodeImageFailsWithoutGPUImpeller) { }); auto turn_off_gpu = [&](Dart_NativeArguments args) { - TurnOffGPU(shell.get()); + auto handle = Dart_GetNativeArgument(args, 0); + bool value = true; + ASSERT_TRUE(Dart_IsBoolean(handle)); + Dart_BooleanValue(handle, &value); + TurnOffGPU(shell.get(), true); }; AddNativeCallback("TurnOffGPU", CREATE_NATIVE_ENTRY(turn_off_gpu)); diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index f9f59f7366de8..c673d4b429c9f 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -382,8 +382,8 @@ size_t ShellTest::GetLiveTrackedPathCount( }); } -void ShellTest::TurnOffGPU(Shell* shell) { - shell->is_gpu_disabled_sync_switch_->SetSwitch(true); +void ShellTest::TurnOffGPU(Shell* shell, bool value) { + shell->is_gpu_disabled_sync_switch_->SetSwitch(value); } } // namespace testing diff --git a/shell/common/shell_test.h b/shell/common/shell_test.h index dfcc22223e5ac..7ded997fbcdc5 100644 --- a/shell/common/shell_test.h +++ b/shell/common/shell_test.h @@ -135,7 +135,7 @@ class ShellTest : public FixtureTest { static size_t GetLiveTrackedPathCount( const std::shared_ptr& tracker); - static void TurnOffGPU(Shell* shell); + static void TurnOffGPU(Shell* shell, bool value); private: ThreadHost thread_host_; From 33f2ae65e566e2e31e3fb9a2f2b7ad6c5b0a8049 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 13 Oct 2023 13:11:11 -0700 Subject: [PATCH 05/11] factored out shared code --- lib/ui/painting/image_encoding_impeller.cc | 84 +++++++++++----------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/lib/ui/painting/image_encoding_impeller.cc b/lib/ui/painting/image_encoding_impeller.cc index 1e02a08c4aedc..593fd196a59b0 100644 --- a/lib/ui/painting/image_encoding_impeller.cc +++ b/lib/ui/painting/image_encoding_impeller.cc @@ -75,6 +75,42 @@ sk_sp ConvertBufferToSkImage( return result; } +void DoConvertImageToRasterImpellerWithRetry( + const sk_sp& dl_image, + const std::function>)>& encode_task, + const std::shared_ptr& is_gpu_disabled_sync_switch, + const std::shared_ptr& impeller_context, + const fml::RefPtr& retry_runner) { + fml::Status status = DoConvertImageToRasterImpeller( + dl_image, std::move(encode_task), is_gpu_disabled_sync_switch, + impeller_context); + if (!status.ok()) { + if (status.code() == fml::StatusCode::kUnavailable) { + impeller_context->StoreTaskForGPU( + [dl_image = std::move(dl_image), encode_task = std::move(encode_task), + is_gpu_disabled_sync_switch, impeller_context, retry_runner] { + auto retry_task = [dl_image = std::move(dl_image), + encode_task = std::move(encode_task), + is_gpu_disabled_sync_switch, impeller_context] { + fml::Status retry_status = DoConvertImageToRasterImpeller( + dl_image, encode_task, is_gpu_disabled_sync_switch, + impeller_context); + if (!retry_status.ok()) { + encode_task(retry_status); + } + }; + if (retry_runner) { + retry_runner->PostTask(retry_task); + } else { + retry_task(); + } + }); + } else { + encode_task(status); + } + } +} + } // namespace void ImageEncodingImpeller::ConvertDlImageToSkImage( @@ -155,25 +191,9 @@ void ImageEncodingImpeller::ConvertImageToRaster( }; if (dl_image->owning_context() != DlImage::OwningContext::kRaster) { - fml::Status status = DoConvertImageToRasterImpeller( - dl_image, encode_task, is_gpu_disabled_sync_switch, impeller_context); - if (!status.ok()) { - if (status.code() == fml::StatusCode::kUnavailable) { - impeller_context->StoreTaskForGPU([dl_image = std::move(dl_image), - encode_task = std::move(encode_task), - is_gpu_disabled_sync_switch, - impeller_context] { - fml::Status retry_status = DoConvertImageToRasterImpeller( - dl_image, encode_task, is_gpu_disabled_sync_switch, - impeller_context); - if (!retry_status.ok()) { - encode_task(retry_status); - } - }); - } else { - encode_task(status); - } - } + DoConvertImageToRasterImpellerWithRetry( + dl_image, encode_task, is_gpu_disabled_sync_switch, impeller_context, + /*retry_runner=*/nullptr); return; } @@ -181,31 +201,9 @@ void ImageEncodingImpeller::ConvertImageToRaster( io_task_runner, is_gpu_disabled_sync_switch, impeller_context, raster_task_runner]() mutable { - fml::Status status = DoConvertImageToRasterImpeller( + DoConvertImageToRasterImpellerWithRetry( dl_image, std::move(encode_task), is_gpu_disabled_sync_switch, - impeller_context); - if (!status.ok()) { - if (status.code() == fml::StatusCode::kUnavailable) { - impeller_context->StoreTaskForGPU( - [dl_image = std::move(dl_image), - encode_task = std::move(encode_task), is_gpu_disabled_sync_switch, - impeller_context, raster_task_runner] { - raster_task_runner->PostTask( - [dl_image = std::move(dl_image), - encode_task = std::move(encode_task), - is_gpu_disabled_sync_switch, impeller_context] { - fml::Status retry_status = DoConvertImageToRasterImpeller( - dl_image, encode_task, is_gpu_disabled_sync_switch, - impeller_context); - if (!retry_status.ok()) { - encode_task(retry_status); - } - }); - }); - } else { - encode_task(status); - } - } + impeller_context, raster_task_runner); }); } From 4d6722cb46e9c3e998be1d3af8f09a925eb6be32 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 13 Oct 2023 13:14:11 -0700 Subject: [PATCH 06/11] quick cleanup --- fml/synchronization/sync_switch.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fml/synchronization/sync_switch.h b/fml/synchronization/sync_switch.h index 388c260f3f864..c01c05aac25ff 100644 --- a/fml/synchronization/sync_switch.h +++ b/fml/synchronization/sync_switch.h @@ -25,9 +25,10 @@ class SyncSwitch { class Observer { public: virtual ~Observer() = default; - /// `new_value` not guaranteed to be the value of the SyncSwitch during - /// execution, it should be checked with calls to SyncSwitch::Execute. - virtual void OnSyncSwitchUpdate(bool new_value) = 0; + /// `new_is_disabled` not guaranteed to be the value of the SyncSwitch + /// during execution, it should be checked with calls to + /// SyncSwitch::Execute. + virtual void OnSyncSwitchUpdate(bool new_is_disabled) = 0; }; /// Represents the 2 code paths available when calling |SyncSwitch::Execute|. From 0edb94627580e5886dac9b4cc1a46545cae8b39a Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 13 Oct 2023 14:00:11 -0700 Subject: [PATCH 07/11] tidy --- lib/ui/painting/image_encoding_impeller.cc | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/ui/painting/image_encoding_impeller.cc b/lib/ui/painting/image_encoding_impeller.cc index 593fd196a59b0..90c7d3e4ee319 100644 --- a/lib/ui/painting/image_encoding_impeller.cc +++ b/lib/ui/painting/image_encoding_impeller.cc @@ -70,27 +70,26 @@ sk_sp ConvertBufferToSkImage( }) .SetIfFalse([&dl_image, &encode_task, &impeller_context] { ImageEncodingImpeller::ConvertDlImageToSkImage( - dl_image, std::move(encode_task), impeller_context); + dl_image, encode_task, impeller_context); })); return result; } void DoConvertImageToRasterImpellerWithRetry( const sk_sp& dl_image, - const std::function>)>& encode_task, + std::function>)>&& encode_task, const std::shared_ptr& is_gpu_disabled_sync_switch, const std::shared_ptr& impeller_context, const fml::RefPtr& retry_runner) { fml::Status status = DoConvertImageToRasterImpeller( - dl_image, std::move(encode_task), is_gpu_disabled_sync_switch, - impeller_context); + dl_image, encode_task, is_gpu_disabled_sync_switch, impeller_context); if (!status.ok()) { if (status.code() == fml::StatusCode::kUnavailable) { impeller_context->StoreTaskForGPU( - [dl_image = std::move(dl_image), encode_task = std::move(encode_task), - is_gpu_disabled_sync_switch, impeller_context, retry_runner] { - auto retry_task = [dl_image = std::move(dl_image), - encode_task = std::move(encode_task), + [dl_image, encode_task = std::move(encode_task), + is_gpu_disabled_sync_switch, impeller_context, + retry_runner]() mutable { + auto retry_task = [dl_image, encode_task = std::move(encode_task), is_gpu_disabled_sync_switch, impeller_context] { fml::Status retry_status = DoConvertImageToRasterImpeller( dl_image, encode_task, is_gpu_disabled_sync_switch, @@ -191,9 +190,10 @@ void ImageEncodingImpeller::ConvertImageToRaster( }; if (dl_image->owning_context() != DlImage::OwningContext::kRaster) { - DoConvertImageToRasterImpellerWithRetry( - dl_image, encode_task, is_gpu_disabled_sync_switch, impeller_context, - /*retry_runner=*/nullptr); + DoConvertImageToRasterImpellerWithRetry(dl_image, std::move(encode_task), + is_gpu_disabled_sync_switch, + impeller_context, + /*retry_runner=*/nullptr); return; } From cd38f208590163fa40261fd1f5ef63fccdeb4f6a Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 13 Oct 2023 14:58:07 -0700 Subject: [PATCH 08/11] Added comments --- lib/ui/painting/image_encoding_impeller.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/ui/painting/image_encoding_impeller.cc b/lib/ui/painting/image_encoding_impeller.cc index 90c7d3e4ee319..0801f7930d12d 100644 --- a/lib/ui/painting/image_encoding_impeller.cc +++ b/lib/ui/painting/image_encoding_impeller.cc @@ -75,6 +75,9 @@ sk_sp ConvertBufferToSkImage( return result; } +/// Same as `DoConvertImageToRasterImpeller` but it will attempt to retry the +/// operation if `DoConvertImageToRasterImpeller` returns kUnavailable when the +/// GPU becomes available again. void DoConvertImageToRasterImpellerWithRetry( const sk_sp& dl_image, std::function>)>&& encode_task, @@ -84,6 +87,8 @@ void DoConvertImageToRasterImpellerWithRetry( fml::Status status = DoConvertImageToRasterImpeller( dl_image, encode_task, is_gpu_disabled_sync_switch, impeller_context); if (!status.ok()) { + // If the conversion failed because of the GPU is unavailable, store the + // task on the Context so it can be executed when the GPU becomes available. if (status.code() == fml::StatusCode::kUnavailable) { impeller_context->StoreTaskForGPU( [dl_image, encode_task = std::move(encode_task), @@ -95,9 +100,13 @@ void DoConvertImageToRasterImpellerWithRetry( dl_image, encode_task, is_gpu_disabled_sync_switch, impeller_context); if (!retry_status.ok()) { + // The retry failed for some reason, maybe the GPU became + // unavailable again. Don't retry again, just fail in this case. encode_task(retry_status); } }; + // If a `retry_runner` is specified, post the retry to it, otherwise + // execute it directly. if (retry_runner) { retry_runner->PostTask(retry_task); } else { @@ -105,6 +114,7 @@ void DoConvertImageToRasterImpellerWithRetry( } }); } else { + // Pass on errors that are not `kUnavailable`. encode_task(status); } } From 56298e9378482ebe472c8606bd1ae136ecd9561b Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 13 Oct 2023 15:13:21 -0700 Subject: [PATCH 09/11] updated docstring --- impeller/renderer/context.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/impeller/renderer/context.h b/impeller/renderer/context.h index 882d7c29ff455..ac7184ea9dc80 100644 --- a/impeller/renderer/context.h +++ b/impeller/renderer/context.h @@ -180,8 +180,15 @@ class Context { CaptureContext capture; + /// Stores a task on the `ContextMTL` that is awaiting access for the GPU. + /// + /// The task will be executed in the event that the GPU access has changed to + /// being available or that the task has been canceled. The task should + /// operate with the `SyncSwitch` to make sure the GPU is accessible. + /// /// Threadsafe. - /// `task` should be executed on the platform thread. + /// + /// `task` will be executed on the platform thread. virtual void StoreTaskForGPU(std::function task) { FML_CHECK(false && "not supported in this context"); } From 551f6ee6b87802d5c4ab8c22196018858b318334 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 13 Oct 2023 15:32:25 -0700 Subject: [PATCH 10/11] more docstrings --- impeller/renderer/context.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/impeller/renderer/context.h b/impeller/renderer/context.h index ac7184ea9dc80..3d0cb9d97ad6e 100644 --- a/impeller/renderer/context.h +++ b/impeller/renderer/context.h @@ -54,6 +54,10 @@ class Context { /// The maximum number of tasks that should ever be stored for /// `StoreTaskForGPU`. + /// + /// This number was arbitrarily chosen. The idea is that this is a somewhat + /// rare situation where tasks happen to get executed in that tiny amount of + /// time while an app is being backgrounded but still executing. static constexpr int32_t kMaxTasksAwaitingGPU = 10; //---------------------------------------------------------------------------- From 3cd40b34180e49b280f8e74bef5b9d817e771520 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Mon, 16 Oct 2023 11:01:51 -0700 Subject: [PATCH 11/11] fixed platform tests to have a syncswitch --- .../ios/framework/Source/FlutterEnginePlatformViewTest.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm index 0b5526a5341a2..51c76156b4f98 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm @@ -65,6 +65,7 @@ @implementation FlutterEnginePlatformViewTest - (void)setUp { fml::MessageLoop::EnsureInitializedForCurrentThread(); auto thread_task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); + auto sync_switch = std::make_shared(); flutter::TaskRunners runners(/*label=*/self.name.UTF8String, /*platform=*/thread_task_runner, /*raster=*/thread_task_runner, @@ -76,7 +77,7 @@ - (void)setUp { /*platform_views_controller=*/nil, /*task_runners=*/runners, /*worker_task_runner=*/nil, - /*is_gpu_disabled_sync_switch=*/nil); + /*is_gpu_disabled_sync_switch=*/sync_switch); weak_factory = std::make_unique>(platform_view.get()); } @@ -98,6 +99,7 @@ - (void)testMsaaSampleCount { fake_delegate.settings_.msaa_samples = 4; auto thread_task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); + auto sync_switch = std::make_shared(); flutter::TaskRunners runners(/*label=*/self.name.UTF8String, /*platform=*/thread_task_runner, /*raster=*/thread_task_runner, @@ -109,7 +111,7 @@ - (void)testMsaaSampleCount { /*platform_views_controller=*/nil, /*task_runners=*/runners, /*worker_task_runner=*/nil, - /*is_gpu_disabled_sync_switch=*/nil); + /*is_gpu_disabled_sync_switch=*/sync_switch); XCTAssertEqual(msaa_4x_platform_view->GetIosContext()->GetMsaaSampleCount(), MsaaSampleCount::kFour);