Skip to content

Commit 6e15260

Browse files
[Impeller] Maintain separate queues of GLES operations for each thread in the reactor (flutter/engine#56573)
Threads that add operations to the ReactorGLES assume that those operations will be executed serially. But prior to this change, the ReactorGLES added all operations into one queue. The reactor would then execute those operations on any thread that can react. This could cause operations that were added to the reactor on the raster thread to be submitted to the GPU on the IO thread (or vice versa). The reactor does not wait for the GPU to finish execution of those operations. So other operations added on the raster thread could be submitted by a reaction before the GPU has completed the operation that was submitted on the IO thread. This PR ensures that operations added to the reactor on a given thread will be executed during a reaction on that same thread. If the thread can not currently react, then the operations will be queued until the thread enables reactions. This also adds a call to CommandBuffer::WaitUntilScheduled to ImageDecoderImpeller. This ensures that the command buffer submitted on the IO thread is flushed before the image is returned. Fixes flutter#158535 Fixes flutter#158388 Fixes flutter#158390
1 parent 219838a commit 6e15260

File tree

14 files changed

+63
-24
lines changed

14 files changed

+63
-24
lines changed

engine/src/flutter/impeller/renderer/backend/gles/command_buffer_gles.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ bool CommandBufferGLES::OnSubmitCommands(CompletionCallback callback) {
3939
}
4040

4141
// |CommandBuffer|
42-
void CommandBufferGLES::OnWaitUntilScheduled() {
43-
reactor_->GetProcTable().Flush();
42+
void CommandBufferGLES::OnWaitUntilCompleted() {
43+
reactor_->GetProcTable().Finish();
4444
}
4545

4646
// |CommandBuffer|

engine/src/flutter/impeller/renderer/backend/gles/command_buffer_gles.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class CommandBufferGLES final : public CommandBuffer {
3535
bool OnSubmitCommands(CompletionCallback callback) override;
3636

3737
// |CommandBuffer|
38-
void OnWaitUntilScheduled() override;
38+
void OnWaitUntilCompleted() override;
3939

4040
// |CommandBuffer|
4141
std::shared_ptr<RenderPass> OnCreateRenderPass(RenderTarget target) override;

engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ struct GLProc {
168168
PROC(DrawElements); \
169169
PROC(Enable); \
170170
PROC(EnableVertexAttribArray); \
171+
PROC(Finish); \
171172
PROC(Flush); \
172173
PROC(FramebufferRenderbuffer); \
173174
PROC(FramebufferTexture2D); \

engine/src/flutter/impeller/renderer/backend/gles/reactor_gles.cc

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,10 @@ bool ReactorGLES::RemoveWorker(WorkerID worker) {
105105
}
106106

107107
bool ReactorGLES::HasPendingOperations() const {
108+
auto thread_id = std::this_thread::get_id();
108109
Lock ops_lock(ops_mutex_);
109-
return !ops_.empty();
110+
auto it = ops_.find(thread_id);
111+
return it != ops_.end() ? !it->second.empty() : false;
110112
}
111113

112114
const ProcTableGLES& ReactorGLES::GetProcTable() const {
@@ -136,9 +138,10 @@ bool ReactorGLES::AddOperation(Operation operation) {
136138
if (!operation) {
137139
return false;
138140
}
141+
auto thread_id = std::this_thread::get_id();
139142
{
140143
Lock ops_lock(ops_mutex_);
141-
ops_.emplace_back(std::move(operation));
144+
ops_[thread_id].emplace_back(std::move(operation));
142145
}
143146
// Attempt a reaction if able but it is not an error if this isn't possible.
144147
[[maybe_unused]] auto result = React();
@@ -191,10 +194,6 @@ bool ReactorGLES::React() {
191194
}
192195
TRACE_EVENT0("impeller", "ReactorGLES::React");
193196
while (HasPendingOperations()) {
194-
// Both the raster thread and the IO thread can flush queued operations.
195-
// Ensure that execution of the ops is serialized.
196-
Lock execution_lock(ops_execution_mutex_);
197-
198197
if (!ReactOnce()) {
199198
return false;
200199
}
@@ -279,10 +278,11 @@ bool ReactorGLES::FlushOps() {
279278

280279
// Do NOT hold the ops or handles locks while performing operations in case
281280
// the ops enqueue more ops.
282-
decltype(ops_) ops;
281+
decltype(ops_)::mapped_type ops;
282+
auto thread_id = std::this_thread::get_id();
283283
{
284284
Lock ops_lock(ops_mutex_);
285-
std::swap(ops_, ops);
285+
std::swap(ops_[thread_id], ops);
286286
}
287287
for (const auto& op : ops) {
288288
TRACE_EVENT0("impeller", "ReactorGLES::Operation");

engine/src/flutter/impeller/renderer/backend/gles/reactor_gles.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,9 @@ class ReactorGLES {
260260

261261
std::unique_ptr<ProcTableGLES> proc_table_;
262262

263-
Mutex ops_execution_mutex_;
264263
mutable Mutex ops_mutex_;
265-
std::vector<Operation> ops_ IPLR_GUARDED_BY(ops_mutex_);
264+
std::map<std::thread::id, std::vector<Operation>> ops_ IPLR_GUARDED_BY(
265+
ops_mutex_);
266266

267267
// Make sure the container is one where erasing items during iteration doesn't
268268
// invalidate other iterators.
@@ -280,7 +280,7 @@ class ReactorGLES {
280280
bool can_set_debug_labels_ = false;
281281
bool is_valid_ = false;
282282

283-
bool ReactOnce() IPLR_REQUIRES(ops_execution_mutex_);
283+
bool ReactOnce();
284284

285285
bool HasPendingOperations() const;
286286

engine/src/flutter/impeller/renderer/backend/gles/test/reactor_unittests.cc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
#include <memory>
6+
#include "flutter/fml/synchronization/waitable_event.h"
67
#include "flutter/testing/testing.h" // IWYU pragma: keep
78
#include "gtest/gtest.h"
89
#include "impeller/renderer/backend/gles/handle_gles.h"
@@ -60,5 +61,37 @@ TEST(ReactorGLES, DeletesHandlesDuringShutdown) {
6061
calls.end());
6162
}
6263

64+
TEST(ReactorGLES, PerThreadOperationQueues) {
65+
auto mock_gles = MockGLES::Init();
66+
ProcTableGLES::Resolver resolver = kMockResolverGLES;
67+
auto proc_table = std::make_unique<ProcTableGLES>(resolver);
68+
auto worker = std::make_shared<TestWorker>();
69+
auto reactor = std::make_shared<ReactorGLES>(std::move(proc_table));
70+
reactor->AddWorker(worker);
71+
72+
bool op1_called = false;
73+
EXPECT_TRUE(
74+
reactor->AddOperation([&](const ReactorGLES&) { op1_called = true; }));
75+
76+
fml::AutoResetWaitableEvent event;
77+
bool op2_called = false;
78+
std::thread thread([&] {
79+
EXPECT_TRUE(
80+
reactor->AddOperation([&](const ReactorGLES&) { op2_called = true; }));
81+
event.Wait();
82+
EXPECT_TRUE(reactor->React());
83+
});
84+
85+
// Reacting on the main thread should only run the main thread's operation.
86+
EXPECT_TRUE(reactor->React());
87+
EXPECT_TRUE(op1_called);
88+
EXPECT_FALSE(op2_called);
89+
90+
// Reacting on the second thread will run the second thread's operation.
91+
event.Signal();
92+
thread.join();
93+
EXPECT_TRUE(op2_called);
94+
}
95+
6396
} // namespace testing
6497
} // namespace impeller

engine/src/flutter/impeller/renderer/backend/metal/command_buffer_mtl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class CommandBufferMTL final : public CommandBuffer {
3737
bool OnSubmitCommands(CompletionCallback callback) override;
3838

3939
// |CommandBuffer|
40-
void OnWaitUntilScheduled() override;
40+
void OnWaitUntilCompleted() override;
4141

4242
// |CommandBuffer|
4343
std::shared_ptr<RenderPass> OnCreateRenderPass(RenderTarget target) override;

engine/src/flutter/impeller/renderer/backend/metal/command_buffer_mtl.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ static bool LogMTLCommandBufferErrorIfPresent(id<MTLCommandBuffer> buffer) {
187187
return true;
188188
}
189189

190-
void CommandBufferMTL::OnWaitUntilScheduled() {}
190+
void CommandBufferMTL::OnWaitUntilCompleted() {}
191191

192192
std::shared_ptr<RenderPass> CommandBufferMTL::OnCreateRenderPass(
193193
RenderTarget target) {

engine/src/flutter/impeller/renderer/backend/vulkan/command_buffer_vk.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ bool CommandBufferVK::OnSubmitCommands(CompletionCallback callback) {
4747
FML_UNREACHABLE()
4848
}
4949

50-
void CommandBufferVK::OnWaitUntilScheduled() {}
50+
void CommandBufferVK::OnWaitUntilCompleted() {}
5151

5252
std::shared_ptr<RenderPass> CommandBufferVK::OnCreateRenderPass(
5353
RenderTarget target) {

engine/src/flutter/impeller/renderer/backend/vulkan/command_buffer_vk.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class CommandBufferVK final
100100
bool OnSubmitCommands(CompletionCallback callback) override;
101101

102102
// |CommandBuffer|
103-
void OnWaitUntilScheduled() override;
103+
void OnWaitUntilCompleted() override;
104104

105105
// |CommandBuffer|
106106
std::shared_ptr<RenderPass> OnCreateRenderPass(RenderTarget target) override;

engine/src/flutter/impeller/renderer/command_buffer.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ bool CommandBuffer::SubmitCommands() {
3030
return SubmitCommands(nullptr);
3131
}
3232

33-
void CommandBuffer::WaitUntilScheduled() {
34-
return OnWaitUntilScheduled();
33+
void CommandBuffer::WaitUntilCompleted() {
34+
return OnWaitUntilCompleted();
3535
}
3636

3737
std::shared_ptr<RenderPass> CommandBuffer::CreateRenderPass(

engine/src/flutter/impeller/renderer/command_buffer.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,10 @@ class CommandBuffer {
6161
virtual void SetLabel(std::string_view label) const = 0;
6262

6363
//----------------------------------------------------------------------------
64-
/// @brief Force execution of pending GPU commands.
64+
/// @brief Block the current thread until the GPU has completed execution
65+
/// of the commands.
6566
///
66-
void WaitUntilScheduled();
67+
void WaitUntilCompleted();
6768

6869
//----------------------------------------------------------------------------
6970
/// @brief Create a render pass to record render commands into.
@@ -102,7 +103,7 @@ class CommandBuffer {
102103

103104
[[nodiscard]] virtual bool OnSubmitCommands(CompletionCallback callback) = 0;
104105

105-
virtual void OnWaitUntilScheduled() = 0;
106+
virtual void OnWaitUntilCompleted() = 0;
106107

107108
virtual std::shared_ptr<ComputePass> OnCreateComputePass() = 0;
108109

engine/src/flutter/impeller/renderer/testing/mocks.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ class MockCommandBuffer : public CommandBuffer {
128128
OnSubmitCommands,
129129
(CompletionCallback callback),
130130
(override));
131-
MOCK_METHOD(void, OnWaitUntilScheduled, (), (override));
131+
MOCK_METHOD(void, OnWaitUntilCompleted, (), (override));
132132
MOCK_METHOD(std::shared_ptr<ComputePass>,
133133
OnCreateComputePass,
134134
(),

engine/src/flutter/lib/ui/painting/image_decoder_impeller.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,10 @@ ImageDecoderImpeller::UnsafeUploadTextureToPrivate(
384384
return std::make_pair(nullptr, decode_error);
385385
}
386386

387+
// Flush the pending command buffer to ensure that its output becomes visible
388+
// to the raster thread.
389+
command_buffer->WaitUntilCompleted();
390+
387391
context->DisposeThreadLocalCachedResources();
388392

389393
return std::make_pair(

0 commit comments

Comments
 (0)