From 82f293334700f536bf8906a65cb3ee49912c27d2 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Mon, 23 Jun 2025 10:38:02 -0700 Subject: [PATCH] [CP] Fix pink images when returning from background on iOS (#170846) Includes: * https://github.com/flutter/flutter/pull/169596 * https://github.com/flutter/flutter/pull/169378 Fix image decode errors on iOS that could occur if a push notification triggered image decoding while the app is backgrounded. --- .../flutter/ci/licenses_golden/excluded_files | 1 + .../backend/metal/playground_impl_mtl.h | 5 +- .../backend/metal/playground_impl_mtl.mm | 4 ++ .../flutter/impeller/playground/playground.cc | 4 ++ .../flutter/impeller/playground/playground.h | 5 ++ .../impeller/playground/playground_impl.h | 2 + .../backend/gles/command_buffer_gles.cc | 3 +- .../backend/gles/command_buffer_gles.h | 3 +- .../impeller/renderer/backend/metal/BUILD.gn | 1 + .../backend/metal/command_buffer_mtl.h | 3 +- .../backend/metal/command_buffer_mtl.mm | 6 +- .../renderer/backend/metal/context_mtl.h | 5 +- .../renderer/backend/metal/context_mtl.mm | 20 +++++- .../backend/metal/context_mtl_unittests.mm | 64 +++++++++++++++++++ .../backend/vulkan/command_buffer_vk.cc | 3 +- .../backend/vulkan/command_buffer_vk.h | 3 +- .../backend/vulkan/command_queue_vk.cc | 3 +- .../backend/vulkan/command_queue_vk.h | 6 +- .../impeller/renderer/command_buffer.cc | 9 +-- .../impeller/renderer/command_buffer.h | 15 +++-- .../impeller/renderer/command_queue.cc | 5 +- .../flutter/impeller/renderer/command_queue.h | 7 +- .../flutter/impeller/renderer/testing/mocks.h | 5 +- .../lib/ui/painting/image_decoder_impeller.cc | 13 +++- .../ui/painting/image_encoding_unittests.cc | 2 +- 25 files changed, 167 insertions(+), 30 deletions(-) create mode 100644 engine/src/flutter/impeller/renderer/backend/metal/context_mtl_unittests.mm diff --git a/engine/src/flutter/ci/licenses_golden/excluded_files b/engine/src/flutter/ci/licenses_golden/excluded_files index c8691cd8db..16fc482572 100644 --- a/engine/src/flutter/ci/licenses_golden/excluded_files +++ b/engine/src/flutter/ci/licenses_golden/excluded_files @@ -190,6 +190,7 @@ ../../../flutter/impeller/renderer/backend/gles/test ../../../flutter/impeller/renderer/backend/gles/unique_handle_gles_unittests.cc ../../../flutter/impeller/renderer/backend/metal/allocator_mtl_unittests.mm +../../../flutter/impeller/renderer/backend/metal/context_mtl_unittests.mm ../../../flutter/impeller/renderer/backend/metal/swapchain_transients_mtl_unittests.mm ../../../flutter/impeller/renderer/backend/metal/texture_mtl_unittests.mm ../../../flutter/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc diff --git a/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.h b/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.h index 0935e5c816..2028a82529 100644 --- a/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.h +++ b/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.h @@ -38,7 +38,7 @@ class PlaygroundImplMTL final : public PlaygroundImpl { std::shared_ptr context_; std::shared_ptr concurrent_loop_; std::shared_ptr swapchain_transients_; - std::shared_ptr is_gpu_disabled_sync_switch_; + std::shared_ptr is_gpu_disabled_sync_switch_; // |PlaygroundImpl| std::shared_ptr GetContext() const override; @@ -50,6 +50,9 @@ class PlaygroundImplMTL final : public PlaygroundImpl { std::unique_ptr AcquireSurfaceFrame( std::shared_ptr context) override; + // |PlaygroundImpl| + void SetGPUDisabled(bool disabled) const override; + PlaygroundImplMTL(const PlaygroundImplMTL&) = delete; PlaygroundImplMTL& operator=(const PlaygroundImplMTL&) = delete; diff --git a/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.mm b/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.mm index 978f01ab41..a276c11aea 100644 --- a/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.mm +++ b/engine/src/flutter/impeller/playground/backend/metal/playground_impl_mtl.mm @@ -138,4 +138,8 @@ fml::Status PlaygroundImplMTL::SetCapabilities( return fml::Status(); } +void PlaygroundImplMTL::SetGPUDisabled(bool disabled) const { + is_gpu_disabled_sync_switch_->SetSwitch(disabled); +} + } // namespace impeller diff --git a/engine/src/flutter/impeller/playground/playground.cc b/engine/src/flutter/impeller/playground/playground.cc index b69d2a3c97..164500ca49 100644 --- a/engine/src/flutter/impeller/playground/playground.cc +++ b/engine/src/flutter/impeller/playground/playground.cc @@ -526,4 +526,8 @@ Playground::VKProcAddressResolver Playground::CreateVKProcAddressResolver() return impl_->CreateVKProcAddressResolver(); } +void Playground::SetGPUDisabled(bool value) const { + impl_->SetGPUDisabled(value); +} + } // namespace impeller diff --git a/engine/src/flutter/impeller/playground/playground.h b/engine/src/flutter/impeller/playground/playground.h index 236e6de89c..6878a59751 100644 --- a/engine/src/flutter/impeller/playground/playground.h +++ b/engine/src/flutter/impeller/playground/playground.h @@ -122,6 +122,11 @@ class Playground { std::function; VKProcAddressResolver CreateVKProcAddressResolver() const; + /// @brief Mark the GPU as unavilable. + /// + /// Only supported on the Metal backend. + void SetGPUDisabled(bool disabled) const; + protected: const PlaygroundSwitches switches_; diff --git a/engine/src/flutter/impeller/playground/playground_impl.h b/engine/src/flutter/impeller/playground/playground_impl.h index 387a2cb5af..ed2593fc98 100644 --- a/engine/src/flutter/impeller/playground/playground_impl.h +++ b/engine/src/flutter/impeller/playground/playground_impl.h @@ -40,6 +40,8 @@ class PlaygroundImpl { virtual Playground::VKProcAddressResolver CreateVKProcAddressResolver() const; + virtual void SetGPUDisabled(bool disabled) const {} + protected: const PlaygroundSwitches switches_; diff --git a/engine/src/flutter/impeller/renderer/backend/gles/command_buffer_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/command_buffer_gles.cc index 4cd6b279b9..15a42acc61 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/command_buffer_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/command_buffer_gles.cc @@ -29,7 +29,8 @@ bool CommandBufferGLES::IsValid() const { } // |CommandBuffer| -bool CommandBufferGLES::OnSubmitCommands(CompletionCallback callback) { +bool CommandBufferGLES::OnSubmitCommands(bool block_on_schedule, + CompletionCallback callback) { const auto result = reactor_->React(); if (callback) { callback(result ? CommandBuffer::Status::kCompleted diff --git a/engine/src/flutter/impeller/renderer/backend/gles/command_buffer_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/command_buffer_gles.h index c7baea2d7b..5178d74ce1 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/command_buffer_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/command_buffer_gles.h @@ -32,7 +32,8 @@ class CommandBufferGLES final : public CommandBuffer { bool IsValid() const override; // |CommandBuffer| - bool OnSubmitCommands(CompletionCallback callback) override; + bool OnSubmitCommands(bool block_on_schedule, + CompletionCallback callback) override; // |CommandBuffer| void OnWaitUntilCompleted() override; diff --git a/engine/src/flutter/impeller/renderer/backend/metal/BUILD.gn b/engine/src/flutter/impeller/renderer/backend/metal/BUILD.gn index f1271c2d81..d7242b852e 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/BUILD.gn +++ b/engine/src/flutter/impeller/renderer/backend/metal/BUILD.gn @@ -72,6 +72,7 @@ impeller_component("metal_unittests") { sources = [ "allocator_mtl_unittests.mm", + "context_mtl_unittests.mm", "swapchain_transients_mtl_unittests.mm", "texture_mtl_unittests.mm", ] diff --git a/engine/src/flutter/impeller/renderer/backend/metal/command_buffer_mtl.h b/engine/src/flutter/impeller/renderer/backend/metal/command_buffer_mtl.h index ed8247c6d8..b43e919d4a 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/command_buffer_mtl.h +++ b/engine/src/flutter/impeller/renderer/backend/metal/command_buffer_mtl.h @@ -34,7 +34,8 @@ class CommandBufferMTL final : public CommandBuffer { bool IsValid() const override; // |CommandBuffer| - bool OnSubmitCommands(CompletionCallback callback) override; + bool OnSubmitCommands(bool block_on_schedule, + CompletionCallback callback) override; // |CommandBuffer| void OnWaitUntilCompleted() override; diff --git a/engine/src/flutter/impeller/renderer/backend/metal/command_buffer_mtl.mm b/engine/src/flutter/impeller/renderer/backend/metal/command_buffer_mtl.mm index 6eb9f2ea05..8b6e5a3e26 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/command_buffer_mtl.mm +++ b/engine/src/flutter/impeller/renderer/backend/metal/command_buffer_mtl.mm @@ -162,7 +162,8 @@ static CommandBuffer::Status ToCommitResult(MTLCommandBufferStatus status) { return CommandBufferMTL::Status::kError; } -bool CommandBufferMTL::OnSubmitCommands(CompletionCallback callback) { +bool CommandBufferMTL::OnSubmitCommands(bool block_on_schedule, + CompletionCallback callback) { auto context = context_.lock(); if (!context) { return false; @@ -182,6 +183,9 @@ bool CommandBufferMTL::OnSubmitCommands(CompletionCallback callback) { } [buffer_ commit]; + if (block_on_schedule) { + [buffer_ waitUntilScheduled]; + } buffer_ = nil; return true; diff --git a/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.h b/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.h index 712cf7c1f6..348830d0fe 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.h +++ b/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.h @@ -146,6 +146,9 @@ class ContextMTL final : public Context, void StoreTaskForGPU(const fml::closure& task, const fml::closure& failure) override; + // visible for testing. + void FlushTasksAwaitingGPU(); + private: class SyncSwitchObserver : public fml::SyncSwitch::Observer { public: @@ -191,8 +194,6 @@ class ContextMTL final : public Context, std::shared_ptr CreateCommandBufferInQueue( id queue) const; - void FlushTasksAwaitingGPU(); - ContextMTL(const ContextMTL&) = delete; ContextMTL& operator=(const ContextMTL&) = delete; diff --git a/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.mm b/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.mm index 30e509bdd8..1f3bcb073a 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.mm +++ b/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.mm @@ -428,8 +428,26 @@ void ContextMTL::FlushTasksAwaitingGPU() { Lock lock(tasks_awaiting_gpu_mutex_); std::swap(tasks_awaiting_gpu, tasks_awaiting_gpu_); } + std::vector tasks_to_queue; for (const auto& task : tasks_awaiting_gpu) { - task.task(); + is_gpu_disabled_sync_switch_->Execute(fml::SyncSwitch::Handlers() + .SetIfFalse([&] { task.task(); }) + .SetIfTrue([&] { + // Lost access to the GPU + // immediately after it was + // activated. This may happen if + // the app was quickly + // foregrounded/backgrounded + // from a push notification. + // Store the tasks on the + // context again. + tasks_to_queue.push_back(task); + })); + } + if (!tasks_to_queue.empty()) { + Lock lock(tasks_awaiting_gpu_mutex_); + tasks_awaiting_gpu_.insert(tasks_awaiting_gpu_.end(), + tasks_to_queue.begin(), tasks_to_queue.end()); } } diff --git a/engine/src/flutter/impeller/renderer/backend/metal/context_mtl_unittests.mm b/engine/src/flutter/impeller/renderer/backend/metal/context_mtl_unittests.mm new file mode 100644 index 0000000000..cb3ee72e59 --- /dev/null +++ b/engine/src/flutter/impeller/renderer/backend/metal/context_mtl_unittests.mm @@ -0,0 +1,64 @@ +// 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/testing/testing.h" +#include "impeller/core/device_buffer_descriptor.h" +#include "impeller/core/formats.h" +#include "impeller/core/texture_descriptor.h" +#include "impeller/playground/playground_test.h" +#include "impeller/renderer/backend/metal/allocator_mtl.h" +#include "impeller/renderer/backend/metal/context_mtl.h" +#include "impeller/renderer/backend/metal/formats_mtl.h" +#include "impeller/renderer/backend/metal/texture_mtl.h" +#include "impeller/renderer/capabilities.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace impeller { +namespace testing { + +using ContextMTLTest = PlaygroundTest; +INSTANTIATE_METAL_PLAYGROUND_SUITE(ContextMTLTest); + +TEST_P(ContextMTLTest, FlushTask) { + auto& context_mtl = ContextMTL::Cast(*GetContext()); + + int executed = 0; + int failed = 0; + context_mtl.StoreTaskForGPU([&]() { executed++; }, [&]() { failed++; }); + + context_mtl.FlushTasksAwaitingGPU(); + + EXPECT_EQ(executed, 1); + EXPECT_EQ(failed, 0); +} + +TEST_P(ContextMTLTest, FlushTaskWithGPULoss) { + auto& context_mtl = ContextMTL::Cast(*GetContext()); + + int executed = 0; + int failed = 0; + context_mtl.StoreTaskForGPU([&]() { executed++; }, [&]() { failed++; }); + + // If tasks are flushed while the GPU is disabled, then + // they should not be executed. + SetGPUDisabled(/*disabled=*/true); + context_mtl.FlushTasksAwaitingGPU(); + + EXPECT_EQ(executed, 0); + EXPECT_EQ(failed, 0); + + // Toggling availibility should flush tasks. + SetGPUDisabled(/*disabled=*/false); + + EXPECT_EQ(executed, 1); + EXPECT_EQ(failed, 0); +} + +} // namespace testing +} // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/command_buffer_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/command_buffer_vk.cc index 0501f2717e..ca1b94da46 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/command_buffer_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/command_buffer_vk.cc @@ -43,7 +43,8 @@ bool CommandBufferVK::IsValid() const { return true; } -bool CommandBufferVK::OnSubmitCommands(CompletionCallback callback) { +bool CommandBufferVK::OnSubmitCommands(bool block_on_schedule, + CompletionCallback callback) { FML_UNREACHABLE() } diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/command_buffer_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/command_buffer_vk.h index 0b7c096dde..c43c4fdf63 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/command_buffer_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/command_buffer_vk.h @@ -98,7 +98,8 @@ class CommandBufferVK final bool IsValid() const override; // |CommandBuffer| - bool OnSubmitCommands(CompletionCallback callback) override; + bool OnSubmitCommands(bool block_on_schedule, + CompletionCallback callback) override; // |CommandBuffer| void OnWaitUntilCompleted() override; diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/command_queue_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/command_queue_vk.cc index 8eb8fc4370..f3f42265ce 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/command_queue_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/command_queue_vk.cc @@ -22,7 +22,8 @@ CommandQueueVK::~CommandQueueVK() = default; fml::Status CommandQueueVK::Submit( const std::vector>& buffers, - const CompletionCallback& completion_callback) { + const CompletionCallback& completion_callback, + bool block_on_schedule) { if (buffers.empty()) { return fml::Status(fml::StatusCode::kInvalidArgument, "No command buffers provided."); diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/command_queue_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/command_queue_vk.h index 6397391c84..20450c2a4b 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/command_queue_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/command_queue_vk.h @@ -17,9 +17,9 @@ class CommandQueueVK : public CommandQueue { ~CommandQueueVK() override; - fml::Status Submit( - const std::vector>& buffers, - const CompletionCallback& completion_callback = {}) override; + fml::Status Submit(const std::vector>& buffers, + const CompletionCallback& completion_callback = {}, + bool block_on_schedule = false) override; private: std::weak_ptr context_; diff --git a/engine/src/flutter/impeller/renderer/command_buffer.cc b/engine/src/flutter/impeller/renderer/command_buffer.cc index 12a79eacaa..f6cf95908b 100644 --- a/engine/src/flutter/impeller/renderer/command_buffer.cc +++ b/engine/src/flutter/impeller/renderer/command_buffer.cc @@ -15,7 +15,8 @@ CommandBuffer::CommandBuffer(std::weak_ptr context) CommandBuffer::~CommandBuffer() = default; -bool CommandBuffer::SubmitCommands(const CompletionCallback& callback) { +bool CommandBuffer::SubmitCommands(bool block_on_schedule, + const CompletionCallback& callback) { if (!IsValid()) { // Already committed or was never valid. Either way, this is caller error. if (callback) { @@ -23,11 +24,11 @@ bool CommandBuffer::SubmitCommands(const CompletionCallback& callback) { } return false; } - return OnSubmitCommands(callback); + return OnSubmitCommands(block_on_schedule, callback); } -bool CommandBuffer::SubmitCommands() { - return SubmitCommands(nullptr); +bool CommandBuffer::SubmitCommands(bool block_on_schedule) { + return SubmitCommands(block_on_schedule, nullptr); } void CommandBuffer::WaitUntilCompleted() { diff --git a/engine/src/flutter/impeller/renderer/command_buffer.h b/engine/src/flutter/impeller/renderer/command_buffer.h index 578bf003ac..90701495f9 100644 --- a/engine/src/flutter/impeller/renderer/command_buffer.h +++ b/engine/src/flutter/impeller/renderer/command_buffer.h @@ -107,7 +107,11 @@ class CommandBuffer { virtual std::shared_ptr OnCreateBlitPass() = 0; - [[nodiscard]] virtual bool OnSubmitCommands(CompletionCallback callback) = 0; + /// @brief Submit the command buffer to the GPU for execution. + /// + /// See also: [SubmitCommands]. + [[nodiscard]] virtual bool OnSubmitCommands(bool block_on_schedule, + CompletionCallback callback) = 0; virtual void OnWaitUntilCompleted() = 0; @@ -124,12 +128,15 @@ class CommandBuffer { /// performed immediately on the calling thread. /// /// A command buffer may only be committed once. - /// + /// @param[in] block_on_schedule If true, this function will not return + /// until the command buffer has been scheduled. This only impacts + /// the Metal backend. /// @param[in] callback The completion callback. /// - [[nodiscard]] bool SubmitCommands(const CompletionCallback& callback); + [[nodiscard]] bool SubmitCommands(bool block_on_schedule, + const CompletionCallback& callback); - [[nodiscard]] bool SubmitCommands(); + [[nodiscard]] bool SubmitCommands(bool block_on_schedule); CommandBuffer(const CommandBuffer&) = delete; diff --git a/engine/src/flutter/impeller/renderer/command_queue.cc b/engine/src/flutter/impeller/renderer/command_queue.cc index 531fa87942..02c9300564 100644 --- a/engine/src/flutter/impeller/renderer/command_queue.cc +++ b/engine/src/flutter/impeller/renderer/command_queue.cc @@ -13,7 +13,8 @@ CommandQueue::~CommandQueue() = default; fml::Status CommandQueue::Submit( const std::vector>& buffers, - const CompletionCallback& completion_callback) { + const CompletionCallback& completion_callback, + bool block_on_schedule) { if (buffers.empty()) { if (completion_callback) { completion_callback(CommandBuffer::Status::kError); @@ -22,7 +23,7 @@ fml::Status CommandQueue::Submit( "No command buffers provided."); } for (const std::shared_ptr& buffer : buffers) { - if (!buffer->SubmitCommands(completion_callback)) { + if (!buffer->SubmitCommands(block_on_schedule, completion_callback)) { return fml::Status(fml::StatusCode::kCancelled, "Failed to submit command buffer."); } diff --git a/engine/src/flutter/impeller/renderer/command_queue.h b/engine/src/flutter/impeller/renderer/command_queue.h index 140b1c396e..2abd7f11d9 100644 --- a/engine/src/flutter/impeller/renderer/command_queue.h +++ b/engine/src/flutter/impeller/renderer/command_queue.h @@ -35,9 +35,14 @@ class CommandQueue { /// Only the Metal and Vulkan backends can give a status beyond /// successful encoding. This callback may be called more than once and /// potentially on a different thread. + /// + /// If [block_on_schedule] is true, this function will not return until + /// the command buffer has been scheduled. This only impacts the Metal + /// backend. virtual fml::Status Submit( const std::vector>& buffers, - const CompletionCallback& completion_callback = {}); + const CompletionCallback& completion_callback = {}, + bool block_on_schedule = false); private: CommandQueue(const CommandQueue&) = delete; diff --git a/engine/src/flutter/impeller/renderer/testing/mocks.h b/engine/src/flutter/impeller/renderer/testing/mocks.h index 2cac383196..f814fccde2 100644 --- a/engine/src/flutter/impeller/renderer/testing/mocks.h +++ b/engine/src/flutter/impeller/renderer/testing/mocks.h @@ -124,7 +124,7 @@ class MockCommandBuffer : public CommandBuffer { MOCK_METHOD(std::shared_ptr, OnCreateBlitPass, (), (override)); MOCK_METHOD(bool, OnSubmitCommands, - (CompletionCallback callback), + (bool block_on_schedule, CompletionCallback callback), (override)); MOCK_METHOD(void, OnWaitUntilCompleted, (), (override)); MOCK_METHOD(void, OnWaitUntilScheduled, (), (override)); @@ -238,7 +238,8 @@ class MockCommandQueue : public CommandQueue { MOCK_METHOD(fml::Status, Submit, (const std::vector>& buffers, - const CompletionCallback& cb), + const CompletionCallback& cb, + bool block_on_schedule), (override)); }; diff --git a/engine/src/flutter/lib/ui/painting/image_decoder_impeller.cc b/engine/src/flutter/lib/ui/painting/image_decoder_impeller.cc index a2830bc2b1..039e8d03ea 100644 --- a/engine/src/flutter/lib/ui/painting/image_decoder_impeller.cc +++ b/engine/src/flutter/lib/ui/painting/image_decoder_impeller.cc @@ -390,8 +390,17 @@ ImageDecoderImpeller::UnsafeUploadTextureToPrivate( result_texture = std::move(resize_texture); } blit_pass->EncodeCommands(); - - if (!context->GetCommandQueue()->Submit({command_buffer}).ok()) { + if (!context->GetCommandQueue() + ->Submit( + {command_buffer}, + [](impeller::CommandBuffer::Status status) { + if (status == impeller::CommandBuffer::Status::kError) { + FML_LOG(ERROR) + << "GPU Error submitting image decoding command buffer."; + } + }, + /*block_on_schedule=*/true) + .ok()) { std::string decode_error("Failed to submit image decoding command buffer."); FML_DLOG(ERROR) << decode_error; return std::make_pair(nullptr, decode_error); diff --git a/engine/src/flutter/lib/ui/painting/image_encoding_unittests.cc b/engine/src/flutter/lib/ui/painting/image_encoding_unittests.cc index 5c9fc173d6..435aad0595 100644 --- a/engine/src/flutter/lib/ui/painting/image_encoding_unittests.cc +++ b/engine/src/flutter/lib/ui/painting/image_encoding_unittests.cc @@ -224,7 +224,7 @@ std::shared_ptr MakeConvertDlImageToSkImageContext( EXPECT_CALL(*context, GetResourceAllocator).WillRepeatedly(Return(allocator)); EXPECT_CALL(*context, CreateCommandBuffer).WillOnce(Return(command_buffer)); EXPECT_CALL(*device_buffer, OnGetContents).WillOnce(Return(buffer.data())); - EXPECT_CALL(*command_queue, Submit(_, _)) + EXPECT_CALL(*command_queue, Submit(_, _, _)) .WillRepeatedly( DoAll(InvokeArgument<1>(impeller::CommandBuffer::Status::kCompleted), Return(fml::Status())));