[Impeller] Exploit dynamic state in OpenGL for fewer program links. (#53764)
Fixes https://github.com/flutter/flutter/issues/145125
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index 312b14e..bd21559 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -42231,6 +42231,8 @@
ORIGIN: ../../../flutter/impeller/renderer/backend/gles/surface_gles.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/gles/texture_gles.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/gles/texture_gles.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/renderer/backend/gles/unique_handle_gles.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/renderer/backend/gles/unique_handle_gles.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/allocator_mtl.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/allocator_mtl.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/blit_pass_mtl.h + ../../../flutter/LICENSE
@@ -45097,6 +45099,8 @@
FILE: ../../../flutter/impeller/renderer/backend/gles/surface_gles.h
FILE: ../../../flutter/impeller/renderer/backend/gles/texture_gles.cc
FILE: ../../../flutter/impeller/renderer/backend/gles/texture_gles.h
+FILE: ../../../flutter/impeller/renderer/backend/gles/unique_handle_gles.cc
+FILE: ../../../flutter/impeller/renderer/backend/gles/unique_handle_gles.h
FILE: ../../../flutter/impeller/renderer/backend/metal/allocator_mtl.h
FILE: ../../../flutter/impeller/renderer/backend/metal/allocator_mtl.mm
FILE: ../../../flutter/impeller/renderer/backend/metal/blit_pass_mtl.h
diff --git a/impeller/fixtures/BUILD.gn b/impeller/fixtures/BUILD.gn
index b121d17..ce20901 100644
--- a/impeller/fixtures/BUILD.gn
+++ b/impeller/fixtures/BUILD.gn
@@ -38,6 +38,8 @@
"sepia.frag",
"sepia.vert",
"simple.vert",
+ "spec_constant.frag",
+ "spec_constant.vert",
"stage1.comp",
"stage2.comp",
"swizzle.frag",
diff --git a/impeller/fixtures/spec_constant.frag b/impeller/fixtures/spec_constant.frag
new file mode 100644
index 0000000..844d5c4
--- /dev/null
+++ b/impeller/fixtures/spec_constant.frag
@@ -0,0 +1,19 @@
+// 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.
+
+in vec4 v_color;
+in vec4 v_color2;
+
+layout(constant_id = 0) const float some_fraction = 1.0;
+
+out vec4 frag_color;
+
+uniform FragInfo {
+ float time;
+}
+frag_info;
+
+void main() {
+ frag_color = mix(v_color, v_color2, some_fraction);
+}
diff --git a/impeller/fixtures/spec_constant.vert b/impeller/fixtures/spec_constant.vert
new file mode 100644
index 0000000..d9ff2b6
--- /dev/null
+++ b/impeller/fixtures/spec_constant.vert
@@ -0,0 +1,16 @@
+// 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.
+
+in vec2 position;
+in vec4 color;
+in vec4 color2;
+
+out vec4 v_color;
+out vec4 v_color2;
+
+void main() {
+ gl_Position = vec4(position, 0.0, 1.0);
+ v_color = color;
+ v_color2 = color2;
+}
diff --git a/impeller/renderer/backend/gles/BUILD.gn b/impeller/renderer/backend/gles/BUILD.gn
index 5b97e1e..3a99fa9 100644
--- a/impeller/renderer/backend/gles/BUILD.gn
+++ b/impeller/renderer/backend/gles/BUILD.gn
@@ -20,11 +20,13 @@
"test/mock_gles.cc",
"test/mock_gles.h",
"test/mock_gles_unittests.cc",
+ "test/pipeline_library_gles_unittests.cc",
"test/proc_table_gles_unittests.cc",
"test/specialization_constants_unittests.cc",
]
deps = [
":gles",
+ "//flutter/impeller/playground:playground_test",
"//flutter/testing:testing_lib",
]
}
@@ -80,6 +82,8 @@
"surface_gles.h",
"texture_gles.cc",
"texture_gles.h",
+ "unique_handle_gles.cc",
+ "unique_handle_gles.h",
]
if (!is_android && !is_fuchsia) {
diff --git a/impeller/renderer/backend/gles/handle_gles.h b/impeller/renderer/backend/gles/handle_gles.h
index 3f6d7bb..cc52c8c 100644
--- a/impeller/renderer/backend/gles/handle_gles.h
+++ b/impeller/renderer/backend/gles/handle_gles.h
@@ -28,16 +28,35 @@
class ReactorGLES;
+//------------------------------------------------------------------------------
+/// @brief Represents a handle to an underlying OpenGL object. Unlike
+/// OpenGL object handles, these handles can be collected on any
+/// thread as long as their destruction is scheduled in a reactor.
+///
struct HandleGLES {
HandleType type = HandleType::kUnknown;
std::optional<UniqueID> name;
+ //----------------------------------------------------------------------------
+ /// @brief Creates a dead handle.
+ ///
+ /// @return The handle.
+ ///
static HandleGLES DeadHandle() {
return HandleGLES{HandleType::kUnknown, std::nullopt};
}
+ //----------------------------------------------------------------------------
+ /// @brief Determines if the handle is dead.
+ ///
+ /// @return True if dead, False otherwise.
+ ///
constexpr bool IsDead() const { return !name.has_value(); }
+ //----------------------------------------------------------------------------
+ /// @brief Get the hash value of this handle. Handles can be used as map
+ /// keys.
+ ///
struct Hash {
std::size_t operator()(const HandleGLES& handle) const {
return fml::HashCombine(
@@ -46,6 +65,9 @@
}
};
+ //----------------------------------------------------------------------------
+ /// @brief A comparer used to test the equality of two handles.
+ ///
struct Equal {
bool operator()(const HandleGLES& lhs, const HandleGLES& rhs) const {
return lhs.type == rhs.type && lhs.name == rhs.name;
diff --git a/impeller/renderer/backend/gles/pipeline_gles.cc b/impeller/renderer/backend/gles/pipeline_gles.cc
index eb99661..557eca2 100644
--- a/impeller/renderer/backend/gles/pipeline_gles.cc
+++ b/impeller/renderer/backend/gles/pipeline_gles.cc
@@ -8,23 +8,19 @@
PipelineGLES::PipelineGLES(ReactorGLES::Ref reactor,
std::weak_ptr<PipelineLibrary> library,
- const PipelineDescriptor& desc)
+ const PipelineDescriptor& desc,
+ std::shared_ptr<UniqueHandleGLES> handle)
: Pipeline(std::move(library), desc),
reactor_(std::move(reactor)),
- handle_(reactor_ ? reactor_->CreateHandle(HandleType::kProgram)
- : HandleGLES::DeadHandle()),
- is_valid_(!handle_.IsDead()) {
+ handle_(std::move(handle)),
+ is_valid_(handle_->IsValid()) {
if (is_valid_) {
- reactor_->SetDebugLabel(handle_, GetDescriptor().GetLabel());
+ reactor_->SetDebugLabel(handle_->Get(), GetDescriptor().GetLabel());
}
}
// |Pipeline|
-PipelineGLES::~PipelineGLES() {
- if (!handle_.IsDead()) {
- reactor_->CollectHandle(handle_);
- }
-}
+PipelineGLES::~PipelineGLES() = default;
// |Pipeline|
bool PipelineGLES::IsValid() const {
@@ -32,6 +28,10 @@
}
const HandleGLES& PipelineGLES::GetProgramHandle() const {
+ return handle_->Get();
+}
+
+const std::shared_ptr<UniqueHandleGLES> PipelineGLES::GetSharedHandle() const {
return handle_;
}
@@ -58,10 +58,10 @@
}
[[nodiscard]] bool PipelineGLES::BindProgram() const {
- if (handle_.IsDead()) {
+ if (!handle_->IsValid()) {
return false;
}
- auto handle = reactor_->GetGLHandle(handle_);
+ auto handle = reactor_->GetGLHandle(handle_->Get());
if (!handle.has_value()) {
return false;
}
diff --git a/impeller/renderer/backend/gles/pipeline_gles.h b/impeller/renderer/backend/gles/pipeline_gles.h
index c4f0aeb..e8a3ae8 100644
--- a/impeller/renderer/backend/gles/pipeline_gles.h
+++ b/impeller/renderer/backend/gles/pipeline_gles.h
@@ -7,8 +7,8 @@
#include "impeller/base/backend_cast.h"
#include "impeller/renderer/backend/gles/buffer_bindings_gles.h"
-#include "impeller/renderer/backend/gles/handle_gles.h"
#include "impeller/renderer/backend/gles/reactor_gles.h"
+#include "impeller/renderer/backend/gles/unique_handle_gles.h"
#include "impeller/renderer/pipeline.h"
namespace impeller {
@@ -24,6 +24,8 @@
const HandleGLES& GetProgramHandle() const;
+ const std::shared_ptr<UniqueHandleGLES> GetSharedHandle() const;
+
[[nodiscard]] bool BindProgram() const;
[[nodiscard]] bool UnbindProgram() const;
@@ -37,7 +39,7 @@
friend PipelineLibraryGLES;
ReactorGLES::Ref reactor_;
- HandleGLES handle_;
+ std::shared_ptr<UniqueHandleGLES> handle_;
std::unique_ptr<BufferBindingsGLES> buffer_bindings_;
bool is_valid_ = false;
@@ -46,7 +48,8 @@
PipelineGLES(ReactorGLES::Ref reactor,
std::weak_ptr<PipelineLibrary> library,
- const PipelineDescriptor& desc);
+ const PipelineDescriptor& desc,
+ std::shared_ptr<UniqueHandleGLES> handle);
PipelineGLES(const PipelineGLES&) = delete;
diff --git a/impeller/renderer/backend/gles/pipeline_library_gles.cc b/impeller/renderer/backend/gles/pipeline_library_gles.cc
index b730f16..5ab1ee5 100644
--- a/impeller/renderer/backend/gles/pipeline_library_gles.cc
+++ b/impeller/renderer/backend/gles/pipeline_library_gles.cc
@@ -179,6 +179,79 @@
return reactor_ != nullptr;
}
+std::shared_ptr<PipelineGLES> PipelineLibraryGLES::CreatePipeline(
+ const std::weak_ptr<PipelineLibrary>& weak_library,
+ const PipelineDescriptor& desc,
+ const std::shared_ptr<const ShaderFunction>& vert_function,
+ const std::shared_ptr<const ShaderFunction>& frag_function) {
+ auto strong_library = weak_library.lock();
+
+ if (!strong_library) {
+ VALIDATION_LOG << "Library was collected before a pending pipeline "
+ "creation could finish.";
+ return nullptr;
+ }
+
+ auto& library = PipelineLibraryGLES::Cast(*strong_library);
+
+ const auto& reactor = library.GetReactor();
+
+ if (!reactor) {
+ return nullptr;
+ }
+
+ auto program_key = ProgramKey{vert_function, frag_function,
+ desc.GetSpecializationConstants()};
+
+ auto cached_program = library.GetProgramForKey(program_key);
+
+ const auto has_cached_program = !!cached_program;
+
+ auto pipeline = std::shared_ptr<PipelineGLES>(new PipelineGLES(
+ reactor, //
+ weak_library, //
+ desc, //
+ has_cached_program
+ ? std::move(cached_program)
+ : std::make_shared<UniqueHandleGLES>(reactor, HandleType::kProgram)));
+
+ auto program = reactor->GetGLHandle(pipeline->GetProgramHandle());
+
+ if (!program.has_value()) {
+ VALIDATION_LOG << "Could not obtain program handle.";
+ return nullptr;
+ }
+
+ const auto link_result = !has_cached_program ? LinkProgram(*reactor, //
+ pipeline, //
+ vert_function, //
+ frag_function //
+ )
+ : true;
+
+ if (!link_result) {
+ VALIDATION_LOG << "Could not link pipeline program.";
+ return nullptr;
+ }
+
+ if (!pipeline->BuildVertexDescriptor(reactor->GetProcTable(),
+ program.value())) {
+ VALIDATION_LOG << "Could not build pipeline vertex descriptors.";
+ return nullptr;
+ }
+
+ if (!pipeline->IsValid()) {
+ VALIDATION_LOG << "Pipeline validation checks failed.";
+ return nullptr;
+ }
+
+ if (!has_cached_program) {
+ library.SetProgramForKey(program_key, pipeline->GetSharedHandle());
+ }
+
+ return pipeline;
+}
+
// |PipelineLibrary|
PipelineFuture<PipelineDescriptor> PipelineLibraryGLES::GetPipeline(
PipelineDescriptor descriptor,
@@ -209,49 +282,16 @@
auto pipeline_future =
PipelineFuture<PipelineDescriptor>{descriptor, promise->get_future()};
pipelines_[descriptor] = pipeline_future;
- auto weak_this = weak_from_this();
- auto result = reactor_->AddOperation(
- [promise, weak_this, reactor_ptr = reactor_, descriptor, vert_function,
- frag_function](const ReactorGLES& reactor) {
- auto strong_this = weak_this.lock();
- if (!strong_this) {
- promise->set_value(nullptr);
- VALIDATION_LOG << "Library was collected before a pending pipeline "
- "creation could finish.";
- return;
- }
- auto pipeline = std::shared_ptr<PipelineGLES>(
- new PipelineGLES(reactor_ptr, strong_this, descriptor));
- auto program = reactor.GetGLHandle(pipeline->GetProgramHandle());
- if (!program.has_value()) {
- promise->set_value(nullptr);
- VALIDATION_LOG << "Could not obtain program handle.";
- return;
- }
- const auto link_result = LinkProgram(reactor, //
- pipeline, //
- vert_function, //
- frag_function //
- );
- if (!link_result) {
- promise->set_value(nullptr);
- VALIDATION_LOG << "Could not link pipeline program.";
- return;
- }
- if (!pipeline->BuildVertexDescriptor(reactor.GetProcTable(),
- program.value())) {
- promise->set_value(nullptr);
- VALIDATION_LOG << "Could not build pipeline vertex descriptors.";
- return;
- }
- if (!pipeline->IsValid()) {
- promise->set_value(nullptr);
- VALIDATION_LOG << "Pipeline validation checks failed.";
- return;
- }
- promise->set_value(std::move(pipeline));
- });
+ const auto result = reactor_->AddOperation([promise, //
+ weak_this = weak_from_this(), //
+ descriptor, //
+ vert_function, //
+ frag_function //
+ ](const ReactorGLES& reactor) {
+ promise->set_value(
+ CreatePipeline(weak_this, descriptor, vert_function, frag_function));
+ });
FML_CHECK(result);
return pipeline_future;
@@ -280,4 +320,25 @@
// |PipelineLibrary|
PipelineLibraryGLES::~PipelineLibraryGLES() = default;
+const ReactorGLES::Ref& PipelineLibraryGLES::GetReactor() const {
+ return reactor_;
+}
+
+std::shared_ptr<UniqueHandleGLES> PipelineLibraryGLES::GetProgramForKey(
+ const ProgramKey& key) {
+ Lock lock(programs_mutex_);
+ auto found = programs_.find(key);
+ if (found != programs_.end()) {
+ return found->second;
+ }
+ return nullptr;
+}
+
+void PipelineLibraryGLES::SetProgramForKey(
+ const ProgramKey& key,
+ std::shared_ptr<UniqueHandleGLES> program) {
+ Lock lock(programs_mutex_);
+ programs_[key] = std::move(program);
+}
+
} // namespace impeller
diff --git a/impeller/renderer/backend/gles/pipeline_library_gles.h b/impeller/renderer/backend/gles/pipeline_library_gles.h
index 1ee7c22..99d5da6 100644
--- a/impeller/renderer/backend/gles/pipeline_library_gles.h
+++ b/impeller/renderer/backend/gles/pipeline_library_gles.h
@@ -5,23 +5,92 @@
#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_PIPELINE_LIBRARY_GLES_H_
#define FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_PIPELINE_LIBRARY_GLES_H_
+#include <unordered_map>
+#include <vector>
+
+#include "flutter/fml/hash_combine.h"
+#include "impeller/base/thread.h"
#include "impeller/renderer/backend/gles/reactor_gles.h"
+#include "impeller/renderer/backend/gles/unique_handle_gles.h"
#include "impeller/renderer/pipeline_library.h"
+#include "impeller/renderer/shader_function.h"
namespace impeller {
class ContextGLES;
+class PipelineGLES;
-class PipelineLibraryGLES final : public PipelineLibrary {
+class PipelineLibraryGLES final
+ : public PipelineLibrary,
+ public BackendCast<PipelineLibraryGLES, PipelineLibrary> {
public:
// |PipelineLibrary|
~PipelineLibraryGLES() override;
+ PipelineLibraryGLES(const PipelineLibraryGLES&) = delete;
+
+ PipelineLibraryGLES& operator=(const PipelineLibraryGLES&) = delete;
+
private:
friend ContextGLES;
+ //----------------------------------------------------------------------------
+ /// @brief A subset of the items in a pipeline descriptor (and the items
+ /// they reference in shader libraries) whose dynamism requires a
+ /// program object re-compilation and link. In all other cases,
+ /// creating a pipeline variant reuses an existing (compatible)
+ /// program object.
+ ///
+ struct ProgramKey {
+ std::shared_ptr<const ShaderFunction> vertex_shader;
+ std::shared_ptr<const ShaderFunction> fragment_shader;
+ //--------------------------------------------------------------------------
+ /// Specialization constants used in the shaders affect defines used when
+ /// compiling and linking the program.
+ ///
+ std::vector<Scalar> specialization_constants;
+
+ ProgramKey(std::shared_ptr<const ShaderFunction> p_vertex_shader,
+ std::shared_ptr<const ShaderFunction> p_fragment_shader,
+ std::vector<Scalar> p_specialization_constants)
+ : vertex_shader(std::move(p_vertex_shader)),
+ fragment_shader(std::move(p_fragment_shader)),
+ specialization_constants(std::move(p_specialization_constants)) {}
+
+ struct Hash {
+ std::size_t operator()(const ProgramKey& key) const {
+ auto seed = fml::HashCombine();
+ if (key.vertex_shader) {
+ fml::HashCombineSeed(seed, key.vertex_shader->GetHash());
+ }
+ if (key.fragment_shader) {
+ fml::HashCombineSeed(seed, key.fragment_shader->GetHash());
+ }
+ for (const auto& constant : key.specialization_constants) {
+ fml::HashCombineSeed(seed, constant);
+ }
+ return seed;
+ }
+ };
+
+ struct Equal {
+ bool operator()(const ProgramKey& lhs, const ProgramKey& rhs) const {
+ return DeepComparePointer(lhs.vertex_shader, rhs.vertex_shader) &&
+ DeepComparePointer(lhs.fragment_shader, rhs.fragment_shader) &&
+ lhs.specialization_constants == rhs.specialization_constants;
+ }
+ };
+ };
+
+ using ProgramMap = std::unordered_map<ProgramKey,
+ std::shared_ptr<UniqueHandleGLES>,
+ ProgramKey::Hash,
+ ProgramKey::Equal>;
+
ReactorGLES::Ref reactor_;
PipelineMap pipelines_;
+ Mutex programs_mutex_;
+ ProgramMap programs_ IPLR_GUARDED_BY(programs_mutex_);
explicit PipelineLibraryGLES(ReactorGLES::Ref reactor);
@@ -41,9 +110,18 @@
void RemovePipelinesWithEntryPoint(
std::shared_ptr<const ShaderFunction> function) override;
- PipelineLibraryGLES(const PipelineLibraryGLES&) = delete;
+ const ReactorGLES::Ref& GetReactor() const;
- PipelineLibraryGLES& operator=(const PipelineLibraryGLES&) = delete;
+ static std::shared_ptr<PipelineGLES> CreatePipeline(
+ const std::weak_ptr<PipelineLibrary>& weak_library,
+ const PipelineDescriptor& desc,
+ const std::shared_ptr<const ShaderFunction>& vert_shader,
+ const std::shared_ptr<const ShaderFunction>& frag_shader);
+
+ std::shared_ptr<UniqueHandleGLES> GetProgramForKey(const ProgramKey& key);
+
+ void SetProgramForKey(const ProgramKey& key,
+ std::shared_ptr<UniqueHandleGLES> program);
};
} // namespace impeller
diff --git a/impeller/renderer/backend/gles/test/pipeline_library_gles_unittests.cc b/impeller/renderer/backend/gles/test/pipeline_library_gles_unittests.cc
new file mode 100644
index 0000000..23d54c4
--- /dev/null
+++ b/impeller/renderer/backend/gles/test/pipeline_library_gles_unittests.cc
@@ -0,0 +1,71 @@
+// 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/fixtures/spec_constant.frag.h"
+#include "impeller/fixtures/spec_constant.vert.h"
+#include "impeller/playground/playground_test.h"
+#include "impeller/renderer/backend/gles/pipeline_gles.h"
+#include "impeller/renderer/backend/gles/pipeline_library_gles.h"
+#include "impeller/renderer/pipeline_library.h"
+
+namespace impeller::testing {
+
+using PipelineLibraryGLESTest = PlaygroundTest;
+INSTANTIATE_OPENGLES_PLAYGROUND_SUITE(PipelineLibraryGLESTest);
+
+// NOLINTBEGIN(bugprone-unchecked-optional-access)
+TEST_P(PipelineLibraryGLESTest, ProgramHandlesAreReused) {
+ using VS = SpecConstantVertexShader;
+ using FS = SpecConstantFragmentShader;
+ auto context = GetContext();
+ ASSERT_TRUE(context);
+ auto desc = PipelineBuilder<VS, FS>::MakeDefaultPipelineDescriptor(*context);
+ ASSERT_TRUE(desc.has_value());
+ auto pipeline = context->GetPipelineLibrary()->GetPipeline(desc).Get();
+ ASSERT_TRUE(pipeline && pipeline->IsValid());
+ auto new_desc = desc;
+ // Changing the sample counts should not result in a new program object.
+ new_desc->SetSampleCount(SampleCount::kCount4);
+ // Make sure we don't hit the top-level descriptor cache. This will cause
+ // caching irrespective of backends.
+ ASSERT_FALSE(desc->IsEqual(new_desc.value()));
+ auto new_pipeline =
+ context->GetPipelineLibrary()->GetPipeline(new_desc).Get();
+ ASSERT_TRUE(new_pipeline && new_pipeline->IsValid());
+ const auto& pipeline_gles = PipelineGLES::Cast(*pipeline);
+ const auto& new_pipeline_gles = PipelineGLES::Cast(*new_pipeline);
+ // The program handles should be live and equal.
+ ASSERT_FALSE(pipeline_gles.GetProgramHandle().IsDead());
+ ASSERT_FALSE(new_pipeline_gles.GetProgramHandle().IsDead());
+ ASSERT_EQ(pipeline_gles.GetProgramHandle().name.value(),
+ new_pipeline_gles.GetProgramHandle().name.value());
+}
+
+TEST_P(PipelineLibraryGLESTest, ChangingSpecConstantsCausesNewProgramObject) {
+ using VS = SpecConstantVertexShader;
+ using FS = SpecConstantFragmentShader;
+ auto context = GetContext();
+ ASSERT_TRUE(context);
+ auto desc = PipelineBuilder<VS, FS>::MakeDefaultPipelineDescriptor(*context);
+ ASSERT_TRUE(desc.has_value());
+ desc->SetSpecializationConstants({2.0f});
+ auto pipeline = context->GetPipelineLibrary()->GetPipeline(desc).Get();
+ ASSERT_TRUE(pipeline && pipeline->IsValid());
+ auto new_desc = desc;
+ // Changing the spec. constants should result in a new program object.
+ new_desc->SetSpecializationConstants({4.0f});
+ auto new_pipeline =
+ context->GetPipelineLibrary()->GetPipeline(new_desc).Get();
+ ASSERT_TRUE(new_pipeline && new_pipeline->IsValid());
+ const auto& pipeline_gles = PipelineGLES::Cast(*pipeline);
+ const auto& new_pipeline_gles = PipelineGLES::Cast(*new_pipeline);
+ // The program handles should be live and equal.
+ ASSERT_FALSE(pipeline_gles.GetProgramHandle().IsDead());
+ ASSERT_FALSE(new_pipeline_gles.GetProgramHandle().IsDead());
+ ASSERT_FALSE(pipeline_gles.GetProgramHandle().name.value() ==
+ new_pipeline_gles.GetProgramHandle().name.value());
+}
+// NOLINTEND(bugprone-unchecked-optional-access)
+
+} // namespace impeller::testing
diff --git a/impeller/renderer/backend/gles/unique_handle_gles.cc b/impeller/renderer/backend/gles/unique_handle_gles.cc
new file mode 100644
index 0000000..ddf4081
--- /dev/null
+++ b/impeller/renderer/backend/gles/unique_handle_gles.cc
@@ -0,0 +1,40 @@
+// 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/unique_handle_gles.h"
+
+#include <utility>
+
+namespace impeller {
+
+UniqueHandleGLES::UniqueHandleGLES(ReactorGLES::Ref reactor, HandleType type)
+ : reactor_(std::move(reactor)) {
+ if (reactor_) {
+ handle_ = reactor_->CreateHandle(type);
+ }
+}
+
+UniqueHandleGLES::UniqueHandleGLES(ReactorGLES::Ref reactor, HandleGLES handle)
+ : reactor_(std::move(reactor)), handle_(handle) {}
+
+UniqueHandleGLES::~UniqueHandleGLES() {
+ if (!handle_.IsDead() && reactor_) {
+ reactor_->CollectHandle(handle_);
+ }
+}
+
+const HandleGLES& UniqueHandleGLES::Get() const {
+ return handle_;
+}
+
+bool UniqueHandleGLES::IsValid() const {
+ return !handle_.IsDead();
+}
+
+UniqueHandleGLES::UniqueHandleGLES(UniqueHandleGLES&& other) {
+ std::swap(reactor_, other.reactor_);
+ std::swap(handle_, other.handle_);
+}
+
+} // namespace impeller
diff --git a/impeller/renderer/backend/gles/unique_handle_gles.h b/impeller/renderer/backend/gles/unique_handle_gles.h
new file mode 100644
index 0000000..91c3bc2
--- /dev/null
+++ b/impeller/renderer/backend/gles/unique_handle_gles.h
@@ -0,0 +1,43 @@
+// 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_UNIQUE_HANDLE_GLES_H_
+#define FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_UNIQUE_HANDLE_GLES_H_
+
+#include "impeller/renderer/backend/gles/handle_gles.h"
+#include "impeller/renderer/backend/gles/reactor_gles.h"
+
+namespace impeller {
+
+//------------------------------------------------------------------------------
+/// @brief A unique handle to an OpenGL object. The collection of this
+/// handle scheduled the destruction of the associated OpenGL object
+/// in the reactor.
+///
+class UniqueHandleGLES {
+ public:
+ UniqueHandleGLES(ReactorGLES::Ref reactor, HandleType type);
+
+ UniqueHandleGLES(ReactorGLES::Ref reactor, HandleGLES handle);
+
+ ~UniqueHandleGLES();
+
+ UniqueHandleGLES(UniqueHandleGLES&&);
+
+ UniqueHandleGLES(const UniqueHandleGLES&) = delete;
+
+ UniqueHandleGLES& operator=(const UniqueHandleGLES&) = delete;
+
+ const HandleGLES& Get() const;
+
+ bool IsValid() const;
+
+ private:
+ ReactorGLES::Ref reactor_ = nullptr;
+ HandleGLES handle_ = HandleGLES::DeadHandle();
+};
+
+} // namespace impeller
+
+#endif // FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_UNIQUE_HANDLE_GLES_H_