Allow more buffer formats in the IOSurface pool
[WebKit-https.git] / Source / WebCore / platform / graphics / cg / IOSurfacePool.cpp
1 /*
2  * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "IOSurfacePool.h"
28
29 #if USE(IOSURFACE)
30
31 #include "GraphicsContextCG.h"
32 #include <CoreGraphics/CoreGraphics.h>
33 #include <chrono>
34 #include <wtf/NeverDestroyed.h>
35
36 const std::chrono::milliseconds collectionInterval = 500_ms;
37 const std::chrono::seconds surfaceAgeBeforeMarkingPurgeable = 2_s;
38 const size_t defaultMaximumBytesCached = 1024 * 1024 * 64;
39
40 // We'll never allow more than 1/2 of the cache to be filled with in-use surfaces, because
41 // they can't be immediately returned when requested (but will be freed up in the future).
42 const size_t maximumInUseBytes = defaultMaximumBytesCached / 2;
43
44 #define ENABLE_IOSURFACE_POOL_STATISTICS false
45 #if ENABLE_IOSURFACE_POOL_STATISTICS
46 #define DUMP_POOL_STATISTICS() do { showPoolStatistics(); } while (0);
47 #else
48 #define DUMP_POOL_STATISTICS() ((void)0)
49 #endif
50
51 namespace WebCore {
52
53 IOSurfacePool::IOSurfacePool()
54     : m_collectionTimer(*this, &IOSurfacePool::collectionTimerFired)
55     , m_bytesCached(0)
56     , m_inUseBytesCached(0)
57     , m_maximumBytesCached(defaultMaximumBytesCached)
58 {
59 }
60
61 IOSurfacePool& IOSurfacePool::sharedPool()
62 {
63     static NeverDestroyed<IOSurfacePool> pool;
64     return pool;
65 }
66
67 static bool surfaceMatchesParameters(IOSurface& surface, IntSize requestedSize, ColorSpace colorSpace, IOSurface::Format format)
68 {
69     if (format != surface.format())
70         return false;
71     if (colorSpace != surface.colorSpace())
72         return false;
73     if (requestedSize != surface.size())
74         return false;
75     return true;
76 }
77
78 void IOSurfacePool::willAddSurface(IOSurface& surface, bool inUse)
79 {
80     CachedSurfaceDetails& details = m_surfaceDetails.add(&surface, CachedSurfaceDetails()).iterator->value;
81     details.resetLastUseTime();
82
83     surface.releaseGraphicsContext();
84
85     size_t surfaceBytes = surface.totalBytes();
86
87     evict(surfaceBytes);
88
89     m_bytesCached += surfaceBytes;
90     if (inUse)
91         m_inUseBytesCached += surfaceBytes;
92 }
93
94 void IOSurfacePool::didRemoveSurface(IOSurface& surface, bool inUse)
95 {
96     size_t surfaceBytes = surface.totalBytes();
97     m_bytesCached -= surfaceBytes;
98     if (inUse)
99         m_inUseBytesCached -= surfaceBytes;
100
101     m_surfaceDetails.remove(&surface);
102 }
103
104 void IOSurfacePool::didUseSurfaceOfSize(IntSize size)
105 {
106     m_sizesInPruneOrder.remove(m_sizesInPruneOrder.reverseFind(size));
107     m_sizesInPruneOrder.append(size);
108 }
109
110 std::unique_ptr<IOSurface> IOSurfacePool::takeSurface(IntSize size, ColorSpace colorSpace, IOSurface::Format format)
111 {
112     CachedSurfaceMap::iterator mapIter = m_cachedSurfaces.find(size);
113
114     if (mapIter == m_cachedSurfaces.end()) {
115         DUMP_POOL_STATISTICS();
116         return nullptr;
117     }
118
119     for (auto surfaceIter = mapIter->value.begin(); surfaceIter != mapIter->value.end(); ++surfaceIter) {
120         if (!surfaceMatchesParameters(*surfaceIter->get(), size, colorSpace, format))
121             continue;
122
123         auto surface = WTF::move(*surfaceIter);
124         mapIter->value.remove(surfaceIter);
125
126         didUseSurfaceOfSize(size);
127
128         if (mapIter->value.isEmpty()) {
129             m_cachedSurfaces.remove(mapIter);
130             m_sizesInPruneOrder.removeLast();
131         }
132
133         didRemoveSurface(*surface, false);
134
135         surface->setIsVolatile(false);
136
137         DUMP_POOL_STATISTICS();
138         return surface;
139     }
140
141     // Some of the in-use surfaces may no longer actually be in-use, but we haven't moved them over yet.
142     for (auto surfaceIter = m_inUseSurfaces.begin(); surfaceIter != m_inUseSurfaces.end(); ++surfaceIter) {
143         if (!surfaceMatchesParameters(*surfaceIter->get(), size, colorSpace, format))
144             continue;
145         if (surfaceIter->get()->isInUse())
146             continue;
147         
148         auto surface = WTF::move(*surfaceIter);
149         m_inUseSurfaces.remove(surfaceIter);
150         didRemoveSurface(*surface, true);
151
152         surface->setIsVolatile(false);
153
154         DUMP_POOL_STATISTICS();
155         return surface;
156     }
157
158     DUMP_POOL_STATISTICS();
159     return nullptr;
160 }
161
162 bool IOSurfacePool::shouldCacheSurface(const IOSurface& surface) const
163 {
164     if (surface.totalBytes() > m_maximumBytesCached)
165         return false;
166
167     // There's no reason to pool empty surfaces; we should never allocate them in the first place.
168     // This also covers isZero(), which would cause trouble when used as the key in m_cachedSurfaces.
169     if (surface.size().isEmpty())
170         return false;
171
172     return true;
173 }
174
175 void IOSurfacePool::addSurface(std::unique_ptr<IOSurface> surface)
176 {
177     if (!shouldCacheSurface(*surface))
178         return;
179
180     bool surfaceIsInUse = surface->isInUse();
181
182     willAddSurface(*surface, surfaceIsInUse);
183
184     if (surfaceIsInUse) {
185         m_inUseSurfaces.prepend(WTF::move(surface));
186         scheduleCollectionTimer();
187         DUMP_POOL_STATISTICS();
188         return;
189     }
190
191     insertSurfaceIntoPool(WTF::move(surface));
192     DUMP_POOL_STATISTICS();
193 }
194
195 void IOSurfacePool::insertSurfaceIntoPool(std::unique_ptr<IOSurface> surface)
196 {
197     IntSize surfaceSize = surface->size();
198     auto insertedTuple = m_cachedSurfaces.add(surfaceSize, CachedSurfaceQueue());
199     insertedTuple.iterator->value.prepend(WTF::move(surface));
200     if (!insertedTuple.isNewEntry)
201         m_sizesInPruneOrder.remove(m_sizesInPruneOrder.reverseFind(surfaceSize));
202     m_sizesInPruneOrder.append(surfaceSize);
203
204     scheduleCollectionTimer();
205 }
206
207 void IOSurfacePool::setPoolSize(size_t poolSizeInBytes)
208 {
209     m_maximumBytesCached = poolSizeInBytes;
210     evict(0);
211 }
212
213 void IOSurfacePool::tryEvictInUseSurface()
214 {
215     if (m_inUseSurfaces.isEmpty())
216         return;
217
218     auto surface = m_inUseSurfaces.takeLast();
219     didRemoveSurface(*surface, true);
220 }
221
222 void IOSurfacePool::tryEvictOldestCachedSurface()
223 {
224     if (m_cachedSurfaces.isEmpty())
225         return;
226
227     if (m_sizesInPruneOrder.isEmpty())
228         return;
229
230     CachedSurfaceMap::iterator surfaceQueueIter = m_cachedSurfaces.find(m_sizesInPruneOrder.first());
231     ASSERT(!surfaceQueueIter->value.isEmpty());
232     auto surface = surfaceQueueIter->value.takeLast();
233     didRemoveSurface(*surface, false);
234
235     if (surfaceQueueIter->value.isEmpty()) {
236         m_cachedSurfaces.remove(surfaceQueueIter);
237         m_sizesInPruneOrder.remove(0);
238     }
239 }
240
241 void IOSurfacePool::evict(size_t additionalSize)
242 {
243     if (additionalSize >= m_maximumBytesCached) {
244         discardAllSurfaces();
245         return;
246     }
247
248     // FIXME: Perhaps purgeable surfaces should count less against the cap?
249     // We don't want to end up with a ton of empty (purged) surfaces, though, as that would defeat the purpose of the pool.
250     size_t targetSize = m_maximumBytesCached - additionalSize;
251
252     // Interleave eviction of old cached surfaces and more recent in-use surfaces.
253     // In-use surfaces are more recently used, but less useful in the pool, as they aren't
254     // immediately available when requested.
255     while (m_bytesCached > targetSize) {
256         tryEvictOldestCachedSurface();
257
258         if (m_inUseBytesCached > maximumInUseBytes || m_bytesCached > targetSize)
259             tryEvictInUseSurface();
260     }
261
262     while (m_inUseBytesCached > maximumInUseBytes || m_bytesCached > targetSize)
263         tryEvictInUseSurface();
264 }
265
266 void IOSurfacePool::collectInUseSurfaces()
267 {
268     CachedSurfaceQueue newInUseSurfaces;
269     for (CachedSurfaceQueue::iterator surfaceIter = m_inUseSurfaces.begin(); surfaceIter != m_inUseSurfaces.end(); ++surfaceIter) {
270         IOSurface* surface = surfaceIter->get();
271         if (surface->isInUse()) {
272             newInUseSurfaces.append(WTF::move(*surfaceIter));
273             continue;
274         }
275
276         m_inUseBytesCached -= surface->totalBytes();
277         insertSurfaceIntoPool(WTF::move(*surfaceIter));
278     }
279
280     m_inUseSurfaces = WTF::move(newInUseSurfaces);
281 }
282
283 bool IOSurfacePool::markOlderSurfacesPurgeable()
284 {
285     bool markedAllSurfaces = true;
286     auto markTime = std::chrono::steady_clock::now();
287
288     for (auto& surfaceAndDetails : m_surfaceDetails) {
289         if (surfaceAndDetails.value.hasMarkedPurgeable)
290             continue;
291
292         if (markTime - surfaceAndDetails.value.lastUseTime < surfaceAgeBeforeMarkingPurgeable) {
293             markedAllSurfaces = false;
294             continue;
295         }
296
297         surfaceAndDetails.key->setIsVolatile(true);
298         surfaceAndDetails.value.hasMarkedPurgeable = true;
299     }
300
301     return markedAllSurfaces;
302 }
303
304 void IOSurfacePool::collectionTimerFired()
305 {
306     collectInUseSurfaces();
307     bool markedAllSurfaces = markOlderSurfacesPurgeable();
308
309     if (!m_inUseSurfaces.size() && markedAllSurfaces)
310         m_collectionTimer.stop();
311
312     platformGarbageCollectNow();
313     DUMP_POOL_STATISTICS();
314 }
315
316 void IOSurfacePool::scheduleCollectionTimer()
317 {
318     if (!m_collectionTimer.isActive())
319         m_collectionTimer.startRepeating(collectionInterval);
320 }
321
322 void IOSurfacePool::discardAllSurfaces()
323 {
324     m_bytesCached = 0;
325     m_inUseBytesCached = 0;
326     m_surfaceDetails.clear();
327     m_cachedSurfaces.clear();
328     m_inUseSurfaces.clear();
329     m_sizesInPruneOrder.clear();
330     m_collectionTimer.stop();
331     platformGarbageCollectNow();
332 }
333
334 void IOSurfacePool::showPoolStatistics()
335 {
336 #if ENABLE_IOSURFACE_POOL_STATISTICS
337     WTFLogAlways("IOSurfacePool Statistics\n");
338     unsigned totalSurfaces = 0;
339     size_t totalSize = 0;
340     size_t totalPurgeableSize = 0;
341
342     for (const auto& keyAndSurfaces : m_cachedSurfaces) {
343         ASSERT(!keyAndSurfaces.value.isEmpty());
344         size_t queueSize = 0;
345         size_t queuePurgeableSize = 0;
346         for (const auto& surface : keyAndSurfaces.value) {
347             size_t surfaceBytes = surface->totalBytes();
348
349             totalSurfaces++;
350             queueSize += surfaceBytes;
351
352             if (surface->isVolatile())
353                 queuePurgeableSize += surfaceBytes;
354         }
355
356         totalSize += queueSize;
357         totalPurgeableSize += queuePurgeableSize;
358
359         WTFLogAlways("   %d x %d: %zu surfaces for %zd KB (%zd KB purgeable)", keyAndSurfaces.key.width(), keyAndSurfaces.key.height(), keyAndSurfaces.value.size(), queueSize / 1024, queuePurgeableSize / 1024);
360     }
361
362     size_t inUseSize = 0;
363     for (const auto& surface : m_inUseSurfaces) {
364         totalSurfaces++;
365         inUseSize += surface->totalBytes();
366     }
367
368     totalSize += inUseSize;
369     WTFLogAlways("   IN USE: %zu surfaces for %zd KB", m_inUseSurfaces.size(), inUseSize / 1024);
370
371     // FIXME: Should move consistency checks elsewhere, and always perform them in debug builds.
372     ASSERT(m_bytesCached == totalSize);
373     ASSERT(m_bytesCached <= m_maximumBytesCached);
374
375     WTFLogAlways("   TOTAL: %d surfaces for %zd KB (%zd KB purgeable)\n", totalSurfaces, totalSize / 1024, totalPurgeableSize / 1024);
376 #endif
377 }
378
379 }
380 #endif // USE(IOSURFACE)