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