| // 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" | |
| #include "third_party/skia/include/gpu/GrTypes.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(const 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_ = false; | |
| 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), | |
| 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 (const GrBackendTexture& texture : textures) { | |
| context->deleteBackendTexture(texture); | |
| } | |
| if (!skia_objects.empty()) { | |
| context->performDeferredCleanup(std::chrono::milliseconds(0)); | |
| } | |
| context->flushAndSubmit(GrSyncCpu::kYes); | |
| } | |
| } | |
| 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_ |