1 /*
2  * Copyright 2015 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #ifndef GrVkResource_DEFINED
9 #define GrVkResource_DEFINED
10 
11 
12 #include "include/private/SkTHash.h"
13 #include "include/utils/SkRandom.h"
14 #include <atomic>
15 
16 class GrVkGpu;
17 
18 // uncomment to enable tracing of resource refs
19 #ifdef SK_DEBUG
20 #define SK_TRACE_VK_RESOURCES
21 #endif
22 
23 /** \class GrVkResource
24 
25   GrVkResource is the base class for Vulkan resources that may be shared by multiple
26   objects. When an existing owner wants to share a reference, it calls ref().
27   When an owner wants to release its reference, it calls unref(). When the
28   shared object's reference count goes to zero as the result of an unref()
29   call, its (virtual) destructor is called. It is an error for the
30   destructor to be called explicitly (or via the object going out of scope on
31   the stack or calling delete) if getRefCnt() > 1.
32 
33   This is nearly identical to SkRefCntBase. The exceptions are that unref()
34   takes a GrVkGpu, and any derived classes must implement freeGPUData() and
35   possibly abandonGPUData().
36 */
37 
38 class GrVkResource : SkNoncopyable {
39 public:
40     // Simple refCount tracing, to ensure that everything ref'ed is unref'ed.
41 #ifdef SK_TRACE_VK_RESOURCES
42     struct Hash {
operatorHash43         uint32_t operator()(const GrVkResource* const& r) const {
44             SkASSERT(r);
45             return r->fKey;
46         }
47     };
48 
49     class Trace {
50     public:
~Trace()51         ~Trace() {
52             fHashSet.foreach([](const GrVkResource* r) {
53                 r->dumpInfo();
54             });
55             SkASSERT(0 == fHashSet.count());
56         }
57 
add(const GrVkResource * r)58         void add(const GrVkResource* r) {
59             fHashSet.add(r);
60         }
61 
remove(const GrVkResource * r)62         void remove(const GrVkResource* r) {
63             fHashSet.remove(r);
64         }
65 
66     private:
67         SkTHashSet<const GrVkResource*, GrVkResource::Hash> fHashSet;
68     };
69 
70     static std::atomic<uint32_t> fKeyCounter;
71 #endif
72 
73     /** Default construct, initializing the reference count to 1.
74      */
GrVkResource()75     GrVkResource() : fRefCnt(1) {
76 #ifdef SK_TRACE_VK_RESOURCES
77         fKey = fKeyCounter.fetch_add(+1, std::memory_order_relaxed);
78         GetTrace()->add(this);
79 #endif
80     }
81 
82     /** Destruct, asserting that the reference count is 1.
83      */
~GrVkResource()84     virtual ~GrVkResource() {
85 #ifdef SK_DEBUG
86         auto count = this->getRefCnt();
87         SkASSERTF(count == 1, "fRefCnt was %d", count);
88         fRefCnt.store(0);    // illegal value, to catch us if we reuse after delete
89 #endif
90     }
91 
92 #ifdef SK_DEBUG
93     /** Return the reference count. Use only for debugging. */
getRefCnt()94     int32_t getRefCnt() const { return fRefCnt.load(); }
95 #endif
96 
97     /** May return true if the caller is the only owner.
98      *  Ensures that all previous owner's actions are complete.
99      */
unique()100     bool unique() const {
101         // The acquire barrier is only really needed if we return true.  It
102         // prevents code conditioned on the result of unique() from running
103         // until previous owners are all totally done calling unref().
104         return 1 == fRefCnt.load(std::memory_order_acquire);
105     }
106 
107     /** Increment the reference count.
108         Must be balanced by a call to unref() or unrefAndFreeResources().
109      */
ref()110     void ref() const {
111         // No barrier required.
112         SkDEBUGCODE(int newRefCount = )fRefCnt.fetch_add(+1, std::memory_order_relaxed);
113         SkASSERT(newRefCount >= 1);
114     }
115 
116     /** Decrement the reference count. If the reference count is 1 before the
117         decrement, then delete the object. Note that if this is the case, then
118         the object needs to have been allocated via new, and not on the stack.
119         Any GPU data associated with this resource will be freed before it's deleted.
120      */
unref(GrVkGpu * gpu)121     void unref(GrVkGpu* gpu) const {
122         SkASSERT(gpu);
123         // A release here acts in place of all releases we "should" have been doing in ref().
124         int newRefCount = fRefCnt.fetch_add(-1, std::memory_order_acq_rel);
125         SkASSERT(newRefCount >= 0);
126         if (newRefCount == 1) {
127             // Like unique(), the acquire is only needed on success, to make sure
128             // code in internal_dispose() doesn't happen before the decrement.
129             this->internal_dispose(gpu);
130         }
131     }
132 
133     /** Unref without freeing GPU data. Used only when we're abandoning the resource */
unrefAndAbandon()134     void unrefAndAbandon() const {
135         SkASSERT(this->getRefCnt() > 0);
136         // A release here acts in place of all releases we "should" have been doing in ref().
137         int newRefCount = fRefCnt.fetch_add(-1, std::memory_order_acq_rel);
138         SkASSERT(newRefCount >= 0);
139         if (newRefCount == 1) {
140             // Like unique(), the acquire is only needed on success, to make sure
141             // code in internal_dispose() doesn't happen before the decrement.
142             this->internal_dispose();
143         }
144     }
145 
146     // Called every time this resource is added to a command buffer.
notifyAddedToCommandBuffer()147     virtual void notifyAddedToCommandBuffer() const {}
148     // Called every time this resource is removed from a command buffer (typically because
149     // the command buffer finished execution on the GPU but also when the command buffer
150     // is abandoned.)
notifyRemovedFromCommandBuffer()151     virtual void notifyRemovedFromCommandBuffer() const {}
152 
153 #ifdef SK_DEBUG
validate()154     void validate() const {
155         SkASSERT(this->getRefCnt() > 0);
156     }
157 #endif
158 
159 #ifdef SK_TRACE_VK_RESOURCES
160     /** Output a human-readable dump of this resource's information
161      */
162     virtual void dumpInfo() const = 0;
163 #endif
164 
165 private:
166 #ifdef SK_TRACE_VK_RESOURCES
GetTrace()167     static Trace* GetTrace() {
168         static Trace kTrace;
169         return &kTrace;
170     }
171 #endif
172 
173     /** Must be implemented by any subclasses.
174      *  Deletes any Vk data associated with this resource
175      */
176     virtual void freeGPUData(GrVkGpu* gpu) const = 0;
177 
178     /**
179      * Called from unrefAndAbandon. Resources should do any necessary cleanup without freeing
180      * underlying Vk objects. This must be overridden by subclasses that themselves store
181      * GrVkResources since those resource will need to be unrefed.
182      */
abandonGPUData()183     virtual void abandonGPUData() const {}
184 
185     /**
186      *  Called when the ref count goes to 0. Will free Vk resources.
187      */
internal_dispose(GrVkGpu * gpu)188     void internal_dispose(GrVkGpu* gpu) const {
189         this->freeGPUData(gpu);
190 #ifdef SK_TRACE_VK_RESOURCES
191         GetTrace()->remove(this);
192 #endif
193 
194 #ifdef SK_DEBUG
195         SkASSERT(0 == this->getRefCnt());
196         fRefCnt.store(1);
197 #endif
198         delete this;
199     }
200 
201     /**
202      *  Internal_dispose without freeing Vk resources. Used when we've lost context.
203      */
internal_dispose()204     void internal_dispose() const {
205         this->abandonGPUData();
206 #ifdef SK_TRACE_VK_RESOURCES
207         GetTrace()->remove(this);
208 #endif
209 
210 #ifdef SK_DEBUG
211         SkASSERT(0 == this->getRefCnt());
212         fRefCnt.store(1);
213 #endif
214         delete this;
215     }
216 
217     mutable std::atomic<int32_t> fRefCnt;
218 #ifdef SK_TRACE_VK_RESOURCES
219     uint32_t fKey;
220 #endif
221 
222     typedef SkNoncopyable INHERITED;
223 };
224 
225 // This subclass allows for recycling
226 class GrVkRecycledResource : public GrVkResource {
227 public:
228     // When recycle is called and there is only one ref left on the resource, we will signal that
229     // the resource can be recycled for reuse. If the sublass (or whoever is managing this resource)
230     // decides not to recycle the objects, it is their responsibility to call unref on the object.
recycle(GrVkGpu * gpu)231     void recycle(GrVkGpu* gpu) const {
232         if (this->unique()) {
233             this->onRecycle(gpu);
234         } else {
235             this->unref(gpu);
236         }
237     }
238 
239 private:
240     virtual void onRecycle(GrVkGpu* gpu) const = 0;
241 };
242 
243 #endif
244