|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "mozilla/gfx/2D.h" |
|
7 #include "nsTArray.h" |
|
8 #include "pldhash.h" |
|
9 #include "nsExpirationTracker.h" |
|
10 #include "nsClassHashtable.h" |
|
11 #include "mozilla/Telemetry.h" |
|
12 #include "gfxGradientCache.h" |
|
13 #include <time.h> |
|
14 |
|
15 namespace mozilla { |
|
16 namespace gfx { |
|
17 |
|
18 using namespace mozilla; |
|
19 |
|
20 struct GradientCacheKey : public PLDHashEntryHdr { |
|
21 typedef const GradientCacheKey& KeyType; |
|
22 typedef const GradientCacheKey* KeyTypePointer; |
|
23 enum { ALLOW_MEMMOVE = true }; |
|
24 const nsTArray<GradientStop> mStops; |
|
25 ExtendMode mExtend; |
|
26 BackendType mBackendType; |
|
27 |
|
28 GradientCacheKey(const nsTArray<GradientStop>& aStops, ExtendMode aExtend, BackendType aBackendType) |
|
29 : mStops(aStops), mExtend(aExtend), mBackendType(aBackendType) |
|
30 { } |
|
31 |
|
32 GradientCacheKey(const GradientCacheKey* aOther) |
|
33 : mStops(aOther->mStops), mExtend(aOther->mExtend), mBackendType(aOther->mBackendType) |
|
34 { } |
|
35 |
|
36 union FloatUint32 |
|
37 { |
|
38 float f; |
|
39 uint32_t u; |
|
40 }; |
|
41 |
|
42 static PLDHashNumber |
|
43 HashKey(const KeyTypePointer aKey) |
|
44 { |
|
45 PLDHashNumber hash = 0; |
|
46 FloatUint32 convert; |
|
47 hash = AddToHash(hash, int(aKey->mBackendType)); |
|
48 hash = AddToHash(hash, int(aKey->mExtend)); |
|
49 for (uint32_t i = 0; i < aKey->mStops.Length(); i++) { |
|
50 hash = AddToHash(hash, aKey->mStops[i].color.ToABGR()); |
|
51 // Use the float bits as hash, except for the cases of 0.0 and -0.0 which both map to 0 |
|
52 convert.f = aKey->mStops[i].offset; |
|
53 hash = AddToHash(hash, convert.f ? convert.u : 0); |
|
54 } |
|
55 return hash; |
|
56 } |
|
57 |
|
58 bool KeyEquals(KeyTypePointer aKey) const |
|
59 { |
|
60 bool sameStops = true; |
|
61 if (aKey->mStops.Length() != mStops.Length()) { |
|
62 sameStops = false; |
|
63 } else { |
|
64 for (uint32_t i = 0; i < mStops.Length(); i++) { |
|
65 if (mStops[i].color.ToABGR() != aKey->mStops[i].color.ToABGR() || |
|
66 mStops[i].offset != aKey->mStops[i].offset) { |
|
67 sameStops = false; |
|
68 break; |
|
69 } |
|
70 } |
|
71 } |
|
72 |
|
73 return sameStops && |
|
74 (aKey->mBackendType == mBackendType) && |
|
75 (aKey->mExtend == mExtend); |
|
76 } |
|
77 static KeyTypePointer KeyToPointer(KeyType aKey) |
|
78 { |
|
79 return &aKey; |
|
80 } |
|
81 }; |
|
82 |
|
83 /** |
|
84 * This class is what is cached. It need to be allocated in an object separated |
|
85 * to the cache entry to be able to be tracked by the nsExpirationTracker. |
|
86 * */ |
|
87 struct GradientCacheData { |
|
88 GradientCacheData(GradientStops* aStops, const GradientCacheKey& aKey) |
|
89 : mStops(aStops), |
|
90 mKey(aKey) |
|
91 {} |
|
92 |
|
93 GradientCacheData(const GradientCacheData& aOther) |
|
94 : mStops(aOther.mStops), |
|
95 mKey(aOther.mKey) |
|
96 { } |
|
97 |
|
98 nsExpirationState *GetExpirationState() { |
|
99 return &mExpirationState; |
|
100 } |
|
101 |
|
102 nsExpirationState mExpirationState; |
|
103 const RefPtr<GradientStops> mStops; |
|
104 GradientCacheKey mKey; |
|
105 }; |
|
106 |
|
107 /** |
|
108 * This class implements a cache with no maximum size, that retains the |
|
109 * gfxPatterns used to draw the gradients. |
|
110 * |
|
111 * The key is the nsStyleGradient that defines the gradient, and the size of the |
|
112 * gradient. |
|
113 * |
|
114 * The value is the gfxPattern, and whether or not we perform an optimization |
|
115 * based on the actual gradient property. |
|
116 * |
|
117 * An entry stays in the cache as long as it is used often. As long as a cache |
|
118 * entry is in the cache, all the references it has are guaranteed to be valid: |
|
119 * the nsStyleRect for the key, the gfxPattern for the value. |
|
120 */ |
|
121 class GradientCache MOZ_FINAL : public nsExpirationTracker<GradientCacheData,4> |
|
122 { |
|
123 public: |
|
124 GradientCache() |
|
125 : nsExpirationTracker<GradientCacheData, 4>(MAX_GENERATION_MS) |
|
126 { |
|
127 srand(time(nullptr)); |
|
128 mTimerPeriod = rand() % MAX_GENERATION_MS + 1; |
|
129 Telemetry::Accumulate(Telemetry::GRADIENT_RETENTION_TIME, mTimerPeriod); |
|
130 } |
|
131 |
|
132 virtual void NotifyExpired(GradientCacheData* aObject) |
|
133 { |
|
134 // This will free the gfxPattern. |
|
135 RemoveObject(aObject); |
|
136 mHashEntries.Remove(aObject->mKey); |
|
137 } |
|
138 |
|
139 GradientCacheData* Lookup(const nsTArray<GradientStop>& aStops, ExtendMode aExtend, BackendType aBackendType) |
|
140 { |
|
141 GradientCacheData* gradient = |
|
142 mHashEntries.Get(GradientCacheKey(aStops, aExtend, aBackendType)); |
|
143 |
|
144 if (gradient) { |
|
145 MarkUsed(gradient); |
|
146 } |
|
147 |
|
148 return gradient; |
|
149 } |
|
150 |
|
151 // Returns true if we successfully register the gradient in the cache, false |
|
152 // otherwise. |
|
153 bool RegisterEntry(GradientCacheData* aValue) |
|
154 { |
|
155 nsresult rv = AddObject(aValue); |
|
156 if (NS_FAILED(rv)) { |
|
157 // We are OOM, and we cannot track this object. We don't want stall |
|
158 // entries in the hash table (since the expiration tracker is responsible |
|
159 // for removing the cache entries), so we avoid putting that entry in the |
|
160 // table, which is a good things considering we are short on memory |
|
161 // anyway, we probably don't want to retain things. |
|
162 return false; |
|
163 } |
|
164 mHashEntries.Put(aValue->mKey, aValue); |
|
165 return true; |
|
166 } |
|
167 |
|
168 protected: |
|
169 uint32_t mTimerPeriod; |
|
170 static const uint32_t MAX_GENERATION_MS = 10000; |
|
171 /** |
|
172 * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey. |
|
173 * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47 |
|
174 */ |
|
175 nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries; |
|
176 }; |
|
177 |
|
178 static GradientCache* gGradientCache = nullptr; |
|
179 |
|
180 GradientStops * |
|
181 gfxGradientCache::GetGradientStops(DrawTarget *aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) |
|
182 { |
|
183 if (!gGradientCache) { |
|
184 gGradientCache = new GradientCache(); |
|
185 } |
|
186 GradientCacheData* cached = gGradientCache->Lookup(aStops, aExtend, aDT->GetType()); |
|
187 return cached ? cached->mStops : nullptr; |
|
188 } |
|
189 |
|
190 GradientStops * |
|
191 gfxGradientCache::GetOrCreateGradientStops(DrawTarget *aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) |
|
192 { |
|
193 RefPtr<GradientStops> gs = GetGradientStops(aDT, aStops, aExtend); |
|
194 if (!gs) { |
|
195 gs = aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend); |
|
196 if (!gs) { |
|
197 return nullptr; |
|
198 } |
|
199 GradientCacheData *cached = new GradientCacheData(gs, GradientCacheKey(aStops, aExtend, aDT->GetType())); |
|
200 if (!gGradientCache->RegisterEntry(cached)) { |
|
201 delete cached; |
|
202 } |
|
203 } |
|
204 return gs; |
|
205 } |
|
206 |
|
207 void |
|
208 gfxGradientCache::Shutdown() |
|
209 { |
|
210 delete gGradientCache; |
|
211 gGradientCache = nullptr; |
|
212 } |
|
213 |
|
214 } |
|
215 } |