[iOS WebKit2] IOSurfacePool should force CA to actually garbage collect surfaces.
[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 "IOSurface.h"
33 #include <CoreGraphics/CoreGraphics.h>
34 #include <chrono>
35 #include <wtf/NeverDestroyed.h>
36
37 const std::chrono::milliseconds collectionInterval = 500_ms;
38 const std::chrono::seconds surfaceAgeBeforeMarkingPurgeable = 2_s;
39 const size_t defaultMaximumBytesCached = 1024 * 1024 * 64;
40
41 // We'll never allow more than 1/2 of the cache to be filled with in-use surfaces, because
42 // they can't be immediately returned when requested (but will be freed up in the future).
43 const size_t maximumInUseBytes = defaultMaximumBytesCached / 2;
44
45 #define ENABLE_IOSURFACE_POOL_STATISTICS false
46 #if ENABLE_IOSURFACE_POOL_STATISTICS
47 #define DUMP_POOL_STATISTICS() do { showPoolStatistics(); } while (0);
48 #else
49 #define DUMP_POOL_STATISTICS() ((void)0)
50 #endif
51
52 namespace WebCore {
53
54 IOSurfacePool::IOSurfacePool()
55     : m_collectionTimer(this, &IOSurfacePool::collectionTimerFired)
56     , m_bytesCached(0)
57     , m_inUseBytesCached(0)
58     , m_maximumBytesCached(defaultMaximumBytesCached)
59 {
60 }
61
62 IOSurfacePool& IOSurfacePool::sharedPool()
63 {
64     static NeverDestroyed<IOSurfacePool> pool;
65     return pool;
66 }
67
68 static bool surfaceMatchesParameters(IOSurface& surface, const IntSize& requestedSize, ColorSpace colorSpace)
69 {
70     IntSize surfaceSize = surface.size();
71     if (colorSpace != surface.colorSpace())
72         return false;
73     if (surfaceSize != requestedSize)
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->clearGraphicsContext();
84
85     size_t surfaceBytes = surface->totalBytes();
86     m_bytesCached += surfaceBytes;
87     if (inUse)
88         m_inUseBytesCached += surfaceBytes;
89     evict(surface->totalBytes());
90 }
91
92 void IOSurfacePool::didRemoveSurface(IOSurface* surface, bool inUse)
93 {
94     size_t surfaceBytes = surface->totalBytes();
95     m_bytesCached -= surfaceBytes;
96     if (inUse)
97         m_inUseBytesCached -= surfaceBytes;
98
99     m_surfaceDetails.remove(surface);
100 }
101
102 void IOSurfacePool::didUseSurfaceOfSize(IntSize size)
103 {
104     m_sizesInPruneOrder.remove(m_sizesInPruneOrder.reverseFind(size));
105     m_sizesInPruneOrder.append(size);
106 }
107
108 PassRefPtr<IOSurface> IOSurfacePool::takeSurface(IntSize size, ColorSpace colorSpace)
109 {
110     CachedSurfaceMap::iterator mapIter = m_cachedSurfaces.find(size);
111
112     if (mapIter == m_cachedSurfaces.end()) {
113         DUMP_POOL_STATISTICS();
114         return nullptr;
115     }
116
117     for (auto surfaceIter = mapIter->value.begin(); surfaceIter != mapIter->value.end(); ++surfaceIter) {
118         if (!surfaceMatchesParameters(*surfaceIter->get(), size, colorSpace))
119             continue;
120         
121         RefPtr<IOSurface> surface = surfaceIter->get();
122         mapIter->value.remove(surfaceIter);
123
124         didUseSurfaceOfSize(size);
125
126         if (mapIter->value.isEmpty()) {
127             m_cachedSurfaces.remove(mapIter);
128             m_sizesInPruneOrder.removeLast();
129         }
130
131         didRemoveSurface(surface.get(), false);
132
133         surface->setIsVolatile(false);
134
135         DUMP_POOL_STATISTICS();
136         return surface.release();
137     }
138
139     // Some of the in-use surfaces may no longer actually be in-use, but we haven't moved them over yet.
140     for (auto surfaceIter = m_inUseSurfaces.begin(); surfaceIter != m_inUseSurfaces.end(); ++surfaceIter) {
141         if (!surfaceMatchesParameters(*surfaceIter->get(), size, colorSpace))
142             continue;
143         if (surfaceIter->get()->isInUse())
144             continue;
145         
146         RefPtr<IOSurface> surface = surfaceIter->get();
147         m_inUseSurfaces.remove(surfaceIter);
148         didRemoveSurface(surface.get(), true);
149
150         surface->setIsVolatile(false);
151
152         DUMP_POOL_STATISTICS();
153         return surface.release();
154     }
155
156     DUMP_POOL_STATISTICS();
157     return nullptr;
158 }
159
160 void IOSurfacePool::addSurface(IOSurface* surface)
161 {
162     if (surface->totalBytes() > m_maximumBytesCached)
163         return;
164
165     bool surfaceIsInUse = surface->isInUse();
166
167     willAddSurface(surface, surfaceIsInUse);
168
169     if (surfaceIsInUse) {
170         m_inUseSurfaces.prepend(surface);
171         scheduleCollectionTimer();
172         DUMP_POOL_STATISTICS();
173         return;
174     }
175
176     insertSurfaceIntoPool(surface);
177     DUMP_POOL_STATISTICS();
178 }
179
180 void IOSurfacePool::insertSurfaceIntoPool(IOSurface* surface)
181 {
182     auto insertedTuple = m_cachedSurfaces.add(surface->size(), CachedSurfaceQueue());
183     insertedTuple.iterator->value.prepend(surface);
184     if (!insertedTuple.isNewEntry)
185         m_sizesInPruneOrder.remove(m_sizesInPruneOrder.reverseFind(surface->size()));
186     m_sizesInPruneOrder.append(surface->size());
187
188     scheduleCollectionTimer();
189 }
190
191 void IOSurfacePool::setPoolSize(size_t poolSizeInBytes)
192 {
193     m_maximumBytesCached = poolSizeInBytes;
194     evict(0);
195 }
196
197 void IOSurfacePool::tryEvictInUseSurface()
198 {
199     if (m_inUseSurfaces.isEmpty())
200         return;
201
202     RefPtr<IOSurface> surface = m_inUseSurfaces.takeLast();
203     didRemoveSurface(surface.get(), true);
204 }
205
206 void IOSurfacePool::tryEvictOldestCachedSurface()
207 {
208     if (m_cachedSurfaces.isEmpty())
209         return;
210
211     if (m_sizesInPruneOrder.isEmpty())
212         return;
213
214     CachedSurfaceMap::iterator surfaceQueueIter = m_cachedSurfaces.find(m_sizesInPruneOrder.first());
215     ASSERT(!surfaceQueueIter->value.isEmpty());
216     RefPtr<IOSurface> surface = surfaceQueueIter->value.takeLast();
217     didRemoveSurface(surface.get(), false);
218
219     if (surfaceQueueIter->value.isEmpty()) {
220         m_cachedSurfaces.remove(surfaceQueueIter);
221         m_sizesInPruneOrder.remove(0);
222     }
223 }
224
225 void IOSurfacePool::evict(size_t additionalSize)
226 {
227     // FIXME: Perhaps purgeable surfaces should count less against the cap?
228     // We don't want to end up with a ton of empty (purged) surfaces, though, as that would defeat the purpose of the pool.
229     size_t targetSize = m_maximumBytesCached - additionalSize;
230
231     // Interleave eviction of old cached surfaces and more recent in-use surfaces.
232     // In-use surfaces are more recently used, but less useful in the pool, as they aren't
233     // immediately available when requested.
234     while (m_bytesCached > targetSize) {
235         tryEvictOldestCachedSurface();
236
237         if (m_inUseBytesCached > maximumInUseBytes)
238             tryEvictInUseSurface();
239     }
240
241     while (m_inUseBytesCached > maximumInUseBytes)
242         tryEvictInUseSurface();
243 }
244
245 void IOSurfacePool::collectInUseSurfaces()
246 {
247     CachedSurfaceQueue newInUseSurfaces;
248     for (CachedSurfaceQueue::iterator surfaceIter = m_inUseSurfaces.begin(); surfaceIter != m_inUseSurfaces.end(); ++surfaceIter) {
249         IOSurface* surface = surfaceIter->get();
250         if (surface->isInUse()) {
251             newInUseSurfaces.append(*surfaceIter);
252             continue;
253         }
254
255         m_inUseBytesCached -= surface->totalBytes();
256         insertSurfaceIntoPool(surface);
257     }
258
259     m_inUseSurfaces = newInUseSurfaces;
260 }
261
262 bool IOSurfacePool::markOlderSurfacesPurgeable()
263 {
264     bool markedAllSurfaces = true;
265     auto markTime = std::chrono::steady_clock::now();
266
267     for (auto& surfaceAndDetails : m_surfaceDetails) {
268         if (surfaceAndDetails.value.hasMarkedPurgeable)
269             continue;
270
271         if (markTime - surfaceAndDetails.value.lastUseTime < surfaceAgeBeforeMarkingPurgeable) {
272             markedAllSurfaces = false;
273             continue;
274         }
275
276         surfaceAndDetails.key->setIsVolatile(true);
277         surfaceAndDetails.value.hasMarkedPurgeable = true;
278     }
279
280     return markedAllSurfaces;
281 }
282
283 void IOSurfacePool::collectionTimerFired(Timer<IOSurfacePool>&)
284 {
285     collectInUseSurfaces();
286     bool markedAllSurfaces = markOlderSurfacesPurgeable();
287
288     if (!m_inUseSurfaces.size() && markedAllSurfaces)
289         m_collectionTimer.stop();
290
291     platformGarbageCollectNow();
292     DUMP_POOL_STATISTICS();
293 }
294
295 void IOSurfacePool::scheduleCollectionTimer()
296 {
297     if (!m_collectionTimer.isActive())
298         m_collectionTimer.startRepeating(collectionInterval);
299 }
300
301 void IOSurfacePool::discardAllSurfaces()
302 {
303     m_bytesCached = 0;
304     m_inUseBytesCached = 0;
305     m_surfaceDetails.clear();
306     m_cachedSurfaces.clear();
307     m_inUseSurfaces.clear();
308     m_sizesInPruneOrder.clear();
309     m_collectionTimer.stop();
310     platformGarbageCollectNow();
311 }
312
313 void IOSurfacePool::showPoolStatistics()
314 {
315 #if ENABLE_IOSURFACE_POOL_STATISTICS
316     WTFLogAlways("IOSurfacePool Statistics\n");
317     unsigned totalSurfaces = 0;
318     size_t totalSize = 0;
319     size_t totalPurgeableSize = 0;
320
321     for (const auto& keyAndSurfaces : m_cachedSurfaces) {
322         ASSERT(!keyAndSurfaces.value.isEmpty());
323         size_t queueSize = 0;
324         size_t queuePurgeableSize = 0;
325         for (const auto& surface : keyAndSurfaces.value) {
326             size_t surfaceBytes = surface->totalBytes();
327
328             totalSurfaces++;
329             queueSize += surfaceBytes;
330
331             if (surface->isVolatile())
332                 queuePurgeableSize += surfaceBytes;
333         }
334
335         totalSize += queueSize;
336         totalPurgeableSize += queuePurgeableSize;
337
338         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);
339     }
340
341     size_t inUseSize = 0;
342     for (const auto& surface : m_inUseSurfaces) {
343         totalSurfaces++;
344         inUseSize += surface->totalBytes();
345     }
346
347     totalSize += inUseSize;
348     WTFLogAlways("   IN USE: %zu surfaces for %zd KB", m_inUseSurfaces.size(), inUseSize / 1024);
349
350     // FIXME: Should move consistency checks elsewhere, and always perform them in debug builds.
351     ASSERT(m_bytesCached == totalSize);
352     ASSERT(m_bytesCached <= m_maximumBytesCached);
353
354     WTFLogAlways("   TOTAL: %d surfaces for %zd KB (%zd KB purgeable)\n", totalSurfaces, totalSize / 1024, totalPurgeableSize / 1024);
355 #endif
356 }
357
358 }
359 #endif // USE(IOSURFACE)