Allow ImageBuffer to re-use IOSurfaces
[WebKit-https.git] / Source / WebCore / platform / graphics / cg / ImageBufferBackingStoreCache.cpp
1 /*
2  * Copyright (C) 2013 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 "ImageBufferBackingStoreCache.h"
28
29 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
30 #include <IOSurface/IOSurface.h>
31
32 namespace WebCore {
33
34 static RetainPtr<IOSurfaceRef> createIOSurface(const IntSize& size)
35 {
36     unsigned pixelFormat = 'BGRA';
37     unsigned bytesPerElement = 4;
38     int width = size.width();
39     int height = size.height();
40
41     unsigned long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.width() * bytesPerElement);
42     if (!bytesPerRow)
43         return 0;
44
45     unsigned long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.height() * bytesPerRow);
46     if (!allocSize)
47         return 0;
48
49     const int kNumCreationParameters = 6;
50     const void* keys[kNumCreationParameters];
51     const void* values[kNumCreationParameters];
52     keys[0] = kIOSurfaceWidth;
53     values[0] = CFNumberCreate(0, kCFNumberIntType, &width);
54     keys[1] = kIOSurfaceHeight;
55     values[1] = CFNumberCreate(0, kCFNumberIntType, &height);
56     keys[2] = kIOSurfacePixelFormat;
57     values[2] = CFNumberCreate(0, kCFNumberIntType, &pixelFormat);
58     keys[3] = kIOSurfaceBytesPerElement;
59     values[3] = CFNumberCreate(0, kCFNumberIntType, &bytesPerElement);
60     keys[4] = kIOSurfaceBytesPerRow;
61     values[4] = CFNumberCreate(0, kCFNumberLongType, &bytesPerRow);
62     keys[5] = kIOSurfaceAllocSize;
63     values[5] = CFNumberCreate(0, kCFNumberLongType, &allocSize);
64
65     RetainPtr<CFDictionaryRef> dict = adoptCF(CFDictionaryCreate(0, keys, values, kNumCreationParameters, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
66     for (unsigned i = 0; i < kNumCreationParameters; i++)
67         CFRelease(values[i]);
68
69     return adoptCF(IOSurfaceCreate(dict.get()));
70 }
71
72 ImageBufferBackingStoreCache& ImageBufferBackingStoreCache::get()
73 {
74     DEFINE_STATIC_LOCAL(ImageBufferBackingStoreCache, cache, ());
75     return cache;
76 }
77
78 bool ImageBufferBackingStoreCache::isAcceptableSurface(const IOSurfaceAndContextWithCreationParams& info, const IntSize& requestedSize, CGColorSpaceRef colorSpace, bool needExactSize) const
79 {
80     IOSurfaceRef surface = info.surface.get();
81     IntSize actualSize(IOSurfaceGetWidth(surface), IOSurfaceGetHeight(surface));
82     if (!CFEqual(info.colorSpace.get(), colorSpace))
83         return false;
84     if (needExactSize && actualSize != requestedSize)
85         return false;
86     if (actualSize.width() < requestedSize.width() || actualSize.height() < requestedSize.height())
87         return false;
88     return true;
89 }
90
91 void ImageBufferBackingStoreCache::insertIntoCache(IOSurfaceAndContextWithCreationParams&& info)
92 {
93     IOSurfaceRef surface = info.surface.get();
94     IntSize surfaceSize(IOSurfaceGetWidth(surface), IOSurfaceGetHeight(surface));
95
96     auto toAdd = new IOSurfaceAndContextWithCreationParams(info);
97     auto insertedTuple = m_cachedSurfaces.add(convertSizeToKey(surfaceSize), InfoLinkedList());
98     insertedTuple.iterator->value.append(toAdd);
99     
100     m_pixelsCached += surfaceSize.area();
101 }
102
103 auto ImageBufferBackingStoreCache::takeFromCache(CachedSurfaceMap::iterator iter, IOSurfaceAndContextWithCreationParams* info) -> IOSurfaceAndContextWithCreationParams
104 {
105     ASSERT(info);
106     ASSERT(iter != m_cachedSurfaces.end());
107
108     IOSurfaceRef surface = info->surface.get();
109     m_pixelsCached -= IOSurfaceGetWidth(surface) * IOSurfaceGetHeight(surface);
110
111     iter->value.remove(info);
112     if (iter->value.isEmpty())
113         m_cachedSurfaces.remove(iter);
114     IOSurfaceAndContextWithCreationParams result = std::move(*info);
115     delete info;
116     return result;
117 }
118
119 bool ImageBufferBackingStoreCache::tryTakeFromCache(const IntSize& size, CGColorSpaceRef colorSpace, bool needExactSize, IOSurfaceAndContextWithCreationParams& outInfo)
120 {
121     CachedSurfaceMap::iterator i = m_cachedSurfaces.find(convertSizeToKey(size));
122     if (i == m_cachedSurfaces.end())
123         return nullptr;
124     InfoLinkedList& ll = i->value;
125     for (auto info = ll.head(); info; info = info->next()) {
126         if (isAcceptableSurface(*info, size, colorSpace, needExactSize)) {
127             outInfo = takeFromCache(i, info);
128             return true;
129         }
130     }
131     return false;
132 }
133
134 ImageBufferBackingStoreCache::IOSurfaceAndContext ImageBufferBackingStoreCache::getOrAllocate(IntSize size, CGColorSpaceRef colorSpace, bool needExactSize)
135 {
136     IOSurfaceAndContextWithCreationParams foundInfo;
137     if (tryTakeFromCache(size, colorSpace, needExactSize, foundInfo)) {
138         IOSurfaceRef surface = foundInfo.surface.get();
139         CGContextRef context = foundInfo.context.get();
140         CGContextSaveGState(context);
141         auto activeInserted = m_activeSurfaces.add(surface, std::move(foundInfo));
142         ASSERT(activeInserted.isNewEntry);
143         return activeInserted.iterator->value;
144     }
145
146     RetainPtr<IOSurfaceRef> surface = createIOSurface(size);
147     if (!surface.get())
148         return IOSurfaceAndContext();
149
150     RetainPtr<CGContextRef> context = adoptCF(wkIOSurfaceContextCreate(surface.get(), size.width(), size.height(), colorSpace));
151     if (!context.get())
152         return IOSurfaceAndContext();
153     CGContextSaveGState(context.get());
154
155     auto insertedTuple = m_activeSurfaces.add(surface, IOSurfaceAndContextWithCreationParams(surface.get(), context.get(), colorSpace));
156     ASSERT(insertedTuple.isNewEntry);
157
158     return insertedTuple.iterator->value;
159 }
160
161 void ImageBufferBackingStoreCache::deallocate(IOSurfaceRef surface)
162 {
163     ActiveSurfaceMap::iterator lookup = m_activeSurfaces.find(surface);
164     ASSERT(lookup != m_activeSurfaces.end());
165     
166     auto info = std::move(lookup->value);
167     m_activeSurfaces.remove(lookup);
168     
169     IOSurfaceRef ioSurface = info.surface.get();
170     CGContextRef context = info.context.get();
171     IntSize surfaceSize(IOSurfaceGetWidth(ioSurface), IOSurfaceGetHeight(ioSurface));
172     int surfaceArea = surfaceSize.area();
173
174     static const int kMaxPixelsCached = 1024 * 1024 * 64; // 256MB
175     if (surfaceArea > kMaxPixelsCached)
176         return;
177
178     // Evict
179     auto bucket = m_cachedSurfaces.find(convertSizeToKey(surfaceSize));
180     if (bucket != m_cachedSurfaces.end()) {
181         for (int itemsInBucket = bucket->value.size();
182             itemsInBucket > 0 && m_pixelsCached + surfaceArea > kMaxPixelsCached;
183             --itemsInBucket)
184             takeFromCache(bucket, bucket->value.head());
185     }
186     while (m_pixelsCached + surfaceArea > kMaxPixelsCached) {
187         CachedSurfaceMap::iterator iter = m_cachedSurfaces.begin();
188         takeFromCache(iter, iter->value.head());
189     }
190
191     CGContextRestoreGState(context);
192     // Clear opportunistically so CG has more time to carry it out.
193     CGContextClearRect(context, CGRectMake(0, 0, surfaceSize.width(), surfaceSize.height()));
194 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 1090
195     CGContextFlush(context);
196 #endif
197
198     insertIntoCache(std::move(info));
199
200     schedulePurgeTimer();
201 }
202
203 void ImageBufferBackingStoreCache::timerFired(Timer<ImageBufferBackingStoreCache>*)
204 {
205     while (!m_cachedSurfaces.isEmpty()) {
206         CachedSurfaceMap::iterator iter = m_cachedSurfaces.begin();
207         takeFromCache(iter, iter->value.head());
208     }
209 }
210
211 void ImageBufferBackingStoreCache::schedulePurgeTimer()
212 {
213     if (m_purgeTimer.isActive())
214         m_purgeTimer.stop();
215
216     static const double purgeInterval = 5;
217     m_purgeTimer.startOneShot(purgeInterval);
218 }
219
220 }
221 #endif // IOSURFACE_CANVAS_BACKING_STORE