blob: 7efe62365bf432f112e12e5a01d9b97fa4f7ec98 [file] [log] [blame]
bsalomonc8dc1f72014-08-21 13:02:13 -07001
2/*
3 * Copyright 2014 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
bsalomon0ea80f42015-02-11 10:49:59 -080010#include "GrResourceCache.h"
bsalomon3582d3e2015-02-13 14:20:05 -080011#include "GrGpuResourceCacheAccess.h"
hendrikw876c3132015-03-04 10:33:49 -080012#include "GrTracing.h"
bsalomon7775c852014-12-30 12:50:52 -080013#include "SkChecksum.h"
bsalomon71cb0c22014-11-14 12:10:14 -080014#include "SkGr.h"
15#include "SkMessageBus.h"
bsalomonddf30e62015-02-19 11:38:44 -080016#include "SkTSort.h"
bsalomon71cb0c22014-11-14 12:10:14 -080017
bsalomon8718aaf2015-02-19 07:24:21 -080018DECLARE_SKMESSAGEBUS_MESSAGE(GrUniqueKeyInvalidatedMessage);
bsalomon71cb0c22014-11-14 12:10:14 -080019
20//////////////////////////////////////////////////////////////////////////////
21
bsalomon7775c852014-12-30 12:50:52 -080022GrScratchKey::ResourceType GrScratchKey::GenerateResourceType() {
bsalomon24db3b12015-01-23 04:24:04 -080023 static int32_t gType = INHERITED::kInvalidDomain + 1;
bsalomonfe369ee2014-11-10 11:59:06 -080024
bsalomon7775c852014-12-30 12:50:52 -080025 int32_t type = sk_atomic_inc(&gType);
robertphillips9790a7b2015-01-05 12:29:15 -080026 if (type > SK_MaxU16) {
bsalomon71cb0c22014-11-14 12:10:14 -080027 SkFAIL("Too many Resource Types");
28 }
29
30 return static_cast<ResourceType>(type);
31}
32
bsalomon8718aaf2015-02-19 07:24:21 -080033GrUniqueKey::Domain GrUniqueKey::GenerateDomain() {
bsalomon24db3b12015-01-23 04:24:04 -080034 static int32_t gDomain = INHERITED::kInvalidDomain + 1;
bsalomon7775c852014-12-30 12:50:52 -080035
bsalomon24db3b12015-01-23 04:24:04 -080036 int32_t domain = sk_atomic_inc(&gDomain);
kkinnunen016dffb2015-01-23 06:43:05 -080037 if (domain > SK_MaxU16) {
bsalomon8718aaf2015-02-19 07:24:21 -080038 SkFAIL("Too many GrUniqueKey Domains");
bsalomon7775c852014-12-30 12:50:52 -080039 }
bsalomon24db3b12015-01-23 04:24:04 -080040
41 return static_cast<Domain>(domain);
42}
bsalomon3f324322015-04-08 11:01:54 -070043
bsalomon24db3b12015-01-23 04:24:04 -080044uint32_t GrResourceKeyHash(const uint32_t* data, size_t size) {
45 return SkChecksum::Compute(data, size);
bsalomon7775c852014-12-30 12:50:52 -080046}
47
bsalomonfe369ee2014-11-10 11:59:06 -080048//////////////////////////////////////////////////////////////////////////////
49
bsalomon0ea80f42015-02-11 10:49:59 -080050class GrResourceCache::AutoValidate : ::SkNoncopyable {
bsalomon71cb0c22014-11-14 12:10:14 -080051public:
bsalomon0ea80f42015-02-11 10:49:59 -080052 AutoValidate(GrResourceCache* cache) : fCache(cache) { cache->validate(); }
bsalomon71cb0c22014-11-14 12:10:14 -080053 ~AutoValidate() { fCache->validate(); }
54private:
bsalomon0ea80f42015-02-11 10:49:59 -080055 GrResourceCache* fCache;
bsalomon71cb0c22014-11-14 12:10:14 -080056};
57
58 //////////////////////////////////////////////////////////////////////////////
59
bsalomon71cb0c22014-11-14 12:10:14 -080060
bsalomon0ea80f42015-02-11 10:49:59 -080061GrResourceCache::GrResourceCache()
bsalomon9f2d1572015-02-17 11:47:40 -080062 : fTimestamp(0)
63 , fMaxCount(kDefaultMaxCount)
bsalomon71cb0c22014-11-14 12:10:14 -080064 , fMaxBytes(kDefaultMaxSize)
bsalomon3f324322015-04-08 11:01:54 -070065 , fMaxUnusedFlushes(kDefaultMaxUnusedFlushes)
bsalomon71cb0c22014-11-14 12:10:14 -080066#if GR_CACHE_STATS
67 , fHighWaterCount(0)
68 , fHighWaterBytes(0)
bsalomondace19e2014-11-17 07:34:06 -080069 , fBudgetedHighWaterCount(0)
70 , fBudgetedHighWaterBytes(0)
bsalomon71cb0c22014-11-14 12:10:14 -080071#endif
bsalomon71cb0c22014-11-14 12:10:14 -080072 , fBytes(0)
bsalomondace19e2014-11-17 07:34:06 -080073 , fBudgetedCount(0)
74 , fBudgetedBytes(0)
bsalomon71cb0c22014-11-14 12:10:14 -080075 , fOverBudgetCB(NULL)
bsalomon3f324322015-04-08 11:01:54 -070076 , fOverBudgetData(NULL)
77 , fFlushTimestamps(NULL)
78 , fLastFlushTimestampIndex(0){
bsalomonf320e042015-02-17 15:09:34 -080079 SkDEBUGCODE(fCount = 0;)
bsalomon3f324322015-04-08 11:01:54 -070080 SkDEBUGCODE(fNewlyPurgeableResourceForValidation = NULL;)
81 this->resetFlushTimestamps();
bsalomon71cb0c22014-11-14 12:10:14 -080082}
83
bsalomon0ea80f42015-02-11 10:49:59 -080084GrResourceCache::~GrResourceCache() {
bsalomonc8dc1f72014-08-21 13:02:13 -070085 this->releaseAll();
mtkleind9f88622015-04-09 03:20:04 -070086 SkDELETE_ARRAY(fFlushTimestamps);
bsalomonc8dc1f72014-08-21 13:02:13 -070087}
88
bsalomon3f324322015-04-08 11:01:54 -070089void GrResourceCache::setLimits(int count, size_t bytes, int maxUnusedFlushes) {
bsalomon71cb0c22014-11-14 12:10:14 -080090 fMaxCount = count;
91 fMaxBytes = bytes;
bsalomon3f324322015-04-08 11:01:54 -070092 fMaxUnusedFlushes = maxUnusedFlushes;
93 this->resetFlushTimestamps();
bsalomon71cb0c22014-11-14 12:10:14 -080094 this->purgeAsNeeded();
95}
96
bsalomon3f324322015-04-08 11:01:54 -070097void GrResourceCache::resetFlushTimestamps() {
mtkleind9f88622015-04-09 03:20:04 -070098 SkDELETE_ARRAY(fFlushTimestamps);
bsalomon3f324322015-04-08 11:01:54 -070099
100 // We assume this number is a power of two when wrapping indices into the timestamp array.
101 fMaxUnusedFlushes = SkNextPow2(fMaxUnusedFlushes);
102
103 // Since our implementation is to store the timestamps of the last fMaxUnusedFlushes flush calls
104 // we just turn the feature off if that array would be large.
105 static const int kMaxSupportedTimestampHistory = 128;
106
107 if (fMaxUnusedFlushes > kMaxSupportedTimestampHistory) {
108 fFlushTimestamps = NULL;
109 return;
110 }
111
112 fFlushTimestamps = SkNEW_ARRAY(uint32_t, fMaxUnusedFlushes);
113 fLastFlushTimestampIndex = 0;
114 // Set all the historical flush timestamps to initially be at the beginning of time (timestamp
115 // 0).
116 sk_bzero(fFlushTimestamps, fMaxUnusedFlushes * sizeof(uint32_t));
117}
118
bsalomon0ea80f42015-02-11 10:49:59 -0800119void GrResourceCache::insertResource(GrGpuResource* resource) {
bsalomon49f085d2014-09-05 13:34:00 -0700120 SkASSERT(resource);
bsalomon16961262014-08-26 14:01:07 -0700121 SkASSERT(!this->isInCache(resource));
bsalomonf320e042015-02-17 15:09:34 -0800122 SkASSERT(!resource->wasDestroyed());
123 SkASSERT(!resource->isPurgeable());
bsalomonddf30e62015-02-19 11:38:44 -0800124
125 // We must set the timestamp before adding to the array in case the timestamp wraps and we wind
126 // up iterating over all the resources that already have timestamps.
127 resource->cacheAccess().setTimestamp(this->getNextTimestamp());
128
bsalomonf320e042015-02-17 15:09:34 -0800129 this->addToNonpurgeableArray(resource);
bsalomon71cb0c22014-11-14 12:10:14 -0800130
bsalomondace19e2014-11-17 07:34:06 -0800131 size_t size = resource->gpuMemorySize();
bsalomonf320e042015-02-17 15:09:34 -0800132 SkDEBUGCODE(++fCount;)
bsalomon84c8e622014-11-17 09:33:27 -0800133 fBytes += size;
bsalomon82b1d622014-11-14 13:59:57 -0800134#if GR_CACHE_STATS
bsalomonf320e042015-02-17 15:09:34 -0800135 fHighWaterCount = SkTMax(this->getResourceCount(), fHighWaterCount);
bsalomon82b1d622014-11-14 13:59:57 -0800136 fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes);
137#endif
bsalomon3582d3e2015-02-13 14:20:05 -0800138 if (resource->resourcePriv().isBudgeted()) {
bsalomondace19e2014-11-17 07:34:06 -0800139 ++fBudgetedCount;
140 fBudgetedBytes += size;
hendrikw876c3132015-03-04 10:33:49 -0800141 TRACE_COUNTER2(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache"), "skia budget", "used",
142 fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
bsalomondace19e2014-11-17 07:34:06 -0800143#if GR_CACHE_STATS
144 fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount);
145 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
146#endif
147 }
bsalomon3582d3e2015-02-13 14:20:05 -0800148 if (resource->resourcePriv().getScratchKey().isValid()) {
bsalomon6dc6f5f2015-06-18 09:12:16 -0700149 SkASSERT(!resource->cacheAccess().isExternal());
bsalomon3582d3e2015-02-13 14:20:05 -0800150 fScratchMap.insert(resource->resourcePriv().getScratchKey(), resource);
bsalomon744998e2014-08-28 09:54:34 -0700151 }
bsalomon9f2d1572015-02-17 11:47:40 -0800152
bsalomon71cb0c22014-11-14 12:10:14 -0800153 this->purgeAsNeeded();
bsalomonc8dc1f72014-08-21 13:02:13 -0700154}
155
bsalomon0ea80f42015-02-11 10:49:59 -0800156void GrResourceCache::removeResource(GrGpuResource* resource) {
bsalomon9f2d1572015-02-17 11:47:40 -0800157 this->validate();
bsalomon16961262014-08-26 14:01:07 -0700158 SkASSERT(this->isInCache(resource));
bsalomondace19e2014-11-17 07:34:06 -0800159
bsalomon9f2d1572015-02-17 11:47:40 -0800160 if (resource->isPurgeable()) {
161 fPurgeableQueue.remove(resource);
bsalomonf320e042015-02-17 15:09:34 -0800162 } else {
163 this->removeFromNonpurgeableArray(resource);
bsalomon9f2d1572015-02-17 11:47:40 -0800164 }
165
bsalomondace19e2014-11-17 07:34:06 -0800166 size_t size = resource->gpuMemorySize();
bsalomonf320e042015-02-17 15:09:34 -0800167 SkDEBUGCODE(--fCount;)
bsalomondace19e2014-11-17 07:34:06 -0800168 fBytes -= size;
bsalomon3582d3e2015-02-13 14:20:05 -0800169 if (resource->resourcePriv().isBudgeted()) {
bsalomondace19e2014-11-17 07:34:06 -0800170 --fBudgetedCount;
171 fBudgetedBytes -= size;
hendrikw876c3132015-03-04 10:33:49 -0800172 TRACE_COUNTER2(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache"), "skia budget", "used",
173 fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
bsalomondace19e2014-11-17 07:34:06 -0800174 }
175
bsalomon3582d3e2015-02-13 14:20:05 -0800176 if (resource->resourcePriv().getScratchKey().isValid()) {
177 fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
bsalomon744998e2014-08-28 09:54:34 -0700178 }
bsalomon8718aaf2015-02-19 07:24:21 -0800179 if (resource->getUniqueKey().isValid()) {
180 fUniqueHash.remove(resource->getUniqueKey());
bsalomon8b79d232014-11-10 10:19:06 -0800181 }
bsalomonb436ed62014-11-17 12:15:56 -0800182 this->validate();
bsalomonc8dc1f72014-08-21 13:02:13 -0700183}
184
bsalomon0ea80f42015-02-11 10:49:59 -0800185void GrResourceCache::abandonAll() {
bsalomon71cb0c22014-11-14 12:10:14 -0800186 AutoValidate av(this);
187
bsalomonf320e042015-02-17 15:09:34 -0800188 while (fNonpurgeableResources.count()) {
189 GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
190 SkASSERT(!back->wasDestroyed());
191 back->cacheAccess().abandon();
bsalomonc8dc1f72014-08-21 13:02:13 -0700192 }
bsalomonf320e042015-02-17 15:09:34 -0800193
194 while (fPurgeableQueue.count()) {
195 GrGpuResource* top = fPurgeableQueue.peek();
196 SkASSERT(!top->wasDestroyed());
197 top->cacheAccess().abandon();
198 }
199
bsalomon744998e2014-08-28 09:54:34 -0700200 SkASSERT(!fScratchMap.count());
bsalomon8718aaf2015-02-19 07:24:21 -0800201 SkASSERT(!fUniqueHash.count());
bsalomonc8dc1f72014-08-21 13:02:13 -0700202 SkASSERT(!fCount);
bsalomonf320e042015-02-17 15:09:34 -0800203 SkASSERT(!this->getResourceCount());
bsalomondace19e2014-11-17 07:34:06 -0800204 SkASSERT(!fBytes);
205 SkASSERT(!fBudgetedCount);
206 SkASSERT(!fBudgetedBytes);
bsalomonc8dc1f72014-08-21 13:02:13 -0700207}
208
bsalomon0ea80f42015-02-11 10:49:59 -0800209void GrResourceCache::releaseAll() {
bsalomon71cb0c22014-11-14 12:10:14 -0800210 AutoValidate av(this);
211
bsalomonf320e042015-02-17 15:09:34 -0800212 while(fNonpurgeableResources.count()) {
213 GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
214 SkASSERT(!back->wasDestroyed());
215 back->cacheAccess().release();
bsalomonc8dc1f72014-08-21 13:02:13 -0700216 }
bsalomonf320e042015-02-17 15:09:34 -0800217
218 while (fPurgeableQueue.count()) {
219 GrGpuResource* top = fPurgeableQueue.peek();
220 SkASSERT(!top->wasDestroyed());
221 top->cacheAccess().release();
222 }
223
bsalomon744998e2014-08-28 09:54:34 -0700224 SkASSERT(!fScratchMap.count());
bsalomon8718aaf2015-02-19 07:24:21 -0800225 SkASSERT(!fUniqueHash.count());
bsalomonc8dc1f72014-08-21 13:02:13 -0700226 SkASSERT(!fCount);
bsalomonf320e042015-02-17 15:09:34 -0800227 SkASSERT(!this->getResourceCount());
bsalomondace19e2014-11-17 07:34:06 -0800228 SkASSERT(!fBytes);
229 SkASSERT(!fBudgetedCount);
230 SkASSERT(!fBudgetedBytes);
bsalomonc8dc1f72014-08-21 13:02:13 -0700231}
bsalomonbcf0a522014-10-08 08:40:09 -0700232
bsalomon0ea80f42015-02-11 10:49:59 -0800233class GrResourceCache::AvailableForScratchUse {
bsalomonbcf0a522014-10-08 08:40:09 -0700234public:
bsalomon000f8292014-10-15 19:04:14 -0700235 AvailableForScratchUse(bool rejectPendingIO) : fRejectPendingIO(rejectPendingIO) { }
bsalomonbcf0a522014-10-08 08:40:09 -0700236
237 bool operator()(const GrGpuResource* resource) const {
bsalomon12299ab2014-11-14 13:33:09 -0800238 if (resource->internalHasRef() || !resource->cacheAccess().isScratch()) {
bsalomon000f8292014-10-15 19:04:14 -0700239 return false;
bsalomonbcf0a522014-10-08 08:40:09 -0700240 }
bsalomon000f8292014-10-15 19:04:14 -0700241 return !fRejectPendingIO || !resource->internalHasPendingIO();
bsalomonbcf0a522014-10-08 08:40:09 -0700242 }
bsalomon1e2530b2014-10-09 09:57:18 -0700243
bsalomonbcf0a522014-10-08 08:40:09 -0700244private:
bsalomon000f8292014-10-15 19:04:14 -0700245 bool fRejectPendingIO;
bsalomonbcf0a522014-10-08 08:40:09 -0700246};
247
bsalomon0ea80f42015-02-11 10:49:59 -0800248GrGpuResource* GrResourceCache::findAndRefScratchResource(const GrScratchKey& scratchKey,
robertphillips6e83ac72015-08-13 05:19:14 -0700249 size_t resourceSize,
bsalomon9f2d1572015-02-17 11:47:40 -0800250 uint32_t flags) {
bsalomon7775c852014-12-30 12:50:52 -0800251 SkASSERT(scratchKey.isValid());
bsalomon000f8292014-10-15 19:04:14 -0700252
bsalomon71cb0c22014-11-14 12:10:14 -0800253 GrGpuResource* resource;
bsalomon000f8292014-10-15 19:04:14 -0700254 if (flags & (kPreferNoPendingIO_ScratchFlag | kRequireNoPendingIO_ScratchFlag)) {
bsalomon71cb0c22014-11-14 12:10:14 -0800255 resource = fScratchMap.find(scratchKey, AvailableForScratchUse(true));
bsalomon000f8292014-10-15 19:04:14 -0700256 if (resource) {
bsalomon9f2d1572015-02-17 11:47:40 -0800257 this->refAndMakeResourceMRU(resource);
bsalomonb436ed62014-11-17 12:15:56 -0800258 this->validate();
259 return resource;
bsalomon000f8292014-10-15 19:04:14 -0700260 } else if (flags & kRequireNoPendingIO_ScratchFlag) {
261 return NULL;
262 }
robertphillips6e83ac72015-08-13 05:19:14 -0700263 if (this->wouldFit(resourceSize)) {
264 // kPrefer is specified, we didn't find a resource without pending io,
265 // but there is still space in our budget for the resource.
266 return NULL;
267 }
bsalomon000f8292014-10-15 19:04:14 -0700268 }
bsalomon71cb0c22014-11-14 12:10:14 -0800269 resource = fScratchMap.find(scratchKey, AvailableForScratchUse(false));
270 if (resource) {
bsalomon9f2d1572015-02-17 11:47:40 -0800271 this->refAndMakeResourceMRU(resource);
bsalomonb436ed62014-11-17 12:15:56 -0800272 this->validate();
bsalomon71cb0c22014-11-14 12:10:14 -0800273 }
274 return resource;
bsalomonbcf0a522014-10-08 08:40:09 -0700275}
bsalomon8b79d232014-11-10 10:19:06 -0800276
bsalomon0ea80f42015-02-11 10:49:59 -0800277void GrResourceCache::willRemoveScratchKey(const GrGpuResource* resource) {
bsalomon3582d3e2015-02-13 14:20:05 -0800278 SkASSERT(resource->resourcePriv().getScratchKey().isValid());
279 fScratchMap.remove(resource->resourcePriv().getScratchKey(), resource);
bsalomon10e23ca2014-11-25 05:52:06 -0800280}
281
bsalomonf99e9612015-02-19 08:24:16 -0800282void GrResourceCache::removeUniqueKey(GrGpuResource* resource) {
bsalomon3f324322015-04-08 11:01:54 -0700283 // Someone has a ref to this resource in order to have removed the key. When the ref count
284 // reaches zero we will get a ref cnt notification and figure out what to do with it.
bsalomonf99e9612015-02-19 08:24:16 -0800285 if (resource->getUniqueKey().isValid()) {
286 SkASSERT(resource == fUniqueHash.find(resource->getUniqueKey()));
287 fUniqueHash.remove(resource->getUniqueKey());
288 }
289 resource->cacheAccess().removeUniqueKey();
290 this->validate();
bsalomon23e619c2015-02-06 11:54:28 -0800291}
292
bsalomonf99e9612015-02-19 08:24:16 -0800293void GrResourceCache::changeUniqueKey(GrGpuResource* resource, const GrUniqueKey& newKey) {
bsalomon8b79d232014-11-10 10:19:06 -0800294 SkASSERT(resource);
bsalomon71cb0c22014-11-14 12:10:14 -0800295 SkASSERT(this->isInCache(resource));
bsalomon8b79d232014-11-10 10:19:06 -0800296
bsalomonf99e9612015-02-19 08:24:16 -0800297 // Remove the entry for this resource if it already has a unique key.
298 if (resource->getUniqueKey().isValid()) {
299 SkASSERT(resource == fUniqueHash.find(resource->getUniqueKey()));
300 fUniqueHash.remove(resource->getUniqueKey());
301 SkASSERT(NULL == fUniqueHash.find(resource->getUniqueKey()));
bsalomon8b79d232014-11-10 10:19:06 -0800302 }
303
bsalomonf99e9612015-02-19 08:24:16 -0800304 // If another resource has the new key, remove its key then install the key on this resource.
305 if (newKey.isValid()) {
306 if (GrGpuResource* old = fUniqueHash.find(newKey)) {
307 // If the old resource using the key is purgeable and is unreachable, then remove it.
308 if (!old->resourcePriv().getScratchKey().isValid() && old->isPurgeable()) {
309 // release may call validate() which will assert that resource is in fUniqueHash
310 // if it has a valid key. So in debug reset the key here before we assign it.
311 SkDEBUGCODE(resource->cacheAccess().removeUniqueKey();)
312 old->cacheAccess().release();
313 } else {
314 fUniqueHash.remove(newKey);
315 old->cacheAccess().removeUniqueKey();
316 }
317 }
318 SkASSERT(NULL == fUniqueHash.find(newKey));
319 resource->cacheAccess().setUniqueKey(newKey);
320 fUniqueHash.add(resource);
321 } else {
322 resource->cacheAccess().removeUniqueKey();
323 }
324
bsalomon71cb0c22014-11-14 12:10:14 -0800325 this->validate();
bsalomon8b79d232014-11-10 10:19:06 -0800326}
bsalomon71cb0c22014-11-14 12:10:14 -0800327
bsalomon9f2d1572015-02-17 11:47:40 -0800328void GrResourceCache::refAndMakeResourceMRU(GrGpuResource* resource) {
bsalomon71cb0c22014-11-14 12:10:14 -0800329 SkASSERT(resource);
330 SkASSERT(this->isInCache(resource));
bsalomonddf30e62015-02-19 11:38:44 -0800331
bsalomon9f2d1572015-02-17 11:47:40 -0800332 if (resource->isPurgeable()) {
333 // It's about to become unpurgeable.
334 fPurgeableQueue.remove(resource);
bsalomonf320e042015-02-17 15:09:34 -0800335 this->addToNonpurgeableArray(resource);
bsalomon9f2d1572015-02-17 11:47:40 -0800336 }
337 resource->ref();
bsalomonddf30e62015-02-19 11:38:44 -0800338
339 resource->cacheAccess().setTimestamp(this->getNextTimestamp());
bsalomonf320e042015-02-17 15:09:34 -0800340 this->validate();
bsalomon71cb0c22014-11-14 12:10:14 -0800341}
342
bsalomon3f324322015-04-08 11:01:54 -0700343void GrResourceCache::notifyCntReachedZero(GrGpuResource* resource, uint32_t flags) {
bsalomon71cb0c22014-11-14 12:10:14 -0800344 SkASSERT(resource);
bsalomon3f324322015-04-08 11:01:54 -0700345 SkASSERT(!resource->wasDestroyed());
346 SkASSERT(flags);
bsalomon71cb0c22014-11-14 12:10:14 -0800347 SkASSERT(this->isInCache(resource));
bsalomon3f324322015-04-08 11:01:54 -0700348 // This resource should always be in the nonpurgeable array when this function is called. It
349 // will be moved to the queue if it is newly purgeable.
350 SkASSERT(fNonpurgeableResources[*resource->cacheAccess().accessCacheIndex()] == resource);
bsalomon71cb0c22014-11-14 12:10:14 -0800351
bsalomon3f324322015-04-08 11:01:54 -0700352 if (SkToBool(ResourceAccess::kRefCntReachedZero_RefNotificationFlag & flags)) {
353#ifdef SK_DEBUG
354 // When the timestamp overflows validate() is called. validate() checks that resources in
355 // the nonpurgeable array are indeed not purgeable. However, the movement from the array to
356 // the purgeable queue happens just below in this function. So we mark it as an exception.
357 if (resource->isPurgeable()) {
358 fNewlyPurgeableResourceForValidation = resource;
359 }
360#endif
361 resource->cacheAccess().setTimestamp(this->getNextTimestamp());
362 SkDEBUGCODE(fNewlyPurgeableResourceForValidation = NULL);
363 }
364
365 if (!SkToBool(ResourceAccess::kAllCntsReachedZero_RefNotificationFlag & flags)) {
366 SkASSERT(!resource->isPurgeable());
367 return;
368 }
369
370 SkASSERT(resource->isPurgeable());
bsalomonf320e042015-02-17 15:09:34 -0800371 this->removeFromNonpurgeableArray(resource);
bsalomon9f2d1572015-02-17 11:47:40 -0800372 fPurgeableQueue.insert(resource);
bsalomon71cb0c22014-11-14 12:10:14 -0800373
bsalomon9f2d1572015-02-17 11:47:40 -0800374 if (!resource->resourcePriv().isBudgeted()) {
bsalomonc2f35b72015-01-23 07:19:22 -0800375 // Check whether this resource could still be used as a scratch resource.
bsalomon6dc6f5f2015-06-18 09:12:16 -0700376 if (!resource->cacheAccess().isExternal() &&
bsalomon9f2d1572015-02-17 11:47:40 -0800377 resource->resourcePriv().getScratchKey().isValid()) {
bsalomonc2f35b72015-01-23 07:19:22 -0800378 // We won't purge an existing resource to make room for this one.
bsalomonf320e042015-02-17 15:09:34 -0800379 if (fBudgetedCount < fMaxCount &&
380 fBudgetedBytes + resource->gpuMemorySize() <= fMaxBytes) {
bsalomon3582d3e2015-02-13 14:20:05 -0800381 resource->resourcePriv().makeBudgeted();
bsalomon9f2d1572015-02-17 11:47:40 -0800382 return;
bsalomonc2f35b72015-01-23 07:19:22 -0800383 }
bsalomonc2f35b72015-01-23 07:19:22 -0800384 }
385 } else {
bsalomon9f2d1572015-02-17 11:47:40 -0800386 // Purge the resource immediately if we're over budget
bsalomon8718aaf2015-02-19 07:24:21 -0800387 // Also purge if the resource has neither a valid scratch key nor a unique key.
bsalomon3582d3e2015-02-13 14:20:05 -0800388 bool noKey = !resource->resourcePriv().getScratchKey().isValid() &&
bsalomon8718aaf2015-02-19 07:24:21 -0800389 !resource->getUniqueKey().isValid();
bsalomonf320e042015-02-17 15:09:34 -0800390 if (!this->overBudget() && !noKey) {
bsalomon9f2d1572015-02-17 11:47:40 -0800391 return;
bsalomonc2f35b72015-01-23 07:19:22 -0800392 }
393 }
bsalomondace19e2014-11-17 07:34:06 -0800394
bsalomonf320e042015-02-17 15:09:34 -0800395 SkDEBUGCODE(int beforeCount = this->getResourceCount();)
bsalomon9f2d1572015-02-17 11:47:40 -0800396 resource->cacheAccess().release();
397 // We should at least free this resource, perhaps dependent resources as well.
bsalomonf320e042015-02-17 15:09:34 -0800398 SkASSERT(this->getResourceCount() < beforeCount);
bsalomon71cb0c22014-11-14 12:10:14 -0800399 this->validate();
400}
401
bsalomon0ea80f42015-02-11 10:49:59 -0800402void GrResourceCache::didChangeGpuMemorySize(const GrGpuResource* resource, size_t oldSize) {
bsalomon71cb0c22014-11-14 12:10:14 -0800403 // SkASSERT(!fPurging); GrPathRange increases size during flush. :(
404 SkASSERT(resource);
405 SkASSERT(this->isInCache(resource));
406
bsalomondace19e2014-11-17 07:34:06 -0800407 ptrdiff_t delta = resource->gpuMemorySize() - oldSize;
408
409 fBytes += delta;
bsalomon82b1d622014-11-14 13:59:57 -0800410#if GR_CACHE_STATS
411 fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes);
412#endif
bsalomon3582d3e2015-02-13 14:20:05 -0800413 if (resource->resourcePriv().isBudgeted()) {
bsalomondace19e2014-11-17 07:34:06 -0800414 fBudgetedBytes += delta;
hendrikw876c3132015-03-04 10:33:49 -0800415 TRACE_COUNTER2(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache"), "skia budget", "used",
416 fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
bsalomondace19e2014-11-17 07:34:06 -0800417#if GR_CACHE_STATS
418 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
419#endif
420 }
bsalomon71cb0c22014-11-14 12:10:14 -0800421
422 this->purgeAsNeeded();
423 this->validate();
424}
425
bsalomon0ea80f42015-02-11 10:49:59 -0800426void GrResourceCache::didChangeBudgetStatus(GrGpuResource* resource) {
bsalomon84c8e622014-11-17 09:33:27 -0800427 SkASSERT(resource);
428 SkASSERT(this->isInCache(resource));
429
430 size_t size = resource->gpuMemorySize();
431
bsalomon3582d3e2015-02-13 14:20:05 -0800432 if (resource->resourcePriv().isBudgeted()) {
bsalomon84c8e622014-11-17 09:33:27 -0800433 ++fBudgetedCount;
434 fBudgetedBytes += size;
bsalomonafe30052015-01-16 07:32:33 -0800435#if GR_CACHE_STATS
436 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes);
437 fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount);
438#endif
bsalomon84c8e622014-11-17 09:33:27 -0800439 this->purgeAsNeeded();
440 } else {
441 --fBudgetedCount;
442 fBudgetedBytes -= size;
443 }
hendrikw876c3132015-03-04 10:33:49 -0800444 TRACE_COUNTER2(TRACE_DISABLED_BY_DEFAULT("skia.gpu.cache"), "skia budget", "used",
445 fBudgetedBytes, "free", fMaxBytes - fBudgetedBytes);
bsalomon84c8e622014-11-17 09:33:27 -0800446
447 this->validate();
448}
449
bsalomon3f324322015-04-08 11:01:54 -0700450void GrResourceCache::purgeAsNeeded() {
451 SkTArray<GrUniqueKeyInvalidatedMessage> invalidKeyMsgs;
452 fInvalidUniqueKeyInbox.poll(&invalidKeyMsgs);
453 if (invalidKeyMsgs.count()) {
454 this->processInvalidUniqueKeys(invalidKeyMsgs);
455 }
bsalomon71cb0c22014-11-14 12:10:14 -0800456
bsalomon3f324322015-04-08 11:01:54 -0700457 if (fFlushTimestamps) {
458 // Assuming kNumFlushesToDeleteUnusedResource is a power of 2.
459 SkASSERT(SkIsPow2(fMaxUnusedFlushes));
460 int oldestFlushIndex = (fLastFlushTimestampIndex + 1) & (fMaxUnusedFlushes - 1);
461
462 uint32_t oldestAllowedTimestamp = fFlushTimestamps[oldestFlushIndex];
463 while (fPurgeableQueue.count()) {
464 uint32_t oldestResourceTimestamp = fPurgeableQueue.peek()->cacheAccess().timestamp();
465 if (oldestAllowedTimestamp < oldestResourceTimestamp) {
466 break;
467 }
468 GrGpuResource* resource = fPurgeableQueue.peek();
469 SkASSERT(resource->isPurgeable());
470 resource->cacheAccess().release();
471 }
472 }
473
474 bool stillOverbudget = this->overBudget();
475 while (stillOverbudget && fPurgeableQueue.count()) {
bsalomon9f2d1572015-02-17 11:47:40 -0800476 GrGpuResource* resource = fPurgeableQueue.peek();
477 SkASSERT(resource->isPurgeable());
478 resource->cacheAccess().release();
bsalomon3f324322015-04-08 11:01:54 -0700479 stillOverbudget = this->overBudget();
bsalomon9f2d1572015-02-17 11:47:40 -0800480 }
bsalomon71cb0c22014-11-14 12:10:14 -0800481
bsalomonb436ed62014-11-17 12:15:56 -0800482 this->validate();
bsalomon9f2d1572015-02-17 11:47:40 -0800483
484 if (stillOverbudget) {
485 // Despite the purge we're still over budget. Call our over budget callback. If this frees
bsalomon3f324322015-04-08 11:01:54 -0700486 // any resources then we'll get notified and take appropriate action.
bsalomon9f2d1572015-02-17 11:47:40 -0800487 (*fOverBudgetCB)(fOverBudgetData);
488 this->validate();
489 }
bsalomon71cb0c22014-11-14 12:10:14 -0800490}
491
bsalomon0ea80f42015-02-11 10:49:59 -0800492void GrResourceCache::purgeAllUnlocked() {
bsalomon9f2d1572015-02-17 11:47:40 -0800493 // We could disable maintaining the heap property here, but it would add a lot of complexity.
494 // Moreover, this is rarely called.
495 while (fPurgeableQueue.count()) {
496 GrGpuResource* resource = fPurgeableQueue.peek();
497 SkASSERT(resource->isPurgeable());
498 resource->cacheAccess().release();
499 }
bsalomon71cb0c22014-11-14 12:10:14 -0800500
bsalomonb436ed62014-11-17 12:15:56 -0800501 this->validate();
bsalomon71cb0c22014-11-14 12:10:14 -0800502}
503
bsalomon8718aaf2015-02-19 07:24:21 -0800504void GrResourceCache::processInvalidUniqueKeys(
505 const SkTArray<GrUniqueKeyInvalidatedMessage>& msgs) {
bsalomon23e619c2015-02-06 11:54:28 -0800506 for (int i = 0; i < msgs.count(); ++i) {
bsalomon8718aaf2015-02-19 07:24:21 -0800507 GrGpuResource* resource = this->findAndRefUniqueResource(msgs[i].key());
bsalomon23e619c2015-02-06 11:54:28 -0800508 if (resource) {
bsalomon8718aaf2015-02-19 07:24:21 -0800509 resource->resourcePriv().removeUniqueKey();
bsalomon3f324322015-04-08 11:01:54 -0700510 resource->unref(); // If this resource is now purgeable, the cache will be notified.
bsalomon23e619c2015-02-06 11:54:28 -0800511 }
512 }
513}
514
bsalomonf320e042015-02-17 15:09:34 -0800515void GrResourceCache::addToNonpurgeableArray(GrGpuResource* resource) {
516 int index = fNonpurgeableResources.count();
517 *fNonpurgeableResources.append() = resource;
518 *resource->cacheAccess().accessCacheIndex() = index;
519}
520
521void GrResourceCache::removeFromNonpurgeableArray(GrGpuResource* resource) {
522 int* index = resource->cacheAccess().accessCacheIndex();
523 // Fill the whole we will create in the array with the tail object, adjust its index, and
524 // then pop the array
525 GrGpuResource* tail = *(fNonpurgeableResources.end() - 1);
526 SkASSERT(fNonpurgeableResources[*index] == resource);
527 fNonpurgeableResources[*index] = tail;
528 *tail->cacheAccess().accessCacheIndex() = *index;
529 fNonpurgeableResources.pop();
530 SkDEBUGCODE(*index = -1);
531}
532
bsalomonddf30e62015-02-19 11:38:44 -0800533uint32_t GrResourceCache::getNextTimestamp() {
534 // If we wrap then all the existing resources will appear older than any resources that get
535 // a timestamp after the wrap.
536 if (0 == fTimestamp) {
537 int count = this->getResourceCount();
538 if (count) {
539 // Reset all the timestamps. We sort the resources by timestamp and then assign
540 // sequential timestamps beginning with 0. This is O(n*lg(n)) but it should be extremely
541 // rare.
542 SkTDArray<GrGpuResource*> sortedPurgeableResources;
543 sortedPurgeableResources.setReserve(fPurgeableQueue.count());
544
545 while (fPurgeableQueue.count()) {
546 *sortedPurgeableResources.append() = fPurgeableQueue.peek();
547 fPurgeableQueue.pop();
548 }
549
550 struct Less {
551 bool operator()(GrGpuResource* a, GrGpuResource* b) {
552 return CompareTimestamp(a,b);
553 }
554 };
555 Less less;
556 SkTQSort(fNonpurgeableResources.begin(), fNonpurgeableResources.end() - 1, less);
557
558 // Pick resources out of the purgeable and non-purgeable arrays based on lowest
559 // timestamp and assign new timestamps.
560 int currP = 0;
561 int currNP = 0;
562 while (currP < sortedPurgeableResources.count() &&
563 currNP < fNonpurgeableResources.count()) {
564 uint32_t tsP = sortedPurgeableResources[currP]->cacheAccess().timestamp();
565 uint32_t tsNP = fNonpurgeableResources[currNP]->cacheAccess().timestamp();
566 SkASSERT(tsP != tsNP);
567 if (tsP < tsNP) {
568 sortedPurgeableResources[currP++]->cacheAccess().setTimestamp(fTimestamp++);
569 } else {
570 // Correct the index in the nonpurgeable array stored on the resource post-sort.
571 *fNonpurgeableResources[currNP]->cacheAccess().accessCacheIndex() = currNP;
572 fNonpurgeableResources[currNP++]->cacheAccess().setTimestamp(fTimestamp++);
573 }
574 }
575
576 // The above loop ended when we hit the end of one array. Finish the other one.
577 while (currP < sortedPurgeableResources.count()) {
578 sortedPurgeableResources[currP++]->cacheAccess().setTimestamp(fTimestamp++);
579 }
580 while (currNP < fNonpurgeableResources.count()) {
581 *fNonpurgeableResources[currNP]->cacheAccess().accessCacheIndex() = currNP;
582 fNonpurgeableResources[currNP++]->cacheAccess().setTimestamp(fTimestamp++);
583 }
584
585 // Rebuild the queue.
586 for (int i = 0; i < sortedPurgeableResources.count(); ++i) {
587 fPurgeableQueue.insert(sortedPurgeableResources[i]);
588 }
589
590 this->validate();
591 SkASSERT(count == this->getResourceCount());
592
593 // count should be the next timestamp we return.
594 SkASSERT(fTimestamp == SkToU32(count));
bsalomon3f324322015-04-08 11:01:54 -0700595
596 // The historical timestamps of flushes are now invalid.
597 this->resetFlushTimestamps();
bsalomonddf30e62015-02-19 11:38:44 -0800598 }
599 }
600 return fTimestamp++;
601}
602
bsalomon3f324322015-04-08 11:01:54 -0700603void GrResourceCache::notifyFlushOccurred() {
604 if (fFlushTimestamps) {
605 SkASSERT(SkIsPow2(fMaxUnusedFlushes));
606 fLastFlushTimestampIndex = (fLastFlushTimestampIndex + 1) & (fMaxUnusedFlushes - 1);
607 // get the timestamp before accessing fFlushTimestamps because getNextTimestamp will
608 // reallocate fFlushTimestamps on timestamp overflow.
609 uint32_t timestamp = this->getNextTimestamp();
610 fFlushTimestamps[fLastFlushTimestampIndex] = timestamp;
611 this->purgeAsNeeded();
612 }
613}
614
bsalomon71cb0c22014-11-14 12:10:14 -0800615#ifdef SK_DEBUG
bsalomon0ea80f42015-02-11 10:49:59 -0800616void GrResourceCache::validate() const {
bsalomonc2f35b72015-01-23 07:19:22 -0800617 // Reduce the frequency of validations for large resource counts.
618 static SkRandom gRandom;
619 int mask = (SkNextPow2(fCount + 1) >> 5) - 1;
620 if (~mask && (gRandom.nextU() & mask)) {
621 return;
622 }
623
bsalomonf320e042015-02-17 15:09:34 -0800624 struct Stats {
625 size_t fBytes;
626 int fBudgetedCount;
627 size_t fBudgetedBytes;
628 int fLocked;
629 int fScratch;
630 int fCouldBeScratch;
631 int fContent;
632 const ScratchMap* fScratchMap;
bsalomon8718aaf2015-02-19 07:24:21 -0800633 const UniqueHash* fUniqueHash;
bsalomon71cb0c22014-11-14 12:10:14 -0800634
bsalomonf320e042015-02-17 15:09:34 -0800635 Stats(const GrResourceCache* cache) {
636 memset(this, 0, sizeof(*this));
637 fScratchMap = &cache->fScratchMap;
bsalomon8718aaf2015-02-19 07:24:21 -0800638 fUniqueHash = &cache->fUniqueHash;
bsalomon71cb0c22014-11-14 12:10:14 -0800639 }
640
bsalomonf320e042015-02-17 15:09:34 -0800641 void update(GrGpuResource* resource) {
642 fBytes += resource->gpuMemorySize();
bsalomondace19e2014-11-17 07:34:06 -0800643
bsalomonf320e042015-02-17 15:09:34 -0800644 if (!resource->isPurgeable()) {
645 ++fLocked;
646 }
bsalomon9f2d1572015-02-17 11:47:40 -0800647
bsalomonf320e042015-02-17 15:09:34 -0800648 if (resource->cacheAccess().isScratch()) {
bsalomon8718aaf2015-02-19 07:24:21 -0800649 SkASSERT(!resource->getUniqueKey().isValid());
bsalomonf320e042015-02-17 15:09:34 -0800650 ++fScratch;
651 SkASSERT(fScratchMap->countForKey(resource->resourcePriv().getScratchKey()));
bsalomon6dc6f5f2015-06-18 09:12:16 -0700652 SkASSERT(!resource->cacheAccess().isExternal());
bsalomonf320e042015-02-17 15:09:34 -0800653 } else if (resource->resourcePriv().getScratchKey().isValid()) {
654 SkASSERT(!resource->resourcePriv().isBudgeted() ||
bsalomon8718aaf2015-02-19 07:24:21 -0800655 resource->getUniqueKey().isValid());
bsalomonf320e042015-02-17 15:09:34 -0800656 ++fCouldBeScratch;
657 SkASSERT(fScratchMap->countForKey(resource->resourcePriv().getScratchKey()));
bsalomon6dc6f5f2015-06-18 09:12:16 -0700658 SkASSERT(!resource->cacheAccess().isExternal());
bsalomonf320e042015-02-17 15:09:34 -0800659 }
bsalomon8718aaf2015-02-19 07:24:21 -0800660 const GrUniqueKey& uniqueKey = resource->getUniqueKey();
661 if (uniqueKey.isValid()) {
bsalomonf320e042015-02-17 15:09:34 -0800662 ++fContent;
bsalomon8718aaf2015-02-19 07:24:21 -0800663 SkASSERT(fUniqueHash->find(uniqueKey) == resource);
bsalomon6dc6f5f2015-06-18 09:12:16 -0700664 SkASSERT(!resource->cacheAccess().isExternal());
bsalomonf320e042015-02-17 15:09:34 -0800665 SkASSERT(resource->resourcePriv().isBudgeted());
666 }
667
668 if (resource->resourcePriv().isBudgeted()) {
669 ++fBudgetedCount;
670 fBudgetedBytes += resource->gpuMemorySize();
671 }
bsalomon9f2d1572015-02-17 11:47:40 -0800672 }
bsalomonf320e042015-02-17 15:09:34 -0800673 };
674
675 Stats stats(this);
676
677 for (int i = 0; i < fNonpurgeableResources.count(); ++i) {
bsalomon3f324322015-04-08 11:01:54 -0700678 SkASSERT(!fNonpurgeableResources[i]->isPurgeable() ||
679 fNewlyPurgeableResourceForValidation == fNonpurgeableResources[i]);
bsalomonf320e042015-02-17 15:09:34 -0800680 SkASSERT(*fNonpurgeableResources[i]->cacheAccess().accessCacheIndex() == i);
681 SkASSERT(!fNonpurgeableResources[i]->wasDestroyed());
682 stats.update(fNonpurgeableResources[i]);
bsalomon71cb0c22014-11-14 12:10:14 -0800683 }
bsalomon9f2d1572015-02-17 11:47:40 -0800684 for (int i = 0; i < fPurgeableQueue.count(); ++i) {
685 SkASSERT(fPurgeableQueue.at(i)->isPurgeable());
bsalomonf320e042015-02-17 15:09:34 -0800686 SkASSERT(*fPurgeableQueue.at(i)->cacheAccess().accessCacheIndex() == i);
687 SkASSERT(!fPurgeableQueue.at(i)->wasDestroyed());
688 stats.update(fPurgeableQueue.at(i));
bsalomon9f2d1572015-02-17 11:47:40 -0800689 }
690
bsalomonf320e042015-02-17 15:09:34 -0800691 SkASSERT(fCount == this->getResourceCount());
bsalomondace19e2014-11-17 07:34:06 -0800692 SkASSERT(fBudgetedCount <= fCount);
bsalomonf320e042015-02-17 15:09:34 -0800693 SkASSERT(fBudgetedBytes <= fBytes);
694 SkASSERT(stats.fBytes == fBytes);
695 SkASSERT(stats.fBudgetedBytes == fBudgetedBytes);
696 SkASSERT(stats.fBudgetedCount == fBudgetedCount);
bsalomon71cb0c22014-11-14 12:10:14 -0800697#if GR_CACHE_STATS
bsalomondace19e2014-11-17 07:34:06 -0800698 SkASSERT(fBudgetedHighWaterCount <= fHighWaterCount);
699 SkASSERT(fBudgetedHighWaterBytes <= fHighWaterBytes);
bsalomonf320e042015-02-17 15:09:34 -0800700 SkASSERT(fBytes <= fHighWaterBytes);
701 SkASSERT(fCount <= fHighWaterCount);
702 SkASSERT(fBudgetedBytes <= fBudgetedHighWaterBytes);
703 SkASSERT(fBudgetedCount <= fBudgetedHighWaterCount);
bsalomon71cb0c22014-11-14 12:10:14 -0800704#endif
bsalomon8718aaf2015-02-19 07:24:21 -0800705 SkASSERT(stats.fContent == fUniqueHash.count());
bsalomonf320e042015-02-17 15:09:34 -0800706 SkASSERT(stats.fScratch + stats.fCouldBeScratch == fScratchMap.count());
bsalomon71cb0c22014-11-14 12:10:14 -0800707
bsalomon3f324322015-04-08 11:01:54 -0700708 // This assertion is not currently valid because we can be in recursive notifyCntReachedZero()
bsalomon12299ab2014-11-14 13:33:09 -0800709 // calls. This will be fixed when subresource registration is explicit.
bsalomondace19e2014-11-17 07:34:06 -0800710 // bool overBudget = budgetedBytes > fMaxBytes || budgetedCount > fMaxCount;
bsalomon12299ab2014-11-14 13:33:09 -0800711 // SkASSERT(!overBudget || locked == count || fPurging);
bsalomon71cb0c22014-11-14 12:10:14 -0800712}
bsalomonf320e042015-02-17 15:09:34 -0800713
714bool GrResourceCache::isInCache(const GrGpuResource* resource) const {
715 int index = *resource->cacheAccess().accessCacheIndex();
716 if (index < 0) {
717 return false;
718 }
719 if (index < fPurgeableQueue.count() && fPurgeableQueue.at(index) == resource) {
720 return true;
721 }
722 if (index < fNonpurgeableResources.count() && fNonpurgeableResources[index] == resource) {
723 return true;
724 }
725 SkDEBUGFAIL("Resource index should be -1 or the resource should be in the cache.");
726 return false;
727}
728
bsalomon71cb0c22014-11-14 12:10:14 -0800729#endif