Title: [162946] trunk/Source/WebKit2
Revision
162946
Author
timothy_hor...@apple.com
Date
2014-01-28 11:33:49 -0800 (Tue, 28 Jan 2014)

Log Message

WebKit2 View Gestures (Swipe): Snapshots should be purgeable
https://bugs.webkit.org/show_bug.cgi?id=127390
<rdar://problem/15876775>

Reviewed by Anders Carlsson.

Make snapshots be purgeable, and implement a straightforward
(if perhaps expensive, for now) cache eviction strategy to limit the
number of snapshots to 20.

* UIProcess/mac/ViewGestureController.mm:
(WebKit::ViewGestureController::beginSwipeGesture):
When beginning a gesture, attempt to make the retrieved snapshot
non-volatile. If it was purged while volatile, we won't use it, but if it
is still valid, we'll go ahead and use it as the swipe layer's contents.

(WebKit::ViewGestureController::removeSwipeSnapshot):
When removing the swipe snapshot, make it volatile once again.

* UIProcess/mac/ViewSnapshotStore.h:
Store a creation time along with the image.
Store and return IOSurfaces instead of CGImages.
Store snapshots and render tree sizes separately, so that we can
throw away snapshots but keep the render tree sizes indefinitely.

* UIProcess/mac/ViewSnapshotStore.mm:
(WebKit::ViewSnapshotStore::pruneSnapshots):
Cap the number of snapshots we'll ever have live at 20.
Enforce this cap by first trying to remove snapshots farthest
from the current back-forward list's current item, falling back
to removing the least recently created snapshot if there are no
snapshots owned by the current back-forward list.

(WebKit::createIOSurfaceFromImage):
Build an IOSurface from the CGImage snapshot we took, for ease of
use of its purgeability API.

(WebKit::ViewSnapshotStore::recordSnapshot):
Bail from taking the snapshot if the image is empty; this can happen
if the view is out of the window when the snapshot is taken.

Mark snapshots as purgeable as soon as they go into the cache.

(WebKit::ViewSnapshotStore::snapshotAndRenderTreeSize):
Return the target render tree size even if there is no snapshot image.
Take care not to look up an empty UUID.

Modified Paths

Diff

Modified: trunk/Source/WebKit2/ChangeLog (162945 => 162946)


--- trunk/Source/WebKit2/ChangeLog	2014-01-28 19:26:21 UTC (rev 162945)
+++ trunk/Source/WebKit2/ChangeLog	2014-01-28 19:33:49 UTC (rev 162946)
@@ -1,3 +1,52 @@
+2014-01-28  Tim Horton  <timothy_hor...@apple.com>
+
+        WebKit2 View Gestures (Swipe): Snapshots should be purgeable
+        https://bugs.webkit.org/show_bug.cgi?id=127390
+        <rdar://problem/15876775>
+
+        Reviewed by Anders Carlsson.
+
+        Make snapshots be purgeable, and implement a straightforward
+        (if perhaps expensive, for now) cache eviction strategy to limit the
+        number of snapshots to 20.
+
+        * UIProcess/mac/ViewGestureController.mm:
+        (WebKit::ViewGestureController::beginSwipeGesture):
+        When beginning a gesture, attempt to make the retrieved snapshot
+        non-volatile. If it was purged while volatile, we won't use it, but if it
+        is still valid, we'll go ahead and use it as the swipe layer's contents.
+
+        (WebKit::ViewGestureController::removeSwipeSnapshot):
+        When removing the swipe snapshot, make it volatile once again.
+
+        * UIProcess/mac/ViewSnapshotStore.h:
+        Store a creation time along with the image.
+        Store and return IOSurfaces instead of CGImages.
+        Store snapshots and render tree sizes separately, so that we can
+        throw away snapshots but keep the render tree sizes indefinitely.
+
+        * UIProcess/mac/ViewSnapshotStore.mm:
+        (WebKit::ViewSnapshotStore::pruneSnapshots):
+        Cap the number of snapshots we'll ever have live at 20.
+        Enforce this cap by first trying to remove snapshots farthest
+        from the current back-forward list's current item, falling back
+        to removing the least recently created snapshot if there are no
+        snapshots owned by the current back-forward list.
+
+        (WebKit::createIOSurfaceFromImage):
+        Build an IOSurface from the CGImage snapshot we took, for ease of
+        use of its purgeability API.
+
+        (WebKit::ViewSnapshotStore::recordSnapshot):
+        Bail from taking the snapshot if the image is empty; this can happen
+        if the view is out of the window when the snapshot is taken.
+
+        Mark snapshots as purgeable as soon as they go into the cache.
+
+        (WebKit::ViewSnapshotStore::snapshotAndRenderTreeSize):
+        Return the target render tree size even if there is no snapshot image.
+        Take care not to look up an empty UUID.
+
 2014-01-27  Alexey Proskuryakov  <a...@apple.com>
 
         Expose SQL database creation and modification times

Modified: trunk/Source/WebKit2/UIProcess/mac/ViewGestureController.mm (162945 => 162946)


--- trunk/Source/WebKit2/UIProcess/mac/ViewGestureController.mm	2014-01-28 19:26:21 UTC (rev 162945)
+++ trunk/Source/WebKit2/UIProcess/mac/ViewGestureController.mm	2014-01-28 19:33:49 UTC (rev 162946)
@@ -41,6 +41,18 @@
 #import <QuartzCore/QuartzCore.h>
 #import <WebCore/WebCoreCALayerExtras.h>
 
+#if defined(__has_include) && __has_include(<IOSurface/IOSurfacePrivate.h>)
+#import <IOSurface/IOSurfacePrivate.h>
+#else
+enum {
+    kIOSurfacePurgeableNonVolatile = 0,
+    kIOSurfacePurgeableVolatile = 1,
+    kIOSurfacePurgeableEmpty = 2,
+};
+#endif
+
+extern "C" IOReturn IOSurfaceSetPurgeable(IOSurfaceRef buffer, uint32_t newState, uint32_t *oldState);
+
 #if defined(__has_include) && __has_include(<QuartzCore/QuartzCorePrivate.h>)
 #import <QuartzCore/QuartzCorePrivate.h>
 #else
@@ -279,8 +291,16 @@
     m_swipeSnapshotLayer = adoptNS([[CALayer alloc] init]);
     [m_swipeSnapshotLayer setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
 
-    RetainPtr<CGImageRef> snapshot = ViewSnapshotStore::shared().snapshotAndRenderTreeSize(targetItem).first;
-    [m_swipeSnapshotLayer setContents:(id)snapshot.get()];
+    RetainPtr<IOSurfaceRef> snapshot = ViewSnapshotStore::shared().snapshotAndRenderTreeSize(targetItem).first;
+
+    if (snapshot) {
+        uint32_t purgeabilityState;
+        IOSurfaceSetPurgeable(snapshot.get(), kIOSurfacePurgeableNonVolatile, &purgeabilityState);
+
+        if (purgeabilityState != kIOSurfacePurgeableEmpty)
+            [m_swipeSnapshotLayer setContents:(id)snapshot.get()];
+    }
+
     [m_swipeSnapshotLayer setContentsGravity:kCAGravityTopLeft];
     [m_swipeSnapshotLayer setContentsScale:m_webPageProxy.deviceScaleFactor()];
     [m_swipeSnapshotLayer setFrame:rootLayer.frame];
@@ -383,6 +403,10 @@
     if (m_activeGestureType != ViewGestureType::Swipe)
         return;
 
+    IOSurfaceRef snapshotSurface = (IOSurfaceRef)[m_swipeSnapshotLayer contents];
+    if (snapshotSurface)
+        IOSurfaceSetPurgeable(snapshotSurface, kIOSurfacePurgeableVolatile, nullptr);
+
     [m_webPageProxy.acceleratedCompositingRootLayer() setPosition:CGPointZero];
     [m_swipeSnapshotLayer removeFromSuperlayer];
     m_swipeSnapshotLayer = nullptr;

Modified: trunk/Source/WebKit2/UIProcess/mac/ViewSnapshotStore.h (162945 => 162946)


--- trunk/Source/WebKit2/UIProcess/mac/ViewSnapshotStore.h	2014-01-28 19:26:21 UTC (rev 162945)
+++ trunk/Source/WebKit2/UIProcess/mac/ViewSnapshotStore.h	2014-01-28 19:33:49 UTC (rev 162946)
@@ -26,6 +26,7 @@
 #ifndef ViewSnapshotStore_h
 #define ViewSnapshotStore_h
 
+#include <chrono>
 #include <wtf/HashMap.h>
 #include <wtf/Noncopyable.h>
 #include <wtf/RetainPtr.h>
@@ -45,14 +46,24 @@
     static ViewSnapshotStore& shared();
 
     void recordSnapshot(WebPageProxy&);
-    std::pair<RetainPtr<CGImageRef>, uint64_t> snapshotAndRenderTreeSize(WebBackForwardListItem*);
+    std::pair<RetainPtr<IOSurfaceRef>, uint64_t> snapshotAndRenderTreeSize(WebBackForwardListItem*);
 
     void disableSnapshotting() { m_enabled = false; }
     void enableSnapshotting() { m_enabled = true; }
 
 private:
-    HashMap<String, std::pair<RetainPtr<CGImageRef>, uint64_t>> m_snapshotMap;
+    void pruneSnapshots(WebPageProxy&);
 
+    struct Snapshot {
+        RetainPtr<IOSurfaceRef> surface;
+        RetainPtr<CGContextRef> surfaceContext;
+
+        std::chrono::steady_clock::time_point creationTime;
+    };
+
+    HashMap<String, Snapshot> m_snapshotMap;
+    HashMap<String, uint64_t> m_renderTreeSizeMap;
+
     bool m_enabled;
 };
 

Modified: trunk/Source/WebKit2/UIProcess/mac/ViewSnapshotStore.mm (162945 => 162946)


--- trunk/Source/WebKit2/UIProcess/mac/ViewSnapshotStore.mm	2014-01-28 19:26:21 UTC (rev 162945)
+++ trunk/Source/WebKit2/UIProcess/mac/ViewSnapshotStore.mm	2014-01-28 19:33:49 UTC (rev 162946)
@@ -29,10 +29,29 @@
 #import "WebBackForwardList.h"
 #import "WebPageProxy.h"
 #import <CoreGraphics/CoreGraphics.h>
+#import <IOSurface/IOSurface.h>
 #import <WebCore/UUID.h>
 
+#if defined(__has_include) && __has_include(<CoreGraphics/CoreGraphicsPrivate.h>)
+#import <CoreGraphics/CoreGraphicsPrivate.h>
+#endif
+
+extern "C" CGContextRef CGIOSurfaceContextCreate(IOSurfaceRef surface, size_t width, size_t height, size_t bitsPerComponent, size_t bitsPerPixel, CGColorSpaceRef space, CGBitmapInfo bitmapInfo);
+
+#if defined(__has_include) && __has_include(<IOSurface/IOSurfacePrivate.h>)
+#import <IOSurface/IOSurfacePrivate.h>
+#else
+enum {
+    kIOSurfacePurgeableVolatile = 1,
+};
+#endif
+
+extern "C" IOReturn IOSurfaceSetPurgeable(IOSurfaceRef buffer, uint32_t newState, uint32_t *oldState);
+
 using namespace WebCore;
 
+static const int maximumSnapshotCount = 20;
+
 namespace WebKit {
 
 ViewSnapshotStore::ViewSnapshotStore()
@@ -50,9 +69,86 @@
     return store;
 }
 
+void ViewSnapshotStore::pruneSnapshots(WebPageProxy& webPageProxy)
+{
+    if (m_snapshotMap.size() <= maximumSnapshotCount)
+        return;
+
+    uint32_t currentIndex = webPageProxy.backForwardList().currentIndex();
+    uint32_t maxDistance = 0;
+    WebBackForwardListItem* mostDistantSnapshottedItem = nullptr;
+    auto backForwardEntries = webPageProxy.backForwardList().entries();
+
+    // First, try to evict the snapshot for the page farthest from the current back-forward item.
+    for (uint32_t i = 0, entryCount = webPageProxy.backForwardList().entries().size(); i < entryCount; i++) {
+        uint32_t distance = std::max(currentIndex, i) - std::min(currentIndex, i);
+
+        if (i == currentIndex || distance < maxDistance)
+            continue;
+
+        WebBackForwardListItem* item = backForwardEntries[i].get();
+        String snapshotUUID = item->snapshotUUID();
+        if (!snapshotUUID.isEmpty() && m_snapshotMap.contains(snapshotUUID)) {
+            mostDistantSnapshottedItem = item;
+            maxDistance = distance;
+        }
+    }
+
+    if (mostDistantSnapshottedItem) {
+        m_snapshotMap.remove(mostDistantSnapshottedItem->snapshotUUID());
+        return;
+    }
+
+    // If we can't find a most distant item (perhaps because all the snapshots are from
+    // a different WebPageProxy's back-forward list), we should evict the the oldest item.
+    std::chrono::steady_clock::time_point oldestSnapshotTime = std::chrono::steady_clock::time_point::max();
+    String oldestSnapshotUUID;
+
+    for (const auto& uuidAndSnapshot : m_snapshotMap) {
+        if (uuidAndSnapshot.value.creationTime < oldestSnapshotTime) {
+            oldestSnapshotTime = uuidAndSnapshot.value.creationTime;
+            oldestSnapshotUUID = uuidAndSnapshot.key;
+        }
+    }
+
+    m_snapshotMap.remove(oldestSnapshotUUID);
+}
+
+static std::pair<RetainPtr<IOSurfaceRef>, RetainPtr<CGContextRef>> createIOSurfaceFromImage(CGImageRef image)
+{
+    unsigned pixelFormat = 'BGRA';
+    size_t bitsPerComponent = 8;
+    size_t bitsPerPixel = 32;
+    size_t bytesPerElement = 4;
+    size_t width = CGImageGetWidth(image);
+    size_t height = CGImageGetHeight(image);
+    size_t bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, width * bytesPerElement);
+    ASSERT(bytesPerRow);
+
+    size_t allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, height * bytesPerRow);
+    ASSERT(allocSize);
+
+    NSDictionary *properties = @{
+        (id)kIOSurfaceWidth: @(width),
+        (id)kIOSurfaceHeight: @(height),
+        (id)kIOSurfacePixelFormat: @(pixelFormat),
+        (id)kIOSurfaceBytesPerElement: @(bytesPerElement),
+        (id)kIOSurfaceBytesPerRow: @(bytesPerRow),
+        (id)kIOSurfaceAllocSize: @(allocSize)
+    };
+
+    RetainPtr<IOSurfaceRef> surface = adoptCF(IOSurfaceCreate((CFDictionaryRef)properties));
+    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
+    RetainPtr<CGContextRef> surfaceContext = adoptCF(CGIOSurfaceContextCreate(surface.get(), width, height, bitsPerComponent, bitsPerPixel, CGImageGetColorSpace(image), bitmapInfo));
+    CGContextDrawImage(surfaceContext.get(), CGRectMake(0, 0, width, height), image);
+    CGContextFlush(surfaceContext.get());
+
+    return std::make_pair(surface, surfaceContext);
+}
+
 void ViewSnapshotStore::recordSnapshot(WebPageProxy& webPageProxy)
 {
-    RefPtr<WebBackForwardListItem> item = webPageProxy.backForwardList().currentItem();
+    WebBackForwardListItem* item = webPageProxy.backForwardList().currentItem();
 
     if (!m_enabled)
         return;
@@ -60,24 +156,49 @@
     if (!item)
         return;
 
-    RetainPtr<CGImageRef> snapshot = webPageProxy.takeViewSnapshot();
+    RetainPtr<CGImageRef> snapshotImage = webPageProxy.takeViewSnapshot();
+    if (!snapshotImage)
+        return;
 
+    pruneSnapshots(webPageProxy);
+
     String oldSnapshotUUID = item->snapshotUUID();
-    if (!oldSnapshotUUID.isEmpty())
+    if (!oldSnapshotUUID.isEmpty()) {
         m_snapshotMap.remove(oldSnapshotUUID);
+        m_renderTreeSizeMap.remove(oldSnapshotUUID);
+    }
 
     item->setSnapshotUUID(createCanonicalUUIDString());
-    m_snapshotMap.add(item->snapshotUUID(), std::make_pair(snapshot, webPageProxy.renderTreeSize()));
+
+    auto surfaceAndContext = createIOSurfaceFromImage(snapshotImage.get());
+
+    Snapshot snapshot;
+    snapshot.surface = surfaceAndContext.first;
+    snapshot.surfaceContext = surfaceAndContext.second;
+    snapshot.creationTime = std::chrono::steady_clock::now();
+
+    IOSurfaceSetPurgeable(snapshot.surface.get(), kIOSurfacePurgeableVolatile, nullptr);
+
+    m_snapshotMap.add(item->snapshotUUID(), snapshot);
+    m_renderTreeSizeMap.add(item->snapshotUUID(), webPageProxy.renderTreeSize());
 }
 
-std::pair<RetainPtr<CGImageRef>, uint64_t> ViewSnapshotStore::snapshotAndRenderTreeSize(WebBackForwardListItem* item)
+std::pair<RetainPtr<IOSurfaceRef>, uint64_t> ViewSnapshotStore::snapshotAndRenderTreeSize(WebBackForwardListItem* item)
 {
-    const auto& snapshotAndRenderTreeSize = m_snapshotMap.find(item->snapshotUUID());
+    if (item->snapshotUUID().isEmpty())
+        return std::make_pair(nullptr, 0);
 
-    if (snapshotAndRenderTreeSize == m_snapshotMap.end())
+    const auto& renderTreeSize = m_renderTreeSizeMap.find(item->snapshotUUID());
+    if (renderTreeSize == m_renderTreeSizeMap.end())
         return std::make_pair(nullptr, 0);
 
-    return snapshotAndRenderTreeSize->value;
+    const auto& snapshot = m_snapshotMap.find(item->snapshotUUID());
+    RetainPtr<IOSurfaceRef> surface;
+
+    if (snapshot != m_snapshotMap.end())
+        surface = snapshot->value.surface;
+
+    return std::make_pair(surface, renderTreeSize->value);
 }
 
 } // namespace WebKit
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to