[Impeller]Use the IO context for OpenGL program setup (#185723)
Fix https://github.com/flutter/flutter/issues/176657
<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->
This PR can improve app startup time for impeller+GLES on low-end
devices.
https://github.com/flutter/flutter/pull/180022 has provided solution for
impeller + vulkan.
Impeller+GLES has a special case: resource loading cannot be arbitrarily
placed on other threads; it needs to be done on the I/O thread because
this thread shares the context with the GPU thread. Therefore, this PR
introduces the I/O task runner into PipelineCompileQueueGLES. However,
since the I/O task runner may handle other tasks, we cannot put all jobs
into the I/O thread at once. My current solution is to wait for the
previous job to finish before putting the next one into the I/O thread.
Main changes:
1. Introduce fml::RefPtr<fml::TaskRunner> into PipelineCompileQueueGLES.
2. Extract new class PipelineCompileQueueVulkan for vulkan, because the
runners are on different threads.
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-new channel
on [Discord].
If this change needs to override an active code freeze, provide a
comment explaining why. The code freeze workflow can be overridden by
code reviewers. See pinned issues for any active code freezes with
guidance.
**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.
<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
---------
Co-authored-by: Jason Simmons <jsimmons@google.com>
diff --git a/engine/src/flutter/fml/BUILD.gn b/engine/src/flutter/fml/BUILD.gn
index 69f88e4..85c6171 100644
--- a/engine/src/flutter/fml/BUILD.gn
+++ b/engine/src/flutter/fml/BUILD.gn
@@ -85,6 +85,8 @@
"task_queue_id.h",
"task_runner.cc",
"task_runner.h",
+ "task_runner_util.cc",
+ "task_runner_util.h",
"task_source.cc",
"task_source.h",
"thread.cc",
@@ -355,6 +357,7 @@
"synchronization/semaphore_unittest.cc",
"synchronization/sync_switch_unittest.cc",
"synchronization/waitable_event_unittest.cc",
+ "task_runner_util_unittests.cc",
"task_source_unittests.cc",
"thread_unittests.cc",
"time/chrono_timestamp_provider.cc",
diff --git a/engine/src/flutter/fml/task_runner_util.cc b/engine/src/flutter/fml/task_runner_util.cc
new file mode 100644
index 0000000..2e12163
--- /dev/null
+++ b/engine/src/flutter/fml/task_runner_util.cc
@@ -0,0 +1,34 @@
+// 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/fml/task_runner_util.h"
+
+namespace fml {
+
+WrapperBasicTaskRunner::WrapperBasicTaskRunner(
+ fml::RefPtr<fml::TaskRunner> task_runner)
+ : task_runner_(std::move(task_runner)) {}
+
+void WrapperBasicTaskRunner::PostTask(const fml::closure& task) {
+ task_runner_->PostTask(task);
+}
+
+ConditionalBasicTaskRunner::ConditionalBasicTaskRunner(
+ fml::RefPtr<fml::TaskRunner> task_runner,
+ std::function<bool()> is_usable)
+ : task_runner_(std::move(task_runner)),
+ is_usable_(
+ std::make_shared<std::function<bool()>>(std::move(is_usable))) {}
+
+void ConditionalBasicTaskRunner::PostTask(const fml::closure& task) {
+ auto task_wrapper = [task, weak_is_usable = std::weak_ptr(is_usable_)] {
+ std::shared_ptr<std::function<bool()>> is_usable = weak_is_usable.lock();
+ if (is_usable && (*is_usable)()) {
+ task();
+ }
+ };
+ task_runner_->PostTask(task_wrapper);
+}
+
+} // namespace fml
diff --git a/engine/src/flutter/fml/task_runner_util.h b/engine/src/flutter/fml/task_runner_util.h
new file mode 100644
index 0000000..e766256
--- /dev/null
+++ b/engine/src/flutter/fml/task_runner_util.h
@@ -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.
+
+#ifndef FLUTTER_FML_TASK_RUNNER_UTIL_H_
+#define FLUTTER_FML_TASK_RUNNER_UTIL_H_
+
+#include <functional>
+
+#include "flutter/fml/memory/ref_ptr.h"
+#include "flutter/fml/task_runner.h"
+
+namespace fml {
+
+/// A BasicTaskRunner that posts tasks to another task runner.
+///
+/// This can be used to adapt an fml::RefPtr<fml::TaskRunner> to APIs that
+/// take a BasicTaskRunner that is not managed by fml::RefPtr.
+class WrapperBasicTaskRunner : public BasicTaskRunner {
+ public:
+ explicit WrapperBasicTaskRunner(fml::RefPtr<fml::TaskRunner> task_runner);
+
+ virtual ~WrapperBasicTaskRunner() = default;
+
+ void PostTask(const fml::closure& task) override;
+
+ private:
+ fml::RefPtr<fml::TaskRunner> task_runner_;
+
+ FML_DISALLOW_COPY_AND_ASSIGN(WrapperBasicTaskRunner);
+};
+
+/// A BasicTaskRunner that wraps another task runner and takes a function
+/// that indicates whether that task runner is still usable.
+///
+/// Before each posted task is run, ConditionalBasicTaskRunner will call the
+/// is_usable function on the underlying task runner's thread. If is_usable
+/// returns false, then the task will not be executed.
+class ConditionalBasicTaskRunner : public BasicTaskRunner {
+ public:
+ explicit ConditionalBasicTaskRunner(fml::RefPtr<fml::TaskRunner> task_runner,
+ std::function<bool()> is_usable);
+
+ virtual ~ConditionalBasicTaskRunner() = default;
+
+ void PostTask(const fml::closure& task) override;
+
+ private:
+ fml::RefPtr<fml::TaskRunner> task_runner_;
+ const std::shared_ptr<std::function<bool()>> is_usable_;
+
+ FML_DISALLOW_COPY_AND_ASSIGN(ConditionalBasicTaskRunner);
+};
+
+} // namespace fml
+
+#endif // FLUTTER_FML_TASK_RUNNER_UTIL_H_
diff --git a/engine/src/flutter/fml/task_runner_util_unittests.cc b/engine/src/flutter/fml/task_runner_util_unittests.cc
new file mode 100644
index 0000000..0579ff9b
--- /dev/null
+++ b/engine/src/flutter/fml/task_runner_util_unittests.cc
@@ -0,0 +1,58 @@
+// 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 <atomic>
+#include <thread>
+
+#include "flutter/fml/synchronization/waitable_event.h"
+#include "flutter/fml/task_runner_util.h"
+#include "flutter/fml/thread.h"
+#include "gtest/gtest.h"
+
+namespace fml {
+namespace testing {
+
+TEST(TaskRunnerUtilTests, WrapperBasicTaskRunnerPostTask) {
+ fml::Thread thread;
+
+ WrapperBasicTaskRunner wrapper(thread.GetTaskRunner());
+
+ std::thread::id wrapper_thread_id;
+ wrapper.PostTask([&]() { wrapper_thread_id = std::this_thread::get_id(); });
+
+ thread.Join();
+
+ EXPECT_NE(wrapper_thread_id, std::this_thread::get_id());
+}
+
+TEST(TaskRunnerUtilTests, ConditionalBasicTaskRunnerPostTask) {
+ fml::Thread thread;
+ std::atomic_bool active = true;
+ ConditionalBasicTaskRunner runner(thread.GetTaskRunner(),
+ [&active]() -> bool { return active; });
+
+ fml::AutoResetWaitableEvent latch;
+ std::atomic_bool task1_called = false;
+ runner.PostTask([&]() {
+ task1_called.store(true);
+ latch.Signal();
+ });
+ latch.Wait();
+
+ active.store(false);
+
+ std::atomic_bool task2_called = false;
+ runner.PostTask([&]() { task2_called.store(true); });
+
+ thread.GetTaskRunner()->PostTask([&]() { latch.Signal(); });
+ latch.Wait();
+
+ thread.Join();
+
+ EXPECT_TRUE(task1_called.load());
+ EXPECT_FALSE(task2_called.load());
+}
+
+} // namespace testing
+} // namespace fml
diff --git a/engine/src/flutter/impeller/renderer/BUILD.gn b/engine/src/flutter/impeller/renderer/BUILD.gn
index ee339a9..fa27c57 100644
--- a/engine/src/flutter/impeller/renderer/BUILD.gn
+++ b/engine/src/flutter/impeller/renderer/BUILD.gn
@@ -108,6 +108,7 @@
"blit_pass_unittests.cc",
"capabilities_unittests.cc",
"device_buffer_unittests.cc",
+ "pipeline_compile_queue_unittests.cc",
"pipeline_descriptor_unittests.cc",
"pipeline_library_unittests.cc",
"pool_unittests.cc",
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/BUILD.gn b/engine/src/flutter/impeller/renderer/backend/gles/BUILD.gn
index deec3f1..6ae9f50 100644
--- a/engine/src/flutter/impeller/renderer/backend/gles/BUILD.gn
+++ b/engine/src/flutter/impeller/renderer/backend/gles/BUILD.gn
@@ -25,6 +25,7 @@
"test/mock_gles.cc",
"test/mock_gles.h",
"test/mock_gles_unittests.cc",
+ "test/pipeline_compile_queue_gles_unittests.cc",
"test/pipeline_library_gles_unittests.cc",
"test/proc_table_gles_unittests.cc",
"test/reactor_unittests.cc",
@@ -35,6 +36,7 @@
]
deps = [
":gles",
+ "//flutter/fml",
"//flutter/impeller/playground:playground_test",
"//flutter/testing:testing_lib",
]
@@ -69,6 +71,8 @@
"gpu_tracer_gles.h",
"handle_gles.cc",
"handle_gles.h",
+ "pipeline_compile_queue_gles.cc",
+ "pipeline_compile_queue_gles.h",
"pipeline_gles.cc",
"pipeline_gles.h",
"pipeline_library_gles.cc",
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/context_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/context_gles.cc
index 3cdadd3..ff274c4 100644
--- a/engine/src/flutter/impeller/renderer/backend/gles/context_gles.cc
+++ b/engine/src/flutter/impeller/renderer/backend/gles/context_gles.cc
@@ -22,16 +22,19 @@
const Flags& flags,
std::unique_ptr<ProcTableGLES> gl,
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries,
- bool enable_gpu_tracing) {
- return std::shared_ptr<ContextGLES>(new ContextGLES(
- flags, std::move(gl), shader_libraries, enable_gpu_tracing));
+ bool enable_gpu_tracing,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner) {
+ return std::shared_ptr<ContextGLES>(
+ new ContextGLES(flags, std::move(gl), shader_libraries,
+ enable_gpu_tracing, std::move(io_task_runner)));
}
ContextGLES::ContextGLES(
const Flags& flags,
std::unique_ptr<ProcTableGLES> gl,
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_mappings,
- bool enable_gpu_tracing)
+ bool enable_gpu_tracing,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner)
: Context(flags) {
reactor_ = std::make_shared<ReactorGLES>(std::move(gl));
if (!reactor_->IsValid()) {
@@ -52,8 +55,8 @@
// Create the pipeline library.
{
- pipeline_library_ =
- std::shared_ptr<PipelineLibraryGLES>(new PipelineLibraryGLES(reactor_));
+ pipeline_library_ = std::shared_ptr<PipelineLibraryGLES>(
+ new PipelineLibraryGLES(reactor_, std::move(io_task_runner)));
}
// Create allocators.
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h
index 1c54844..ba4a284 100644
--- a/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h
+++ b/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h
@@ -28,7 +28,8 @@
const Flags& flags,
std::unique_ptr<ProcTableGLES> gl,
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries,
- bool enable_gpu_tracing);
+ bool enable_gpu_tracing,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner = nullptr);
// |Context|
~ContextGLES() override;
@@ -70,7 +71,8 @@
const Flags& flags,
std::unique_ptr<ProcTableGLES> gl,
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries,
- bool enable_gpu_tracing);
+ bool enable_gpu_tracing,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner = nullptr);
// |Context|
std::string DescribeGpuModel() const override;
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.cc
new file mode 100644
index 0000000..b895544
--- /dev/null
+++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.cc
@@ -0,0 +1,72 @@
+// 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/gles/pipeline_compile_queue_gles.h"
+
+#include "flutter/fml/logging.h"
+#include "flutter/fml/trace_event.h"
+#include "impeller/base/validation.h"
+
+namespace impeller {
+
+std::shared_ptr<PipelineCompileQueueGLES> PipelineCompileQueueGLES::Create(
+ std::shared_ptr<fml::BasicTaskRunner> worker_task_runner) {
+ if (!worker_task_runner) {
+ return nullptr;
+ }
+ return std::shared_ptr<PipelineCompileQueueGLES>(
+ new PipelineCompileQueueGLES(std::move(worker_task_runner)));
+}
+
+PipelineCompileQueueGLES::PipelineCompileQueueGLES(
+ std::shared_ptr<fml::BasicTaskRunner> worker_task_runner)
+ : worker_task_runner_(std::move(worker_task_runner)) {}
+
+PipelineCompileQueueGLES::~PipelineCompileQueueGLES() = default;
+
+void PipelineCompileQueueGLES::OnJobAdded() {
+ // To prevent potential deadlocks and reduce lock contention, avoid calling
+ // external or virtual methods (such as DrainPendingJobs, which posts tasks
+ // to the task runner) while holding a mutex. Instead, minimize the scope of
+ // the lock by using a local boolean flag to trigger the draining process
+ // outside the lock block.
+ bool should_drain = false;
+ {
+ Lock lock(processing_mutex_);
+ if (!is_processing_) {
+ is_processing_ = true;
+ should_drain = true;
+ }
+ }
+ if (should_drain) {
+ DrainPendingJobs();
+ }
+}
+
+void PipelineCompileQueueGLES::PostJob(const fml::closure& job) {
+ if (!job) {
+ return;
+ }
+
+ worker_task_runner_->PostTask(job);
+}
+
+void PipelineCompileQueueGLES::DrainPendingJobs() {
+ PostJob([weak_queue = weak_from_this()]() {
+ if (auto queue = std::static_pointer_cast<PipelineCompileQueueGLES>(
+ weak_queue.lock())) {
+ queue->DoOneJob();
+ {
+ Lock lock(queue->processing_mutex_);
+ if (!queue->HasPendingJobs()) {
+ queue->is_processing_ = false;
+ return;
+ }
+ }
+ queue->DrainPendingJobs();
+ }
+ });
+}
+
+} // namespace impeller
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.h
new file mode 100644
index 0000000..cf013d6
--- /dev/null
+++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.h
@@ -0,0 +1,63 @@
+// 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_IMPELLER_RENDERER_BACKEND_GLES_PIPELINE_COMPILE_QUEUE_GLES_H_
+#define FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_PIPELINE_COMPILE_QUEUE_GLES_H_
+
+#include "flutter/fml/closure.h"
+#include "flutter/fml/task_runner.h"
+#include "impeller/base/thread.h"
+#include "impeller/renderer/pipeline_compile_queue.h"
+
+namespace impeller {
+
+//------------------------------------------------------------------------------
+/// @brief A task queue designed for managing compilation of pipeline state
+/// objects for OpenGL ES backend.
+///
+/// This subclass uses a fml::TaskRunner as the worker task runner
+/// and implements a sequential job processing mechanism to prevent
+/// blocking the IO task runner.
+///
+/// Key characteristics:
+/// - Uses fml::RefPtr<fml::TaskRunner> for worker_task_runner_
+/// - Processes jobs sequentially: loads one job at a time before
+/// proceeding to the next, preventing IO task runner blocking
+/// - Uses DrainPendingJobs() to recursively process jobs one by one
+/// - Employs is_processing_ flag and processing_mutex_ to control
+/// sequential processing
+///
+/// The sequential processing ensures that pipeline compilation jobs
+/// do not overwhelm the task runner, which is particularly
+/// important for GLES backend where resource loading patterns
+/// differ from Vulkan.
+///
+class PipelineCompileQueueGLES : public PipelineCompileQueue {
+ public:
+ static std::shared_ptr<PipelineCompileQueueGLES> Create(
+ std::shared_ptr<fml::BasicTaskRunner> worker_task_runner);
+
+ ~PipelineCompileQueueGLES() override;
+
+ PipelineCompileQueueGLES(const PipelineCompileQueueGLES&) = delete;
+
+ PipelineCompileQueueGLES& operator=(const PipelineCompileQueueGLES&) = delete;
+
+ void PostJob(const fml::closure& job) override;
+
+ void OnJobAdded() override;
+
+ private:
+ explicit PipelineCompileQueueGLES(
+ std::shared_ptr<fml::BasicTaskRunner> worker_task_runner);
+ void DrainPendingJobs();
+
+ std::shared_ptr<fml::BasicTaskRunner> worker_task_runner_;
+ Mutex processing_mutex_;
+ bool is_processing_ IPLR_GUARDED_BY(processing_mutex_) = false;
+};
+
+} // namespace impeller
+
+#endif // FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_PIPELINE_COMPILE_QUEUE_GLES_H_
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.cc
index 240891d..f5643ee 100644
--- a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.cc
+++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.cc
@@ -16,8 +16,12 @@
namespace impeller {
-PipelineLibraryGLES::PipelineLibraryGLES(std::shared_ptr<ReactorGLES> reactor)
- : reactor_(std::move(reactor)) {}
+PipelineLibraryGLES::PipelineLibraryGLES(
+ std::shared_ptr<ReactorGLES> reactor,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner)
+ : reactor_(std::move(reactor)),
+ compile_queue_(
+ PipelineCompileQueueGLES::Create(std::move(io_task_runner))) {}
static std::string GetShaderInfoLog(const ProcTableGLES& gl, GLuint shader) {
GLint log_length = 0;
@@ -296,17 +300,34 @@
PipelineFuture<PipelineDescriptor>{descriptor, promise->get_future()};
pipelines_[descriptor] = pipeline_future;
- const auto result = reactor_->AddOperation([promise, //
- weak_this = weak_from_this(), //
- descriptor, //
- vert_function, //
- frag_function, //
- threadsafe //
- ](const ReactorGLES& reactor) {
- promise->set_value(CreatePipeline(weak_this, descriptor, vert_function,
- frag_function, threadsafe));
- });
- FML_CHECK(result);
+ std::weak_ptr<PipelineLibrary> weak_this = weak_from_this();
+ std::shared_ptr<ReactorGLES> reactor = reactor_;
+ auto generation_task = [promise, weak_this, descriptor, vert_function,
+ frag_function, threadsafe, reactor]() {
+ auto thiz = weak_this.lock();
+ if (!thiz) {
+ promise->set_value(nullptr);
+ return;
+ }
+ const bool result = reactor->AddOperation([promise, //
+ weak_this, //
+ descriptor, //
+ vert_function, //
+ frag_function, //
+ threadsafe //
+ ](const ReactorGLES& reactor) {
+ promise->set_value(CreatePipeline(weak_this, descriptor, vert_function,
+ frag_function, threadsafe));
+ });
+ FML_CHECK(result);
+ };
+
+ if (async && compile_queue_) {
+ compile_queue_->PostJobForDescriptor(descriptor,
+ std::move(generation_task));
+ } else {
+ generation_task();
+ }
return pipeline_future;
}
@@ -373,4 +394,8 @@
programs_[key] = std::move(program);
}
+PipelineCompileQueue* PipelineLibraryGLES::GetPipelineCompileQueue() const {
+ return compile_queue_.get();
+}
+
} // namespace impeller
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.h
index 25c51c8..f2ce764 100644
--- a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.h
+++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.h
@@ -9,7 +9,9 @@
#include <vector>
#include "flutter/fml/hash_combine.h"
+#include "flutter/fml/task_runner.h"
#include "impeller/base/thread.h"
+#include "impeller/renderer/backend/gles/pipeline_compile_queue_gles.h"
#include "impeller/renderer/backend/gles/reactor_gles.h"
#include "impeller/renderer/backend/gles/unique_handle_gles.h"
#include "impeller/renderer/pipeline_library.h"
@@ -91,8 +93,11 @@
PipelineMap pipelines_;
Mutex programs_mutex_;
ProgramMap programs_ IPLR_GUARDED_BY(programs_mutex_);
+ std::shared_ptr<PipelineCompileQueueGLES> compile_queue_;
- explicit PipelineLibraryGLES(std::shared_ptr<ReactorGLES> reactor);
+ explicit PipelineLibraryGLES(
+ std::shared_ptr<ReactorGLES> reactor,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner);
// |PipelineLibrary|
bool IsValid() const override;
@@ -127,6 +132,8 @@
void SetProgramForKey(const ProgramKey& key,
std::shared_ptr<UniqueHandleGLES> program);
+ // |PipelineLibrary|
+ PipelineCompileQueue* GetPipelineCompileQueue() const override;
};
} // namespace impeller
diff --git a/engine/src/flutter/impeller/renderer/backend/gles/test/pipeline_compile_queue_gles_unittests.cc b/engine/src/flutter/impeller/renderer/backend/gles/test/pipeline_compile_queue_gles_unittests.cc
new file mode 100644
index 0000000..ff726fa
--- /dev/null
+++ b/engine/src/flutter/impeller/renderer/backend/gles/test/pipeline_compile_queue_gles_unittests.cc
@@ -0,0 +1,199 @@
+// 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/gles/pipeline_compile_queue_gles.h"
+
+#include <atomic>
+#include <memory>
+#include <vector>
+
+#include "flutter/fml/synchronization/count_down_latch.h"
+#include "flutter/fml/task_runner.h"
+#include "flutter/fml/task_runner_util.h"
+#include "flutter/fml/thread.h"
+#include "flutter/testing/testing.h"
+#include "impeller/renderer/pipeline_descriptor.h"
+
+namespace impeller {
+namespace testing {
+
+namespace {
+
+std::shared_ptr<fml::BasicTaskRunner> CreateBasicTaskRunner(
+ const fml::Thread& thread) {
+ return std::make_shared<fml::WrapperBasicTaskRunner>(thread.GetTaskRunner());
+}
+
+} // namespace
+
+TEST(PipelineCompileQueueGLESTest, CreateReturnsNullWithNullTaskRunner) {
+ auto queue = PipelineCompileQueueGLES::Create(nullptr);
+ EXPECT_EQ(queue, nullptr);
+}
+
+TEST(PipelineCompileQueueGLESTest, CreateSucceedsWithValidTaskRunner) {
+ fml::Thread thread;
+ auto queue = PipelineCompileQueueGLES::Create(CreateBasicTaskRunner(thread));
+ EXPECT_NE(queue, nullptr);
+ thread.Join();
+}
+
+TEST(PipelineCompileQueueGLESTest, PostJobDoesNothingWithNullClosure) {
+ fml::Thread thread;
+ auto queue = PipelineCompileQueueGLES::Create(CreateBasicTaskRunner(thread));
+ ASSERT_NE(queue, nullptr);
+ queue->PostJob(nullptr);
+ thread.Join();
+}
+
+TEST(PipelineCompileQueueGLESTest, OnJobAddedProcessesJobsSequentially) {
+ fml::Thread thread;
+ auto queue = PipelineCompileQueueGLES::Create(CreateBasicTaskRunner(thread));
+ ASSERT_NE(queue, nullptr);
+
+ std::atomic<int> completed_jobs{0};
+ fml::CountDownLatch latch(3);
+
+ PipelineDescriptor desc1;
+ desc1.SetSampleCount(SampleCount::kCount1);
+ desc1.SetCullMode(CullMode::kNone);
+
+ PipelineDescriptor desc2;
+ desc2.SetSampleCount(SampleCount::kCount1);
+ desc2.SetCullMode(CullMode::kFrontFace);
+
+ PipelineDescriptor desc3;
+ desc3.SetSampleCount(SampleCount::kCount1);
+ desc3.SetCullMode(CullMode::kBackFace);
+
+ queue->PostJobForDescriptor(desc1, [&]() {
+ std::this_thread::sleep_for(std::chrono::milliseconds(80));
+ completed_jobs++;
+ latch.CountDown();
+ });
+
+ queue->PostJobForDescriptor(desc2, [&]() {
+ std::this_thread::sleep_for(std::chrono::milliseconds(80));
+ completed_jobs++;
+ latch.CountDown();
+ });
+
+ queue->PostJobForDescriptor(desc3, [&]() {
+ std::this_thread::sleep_for(std::chrono::milliseconds(80));
+ completed_jobs++;
+ latch.CountDown();
+ });
+
+ latch.Wait();
+
+ EXPECT_EQ(completed_jobs, 3);
+
+ thread.Join();
+}
+
+TEST(PipelineCompileQueueGLESTest,
+ PostJobForDescriptorWithDuplicateRunsEagerly) {
+ fml::Thread thread;
+ auto queue = PipelineCompileQueueGLES::Create(CreateBasicTaskRunner(thread));
+ ASSERT_NE(queue, nullptr);
+
+ std::atomic<int> first_job_count{0};
+ std::atomic<int> second_job_count{0};
+ fml::CountDownLatch latch(2);
+
+ PipelineDescriptor desc;
+
+ queue->PostJobForDescriptor(desc, [&]() {
+ first_job_count++;
+ latch.CountDown();
+ });
+
+ queue->PostJobForDescriptor(desc, [&]() {
+ second_job_count++;
+ latch.CountDown();
+ });
+
+ latch.Wait();
+
+ EXPECT_EQ(first_job_count, 1);
+ EXPECT_EQ(second_job_count, 1);
+ thread.Join();
+}
+
+TEST(PipelineCompileQueueGLESTest, IsProcessingResetsAfterAllJobsComplete) {
+ fml::Thread thread;
+ auto queue = PipelineCompileQueueGLES::Create(CreateBasicTaskRunner(thread));
+ ASSERT_NE(queue, nullptr);
+
+ fml::CountDownLatch latch(1);
+
+ queue->PostJobForDescriptor(PipelineDescriptor{},
+ [&]() { latch.CountDown(); });
+
+ latch.Wait();
+
+ fml::CountDownLatch latch2(1);
+ queue->PostJobForDescriptor(PipelineDescriptor{},
+ [&]() { latch2.CountDown(); });
+
+ latch2.Wait();
+
+ SUCCEED();
+ thread.Join();
+}
+
+TEST(PipelineCompileQueueGLESTest, DestroyQueueWithPendingTasks) {
+ fml::Thread thread;
+ std::atomic<int> completed_jobs{0};
+ fml::CountDownLatch latch(3);
+
+ {
+ auto queue =
+ PipelineCompileQueueGLES::Create(CreateBasicTaskRunner(thread));
+ ASSERT_NE(queue, nullptr);
+
+ PipelineDescriptor desc1;
+ desc1.SetSampleCount(SampleCount::kCount1);
+ desc1.SetCullMode(CullMode::kNone);
+
+ PipelineDescriptor desc2;
+ desc2.SetSampleCount(SampleCount::kCount1);
+ desc2.SetCullMode(CullMode::kFrontFace);
+
+ PipelineDescriptor desc3;
+ desc3.SetSampleCount(SampleCount::kCount1);
+ desc3.SetCullMode(CullMode::kBackFace);
+
+ queue->PostJobForDescriptor(desc1, [&]() {
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ completed_jobs++;
+ latch.CountDown();
+ });
+
+ queue->PostJobForDescriptor(desc2, [&]() {
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ completed_jobs++;
+ latch.CountDown();
+ });
+
+ queue->PostJobForDescriptor(desc3, [&]() {
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ completed_jobs++;
+ latch.CountDown();
+ });
+
+ // Queue will be destroyed here with pending jobs.
+ // The destructor should ensure that the pending jobs are either executed
+ // or posted to the queue's thread.
+ }
+
+ // Wait for completion of the jobs.
+ latch.Wait();
+ EXPECT_EQ(completed_jobs, 3);
+
+ thread.Join();
+}
+
+} // namespace testing
+} // namespace impeller
diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn b/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn
index 3135074..afa6489 100644
--- a/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn
+++ b/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn
@@ -41,6 +41,7 @@
"surface_context_vk_unittests.cc",
"test/gpu_tracer_unittests.cc",
"test/mock_vulkan_unittests.cc",
+ "test/pipeline_compile_queue_vulkan_unittests.cc",
"test/sampler_library_vk_unittests.cc",
"test/swapchain_unittests.cc",
]
@@ -94,6 +95,8 @@
"pipeline_cache_data_vk.h",
"pipeline_cache_vk.cc",
"pipeline_cache_vk.h",
+ "pipeline_compile_queue_vulkan.cc",
+ "pipeline_compile_queue_vulkan.h",
"pipeline_library_vk.cc",
"pipeline_library_vk.h",
"pipeline_vk.cc",
diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.cc
new file mode 100644
index 0000000..aed83a4
--- /dev/null
+++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.cc
@@ -0,0 +1,45 @@
+// 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/vulkan/pipeline_compile_queue_vulkan.h"
+
+#include "flutter/fml/logging.h"
+#include "flutter/fml/trace_event.h"
+
+namespace impeller {
+
+std::shared_ptr<PipelineCompileQueueVulkan> PipelineCompileQueueVulkan::Create(
+ std::shared_ptr<fml::BasicTaskRunner> worker_task_runner) {
+ if (!worker_task_runner) {
+ return nullptr;
+ }
+ return std::shared_ptr<PipelineCompileQueueVulkan>(
+ new PipelineCompileQueueVulkan(std::move(worker_task_runner)));
+}
+
+PipelineCompileQueueVulkan::PipelineCompileQueueVulkan(
+ std::shared_ptr<fml::BasicTaskRunner> worker_task_runner)
+ : PipelineCompileQueue(),
+ worker_task_runner_(std::move(worker_task_runner)) {}
+
+PipelineCompileQueueVulkan::~PipelineCompileQueueVulkan() = default;
+
+void PipelineCompileQueueVulkan::OnJobAdded() {
+ PostJob([weak_queue = weak_from_this()]() {
+ if (auto queue = std::static_pointer_cast<PipelineCompileQueueVulkan>(
+ weak_queue.lock())) {
+ queue->DoOneJob();
+ }
+ });
+}
+
+void PipelineCompileQueueVulkan::PostJob(const fml::closure& job) {
+ if (!job) {
+ return;
+ }
+
+ worker_task_runner_->PostTask(job);
+}
+
+} // namespace impeller
diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h
new file mode 100644
index 0000000..5a65c0e
--- /dev/null
+++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.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_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_COMPILE_QUEUE_VULKAN_H_
+#define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_COMPILE_QUEUE_VULKAN_H_
+
+#include "flutter/fml/closure.h"
+#include "flutter/fml/task_runner.h"
+#include "impeller/renderer/pipeline_compile_queue.h"
+
+namespace impeller {
+
+//------------------------------------------------------------------------------
+/// @brief A task queue designed for managing compilation of pipeline state
+/// objects for Vulkan backend.
+///
+/// This subclass uses a fml::BasicTaskRunner as the worker task
+/// runner and dispatches compile jobs directly without sequential
+/// processing constraints.
+///
+/// Key characteristics:
+/// - Uses std::shared_ptr<fml::BasicTaskRunner> for
+/// worker_task_runner_
+/// - Dispatches jobs directly to the task runner in OnJobAdded()
+/// - Does not implement sequential processing like GLES version
+///
+/// The Vulkan backend benefits from the parallel nature of pipeline
+/// compilation, allowing multiple compile jobs to be processed
+/// concurrently through the task runner.
+///
+class PipelineCompileQueueVulkan : public PipelineCompileQueue {
+ public:
+ static std::shared_ptr<PipelineCompileQueueVulkan> Create(
+ std::shared_ptr<fml::BasicTaskRunner> worker_task_runner);
+
+ ~PipelineCompileQueueVulkan() override;
+
+ PipelineCompileQueueVulkan(const PipelineCompileQueueVulkan&) = delete;
+
+ PipelineCompileQueueVulkan& operator=(const PipelineCompileQueueVulkan&) =
+ delete;
+
+ void PostJob(const fml::closure& job) override;
+
+ void OnJobAdded() override;
+
+ private:
+ explicit PipelineCompileQueueVulkan(
+ std::shared_ptr<fml::BasicTaskRunner> worker_task_runner);
+ std::shared_ptr<fml::BasicTaskRunner> worker_task_runner_;
+};
+
+} // namespace impeller
+
+#endif // FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_COMPILE_QUEUE_VULKAN_H_
diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc
index 95b3351..6e8d954 100644
--- a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc
+++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc
@@ -27,7 +27,7 @@
device_holder,
std::move(cache_directory))),
worker_task_runner_(std::move(worker_task_runner)),
- compile_queue_(PipelineCompileQueue::Create(worker_task_runner_)) {
+ compile_queue_(PipelineCompileQueueVulkan::Create(worker_task_runner_)) {
FML_DCHECK(worker_task_runner_);
if (!pso_cache_->IsValid() || !worker_task_runner_) {
return;
diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.h
index 3ead3c7..4304db9 100644
--- a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.h
+++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.h
@@ -13,10 +13,10 @@
#include "impeller/base/thread.h"
#include "impeller/renderer/backend/vulkan/compute_pipeline_vk.h"
#include "impeller/renderer/backend/vulkan/pipeline_cache_vk.h"
+#include "impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h"
#include "impeller/renderer/backend/vulkan/pipeline_vk.h"
#include "impeller/renderer/backend/vulkan/vk.h"
#include "impeller/renderer/pipeline.h"
-#include "impeller/renderer/pipeline_compile_queue.h"
#include "impeller/renderer/pipeline_library.h"
namespace impeller {
@@ -49,7 +49,7 @@
PipelineKey pipeline_key_ IPLR_GUARDED_BY(pipelines_mutex_) = 1;
bool is_valid_ = false;
bool cache_dirty_ = false;
- std::shared_ptr<PipelineCompileQueue> compile_queue_;
+ std::shared_ptr<PipelineCompileQueueVulkan> compile_queue_;
PipelineLibraryVK(
const std::shared_ptr<DeviceHolderVK>& device_holder,
diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/test/pipeline_compile_queue_vulkan_unittests.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/test/pipeline_compile_queue_vulkan_unittests.cc
new file mode 100644
index 0000000..6ee3735
--- /dev/null
+++ b/engine/src/flutter/impeller/renderer/backend/vulkan/test/pipeline_compile_queue_vulkan_unittests.cc
@@ -0,0 +1,181 @@
+// 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/vulkan/pipeline_compile_queue_vulkan.h"
+
+#include <atomic>
+#include <memory>
+#include <mutex>
+#include <vector>
+
+#include "flutter/fml/synchronization/count_down_latch.h"
+#include "flutter/fml/task_runner.h"
+#include "flutter/testing/testing.h"
+#include "impeller/renderer/pipeline_descriptor.h"
+
+namespace impeller {
+namespace testing {
+
+TEST(PipelineCompileQueueVulkanTest, CreateSucceedsWithValidTaskRunner) {
+ auto loop = fml::ConcurrentMessageLoop::Create();
+ auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner());
+ EXPECT_NE(queue, nullptr);
+}
+
+TEST(PipelineCompileQueueVulkanTest, PostJobDoesNothingWithNullClosure) {
+ auto loop = fml::ConcurrentMessageLoop::Create();
+ auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner());
+ ASSERT_NE(queue, nullptr);
+
+ queue->PostJob(nullptr);
+}
+
+TEST(PipelineCompileQueueVulkanTest, OnJobAddedProcessesJobsInParallel) {
+ auto loop = fml::ConcurrentMessageLoop::Create();
+ auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner());
+ ASSERT_NE(queue, nullptr);
+
+ std::atomic<int> concurrent_jobs{0};
+ std::atomic<int> max_concurrent{0};
+ fml::CountDownLatch latch(3);
+
+ PipelineDescriptor desc1;
+ desc1.SetSampleCount(SampleCount::kCount1);
+ desc1.SetCullMode(CullMode::kNone);
+
+ PipelineDescriptor desc2;
+ desc2.SetSampleCount(SampleCount::kCount1);
+ desc2.SetCullMode(CullMode::kFrontFace);
+
+ PipelineDescriptor desc3;
+ desc3.SetSampleCount(SampleCount::kCount1);
+ desc3.SetCullMode(CullMode::kBackFace);
+
+ queue->PostJobForDescriptor(desc1, [&]() {
+ int current = ++concurrent_jobs;
+ int prev_max = max_concurrent.load();
+ while (current > prev_max &&
+ !max_concurrent.compare_exchange_weak(prev_max, current)) {
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ concurrent_jobs--;
+ latch.CountDown();
+ });
+
+ queue->PostJobForDescriptor(desc2, [&]() {
+ int current = ++concurrent_jobs;
+ int prev_max = max_concurrent.load();
+ while (current > prev_max &&
+ !max_concurrent.compare_exchange_weak(prev_max, current)) {
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ concurrent_jobs--;
+ latch.CountDown();
+ });
+
+ queue->PostJobForDescriptor(desc3, [&]() {
+ int current = ++concurrent_jobs;
+ int prev_max = max_concurrent.load();
+ while (current > prev_max &&
+ !max_concurrent.compare_exchange_weak(prev_max, current)) {
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ concurrent_jobs--;
+ latch.CountDown();
+ });
+
+ latch.Wait();
+
+ EXPECT_GE(max_concurrent.load(), 1);
+}
+
+TEST(PipelineCompileQueueVulkanTest,
+ PostJobForDescriptorWithDuplicateRunsEagerly) {
+ auto loop = fml::ConcurrentMessageLoop::Create();
+ auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner());
+ ASSERT_NE(queue, nullptr);
+
+ std::atomic<int> first_job_count{0};
+ std::atomic<int> second_job_count{0};
+ fml::CountDownLatch latch(2);
+
+ PipelineDescriptor desc;
+
+ queue->PostJobForDescriptor(desc, [&]() {
+ first_job_count++;
+ latch.CountDown();
+ });
+
+ queue->PostJobForDescriptor(desc, [&]() {
+ second_job_count++;
+ latch.CountDown();
+ });
+
+ latch.Wait();
+
+ EXPECT_EQ(first_job_count, 1);
+ EXPECT_EQ(second_job_count, 1);
+}
+
+TEST(PipelineCompileQueueVulkanTest, MultipleJobsCompleteSuccessfully) {
+ auto loop = fml::ConcurrentMessageLoop::Create();
+ auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner());
+ ASSERT_NE(queue, nullptr);
+
+ std::atomic<int> completed_jobs{0};
+ fml::CountDownLatch latch(5);
+
+ PipelineDescriptor desc1;
+ desc1.SetSampleCount(SampleCount::kCount1);
+ desc1.SetCullMode(CullMode::kNone);
+
+ PipelineDescriptor desc2;
+ desc2.SetSampleCount(SampleCount::kCount1);
+ desc2.SetCullMode(CullMode::kFrontFace);
+
+ PipelineDescriptor desc3;
+ desc3.SetSampleCount(SampleCount::kCount1);
+ desc3.SetCullMode(CullMode::kBackFace);
+
+ PipelineDescriptor desc4;
+ desc4.SetSampleCount(SampleCount::kCount4);
+ desc4.SetCullMode(CullMode::kNone);
+
+ PipelineDescriptor desc5;
+ desc5.SetSampleCount(SampleCount::kCount4);
+ desc5.SetCullMode(CullMode::kFrontFace);
+
+ // Post 5 jobs with distinct descriptors
+ queue->PostJobForDescriptor(desc1, [&]() {
+ completed_jobs++;
+ latch.CountDown();
+ });
+
+ queue->PostJobForDescriptor(desc2, [&]() {
+ completed_jobs++;
+ latch.CountDown();
+ });
+
+ queue->PostJobForDescriptor(desc3, [&]() {
+ completed_jobs++;
+ latch.CountDown();
+ });
+
+ queue->PostJobForDescriptor(desc4, [&]() {
+ completed_jobs++;
+ latch.CountDown();
+ });
+
+ queue->PostJobForDescriptor(desc5, [&]() {
+ completed_jobs++;
+ latch.CountDown();
+ });
+
+ latch.Wait();
+
+ EXPECT_EQ(completed_jobs, 5);
+}
+
+} // namespace testing
+} // namespace impeller
diff --git a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.cc b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.cc
index 124e11e..da10b9b 100644
--- a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.cc
+++ b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.cc
@@ -9,16 +9,6 @@
namespace impeller {
-std::shared_ptr<PipelineCompileQueue> PipelineCompileQueue::Create(
- std::shared_ptr<fml::ConcurrentTaskRunner> worker_task_runner) {
- return std::shared_ptr<PipelineCompileQueue>(
- new PipelineCompileQueue(std::move(worker_task_runner)));
-}
-
-PipelineCompileQueue::PipelineCompileQueue(
- std::shared_ptr<fml::ConcurrentTaskRunner> worker_task_runner)
- : worker_task_runner_(std::move(worker_task_runner)) {}
-
PipelineCompileQueue::~PipelineCompileQueue() {
FinishAllJobs();
}
@@ -29,31 +19,33 @@
return false;
}
- {
- Lock lock(pending_jobs_mutex_);
- auto insertion_result = pending_jobs_.insert(std::make_pair(desc, job));
- if (!insertion_result.second) {
- // This bit is being extremely conservative. If insertion did not take
- // place, someone gave the compile queue a job for the same description.
- // This is highly unusual but technically not impossible. Just run the job
- // eagerly.
- FML_LOG(ERROR) << "Got multiple compile jobs for the same descriptor. "
+ if (!AddJob(desc, job)) {
+ // This bit is being extremely conservative. If insertion did not take
+ // place, someone gave the compile queue a job for the same description.
+ // This is highly unusual but technically not impossible. Just run the job
+ // eagerly.
+ FML_LOG(WARNING) << "Got multiple compile jobs for the same descriptor. "
"Running eagerly.";
- // Don't invoke the job here has there are we have currently acquired a
- // mutex.
- worker_task_runner_->PostTask(job);
- return true;
- }
+ PostJob(job);
+ return true;
}
- worker_task_runner_->PostTask([weak_queue = weak_from_this()]() {
- if (auto queue = weak_queue.lock()) {
- queue->DoOneJob();
- }
- });
+ OnJobAdded();
return true;
}
+bool PipelineCompileQueue::AddJob(const PipelineDescriptor& desc,
+ const fml::closure& job) {
+ Lock lock(pending_jobs_mutex_);
+ auto insertion_result = pending_jobs_.insert(std::make_pair(desc, job));
+ return insertion_result.second;
+}
+
+bool PipelineCompileQueue::HasPendingJobs() {
+ Lock lock(pending_jobs_mutex_);
+ return !pending_jobs_.empty();
+}
+
fml::closure PipelineCompileQueue::TakeNextJob() {
Lock lock(pending_jobs_mutex_);
if (pending_jobs_.empty()) {
diff --git a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h
index d166679..901ac76 100644
--- a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h
+++ b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h
@@ -39,13 +39,12 @@
/// entirely optional. The queue skipping mechanism all assume the
/// optional availability of a compile queue.
///
-class PipelineCompileQueue final
+class PipelineCompileQueue
: public std::enable_shared_from_this<PipelineCompileQueue> {
public:
- static std::shared_ptr<PipelineCompileQueue> Create(
- std::shared_ptr<fml::ConcurrentTaskRunner> worker_task_runner);
+ PipelineCompileQueue() = default;
- ~PipelineCompileQueue();
+ virtual ~PipelineCompileQueue();
PipelineCompileQueue(const PipelineCompileQueue&) = delete;
@@ -72,26 +71,68 @@
///
void PerformJobEagerly(const PipelineDescriptor& desc);
- private:
- std::shared_ptr<fml::ConcurrentTaskRunner> worker_task_runner_;
- Mutex pending_jobs_mutex_;
- size_t priorities_elevated_ = {};
+ protected:
+ //----------------------------------------------------------------------------
+ /// @brief Post a compilation job to the worker task runner.
+ ///
+ /// This is a pure virtual function that must be implemented by
+ /// subclasses. It is responsible for actually dispatching the
+ /// job closure to the appropriate task runner for execution.
+ ///
+ /// @param[in] job The compilation job closure to post
+ ///
+ virtual void PostJob(const fml::closure& job) = 0;
+ //----------------------------------------------------------------------------
+ /// @brief Called by PostJobForDescriptor after a job has been
+ /// successfully added to the queue. Subclasses must implement
+ /// this to define their scheduling strategy.
+ ///
+ /// The default implementation for duplicate descriptors is to
+ /// run the job eagerly. Subclasses can override this behavior
+ /// by checking for duplicates before calling the base class.
+ ///
+ virtual void OnJobAdded() = 0;
+
+ //----------------------------------------------------------------------------
+ /// @brief Execute one pending compilation job from the queue.
+ ///
+ /// This method retrieves and executes a single job from the
+ /// pending jobs queue. It is typically called by subclasses
+ /// when they are ready to process the next job in the queue.
+ ///
+ void DoOneJob();
+
+ //----------------------------------------------------------------------------
+ /// @brief Add a compilation job to the pending queue for the specified
+ /// descriptor.
+ ///
+ /// @param[in] desc The pipeline descriptor that identifies the job
+ /// @param[in] job The compilation job closure to add
+ ///
+ /// @return True if the job was successfully added to the queue, false
+ /// if a job for this descriptor already exists.
+ ///
+ bool AddJob(const PipelineDescriptor& desc, const fml::closure& job);
+
+ //----------------------------------------------------------------------------
+ /// @brief Check if there are any pending compilation jobs in the queue.
+ ///
+ /// @return True if there are pending jobs waiting to be processed,
+ /// false otherwise.
+ ///
+ bool HasPendingJobs();
+
+ private:
+ Mutex pending_jobs_mutex_;
std::unordered_map<PipelineDescriptor,
fml::closure,
ComparableHash<PipelineDescriptor>,
ComparableEqual<PipelineDescriptor>>
pending_jobs_ IPLR_GUARDED_BY(pending_jobs_mutex_);
-
- explicit PipelineCompileQueue(
- std::shared_ptr<fml::ConcurrentTaskRunner> worker_task_runner);
-
+ size_t priorities_elevated_ = {};
fml::closure TakeJob(const PipelineDescriptor& desc);
-
fml::closure TakeNextJob();
-
- void DoOneJob();
-
void FinishAllJobs();
};
diff --git a/engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc b/engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc
new file mode 100644
index 0000000..14be51d
--- /dev/null
+++ b/engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc
@@ -0,0 +1,96 @@
+// 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/pipeline_compile_queue.h"
+
+#include <memory>
+
+#include "flutter/fml/closure.h"
+#include "flutter/testing/testing.h"
+
+namespace impeller {
+namespace testing {
+
+class TestPipelineCompileQueue : public PipelineCompileQueue {
+ public:
+ void PostJob(const fml::closure& job) override {
+ if (job) {
+ job();
+ }
+ }
+
+ void OnJobAdded() override {}
+
+ bool AddJobForTest(const PipelineDescriptor& desc, const fml::closure& job) {
+ return AddJob(desc, job);
+ }
+
+ bool HasPendingJobsForTest() { return HasPendingJobs(); }
+};
+
+TEST(PipelineCompileQueueTest, AddJobReturnsTrueForNewDescriptor) {
+ TestPipelineCompileQueue queue;
+ PipelineDescriptor desc;
+ bool job_executed = false;
+ fml::closure job = [&job_executed]() { job_executed = true; };
+
+ bool result = queue.AddJobForTest(desc, job);
+ EXPECT_TRUE(result);
+}
+
+TEST(PipelineCompileQueueTest, AddJobReturnsFalseForDuplicateDescriptor) {
+ TestPipelineCompileQueue queue;
+ PipelineDescriptor desc;
+ bool job1_executed = false;
+ bool job2_executed = false;
+ fml::closure job1 = [&job1_executed]() { job1_executed = true; };
+ fml::closure job2 = [&job2_executed]() { job2_executed = true; };
+
+ bool result1 = queue.AddJobForTest(desc, job1);
+ bool result2 = queue.AddJobForTest(desc, job2);
+
+ EXPECT_TRUE(result1);
+ EXPECT_FALSE(result2);
+}
+
+TEST(PipelineCompileQueueTest, HasPendingJobsReturnsCorrectState) {
+ TestPipelineCompileQueue queue;
+ PipelineDescriptor desc;
+ fml::closure job = []() {};
+
+ EXPECT_FALSE(queue.HasPendingJobsForTest());
+
+ queue.AddJobForTest(desc, job);
+ EXPECT_TRUE(queue.HasPendingJobsForTest());
+}
+
+TEST(PipelineCompileQueueTest, PerformJobEagerlyExecutesJob) {
+ TestPipelineCompileQueue queue;
+ PipelineDescriptor desc;
+ bool job_executed = false;
+ fml::closure job = [&job_executed]() { job_executed = true; };
+
+ queue.AddJobForTest(desc, job);
+ queue.PerformJobEagerly(desc);
+
+ EXPECT_TRUE(job_executed);
+ EXPECT_FALSE(queue.HasPendingJobsForTest());
+}
+
+TEST(PipelineCompileQueueTest, FinishAllJobsDrainsQueue) {
+ auto queue = std::make_shared<TestPipelineCompileQueue>();
+ PipelineDescriptor desc;
+ bool job_executed = false;
+ fml::closure job = [&job_executed]() { job_executed = true; };
+
+ queue->AddJobForTest(desc, job);
+ EXPECT_TRUE(queue->HasPendingJobsForTest());
+
+ queue.reset();
+
+ EXPECT_TRUE(job_executed);
+}
+
+} // namespace testing
+} // namespace impeller
diff --git a/engine/src/flutter/shell/common/platform_view.h b/engine/src/flutter/shell/common/platform_view.h
index 95384c3..1c74417 100644
--- a/engine/src/flutter/shell/common/platform_view.h
+++ b/engine/src/flutter/shell/common/platform_view.h
@@ -386,6 +386,16 @@
/// @return The settings.
///
virtual const Settings& OnPlatformViewGetSettings() const = 0;
+
+ //--------------------------------------------------------------------------
+ /// @brief Returns a task runner that executes tasks on the IO thread
+ /// and stops running tasks after the shell shuts down the IO
+ /// thread.
+ ///
+ /// @return The task runner.
+ ///
+ virtual std::shared_ptr<fml::BasicTaskRunner>
+ OnPlatformViewGetShutdownSafeIOTaskRunner() const = 0;
};
//----------------------------------------------------------------------------
diff --git a/engine/src/flutter/shell/common/rasterizer.cc b/engine/src/flutter/shell/common/rasterizer.cc
index 6af8bfc..b003f82 100644
--- a/engine/src/flutter/shell/common/rasterizer.cc
+++ b/engine/src/flutter/shell/common/rasterizer.cc
@@ -129,6 +129,8 @@
}
#endif // !SLIMPELLER
}
+ context_switch.reset();
+ surface_->ClearRenderContext();
surface_.reset();
}
diff --git a/engine/src/flutter/shell/common/rasterizer_unittests.cc b/engine/src/flutter/shell/common/rasterizer_unittests.cc
index bab565e..ca1b776 100644
--- a/engine/src/flutter/shell/common/rasterizer_unittests.cc
+++ b/engine/src/flutter/shell/common/rasterizer_unittests.cc
@@ -1215,6 +1215,40 @@
EXPECT_EQ(context->getResourceCachePurgeableBytes(), 0ul);
}
+TEST(RasterizerTest, TeardownClearsRenderContext) {
+ std::string test_name =
+ ::testing::UnitTest::GetInstance()->current_test_info()->name();
+ ThreadHost thread_host("io.flutter.test." + test_name + ".",
+ ThreadHost::Type::kPlatform |
+ ThreadHost::Type::kRaster | ThreadHost::Type::kIo |
+ ThreadHost::Type::kUi);
+ TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
+ thread_host.raster_thread->GetTaskRunner(),
+ thread_host.ui_thread->GetTaskRunner(),
+ thread_host.io_thread->GetTaskRunner());
+ NiceMock<MockDelegate> delegate;
+ Settings settings;
+ ON_CALL(delegate, GetSettings()).WillByDefault(ReturnRef(settings));
+ ON_CALL(delegate, GetTaskRunners()).WillByDefault(ReturnRef(task_runners));
+ auto rasterizer = std::make_unique<Rasterizer>(delegate);
+ auto surface = std::make_unique<NiceMock<MockSurface>>();
+ bool render_context_is_current = false;
+ EXPECT_CALL(*surface, MakeRenderContextCurrent()).WillRepeatedly([&]() {
+ render_context_is_current = true;
+ return std::make_unique<GLContextDefaultResult>(true);
+ });
+ EXPECT_CALL(*surface, ClearRenderContext()).WillRepeatedly([&]() {
+ render_context_is_current = false;
+ return true;
+ });
+
+ rasterizer->Setup(std::move(surface));
+ EXPECT_TRUE(render_context_is_current);
+
+ rasterizer->Teardown();
+ EXPECT_FALSE(render_context_is_current);
+}
+
TEST(RasterizerTest, TeardownNoSurface) {
std::string test_name =
::testing::UnitTest::GetInstance()->current_test_info()->name();
diff --git a/engine/src/flutter/shell/common/shell.cc b/engine/src/flutter/shell/common/shell.cc
index 526d2a7..2200280 100644
--- a/engine/src/flutter/shell/common/shell.cc
+++ b/engine/src/flutter/shell/common/shell.cc
@@ -25,6 +25,7 @@
#include "flutter/fml/make_copyable.h"
#include "flutter/fml/message_loop.h"
#include "flutter/fml/paths.h"
+#include "flutter/fml/task_runner_util.h"
#include "flutter/fml/trace_event.h"
#include "flutter/runtime/dart_vm.h"
#include "flutter/shell/common/base64.h"
@@ -545,6 +546,15 @@
resource_cache_limit_calculator->AddResourceCacheLimitItem(
weak_factory_.GetWeakPtr());
+ std::shared_future<fml::WeakPtr<ShellIOManager>> weak_io_manager_future(
+ weak_io_manager_promise_.get_future());
+ shutdown_safe_io_task_runner_ =
+ std::make_shared<fml::ConditionalBasicTaskRunner>(
+ task_runners_.GetIOTaskRunner(),
+ [weak_io_manager_future = std::move(weak_io_manager_future)] {
+ return static_cast<bool>(weak_io_manager_future.get());
+ });
+
// Generate a WeakPtrFactory for use with the raster thread. This does not
// need to wait on a latch because it can only ever be used from the raster
// thread from this class, so we have ordering guarantees.
@@ -873,6 +883,7 @@
engine_ = std::move(engine);
rasterizer_ = std::move(rasterizer);
io_manager_ = io_manager;
+ weak_io_manager_promise_.set_value(io_manager_->GetWeakPtr());
// Set the external view embedder for the rasterizer.
auto view_embedder = platform_view_->CreateExternalViewEmbedder();
@@ -951,6 +962,10 @@
return io_manager_->GetWeakPtr();
}
+std::shared_ptr<fml::BasicTaskRunner> Shell::GetShutdownSafeIOTaskRunner() {
+ return shutdown_safe_io_task_runner_;
+}
+
DartVM* Shell::GetDartVM() {
return &vm_;
}
@@ -1367,6 +1382,12 @@
return settings_;
}
+// |PlatformView::Delegate|
+std::shared_ptr<fml::BasicTaskRunner>
+Shell::OnPlatformViewGetShutdownSafeIOTaskRunner() const {
+ return shutdown_safe_io_task_runner_;
+}
+
// |Animator::Delegate|
void Shell::OnAnimatorBeginFrame(fml::TimePoint frame_target_time,
uint64_t frame_number) {
diff --git a/engine/src/flutter/shell/common/shell.h b/engine/src/flutter/shell/common/shell.h
index 9953a56..0eb7248 100644
--- a/engine/src/flutter/shell/common/shell.h
+++ b/engine/src/flutter/shell/common/shell.h
@@ -280,6 +280,19 @@
///
fml::WeakPtr<ShellIOManager> GetIOManager();
+ //----------------------------------------------------------------------------
+ /// @brief The IO thread can be used for background tasks, including
+ /// tasks that perform graphics operations using the resource
+ /// context. But the IO thread will lose the resource context
+ /// during shutdown of the Shell. Tasks that require the IO
+ /// manager or the resource context must not run after that
+ /// phase of shutdown.
+ ///
+ /// @return A BasicTaskRunner that posts tasks to the IO thread but stops
+ /// running tasks after the Shell shuts down the IO manager.
+ ///
+ std::shared_ptr<fml::BasicTaskRunner> GetShutdownSafeIOTaskRunner();
+
// Embedders should call this under low memory conditions to free up
// internal caches used.
//
@@ -480,6 +493,9 @@
fml::WeakPtr<PlatformView>
weak_platform_view_; // to be shared across threads
+ std::promise<fml::WeakPtr<ShellIOManager>> weak_io_manager_promise_;
+ std::shared_ptr<fml::BasicTaskRunner> shutdown_safe_io_task_runner_;
+
std::unordered_map<std::string_view, // method
std::pair<fml::RefPtr<fml::TaskRunner>,
ServiceProtocolHandler> // task-runner/function
@@ -641,6 +657,10 @@
const Settings& OnPlatformViewGetSettings() const override;
// |PlatformView::Delegate|
+ std::shared_ptr<fml::BasicTaskRunner>
+ OnPlatformViewGetShutdownSafeIOTaskRunner() const override;
+
+ // |PlatformView::Delegate|
void LoadDartDeferredLibrary(
intptr_t loading_unit_id,
std::unique_ptr<const fml::Mapping> snapshot_data,
diff --git a/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc b/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc
index 5ee5e7b..20383e3 100644
--- a/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc
+++ b/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc
@@ -8,6 +8,7 @@
#include <EGL/egl.h>
+#include "flutter/fml/task_runner_util.h"
#include "flutter/shell/gpu/gpu_surface_gl_skia.h"
#include "impeller/entity/gles/entity_shaders_gles.h"
@@ -61,7 +62,9 @@
return;
}
impeller_context_ = impeller::ContextGLES::Create(
- impeller::Flags{}, std::move(gl), ShaderLibraryMappings(), true);
+ impeller::Flags{}, std::move(gl), ShaderLibraryMappings(), true,
+ std::make_shared<fml::WrapperBasicTaskRunner>(
+ task_runners.GetIOTaskRunner()));
}
}
diff --git a/engine/src/flutter/shell/common/shell_unittests.cc b/engine/src/flutter/shell/common/shell_unittests.cc
index 60ff1ec..56ca523 100644
--- a/engine/src/flutter/shell/common/shell_unittests.cc
+++ b/engine/src/flutter/shell/common/shell_unittests.cc
@@ -186,6 +186,11 @@
(),
(const, override));
+ MOCK_METHOD(std::shared_ptr<fml::BasicTaskRunner>,
+ OnPlatformViewGetShutdownSafeIOTaskRunner,
+ (),
+ (const, override));
+
MOCK_METHOD(void,
LoadDartDeferredLibrary,
(intptr_t loading_unit_id,
diff --git a/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc b/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc
index c65f926..94f38b0 100644
--- a/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc
+++ b/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc
@@ -140,9 +140,11 @@
} // namespace
AndroidContextDynamicImpeller::AndroidContextDynamicImpeller(
- const AndroidContext::ContextSettings& settings)
+ const AndroidContext::ContextSettings& settings,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner)
: AndroidContext(AndroidRenderingAPI::kImpellerVulkan),
- settings_(settings) {}
+ settings_(settings),
+ io_task_runner_(std::move(io_task_runner)) {}
AndroidContextDynamicImpeller::~AndroidContextDynamicImpeller() = default;
@@ -186,7 +188,7 @@
if (!vk_context_) {
gl_context_ = std::make_shared<AndroidContextGLImpeller>(
std::make_unique<impeller::egl::Display>(),
- settings_.enable_gpu_tracing);
+ settings_.enable_gpu_tracing, io_task_runner_);
}
}
diff --git a/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h b/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h
index 4de037b..17aa12f 100644
--- a/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h
+++ b/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h
@@ -23,7 +23,8 @@
class AndroidContextDynamicImpeller : public AndroidContext {
public:
explicit AndroidContextDynamicImpeller(
- const AndroidContext::ContextSettings& settings);
+ const AndroidContext::ContextSettings& settings,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner);
~AndroidContextDynamicImpeller();
@@ -51,6 +52,7 @@
const AndroidContext::ContextSettings settings_;
std::shared_ptr<AndroidContextGLImpeller> gl_context_;
std::shared_ptr<AndroidContextVKImpeller> vk_context_;
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner_;
FML_DISALLOW_COPY_AND_ASSIGN(AndroidContextDynamicImpeller);
};
diff --git a/engine/src/flutter/shell/platform/android/android_context_gl_impeller.cc b/engine/src/flutter/shell/platform/android/android_context_gl_impeller.cc
index 79c0ec6..128fe13 100644
--- a/engine/src/flutter/shell/platform/android/android_context_gl_impeller.cc
+++ b/engine/src/flutter/shell/platform/android/android_context_gl_impeller.cc
@@ -51,7 +51,8 @@
static std::shared_ptr<impeller::Context> CreateImpellerContext(
const std::shared_ptr<impeller::ReactorGLES::Worker>& worker,
- bool enable_gpu_tracing) {
+ bool enable_gpu_tracing,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner) {
auto proc_table = std::make_unique<impeller::ProcTableGLES>(
impeller::egl::CreateProcAddressResolver());
@@ -85,11 +86,11 @@
auto context = impeller::ContextGLES::Create(
impeller::Flags{}, std::move(proc_table),
is_gles3 ? gles3_shader_mappings : gles2_shader_mappings,
- enable_gpu_tracing);
+ enable_gpu_tracing, std::move(io_task_runner));
#else
- auto context =
- impeller::ContextGLES::Create(impeller::Flags{}, std::move(proc_table),
- gles2_shader_mappings, enable_gpu_tracing);
+ auto context = impeller::ContextGLES::Create(
+ impeller::Flags{}, std::move(proc_table), gles2_shader_mappings,
+ enable_gpu_tracing, std::move(io_task_runner));
#endif // !SLIMPELLER
if (!context) {
@@ -107,10 +108,12 @@
AndroidContextGLImpeller::AndroidContextGLImpeller(
std::unique_ptr<impeller::egl::Display> display,
- bool enable_gpu_tracing)
+ bool enable_gpu_tracing,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner)
: AndroidContext(AndroidRenderingAPI::kImpellerOpenGLES),
reactor_worker_(std::shared_ptr<ReactorWorker>(new ReactorWorker())),
- display_(std::move(display)) {
+ display_(std::move(display)),
+ io_task_runner_(std::move(io_task_runner)) {
if (!display_ || !display_->IsValid()) {
FML_LOG(ERROR) << "Could not create context with invalid EGL display.";
return;
@@ -174,8 +177,8 @@
return;
}
- auto impeller_context =
- CreateImpellerContext(reactor_worker_, enable_gpu_tracing);
+ auto impeller_context = CreateImpellerContext(
+ reactor_worker_, enable_gpu_tracing, io_task_runner_);
if (!impeller_context) {
FML_LOG(ERROR) << "Could not create Impeller context.";
diff --git a/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h b/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h
index e6d68d8..9983a69 100644
--- a/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h
+++ b/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h
@@ -6,6 +6,7 @@
#define FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_CONTEXT_GL_IMPELLER_H_
#include "flutter/fml/macros.h"
+#include "flutter/fml/task_runner.h"
#include "flutter/impeller/toolkit/egl/display.h"
#include "flutter/shell/platform/android/context/android_context.h"
@@ -13,8 +14,10 @@
class AndroidContextGLImpeller : public AndroidContext {
public:
- AndroidContextGLImpeller(std::unique_ptr<impeller::egl::Display> display,
- bool enable_gpu_tracing);
+ AndroidContextGLImpeller(
+ std::unique_ptr<impeller::egl::Display> display,
+ bool enable_gpu_tracing,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner);
~AndroidContextGLImpeller();
@@ -42,6 +45,7 @@
std::unique_ptr<impeller::egl::Context> onscreen_context_;
std::unique_ptr<impeller::egl::Context> offscreen_context_;
bool is_valid_ = false;
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner_;
FML_DISALLOW_COPY_AND_ASSIGN(AndroidContextGLImpeller);
};
diff --git a/engine/src/flutter/shell/platform/android/android_context_gl_impeller_unittests.cc b/engine/src/flutter/shell/platform/android/android_context_gl_impeller_unittests.cc
index b74c068..ab43170 100644
--- a/engine/src/flutter/shell/platform/android/android_context_gl_impeller_unittests.cc
+++ b/engine/src/flutter/shell/platform/android/android_context_gl_impeller_unittests.cc
@@ -84,8 +84,8 @@
.WillOnce(Return(ByMove(std::move(second_result))));
ON_CALL(*display, ChooseConfig(_))
.WillByDefault(Return(ByMove(std::unique_ptr<Config>())));
- auto context =
- std::make_unique<AndroidContextGLImpeller>(std::move(display), true);
+ auto context = std::make_unique<AndroidContextGLImpeller>(std::move(display),
+ true, nullptr);
ASSERT_TRUE(context);
}
@@ -131,8 +131,8 @@
.WillOnce(Return(ByMove(std::move(fourth_result))));
ON_CALL(*display, ChooseConfig(_))
.WillByDefault(Return(ByMove(std::unique_ptr<Config>())));
- auto context =
- std::make_unique<AndroidContextGLImpeller>(std::move(display), true);
+ auto context = std::make_unique<AndroidContextGLImpeller>(std::move(display),
+ true, nullptr);
ASSERT_TRUE(context);
}
diff --git a/engine/src/flutter/shell/platform/android/platform_view_android.cc b/engine/src/flutter/shell/platform/android/platform_view_android.cc
index dfafb67..15915dd 100644
--- a/engine/src/flutter/shell/platform/android/platform_view_android.cc
+++ b/engine/src/flutter/shell/platform/android/platform_view_android.cc
@@ -106,7 +106,8 @@
const flutter::TaskRunners& task_runners,
AndroidRenderingAPI android_rendering_api,
bool enable_opengl_gpu_tracing,
- const AndroidContext::ContextSettings& settings) {
+ const AndroidContext::ContextSettings& settings,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner) {
switch (android_rendering_api) {
#if !SLIMPELLER
case AndroidRenderingAPI::kSoftware:
@@ -121,11 +122,12 @@
return std::make_unique<AndroidContextVKImpeller>(settings);
case AndroidRenderingAPI::kImpellerOpenGLES:
return std::make_unique<AndroidContextGLImpeller>(
- std::make_unique<impeller::egl::Display>(),
- enable_opengl_gpu_tracing);
+ std::make_unique<impeller::egl::Display>(), enable_opengl_gpu_tracing,
+ std::move(io_task_runner));
case AndroidRenderingAPI::kImpellerAutoselect:
// Determine if we're using GL or Vulkan.
- return std::make_unique<AndroidContextDynamicImpeller>(settings);
+ return std::make_unique<AndroidContextDynamicImpeller>(
+ settings, std::move(io_task_runner));
}
FML_UNREACHABLE();
}
@@ -143,7 +145,8 @@
task_runners,
rendering_api,
delegate.OnPlatformViewGetSettings().enable_opengl_gpu_tracing,
- CreateContextSettings(delegate.OnPlatformViewGetSettings()))) {}
+ CreateContextSettings(delegate.OnPlatformViewGetSettings()),
+ delegate.OnPlatformViewGetShutdownSafeIOTaskRunner())) {}
PlatformViewAndroid::PlatformViewAndroid(
PlatformView::Delegate& delegate,
diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm
index 7cc1366..e4b3a64 100644
--- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm
+++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm
@@ -57,6 +57,9 @@
bool transient) override {}
void UpdateAssetResolverByType(std::unique_ptr<AssetResolver> updated_asset_resolver,
AssetResolver::AssetResolverType type) override {}
+ std::shared_ptr<fml::BasicTaskRunner> OnPlatformViewGetShutdownSafeIOTaskRunner() const override {
+ return nullptr;
+ }
flutter::Settings settings_;
};
diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
index e9f8222..1fdcb42 100644
--- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
+++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
@@ -310,6 +310,9 @@
bool transient) override {}
void UpdateAssetResolverByType(std::unique_ptr<flutter::AssetResolver> updated_asset_resolver,
flutter::AssetResolver::AssetResolverType type) override {}
+ std::shared_ptr<fml::BasicTaskRunner> OnPlatformViewGetShutdownSafeIOTaskRunner() const override {
+ return nullptr;
+ }
flutter::Settings settings_;
};
diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm
index 8f00a4a..907e2ff 100644
--- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm
+++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm
@@ -125,6 +125,9 @@
bool transient) override {}
void UpdateAssetResolverByType(std::unique_ptr<flutter::AssetResolver> updated_asset_resolver,
flutter::AssetResolver::AssetResolverType type) override {}
+ std::shared_ptr<fml::BasicTaskRunner> OnPlatformViewGetShutdownSafeIOTaskRunner() const override {
+ return nullptr;
+ }
flutter::Settings settings_;
};
diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
index 40be0c6..ff0b5ba 100644
--- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
+++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
@@ -113,6 +113,9 @@
bool transient) override {}
void UpdateAssetResolverByType(std::unique_ptr<flutter::AssetResolver> updated_asset_resolver,
flutter::AssetResolver::AssetResolverType type) override {}
+ std::shared_ptr<fml::BasicTaskRunner> OnPlatformViewGetShutdownSafeIOTaskRunner() const override {
+ return nullptr;
+ }
flutter::Settings settings_;
};
diff --git a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios_test.mm b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios_test.mm
index 87cb679..0852c30 100644
--- a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios_test.mm
+++ b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios_test.mm
@@ -53,6 +53,9 @@
bool transient) override {}
void UpdateAssetResolverByType(std::unique_ptr<flutter::AssetResolver> updated_asset_resolver,
flutter::AssetResolver::AssetResolverType type) override {}
+ std::shared_ptr<fml::BasicTaskRunner> OnPlatformViewGetShutdownSafeIOTaskRunner() const override {
+ return nullptr;
+ }
flutter::Settings settings_;
};
diff --git a/engine/src/flutter/shell/platform/embedder/embedder.cc b/engine/src/flutter/shell/platform/embedder/embedder.cc
index 075232e..29d75fb 100644
--- a/engine/src/flutter/shell/platform/embedder/embedder.cc
+++ b/engine/src/flutter/shell/platform/embedder/embedder.cc
@@ -495,6 +495,7 @@
shell.GetTaskRunners(), // task runners
std::make_unique<flutter::EmbedderSurfaceGLImpeller>(
gl_dispatch_table, fbo_reset_after_present, view_embedder,
+ shell.GetShutdownSafeIOTaskRunner(),
impeller_flags), // embedder_surface
platform_dispatch_table, // embedder platform dispatch table
view_embedder // external view embedder
diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface.cc b/engine/src/flutter/shell/platform/embedder/embedder_surface.cc
index 029833f..8e7eef6 100644
--- a/engine/src/flutter/shell/platform/embedder/embedder_surface.cc
+++ b/engine/src/flutter/shell/platform/embedder/embedder_surface.cc
@@ -19,4 +19,6 @@
return nullptr;
}
+void EmbedderSurface::ReleaseResourceContext() const {}
+
} // namespace flutter
diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface.h b/engine/src/flutter/shell/platform/embedder/embedder_surface.h
index 32873f1..b00433e 100644
--- a/engine/src/flutter/shell/platform/embedder/embedder_surface.h
+++ b/engine/src/flutter/shell/platform/embedder/embedder_surface.h
@@ -26,6 +26,12 @@
virtual sk_sp<GrDirectContext> CreateResourceContext() const;
+ /// Release any platform specific resources associated with the graphics
+ /// context created by `CreateResourceContext`.
+ ///
+ /// @see `PlatformView::ReleaseResourceContext`
+ virtual void ReleaseResourceContext() const;
+
private:
FML_DISALLOW_COPY_AND_ASSIGN(EmbedderSurface);
};
diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc
index 1aad505..bb4cc40 100644
--- a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc
+++ b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc
@@ -78,6 +78,7 @@
EmbedderSurfaceGLSkia::GLDispatchTable gl_dispatch_table,
bool fbo_reset_after_present,
std::shared_ptr<EmbedderExternalViewEmbedder> external_view_embedder,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner,
impeller::Flags impeller_flags)
: gl_dispatch_table_(std::move(gl_dispatch_table)),
fbo_reset_after_present_(fbo_reset_after_present),
@@ -108,7 +109,7 @@
impeller_context_ = impeller::ContextGLES::Create(
impeller_flags, std::move(gl), shader_mappings,
- /*enable_gpu_tracing=*/false);
+ /*enable_gpu_tracing=*/false, std::move(io_task_runner));
if (!impeller_context_) {
FML_LOG(ERROR) << "Could not create Impeller context.";
@@ -230,4 +231,10 @@
return nullptr;
}
+// |EmbedderSurface|
+void EmbedderSurfaceGLImpeller::ReleaseResourceContext() const {
+ worker_->SetReactionsAllowedOnCurrentThread(false);
+ gl_dispatch_table_.gl_clear_current_callback();
+}
+
} // namespace flutter
diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h
index 598925f..2d420af 100644
--- a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h
+++ b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h
@@ -33,6 +33,7 @@
EmbedderSurfaceGLSkia::GLDispatchTable gl_dispatch_table,
bool fbo_reset_after_present,
std::shared_ptr<EmbedderExternalViewEmbedder> external_view_embedder,
+ std::shared_ptr<fml::BasicTaskRunner> io_task_runner,
impeller::Flags impeller_flags = {});
~EmbedderSurfaceGLImpeller() override;
@@ -85,6 +86,9 @@
// |EmbedderSurface|
sk_sp<GrDirectContext> CreateResourceContext() const override;
+ // |EmbedderSurface|
+ void ReleaseResourceContext() const override;
+
FML_DISALLOW_COPY_AND_ASSIGN(EmbedderSurfaceGLImpeller);
};
diff --git a/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc b/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc
index 712a528..04f587c 100644
--- a/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc
+++ b/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc
@@ -169,6 +169,15 @@
}
// |PlatformView|
+void PlatformViewEmbedder::ReleaseResourceContext() const {
+ if (embedder_surface_ == nullptr) {
+ FML_LOG(ERROR) << "Embedder surface was null.";
+ return;
+ }
+ embedder_surface_->ReleaseResourceContext();
+}
+
+// |PlatformView|
std::unique_ptr<VsyncWaiter> PlatformViewEmbedder::CreateVSyncWaiter() {
if (!platform_dispatch_table_.vsync_callback) {
// Superclass implementation creates a timer based fallback.
diff --git a/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h b/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h
index 1b9f500..6f0bc97 100644
--- a/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h
+++ b/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h
@@ -136,6 +136,9 @@
sk_sp<GrDirectContext> CreateResourceContext() const override;
// |PlatformView|
+ void ReleaseResourceContext() const override;
+
+ // |PlatformView|
std::unique_ptr<VsyncWaiter> CreateVSyncWaiter() override;
// |PlatformView|
diff --git a/engine/src/flutter/shell/platform/embedder/platform_view_embedder_unittests.cc b/engine/src/flutter/shell/platform/embedder/platform_view_embedder_unittests.cc
index d82265f..bf6e1b8 100644
--- a/engine/src/flutter/shell/platform/embedder/platform_view_embedder_unittests.cc
+++ b/engine/src/flutter/shell/platform/embedder/platform_view_embedder_unittests.cc
@@ -104,6 +104,10 @@
OnPlatformViewGetSettings,
(),
(const, override));
+ MOCK_METHOD(std::shared_ptr<fml::BasicTaskRunner>,
+ OnPlatformViewGetShutdownSafeIOTaskRunner,
+ (),
+ (const, override));
};
class MockResponse : public PlatformMessageResponse {
diff --git a/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc b/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc
index db76a1c..59296de 100644
--- a/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc
+++ b/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc
@@ -39,7 +39,8 @@
StubDispatchTable(/* version */ "OpenGL ES 3.0");
const auto surface = EmbedderSurfaceGLImpeller(
gl_dispatch_table, /* fbo_reset_after_present */ false,
- /* external_view_embedder */ nullptr);
+ /* external_view_embedder */ nullptr,
+ /* io_task_runner */ nullptr);
const std::shared_ptr<impeller::Context> context =
surface.CreateImpellerContext();
@@ -61,7 +62,8 @@
StubDispatchTable(/* version */ "OpenGL ES 2.0");
const auto surface = EmbedderSurfaceGLImpeller(
gl_dispatch_table, /* fbo_reset_after_present */ false,
- /* external_view_embedder */ nullptr);
+ /* external_view_embedder */ nullptr,
+ /* io_task_runner */ nullptr);
const std::shared_ptr<impeller::Context> context =
surface.CreateImpellerContext();
diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc
index 9a7f8fa..d6e5ecd 100644
--- a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc
+++ b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc
@@ -107,6 +107,11 @@
return settings_;
}
// |flutter::PlatformView::Delegate|
+ std::shared_ptr<fml::BasicTaskRunner>
+ OnPlatformViewGetShutdownSafeIOTaskRunner() const {
+ return nullptr;
+ }
+ // |flutter::PlatformView::Delegate|
void OnPlatformViewDispatchPlatformMessage(
std::unique_ptr<flutter::PlatformMessage> message) {
message_ = std::move(message);