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