2 * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "IOSurfacePool.h"
31 #include "GraphicsContextCG.h"
32 #include "IOSurface.h"
33 #include <CoreGraphics/CoreGraphics.h>
35 #include <wtf/NeverDestroyed.h>
37 const std::chrono::milliseconds collectionInterval = 500_ms;
38 const std::chrono::seconds surfaceAgeBeforeMarkingPurgeable = 2_s;
39 const size_t defaultMaximumBytesCached = 1024 * 1024 * 64;
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;
45 #define ENABLE_IOSURFACE_POOL_STATISTICS false
46 #if ENABLE_IOSURFACE_POOL_STATISTICS
47 #define DUMP_POOL_STATISTICS() do { showPoolStatistics(); } while (0);
49 #define DUMP_POOL_STATISTICS() ((void)0)
54 IOSurfacePool::IOSurfacePool()
55 : m_collectionTimer(this, &IOSurfacePool::collectionTimerFired)
57 , m_inUseBytesCached(0)
58 , m_maximumBytesCached(defaultMaximumBytesCached)
62 IOSurfacePool& IOSurfacePool::sharedPool()
64 static NeverDestroyed<IOSurfacePool> pool;
68 static bool surfaceMatchesParameters(IOSurface& surface, const IntSize& requestedSize, ColorSpace colorSpace)
70 IntSize surfaceSize = surface.size();
71 if (colorSpace != surface.colorSpace())
73 if (surfaceSize != requestedSize)
78 void IOSurfacePool::willAddSurface(IOSurface* surface, bool inUse)
80 CachedSurfaceDetails& details = m_surfaceDetails.add(surface, CachedSurfaceDetails()).iterator->value;
81 details.resetLastUseTime();
83 surface->releaseGraphicsContext();
85 size_t surfaceBytes = surface->totalBytes();
89 m_bytesCached += surfaceBytes;
91 m_inUseBytesCached += surfaceBytes;
94 void IOSurfacePool::didRemoveSurface(IOSurface* surface, bool inUse)
96 size_t surfaceBytes = surface->totalBytes();
97 m_bytesCached -= surfaceBytes;
99 m_inUseBytesCached -= surfaceBytes;
101 m_surfaceDetails.remove(surface);
104 void IOSurfacePool::didUseSurfaceOfSize(IntSize size)
106 m_sizesInPruneOrder.remove(m_sizesInPruneOrder.reverseFind(size));
107 m_sizesInPruneOrder.append(size);
110 PassRefPtr<IOSurface> IOSurfacePool::takeSurface(IntSize size, ColorSpace colorSpace)
112 CachedSurfaceMap::iterator mapIter = m_cachedSurfaces.find(size);
114 if (mapIter == m_cachedSurfaces.end()) {
115 DUMP_POOL_STATISTICS();
119 for (auto surfaceIter = mapIter->value.begin(); surfaceIter != mapIter->value.end(); ++surfaceIter) {
120 if (!surfaceMatchesParameters(*surfaceIter->get(), size, colorSpace))
123 RefPtr<IOSurface> surface = surfaceIter->get();
124 mapIter->value.remove(surfaceIter);
126 didUseSurfaceOfSize(size);
128 if (mapIter->value.isEmpty()) {
129 m_cachedSurfaces.remove(mapIter);
130 m_sizesInPruneOrder.removeLast();
133 didRemoveSurface(surface.get(), false);
135 surface->setIsVolatile(false);
137 DUMP_POOL_STATISTICS();
138 return surface.release();
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))
145 if (surfaceIter->get()->isInUse())
148 RefPtr<IOSurface> surface = surfaceIter->get();
149 m_inUseSurfaces.remove(surfaceIter);
150 didRemoveSurface(surface.get(), true);
152 surface->setIsVolatile(false);
154 DUMP_POOL_STATISTICS();
155 return surface.release();
158 DUMP_POOL_STATISTICS();
162 void IOSurfacePool::addSurface(IOSurface* surface)
164 if (surface->totalBytes() > m_maximumBytesCached)
167 bool surfaceIsInUse = surface->isInUse();
169 willAddSurface(surface, surfaceIsInUse);
171 if (surfaceIsInUse) {
172 m_inUseSurfaces.prepend(surface);
173 scheduleCollectionTimer();
174 DUMP_POOL_STATISTICS();
178 insertSurfaceIntoPool(surface);
179 DUMP_POOL_STATISTICS();
182 void IOSurfacePool::insertSurfaceIntoPool(IOSurface* surface)
184 auto insertedTuple = m_cachedSurfaces.add(surface->size(), CachedSurfaceQueue());
185 insertedTuple.iterator->value.prepend(surface);
186 if (!insertedTuple.isNewEntry)
187 m_sizesInPruneOrder.remove(m_sizesInPruneOrder.reverseFind(surface->size()));
188 m_sizesInPruneOrder.append(surface->size());
190 scheduleCollectionTimer();
193 void IOSurfacePool::setPoolSize(size_t poolSizeInBytes)
195 m_maximumBytesCached = poolSizeInBytes;
199 void IOSurfacePool::tryEvictInUseSurface()
201 if (m_inUseSurfaces.isEmpty())
204 RefPtr<IOSurface> surface = m_inUseSurfaces.takeLast();
205 didRemoveSurface(surface.get(), true);
208 void IOSurfacePool::tryEvictOldestCachedSurface()
210 if (m_cachedSurfaces.isEmpty())
213 if (m_sizesInPruneOrder.isEmpty())
216 CachedSurfaceMap::iterator surfaceQueueIter = m_cachedSurfaces.find(m_sizesInPruneOrder.first());
217 ASSERT(!surfaceQueueIter->value.isEmpty());
218 RefPtr<IOSurface> surface = surfaceQueueIter->value.takeLast();
219 didRemoveSurface(surface.get(), false);
221 if (surfaceQueueIter->value.isEmpty()) {
222 m_cachedSurfaces.remove(surfaceQueueIter);
223 m_sizesInPruneOrder.remove(0);
227 void IOSurfacePool::evict(size_t additionalSize)
229 if (additionalSize >= m_maximumBytesCached) {
230 discardAllSurfaces();
234 // FIXME: Perhaps purgeable surfaces should count less against the cap?
235 // We don't want to end up with a ton of empty (purged) surfaces, though, as that would defeat the purpose of the pool.
236 size_t targetSize = m_maximumBytesCached - additionalSize;
238 // Interleave eviction of old cached surfaces and more recent in-use surfaces.
239 // In-use surfaces are more recently used, but less useful in the pool, as they aren't
240 // immediately available when requested.
241 while (m_bytesCached > targetSize) {
242 tryEvictOldestCachedSurface();
244 if (m_inUseBytesCached > maximumInUseBytes || m_bytesCached > targetSize)
245 tryEvictInUseSurface();
248 while (m_inUseBytesCached > maximumInUseBytes || m_bytesCached > targetSize)
249 tryEvictInUseSurface();
252 void IOSurfacePool::collectInUseSurfaces()
254 CachedSurfaceQueue newInUseSurfaces;
255 for (CachedSurfaceQueue::iterator surfaceIter = m_inUseSurfaces.begin(); surfaceIter != m_inUseSurfaces.end(); ++surfaceIter) {
256 IOSurface* surface = surfaceIter->get();
257 if (surface->isInUse()) {
258 newInUseSurfaces.append(*surfaceIter);
262 m_inUseBytesCached -= surface->totalBytes();
263 insertSurfaceIntoPool(surface);
266 m_inUseSurfaces = newInUseSurfaces;
269 bool IOSurfacePool::markOlderSurfacesPurgeable()
271 bool markedAllSurfaces = true;
272 auto markTime = std::chrono::steady_clock::now();
274 for (auto& surfaceAndDetails : m_surfaceDetails) {
275 if (surfaceAndDetails.value.hasMarkedPurgeable)
278 if (markTime - surfaceAndDetails.value.lastUseTime < surfaceAgeBeforeMarkingPurgeable) {
279 markedAllSurfaces = false;
283 surfaceAndDetails.key->setIsVolatile(true);
284 surfaceAndDetails.value.hasMarkedPurgeable = true;
287 return markedAllSurfaces;
290 void IOSurfacePool::collectionTimerFired(Timer<IOSurfacePool>&)
292 collectInUseSurfaces();
293 bool markedAllSurfaces = markOlderSurfacesPurgeable();
295 if (!m_inUseSurfaces.size() && markedAllSurfaces)
296 m_collectionTimer.stop();
298 platformGarbageCollectNow();
299 DUMP_POOL_STATISTICS();
302 void IOSurfacePool::scheduleCollectionTimer()
304 if (!m_collectionTimer.isActive())
305 m_collectionTimer.startRepeating(collectionInterval);
308 void IOSurfacePool::discardAllSurfaces()
311 m_inUseBytesCached = 0;
312 m_surfaceDetails.clear();
313 m_cachedSurfaces.clear();
314 m_inUseSurfaces.clear();
315 m_sizesInPruneOrder.clear();
316 m_collectionTimer.stop();
317 platformGarbageCollectNow();
320 void IOSurfacePool::showPoolStatistics()
322 #if ENABLE_IOSURFACE_POOL_STATISTICS
323 WTFLogAlways("IOSurfacePool Statistics\n");
324 unsigned totalSurfaces = 0;
325 size_t totalSize = 0;
326 size_t totalPurgeableSize = 0;
328 for (const auto& keyAndSurfaces : m_cachedSurfaces) {
329 ASSERT(!keyAndSurfaces.value.isEmpty());
330 size_t queueSize = 0;
331 size_t queuePurgeableSize = 0;
332 for (const auto& surface : keyAndSurfaces.value) {
333 size_t surfaceBytes = surface->totalBytes();
336 queueSize += surfaceBytes;
338 if (surface->isVolatile())
339 queuePurgeableSize += surfaceBytes;
342 totalSize += queueSize;
343 totalPurgeableSize += queuePurgeableSize;
345 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);
348 size_t inUseSize = 0;
349 for (const auto& surface : m_inUseSurfaces) {
351 inUseSize += surface->totalBytes();
354 totalSize += inUseSize;
355 WTFLogAlways(" IN USE: %zu surfaces for %zd KB", m_inUseSurfaces.size(), inUseSize / 1024);
357 // FIXME: Should move consistency checks elsewhere, and always perform them in debug builds.
358 ASSERT(m_bytesCached == totalSize);
359 ASSERT(m_bytesCached <= m_maximumBytesCached);
361 WTFLogAlways(" TOTAL: %d surfaces for %zd KB (%zd KB purgeable)\n", totalSurfaces, totalSize / 1024, totalPurgeableSize / 1024);
366 #endif // USE(IOSURFACE)