[Impeller] flush all GLES cmd buffers together. (flutter/engine#56724)

Locally this gives much better performance, about doubling frame time on the Pixel 4. This avoids multiple glbuffersubdata calls that seems to perform particularly bad on mobile devices.

Thinking about it more, I'm not sure that having a separate EncodeCommands API is useful for RenderPass/BlitPass. instead they should probably just use submit. but that is a refactor for another day.

https://github.com/flutter/flutter/issues/159177
This commit is contained in:
Jonah Williams 2024-11-20 09:41:14 -08:00 committed by GitHub
parent 957a442547
commit c9bb068cfa
6 changed files with 57 additions and 11 deletions

View File

@ -159,6 +159,16 @@ void ContextGLES::ResetThreadLocalState() const {
}); });
} }
bool ContextGLES::EnqueueCommandBuffer(
std::shared_ptr<CommandBuffer> command_buffer) {
return true;
}
// |Context|
[[nodiscard]] bool ContextGLES::FlushCommandBuffers() {
return reactor_->React();
}
// |Context| // |Context|
bool ContextGLES::AddTrackingFence( bool ContextGLES::AddTrackingFence(
const std::shared_ptr<Texture>& texture) const { const std::shared_ptr<Texture>& texture) const {

View File

@ -99,6 +99,13 @@ class ContextGLES final : public Context,
// |Context| // |Context|
void ResetThreadLocalState() const override; void ResetThreadLocalState() const override;
// |Context|
[[nodiscard]] bool EnqueueCommandBuffer(
std::shared_ptr<CommandBuffer> command_buffer) override;
// |Context|
[[nodiscard]] bool FlushCommandBuffers() override;
ContextGLES(const ContextGLES&) = delete; ContextGLES(const ContextGLES&) = delete;
ContextGLES& operator=(const ContextGLES&) = delete; ContextGLES& operator=(const ContextGLES&) = delete;

View File

@ -166,7 +166,7 @@ std::optional<GLsync> ReactorGLES::GetGLFence(const HandleGLES& handle) const {
return std::nullopt; return std::nullopt;
} }
bool ReactorGLES::AddOperation(Operation operation) { bool ReactorGLES::AddOperation(Operation operation, bool defer) {
if (!operation) { if (!operation) {
return false; return false;
} }
@ -176,7 +176,9 @@ bool ReactorGLES::AddOperation(Operation operation) {
ops_[thread_id].emplace_back(std::move(operation)); ops_[thread_id].emplace_back(std::move(operation));
} }
// Attempt a reaction if able but it is not an error if this isn't possible. // Attempt a reaction if able but it is not an error if this isn't possible.
if (!defer) {
[[maybe_unused]] auto result = React(); [[maybe_unused]] auto result = React();
}
return true; return true;
} }

View File

@ -214,10 +214,12 @@ class ReactorGLES {
/// torn down. /// torn down.
/// ///
/// @param[in] operation The operation /// @param[in] operation The operation
/// @param[in] defer If false, the reactor attempts to React after
/// adding this operation.
/// ///
/// @return If the operation was successfully queued for completion. /// @return If the operation was successfully queued for completion.
/// ///
[[nodiscard]] bool AddOperation(Operation operation); [[nodiscard]] bool AddOperation(Operation operation, bool defer = false);
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
/// @brief Register a cleanup callback that will be invokved with the /// @brief Register a cleanup callback that will be invokved with the

View File

@ -585,14 +585,15 @@ bool RenderPassGLES::OnEncodeCommands(const Context& context) const {
std::shared_ptr<const RenderPassGLES> shared_this = shared_from_this(); std::shared_ptr<const RenderPassGLES> shared_this = shared_from_this();
auto tracer = ContextGLES::Cast(context).GetGPUTracer(); auto tracer = ContextGLES::Cast(context).GetGPUTracer();
return reactor_->AddOperation([pass_data, return reactor_->AddOperation(
allocator = context.GetResourceAllocator(), [pass_data, allocator = context.GetResourceAllocator(),
render_pass = std::move(shared_this), render_pass = std::move(shared_this), tracer](const auto& reactor) {
tracer](const auto& reactor) {
auto result = EncodeCommandsInReactor(*pass_data, allocator, reactor, auto result = EncodeCommandsInReactor(*pass_data, allocator, reactor,
render_pass->commands_, tracer); render_pass->commands_, tracer);
FML_CHECK(result) << "Must be able to encode GL commands without error."; FML_CHECK(result)
}); << "Must be able to encode GL commands without error.";
},
/*defer=*/true);
} }
} // namespace impeller } // namespace impeller

View File

@ -93,5 +93,29 @@ TEST(ReactorGLES, PerThreadOperationQueues) {
EXPECT_TRUE(op2_called); EXPECT_TRUE(op2_called);
} }
TEST(ReactorGLES, CanDeferOperations) {
auto mock_gles = MockGLES::Init();
ProcTableGLES::Resolver resolver = kMockResolverGLES;
auto proc_table = std::make_unique<ProcTableGLES>(resolver);
auto worker = std::make_shared<TestWorker>();
auto reactor = std::make_shared<ReactorGLES>(std::move(proc_table));
reactor->AddWorker(worker);
// Add operation executes tasks as long as the reactor can run tasks on
// the current thread.
bool did_run = false;
EXPECT_TRUE(
reactor->AddOperation([&](const ReactorGLES&) { did_run = true; }));
EXPECT_TRUE(did_run);
//...unless defer=true is specified, which only enqueues in the reactor.
did_run = false;
EXPECT_TRUE(reactor->AddOperation([&](const ReactorGLES&) { did_run = true; },
/*defer=*/true));
EXPECT_FALSE(did_run);
EXPECT_TRUE(reactor->React());
EXPECT_TRUE(did_run);
}
} // namespace testing } // namespace testing
} // namespace impeller } // namespace impeller