[Impeller] Delete GLES framebuffer objects only on the thread where they were created (#179768)

OpenGL does not allow cross-context sharing of container objects (such
as framebuffers) that hold references to other objects. ReactorGLES must
ensure that framebuffers are deleted using the thread and context where
the object was created.

Fixes https://github.com/flutter/flutter/issues/178276
This commit is contained in:
Jason Simmons 2025-12-15 10:55:09 -08:00 committed by GitHub
parent 795b1d2266
commit e32ddabf31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 58 additions and 2 deletions

View File

@ -28,4 +28,8 @@ std::string HandleTypeToString(HandleType type) {
FML_UNREACHABLE();
}
bool HandleTypeIsShareable(HandleType type) {
return type != HandleType::kFrameBuffer;
}
} // namespace impeller

View File

@ -8,6 +8,7 @@
#include <optional>
#include <sstream>
#include <string>
#include <thread>
#include <type_traits>
#include "flutter/fml/hash_combine.h"
@ -27,6 +28,9 @@ enum class HandleType {
std::string HandleTypeToString(HandleType type);
// Returns whether this type of handle can be shared across OpenGL contexts.
bool HandleTypeIsShareable(HandleType type);
class ReactorGLES;
//------------------------------------------------------------------------------
@ -80,10 +84,11 @@ class HandleGLES {
std::optional<UniqueID> name_;
std::size_t hash_;
std::optional<uint64_t> untracked_id_;
std::optional<std::thread::id> owner_thread_;
friend class ReactorGLES;
HandleGLES(HandleType p_type, UniqueID p_name)
HandleGLES(HandleType p_type, UniqueID p_name, std::thread::id p_owner_thread)
: type_(p_type),
name_(p_name),
hash_(fml::HashCombine(
@ -98,7 +103,11 @@ class HandleGLES {
p_name)) {}
static HandleGLES Create(HandleType type) {
return HandleGLES{type, UniqueID{}};
HandleGLES handle{type, UniqueID{}};
if (!HandleTypeIsShareable(type)) {
handle.owner_thread_ = std::this_thread::get_id();
}
return handle;
}
};

View File

@ -296,6 +296,7 @@ bool ReactorGLES::ReactOnce() {
bool ReactorGLES::ConsolidateHandles() {
TRACE_EVENT0("impeller", __FUNCTION__);
const auto& gl = GetProcTable();
std::thread::id current_thread = std::this_thread::get_id();
std::vector<std::tuple<HandleGLES, std::optional<GLStorage>>>
handles_to_delete;
std::vector<std::tuple<DebugResourceType, GLint, std::string>>
@ -307,6 +308,13 @@ bool ReactorGLES::ConsolidateHandles() {
for (auto& handle : handles_) {
// Collect dead handles.
if (handle.second.pending_collection) {
// Some objects can only be deleted on a specific thread. Collection
// of these objects will be deferred until the owner thead executes
// a reaction and triggers collection of handles.
const auto& owner_thread = handle.first.owner_thread_;
if (owner_thread.has_value() && *owner_thread != current_thread) {
continue;
}
handles_to_delete.emplace_back(
std::make_tuple(handle.first, handle.second.name));
continue;

View File

@ -180,5 +180,40 @@ TEST(ReactorGLES, CanDeferOperations) {
EXPECT_TRUE(did_run);
}
TEST(ReactorGLES, FramebufferDeletedOnOwnerThread) {
auto mock_gles_impl = std::make_unique<MockGLESImpl>();
EXPECT_CALL(*mock_gles_impl, GenFramebuffers(1, _))
.WillOnce([](GLsizei size, GLuint* queries) { queries[0] = 1234; });
std::shared_ptr<MockGLES> mock_gles =
MockGLES::Init(std::move(mock_gles_impl));
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);
HandleGLES handle = reactor->CreateHandle(HandleType::kFrameBuffer);
std::thread::id cleanup_thread;
reactor->RegisterCleanupCallback(
handle, [&]() { cleanup_thread = std::this_thread::get_id(); });
reactor->CollectHandle(handle);
std::thread thread([&] {
EXPECT_TRUE(reactor->AddOperation([](const ReactorGLES& reactor) {}));
EXPECT_TRUE(reactor->React());
});
thread.join();
EXPECT_EQ(cleanup_thread, std::thread::id());
EXPECT_TRUE(reactor->AddOperation([](const ReactorGLES& reactor) {}));
EXPECT_TRUE(reactor->React());
EXPECT_EQ(cleanup_thread, std::this_thread::get_id());
}
} // namespace testing
} // namespace impeller