| // 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/reactor_gles.h" |
| |
| #include <algorithm> |
| |
| #include "flutter/fml/trace_event.h" |
| #include "impeller/base/validation.h" |
| |
| namespace impeller { |
| |
| ReactorGLES::ReactorGLES(std::unique_ptr<ProcTableGLES> gl) |
| : proc_table_(std::move(gl)) { |
| if (!proc_table_ || !proc_table_->IsValid()) { |
| VALIDATION_LOG << "Proc table was invalid."; |
| return; |
| } |
| can_set_debug_labels_ = proc_table_->GetDescription()->HasDebugExtension(); |
| is_valid_ = true; |
| } |
| |
| ReactorGLES::~ReactorGLES() = default; |
| |
| bool ReactorGLES::IsValid() const { |
| return is_valid_; |
| } |
| |
| ReactorGLES::WorkerID ReactorGLES::AddWorker(std::weak_ptr<Worker> worker) { |
| Lock lock(workers_mutex_); |
| auto id = WorkerID{}; |
| workers_[id] = std::move(worker); |
| return id; |
| } |
| |
| bool ReactorGLES::RemoveWorker(WorkerID worker) { |
| Lock lock(workers_mutex_); |
| return workers_.erase(worker) == 1; |
| } |
| |
| bool ReactorGLES::HasPendingOperations() const { |
| Lock ops_lock(ops_mutex_); |
| return !ops_.empty(); |
| } |
| |
| const ProcTableGLES& ReactorGLES::GetProcTable() const { |
| FML_DCHECK(IsValid()); |
| return *proc_table_; |
| } |
| |
| std::optional<GLuint> ReactorGLES::GetGLHandle(const HandleGLES& handle) const { |
| ReaderLock handles_lock(handles_mutex_); |
| if (auto found = handles_.find(handle); found != handles_.end()) { |
| if (found->second.pending_collection) { |
| VALIDATION_LOG |
| << "Attempted to acquire a handle that was pending collection."; |
| return std::nullopt; |
| } |
| if (!found->second.name.has_value()) { |
| VALIDATION_LOG << "Attempt to acquire a handle outside of an operation."; |
| return std::nullopt; |
| } |
| return found->second.name; |
| } |
| VALIDATION_LOG << "Attempted to acquire an invalid GL handle."; |
| return std::nullopt; |
| } |
| |
| bool ReactorGLES::AddOperation(Operation operation) { |
| if (!operation) { |
| return false; |
| } |
| { |
| Lock ops_lock(ops_mutex_); |
| ops_.emplace_back(std::move(operation)); |
| } |
| // Attempt a reaction if able but it is not an error if this isn't possible. |
| [[maybe_unused]] auto result = React(); |
| return true; |
| } |
| |
| static std::optional<GLuint> CreateGLHandle(const ProcTableGLES& gl, |
| HandleType type) { |
| GLuint handle = GL_NONE; |
| switch (type) { |
| case HandleType::kUnknown: |
| return std::nullopt; |
| case HandleType::kTexture: |
| gl.GenTextures(1u, &handle); |
| return handle; |
| case HandleType::kBuffer: |
| gl.GenBuffers(1u, &handle); |
| return handle; |
| case HandleType::kProgram: |
| return gl.CreateProgram(); |
| case HandleType::kRenderBuffer: |
| gl.GenRenderbuffers(1u, &handle); |
| return handle; |
| case HandleType::kFrameBuffer: |
| gl.GenFramebuffers(1u, &handle); |
| return handle; |
| } |
| return std::nullopt; |
| } |
| |
| static bool CollectGLHandle(const ProcTableGLES& gl, |
| HandleType type, |
| GLuint handle) { |
| switch (type) { |
| case HandleType::kUnknown: |
| return false; |
| case HandleType::kTexture: |
| gl.DeleteTextures(1u, &handle); |
| return true; |
| case HandleType::kBuffer: |
| gl.DeleteBuffers(1u, &handle); |
| return true; |
| case HandleType::kProgram: |
| gl.DeleteProgram(handle); |
| return true; |
| case HandleType::kRenderBuffer: |
| gl.DeleteRenderbuffers(1u, &handle); |
| return true; |
| case HandleType::kFrameBuffer: |
| gl.DeleteFramebuffers(1u, &handle); |
| return true; |
| } |
| return false; |
| } |
| |
| HandleGLES ReactorGLES::CreateHandle(HandleType type) { |
| if (type == HandleType::kUnknown) { |
| return HandleGLES::DeadHandle(); |
| } |
| auto new_handle = HandleGLES::Create(type); |
| if (new_handle.IsDead()) { |
| return HandleGLES::DeadHandle(); |
| } |
| WriterLock handles_lock(handles_mutex_); |
| auto gl_handle = CanReactOnCurrentThread() |
| ? CreateGLHandle(GetProcTable(), type) |
| : std::nullopt; |
| handles_[new_handle] = LiveHandle{gl_handle}; |
| return new_handle; |
| } |
| |
| void ReactorGLES::CollectHandle(HandleGLES handle) { |
| WriterLock handles_lock(handles_mutex_); |
| if (auto found = handles_.find(handle); found != handles_.end()) { |
| found->second.pending_collection = true; |
| } |
| } |
| |
| bool ReactorGLES::React() { |
| if (!CanReactOnCurrentThread()) { |
| return false; |
| } |
| TRACE_EVENT0("impeller", "ReactorGLES::React"); |
| while (HasPendingOperations()) { |
| if (!ReactOnce()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static DebugResourceType ToDebugResourceType(HandleType type) { |
| switch (type) { |
| case HandleType::kUnknown: |
| FML_UNREACHABLE(); |
| case HandleType::kTexture: |
| return DebugResourceType::kTexture; |
| case HandleType::kBuffer: |
| return DebugResourceType::kBuffer; |
| case HandleType::kProgram: |
| return DebugResourceType::kProgram; |
| case HandleType::kRenderBuffer: |
| return DebugResourceType::kRenderBuffer; |
| case HandleType::kFrameBuffer: |
| return DebugResourceType::kFrameBuffer; |
| } |
| FML_UNREACHABLE(); |
| } |
| |
| bool ReactorGLES::ReactOnce() { |
| if (!IsValid()) { |
| return false; |
| } |
| TRACE_EVENT0("impeller", __FUNCTION__); |
| return ConsolidateHandles() && FlushOps(); |
| } |
| |
| bool ReactorGLES::ConsolidateHandles() { |
| TRACE_EVENT0("impeller", __FUNCTION__); |
| const auto& gl = GetProcTable(); |
| WriterLock handles_lock(handles_mutex_); |
| std::vector<HandleGLES> handles_to_delete; |
| for (auto& handle : handles_) { |
| // Collect dead handles. |
| if (handle.second.pending_collection) { |
| // This could be false if the handle was created and collected without |
| // use. We still need to get rid of map entry. |
| if (handle.second.name.has_value()) { |
| CollectGLHandle(gl, handle.first.type, handle.second.name.value()); |
| } |
| handles_to_delete.push_back(handle.first); |
| continue; |
| } |
| // Create live handles. |
| if (!handle.second.name.has_value()) { |
| auto gl_handle = CreateGLHandle(gl, handle.first.type); |
| if (!gl_handle) { |
| VALIDATION_LOG << "Could not create GL handle."; |
| return false; |
| } |
| handle.second.name = gl_handle; |
| } |
| // Set pending debug labels. |
| if (handle.second.pending_debug_label.has_value()) { |
| if (gl.SetDebugLabel(ToDebugResourceType(handle.first.type), |
| handle.second.name.value(), |
| handle.second.pending_debug_label.value())) { |
| handle.second.pending_debug_label = std::nullopt; |
| } |
| } |
| } |
| for (const auto& handle_to_delete : handles_to_delete) { |
| handles_.erase(handle_to_delete); |
| } |
| return true; |
| } |
| |
| bool ReactorGLES::FlushOps() { |
| TRACE_EVENT0("impeller", __FUNCTION__); |
| // Do NOT hold the ops or handles locks while performing operations in case |
| // the ops enqueue more ops. |
| decltype(ops_) ops; |
| { |
| Lock ops_lock(ops_mutex_); |
| std::swap(ops_, ops); |
| } |
| for (const auto& op : ops) { |
| TRACE_EVENT0("impeller", "ReactorGLES::Operation"); |
| op(*this); |
| } |
| return true; |
| } |
| |
| void ReactorGLES::SetDebugLabel(const HandleGLES& handle, std::string label) { |
| if (!can_set_debug_labels_) { |
| return; |
| } |
| if (handle.IsDead()) { |
| return; |
| } |
| WriterLock handles_lock(handles_mutex_); |
| if (auto found = handles_.find(handle); found != handles_.end()) { |
| found->second.pending_debug_label = std::move(label); |
| } |
| } |
| |
| bool ReactorGLES::CanReactOnCurrentThread() const { |
| std::vector<WorkerID> dead_workers; |
| Lock lock(workers_mutex_); |
| for (const auto& worker : workers_) { |
| auto worker_ptr = worker.second.lock(); |
| if (!worker_ptr) { |
| dead_workers.push_back(worker.first); |
| continue; |
| } |
| if (worker_ptr->CanReactorReactOnCurrentThreadNow(*this)) { |
| return true; |
| } |
| } |
| for (const auto& worker_id : dead_workers) { |
| workers_.erase(worker_id); |
| } |
| return false; |
| } |
| |
| } // namespace impeller |