| // 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_FLOW_SKIA_GPU_OBJECT_H_ |
| #define FLUTTER_FLOW_SKIA_GPU_OBJECT_H_ |
| |
| #include <mutex> |
| #include <queue> |
| |
| #include "flutter/fml/memory/ref_counted.h" |
| #include "flutter/fml/memory/weak_ptr.h" |
| #include "flutter/fml/task_runner.h" |
| #include "flutter/fml/trace_event.h" |
| #include "third_party/skia/include/core/SkRefCnt.h" |
| #include "third_party/skia/include/gpu/GrBackendSurface.h" |
| #include "third_party/skia/include/gpu/GrDirectContext.h" |
| |
| namespace flutter { |
| |
| // A queue that holds Skia objects that must be destructed on the given task |
| // runner. |
| template <class T> |
| class UnrefQueue : public fml::RefCountedThreadSafe<UnrefQueue<T>> { |
| public: |
| using ResourceContext = T; |
| |
| void Unref(SkRefCnt* object) { |
| if (drain_immediate_) { |
| object->unref(); |
| return; |
| } |
| std::scoped_lock lock(mutex_); |
| objects_.push_back(object); |
| if (!drain_pending_) { |
| drain_pending_ = true; |
| task_runner_->PostDelayedTask( |
| [strong = fml::Ref(this)]() { strong->Drain(); }, drain_delay_); |
| } |
| } |
| |
| void DeleteTexture(GrBackendTexture texture) { |
| // drain_immediate_ should only be used on Impeller. |
| FML_DCHECK(!drain_immediate_); |
| std::scoped_lock lock(mutex_); |
| textures_.push_back(texture); |
| if (!drain_pending_) { |
| drain_pending_ = true; |
| task_runner_->PostDelayedTask( |
| [strong = fml::Ref(this)]() { strong->Drain(); }, drain_delay_); |
| } |
| } |
| |
| // Usually, the drain is called automatically. However, during IO manager |
| // shutdown (when the platform side reference to the OpenGL context is about |
| // to go away), we may need to pre-emptively drain the unref queue. It is the |
| // responsibility of the caller to ensure that no further unrefs are queued |
| // after this call. |
| void Drain() { |
| TRACE_EVENT0("flutter", "SkiaUnrefQueue::Drain"); |
| std::deque<SkRefCnt*> skia_objects; |
| std::deque<GrBackendTexture> textures; |
| { |
| std::scoped_lock lock(mutex_); |
| objects_.swap(skia_objects); |
| textures_.swap(textures); |
| drain_pending_ = false; |
| } |
| DoDrain(skia_objects, textures, context_); |
| } |
| |
| void UpdateResourceContext(sk_sp<ResourceContext> context) { |
| context_ = context; |
| } |
| |
| private: |
| const fml::RefPtr<fml::TaskRunner> task_runner_; |
| const fml::TimeDelta drain_delay_; |
| std::mutex mutex_; |
| std::deque<SkRefCnt*> objects_; |
| std::deque<GrBackendTexture> textures_; |
| bool drain_pending_; |
| sk_sp<ResourceContext> context_; |
| // Enabled when there is an impeller context, which removes the usage of |
| // the queue altogether. |
| bool drain_immediate_; |
| |
| // The `GrDirectContext* context` is only used for signaling Skia to |
| // performDeferredCleanup. It can be nullptr when such signaling is not needed |
| // (e.g., in unit tests). |
| UnrefQueue(fml::RefPtr<fml::TaskRunner> task_runner, |
| fml::TimeDelta delay, |
| sk_sp<ResourceContext> context = nullptr, |
| bool drain_immediate = false) |
| : task_runner_(std::move(task_runner)), |
| drain_delay_(delay), |
| drain_pending_(false), |
| context_(context), |
| drain_immediate_(drain_immediate) {} |
| |
| ~UnrefQueue() { |
| // The ResourceContext must be deleted on the task runner thread. |
| // Transfer ownership of the UnrefQueue's ResourceContext reference |
| // into a task queued to that thread. |
| ResourceContext* raw_context = context_.release(); |
| fml::TaskRunner::RunNowOrPostTask( |
| task_runner_, [objects = std::move(objects_), |
| textures = std::move(textures_), raw_context]() mutable { |
| sk_sp<ResourceContext> context(raw_context); |
| DoDrain(objects, textures, context); |
| context.reset(); |
| }); |
| } |
| |
| // static |
| static void DoDrain(const std::deque<SkRefCnt*>& skia_objects, |
| const std::deque<GrBackendTexture>& textures, |
| sk_sp<ResourceContext> context) { |
| for (SkRefCnt* skia_object : skia_objects) { |
| skia_object->unref(); |
| } |
| |
| if (context) { |
| for (GrBackendTexture texture : textures) { |
| context->deleteBackendTexture(texture); |
| } |
| |
| if (!skia_objects.empty()) { |
| context->performDeferredCleanup(std::chrono::milliseconds(0)); |
| } |
| } |
| } |
| |
| FML_FRIEND_REF_COUNTED_THREAD_SAFE(UnrefQueue); |
| FML_FRIEND_MAKE_REF_COUNTED(UnrefQueue); |
| FML_DISALLOW_COPY_AND_ASSIGN(UnrefQueue); |
| }; |
| |
| using SkiaUnrefQueue = UnrefQueue<GrDirectContext>; |
| |
| /// An object whose deallocation needs to be performed on an specific unref |
| /// queue. The template argument U need to have a call operator that returns |
| /// that unref queue. |
| template <class T> |
| class SkiaGPUObject { |
| public: |
| using SkiaObjectType = T; |
| |
| SkiaGPUObject() = default; |
| SkiaGPUObject(sk_sp<SkiaObjectType> object, fml::RefPtr<SkiaUnrefQueue> queue) |
| : object_(std::move(object)), queue_(std::move(queue)) { |
| FML_DCHECK(object_); |
| } |
| SkiaGPUObject(SkiaGPUObject&&) = default; |
| ~SkiaGPUObject() { reset(); } |
| |
| SkiaGPUObject& operator=(SkiaGPUObject&&) = default; |
| |
| sk_sp<SkiaObjectType> skia_object() const { return object_; } |
| |
| void reset() { |
| if (object_ && queue_) { |
| queue_->Unref(object_.release()); |
| } |
| queue_ = nullptr; |
| FML_DCHECK(object_ == nullptr); |
| } |
| |
| private: |
| sk_sp<SkiaObjectType> object_; |
| fml::RefPtr<SkiaUnrefQueue> queue_; |
| |
| FML_DISALLOW_COPY_AND_ASSIGN(SkiaGPUObject); |
| }; |
| |
| } // namespace flutter |
| |
| #endif // FLUTTER_FLOW_SKIA_GPU_OBJECT_H_ |