Commit a6775c18 authored by antti@apple.com's avatar antti@apple.com

Throttle compositing layer flushes during page loading

https://bugs.webkit.org/show_bug.cgi?id=113786

Reviewed by Simon Fraser.
        
Page content can change rapidly during page loading triggering excessive layer flushes and repainting. We should avoid this unnecessary work.
        
This patch reduces layer flushes (and painting) during loading by 50-70% on many popular pages.

* loader/FrameLoader.cpp:
(WebCore::FrameLoader::loadProgressingStatusChanged):        
* loader/FrameLoader.h:
* loader/ProgressTracker.cpp:
(WebCore::ProgressTracker::ProgressTracker):
(WebCore::ProgressTracker::reset):
(WebCore::ProgressTracker::progressStarted):
(WebCore::ProgressTracker::finalProgressComplete):
(WebCore::ProgressTracker::isLoadProgressing):
(WebCore::ProgressTracker::progressHeartbeatTimerFired):
* loader/ProgressTracker.h:
        
    Track if the document load is progressing. This is done with a heartbeat timer that checks every 100ms if we have received more than 1k of data.
    If four heartbeats pass without progress then we consider the load stalled.

* page/FrameView.cpp:
(WebCore::FrameView::resetDeferredRepaintDelay):
        
    Disable throttling temporary on user interaction so the page stays as responsive as possible even during loading.

(WebCore::FrameView::updateLayerFlushThrottling):
        
    Enable throttling when the load is progressing, disable otherwise.

* page/FrameView.h:
* platform/graphics/GraphicsLayer.h:
(WebCore::GraphicsLayer::canThrottleLayerFlush):
* platform/graphics/ca/GraphicsLayerCA.cpp:
(WebCore::GraphicsLayerCA::platformCALayerDidCreateTiles):
(WebCore::GraphicsLayerCA::canThrottleLayerFlush):
        
    Don't throttle if new tiles have been added by the tile controller. They may have stale content and need to be flushed immediately.

(WebCore::GraphicsLayerCA::noteLayerPropertyChanged):
        
    Set the new TilesAdded change flag.

* platform/graphics/ca/GraphicsLayerCA.h:
* rendering/RenderLayerBacking.cpp:
(WebCore::RenderLayerBacking::notifyFlushRequired):
* rendering/RenderLayerCompositor.cpp:
(WebCore::RenderLayerCompositor::RenderLayerCompositor):
(WebCore::RenderLayerCompositor::notifyFlushRequired):
(WebCore::RenderLayerCompositor::scheduleLayerFlushNow):
        
    Factor the actual flush scheduling to private function.

(WebCore::RenderLayerCompositor::scheduleLayerFlush):
        
    Mark the compositor for flush and return without flushing if the flushes are currently being throttled.

(WebCore::RenderLayerCompositor::flushPendingLayerChanges):
        
    After a flush, start the throtting timer (currently 0.5s) coalescing the subsequent flushes.

(WebCore::RenderLayerCompositor::didChangeVisibleRect):
        
    Do immediately flush if needed.

(WebCore::RenderLayerCompositor::setLayerFlushThrottlingEnabled):
        
    Flush immediately if disabled.

(WebCore::RenderLayerCompositor::disableLayerFlushThrottlingTemporarilyForInteraction):
(WebCore::RenderLayerCompositor::isThrottlingLayerFlushes):
(WebCore::RenderLayerCompositor::startLayerFlushTimerIfNeeded):
(WebCore::RenderLayerCompositor::layerFlushTimerFired):
        
    Flush when the timer fires timer.

* rendering/RenderLayerCompositor.h:



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@147797 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent a4946bc1
2013-04-05 Antti Koivisto <antti@apple.com>
Throttle compositing layer flushes during page loading
https://bugs.webkit.org/show_bug.cgi?id=113786
Reviewed by Simon Fraser.
Page content can change rapidly during page loading triggering excessive layer flushes and repainting. We should avoid this unnecessary work.
This patch reduces layer flushes (and painting) during loading by 50-70% on many popular pages.
* loader/FrameLoader.cpp:
(WebCore::FrameLoader::loadProgressingStatusChanged):
* loader/FrameLoader.h:
* loader/ProgressTracker.cpp:
(WebCore::ProgressTracker::ProgressTracker):
(WebCore::ProgressTracker::reset):
(WebCore::ProgressTracker::progressStarted):
(WebCore::ProgressTracker::finalProgressComplete):
(WebCore::ProgressTracker::isLoadProgressing):
(WebCore::ProgressTracker::progressHeartbeatTimerFired):
* loader/ProgressTracker.h:
Track if the document load is progressing. This is done with a heartbeat timer that checks every 100ms if we have received more than 1k of data.
If four heartbeats pass without progress then we consider the load stalled.
* page/FrameView.cpp:
(WebCore::FrameView::resetDeferredRepaintDelay):
Disable throttling temporary on user interaction so the page stays as responsive as possible even during loading.
(WebCore::FrameView::updateLayerFlushThrottling):
Enable throttling when the load is progressing, disable otherwise.
* page/FrameView.h:
* platform/graphics/GraphicsLayer.h:
(WebCore::GraphicsLayer::canThrottleLayerFlush):
* platform/graphics/ca/GraphicsLayerCA.cpp:
(WebCore::GraphicsLayerCA::platformCALayerDidCreateTiles):
(WebCore::GraphicsLayerCA::canThrottleLayerFlush):
Don't throttle if new tiles have been added by the tile controller. They may have stale content and need to be flushed immediately.
(WebCore::GraphicsLayerCA::noteLayerPropertyChanged):
Set the new TilesAdded change flag.
* platform/graphics/ca/GraphicsLayerCA.h:
* rendering/RenderLayerBacking.cpp:
(WebCore::RenderLayerBacking::notifyFlushRequired):
* rendering/RenderLayerCompositor.cpp:
(WebCore::RenderLayerCompositor::RenderLayerCompositor):
(WebCore::RenderLayerCompositor::notifyFlushRequired):
(WebCore::RenderLayerCompositor::scheduleLayerFlushNow):
Factor the actual flush scheduling to private function.
(WebCore::RenderLayerCompositor::scheduleLayerFlush):
Mark the compositor for flush and return without flushing if the flushes are currently being throttled.
(WebCore::RenderLayerCompositor::flushPendingLayerChanges):
After a flush, start the throtting timer (currently 0.5s) coalescing the subsequent flushes.
(WebCore::RenderLayerCompositor::didChangeVisibleRect):
Do immediately flush if needed.
(WebCore::RenderLayerCompositor::setLayerFlushThrottlingEnabled):
Flush immediately if disabled.
(WebCore::RenderLayerCompositor::disableLayerFlushThrottlingTemporarilyForInteraction):
(WebCore::RenderLayerCompositor::isThrottlingLayerFlushes):
(WebCore::RenderLayerCompositor::startLayerFlushTimerIfNeeded):
(WebCore::RenderLayerCompositor::layerFlushTimerFired):
Flush when the timer fires timer.
* rendering/RenderLayerCompositor.h:
2013-04-05 Benjamin Poulain <benjamin@webkit.org>
Clean the chromium bits of WebCore's WebDatabase
......@@ -3316,6 +3316,12 @@ NetworkingContext* FrameLoader::networkingContext() const
return m_networkingContext.get();
}
void FrameLoader::loadProgressingStatusChanged()
{
bool isLoadProgressing = m_frame->page()->progress()->isLoadProgressing();
m_frame->page()->mainFrame()->view()->updateLayerFlushThrottling(isLoadProgressing);
}
void FrameLoader::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::Loader);
......
......@@ -281,6 +281,8 @@ public:
NetworkingContext* networkingContext() const;
void loadProgressingStatusChanged();
const KURL& previousURL() const { return m_previousURL; }
void reportMemoryUsage(MemoryObjectInfo*) const;
......
......@@ -51,6 +51,13 @@ static const double finalProgressValue = 0.9; // 1.0 - initialProgressValue
static const int progressItemDefaultEstimatedLength = 1024 * 16;
// Check if the load is progressing this often.
static const double progressHeartbeatInterval = 0.1;
// How many heartbeats must pass without progress before deciding the load is currently stalled.
static const unsigned loadStalledHeartbeatCount = 4;
// How many bytes are required between heartbeats to consider it progress.
static const unsigned minumumBytesPerHeartbeatForProgress = 1024;
struct ProgressItem {
WTF_MAKE_NONCOPYABLE(ProgressItem); WTF_MAKE_FAST_ALLOCATED;
public:
......@@ -74,6 +81,9 @@ ProgressTracker::ProgressTracker()
, m_finalProgressChangedSent(false)
, m_progressValue(0)
, m_numProgressTrackedFrames(0)
, m_progressHeartbeatTimer(this, &ProgressTracker::progressHeartbeatTimerFired)
, m_heartbeatsWithNoProgress(0)
, m_totalBytesReceivedBeforePreviousHeartbeat(0)
{
}
......@@ -103,6 +113,10 @@ void ProgressTracker::reset()
m_finalProgressChangedSent = false;
m_numProgressTrackedFrames = 0;
m_originatingProgressFrame = 0;
m_heartbeatsWithNoProgress = 0;
m_totalBytesReceivedBeforePreviousHeartbeat = 0;
m_progressHeartbeatTimer.stop();
}
void ProgressTracker::progressStarted(Frame* frame)
......@@ -115,7 +129,10 @@ void ProgressTracker::progressStarted(Frame* frame)
reset();
m_progressValue = initialProgressValue;
m_originatingProgressFrame = frame;
m_progressHeartbeatTimer.startRepeating(progressHeartbeatInterval);
m_originatingProgressFrame->loader()->loadProgressingStatusChanged();
m_originatingProgressFrame->loader()->client()->postProgressStartedNotification();
}
m_numProgressTrackedFrames++;
......@@ -157,6 +174,8 @@ void ProgressTracker::finalProgressComplete()
frame->loader()->client()->setMainFrameDocumentReady(true);
frame->loader()->client()->postProgressFinishedNotification();
frame->loader()->loadProgressingStatusChanged();
InspectorInstrumentation::frameStoppedLoading(frame.get());
}
......@@ -264,5 +283,24 @@ unsigned long ProgressTracker::createUniqueIdentifier()
return ++s_uniqueIdentifier;
}
bool ProgressTracker::isLoadProgressing() const
{
return m_progressValue && m_progressValue < finalProgressValue && m_heartbeatsWithNoProgress < loadStalledHeartbeatCount;
}
void ProgressTracker::progressHeartbeatTimerFired(Timer<ProgressTracker>*)
{
if (m_totalBytesReceived < m_totalBytesReceivedBeforePreviousHeartbeat + minumumBytesPerHeartbeatForProgress)
++m_heartbeatsWithNoProgress;
else
m_heartbeatsWithNoProgress = 0;
m_totalBytesReceivedBeforePreviousHeartbeat = m_totalBytesReceived;
m_originatingProgressFrame->loader()->loadProgressingStatusChanged();
if (m_progressValue >= finalProgressValue)
m_progressHeartbeatTimer.stop();
}
}
......@@ -26,6 +26,7 @@
#ifndef ProgressTracker_h
#define ProgressTracker_h
#include "Timer.h"
#include <wtf/Forward.h>
#include <wtf/HashMap.h>
#include <wtf/Noncopyable.h>
......@@ -58,11 +59,15 @@ public:
long long totalPageAndResourceBytesToLoad() const { return m_totalPageAndResourceBytesToLoad; }
long long totalBytesReceived() const { return m_totalBytesReceived; }
bool isLoadProgressing() const;
private:
ProgressTracker();
void reset();
void finalProgressComplete();
void progressHeartbeatTimerFired(Timer<ProgressTracker>*);
static unsigned long s_uniqueIdentifier;
......@@ -78,6 +83,10 @@ private:
int m_numProgressTrackedFrames;
HashMap<unsigned long, OwnPtr<ProgressItem> > m_progressItems;
Timer<ProgressTracker> m_progressHeartbeatTimer;
unsigned m_heartbeatsWithNoProgress;
long long m_totalBytesReceivedBeforePreviousHeartbeat;
};
}
......
......@@ -2258,6 +2258,10 @@ void FrameView::resetDeferredRepaintDelay()
if (!m_deferringRepaints)
doDeferredRepaints();
}
#if USE(ACCELERATED_COMPOSITING)
if (RenderView* view = renderView())
view->compositor()->disableLayerFlushThrottlingTemporarilyForInteraction();
#endif
}
double FrameView::adjustedDeferredRepaintDelay() const
......@@ -2285,6 +2289,16 @@ void FrameView::endDisableRepaints()
m_disableRepaints--;
}
void FrameView::updateLayerFlushThrottling(bool isLoadProgressing)
{
#if USE(ACCELERATED_COMPOSITING)
if (RenderView* view = renderView())
view->compositor()->setLayerFlushThrottlingEnabled(isLoadProgressing);
#else
UNUSED_PARAM(isLoadProgressing);
#endif
}
void FrameView::layoutTimerFired(Timer<FrameView>*)
{
#ifdef INSTRUMENT_LAYOUT_SCHEDULING
......
......@@ -236,6 +236,8 @@ public:
void startDeferredRepaintTimer(double delay);
void resetDeferredRepaintDelay();
void updateLayerFlushThrottling(bool isLoadProgressing);
void beginDisableRepaints();
void endDisableRepaints();
bool repaintsDisabled() { return m_disableRepaints > 0; }
......
......@@ -437,6 +437,8 @@ public:
void updateDebugIndicators();
virtual bool canThrottleLayerFlush() const { return false; }
virtual void reportMemoryUsage(MemoryObjectInfo*) const;
protected:
......
......@@ -1110,9 +1110,7 @@ void GraphicsLayerCA::platformCALayerDidCreateTiles(const Vector<FloatRect>& dir
for (size_t i = 0; i < dirtyRects.size(); ++i)
setNeedsDisplayInRect(dirtyRects[i]);
// Ensure that the layout is up to date before any individual tiles are painted by telling the client
// that it needs to flush its layer state, which will end up scheduling the layer flusher.
client()->notifyFlushRequired(this);
noteLayerPropertyChanged(TilesAdded);
}
float GraphicsLayerCA::platformCALayerDeviceScaleFactor()
......@@ -3052,12 +3050,22 @@ void GraphicsLayerCA::noteSublayersChanged()
propagateLayerChangeToReplicas();
}
bool GraphicsLayerCA::canThrottleLayerFlush() const
{
// Tile layers are currently plain CA layers, attached directly by TileController. They require immediate flush as they may contain garbage.
return !(m_uncommittedChanges & TilesAdded);
}
void GraphicsLayerCA::noteLayerPropertyChanged(LayerChangeFlags flags)
{
if (!m_uncommittedChanges && m_client)
m_client->notifyFlushRequired(this);
bool hadUncommittedChanges = !!m_uncommittedChanges;
bool oldCanThrottleLayerFlush = canThrottleLayerFlush();
m_uncommittedChanges |= flags;
bool needsFlush = !hadUncommittedChanges || oldCanThrottleLayerFlush != canThrottleLayerFlush();
if (needsFlush && m_client)
m_client->notifyFlushRequired(this);
}
double GraphicsLayerCA::backingStoreMemoryEstimate() const
......
......@@ -254,6 +254,8 @@ private:
bool recursiveVisibleRectChangeRequiresFlush(const TransformState&) const;
virtual bool canThrottleLayerFlush() const;
// Used to track the path down the tree for replica layers.
struct ReplicaState {
static const size_t maxReplicaDepth = 16;
......@@ -397,7 +399,8 @@ private:
ContentsVisibilityChanged = 1 << 25,
VisibleRectChanged = 1 << 26,
FiltersChanged = 1 << 27,
DebugIndicatorsChanged = 1 << 28
TilesAdded = 1 < 28,
DebugIndicatorsChanged = 1 << 29
};
typedef unsigned LayerChangeFlags;
void noteLayerPropertyChanged(LayerChangeFlags flags);
......
......@@ -2148,10 +2148,11 @@ void RenderLayerBacking::notifyAnimationStarted(const GraphicsLayer*, double tim
renderer()->animation()->notifyAnimationStarted(renderer(), time);
}
void RenderLayerBacking::notifyFlushRequired(const GraphicsLayer*)
void RenderLayerBacking::notifyFlushRequired(const GraphicsLayer* layer)
{
if (!renderer()->documentBeingDestroyed())
compositor()->scheduleLayerFlush();
if (renderer()->documentBeingDestroyed())
return;
compositor()->scheduleLayerFlush(layer->canThrottleLayerFlush());
}
void RenderLayerBacking::notifyFlushBeforeDisplayRefresh(const GraphicsLayer* layer)
......
......@@ -84,10 +84,12 @@ bool WebCoreHas3DRendering = true;
#define WTF_USE_COMPOSITING_FOR_SMALL_CANVASES 1
#endif
static const int canvasAreaThresholdRequiringCompositing = 50 * 100;
namespace WebCore {
static const int canvasAreaThresholdRequiringCompositing = 50 * 100;
// During page loading delay layer flushes up to this many seconds to allow them coalesce, reducing workload.
static const double throttledLayerFlushDelay = .5;
using namespace HTMLNames;
class RenderLayerCompositor::OverlapMap {
......@@ -211,6 +213,10 @@ RenderLayerCompositor::RenderLayerCompositor(RenderView* renderView)
, m_isTrackingRepaints(false)
, m_layersWithTiledBackingCount(0)
, m_rootLayerAttachment(RootLayerUnattached)
, m_layerFlushTimer(this, &RenderLayerCompositor::layerFlushTimerFired)
, m_layerFlushThrottlingEnabled(false)
, m_layerFlushThrottlingTemporarilyDisabledForInteraction(false)
, m_hasPendingLayerFlush(false)
#if !LOG_DISABLED
, m_rootLayerUpdateCount(0)
, m_obligateCompositedLayerCount(0)
......@@ -317,12 +323,27 @@ void RenderLayerCompositor::customPositionForVisibleRectComputation(const Graphi
position = -scrollPosition;
}
void RenderLayerCompositor::scheduleLayerFlush()
void RenderLayerCompositor::notifyFlushRequired(const GraphicsLayer* layer)
{
scheduleLayerFlush(layer->canThrottleLayerFlush());
}
void RenderLayerCompositor::scheduleLayerFlushNow()
{
m_hasPendingLayerFlush = false;
if (Page* page = this->page())
page->chrome()->client()->scheduleCompositingLayerFlush();
}
void RenderLayerCompositor::scheduleLayerFlush(bool canThrottle)
{
if (canThrottle && isThrottlingLayerFlushes()) {
m_hasPendingLayerFlush = true;
return;
}
scheduleLayerFlushNow();
}
void RenderLayerCompositor::flushPendingLayerChanges(bool isFlushRoot)
{
// FrameView::flushCompositingStateIncludingSubframes() flushes each subframe,
......@@ -361,6 +382,7 @@ void RenderLayerCompositor::flushPendingLayerChanges(bool isFlushRoot)
m_viewportConstrainedLayersNeedingUpdate.clear();
}
startLayerFlushTimerIfNeeded();
}
void RenderLayerCompositor::didFlushChangesForLayer(RenderLayer* layer, const GraphicsLayer* graphicsLayer)
......@@ -384,8 +406,9 @@ void RenderLayerCompositor::didChangeVisibleRect()
return;
IntRect visibleRect = m_clipLayer ? IntRect(IntPoint(), frameView->contentsSize()) : frameView->visibleContentRect();
if (rootLayer->visibleRectChangeRequiresFlush(visibleRect))
scheduleLayerFlush();
if (!rootLayer->visibleRectChangeRequiresFlush(visibleRect))
return;
scheduleLayerFlushNow();
}
void RenderLayerCompositor::notifyFlushBeforeDisplayRefresh(const GraphicsLayer*)
......@@ -3124,6 +3147,51 @@ Page* RenderLayerCompositor::page() const
return 0;
}
void RenderLayerCompositor::setLayerFlushThrottlingEnabled(bool enabled)
{
m_layerFlushThrottlingEnabled = enabled;
if (m_layerFlushThrottlingEnabled)
return;
m_layerFlushTimer.stop();
if (!m_hasPendingLayerFlush)
return;
scheduleLayerFlushNow();
}
void RenderLayerCompositor::disableLayerFlushThrottlingTemporarilyForInteraction()
{
if (m_layerFlushThrottlingTemporarilyDisabledForInteraction)
return;
m_layerFlushThrottlingTemporarilyDisabledForInteraction = true;
}
bool RenderLayerCompositor::isThrottlingLayerFlushes() const
{
if (!m_layerFlushThrottlingEnabled)
return false;
if (!m_layerFlushTimer.isActive())
return false;
if (m_layerFlushThrottlingTemporarilyDisabledForInteraction)
return false;
return true;
}
void RenderLayerCompositor::startLayerFlushTimerIfNeeded()
{
m_layerFlushThrottlingTemporarilyDisabledForInteraction = false;
m_layerFlushTimer.stop();
if (!m_layerFlushThrottlingEnabled)
return;
m_layerFlushTimer.startOneShot(throttledLayerFlushDelay);
}
void RenderLayerCompositor::layerFlushTimerFired(Timer<RenderLayerCompositor>*)
{
if (!m_hasPendingLayerFlush)
return;
scheduleLayerFlushNow();
}
void RenderLayerCompositor::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
MemoryClassInfo info(memoryObjectInfo, this, PlatformMemoryTypes::Rendering);
......
......@@ -126,7 +126,7 @@ public:
// GraphicsLayers buffer state, which gets pushed to the underlying platform layers
// at specific times.
void scheduleLayerFlush();
void scheduleLayerFlush(bool canThrottle);
void flushPendingLayerChanges(bool isFlushRoot = true);
// flushPendingLayerChanges() flushes the entire GraphicsLayer tree, which can cross frame boundaries.
......@@ -277,13 +277,16 @@ public:
bool hasNonMainLayersWithTiledBacking() const { return m_layersWithTiledBackingCount; }
CompositingReasons reasonsForCompositing(const RenderLayer*) const;
void setLayerFlushThrottlingEnabled(bool);
void disableLayerFlushThrottlingTemporarilyForInteraction();
private:
class OverlapMap;
// GraphicsLayerClient implementation
virtual void notifyAnimationStarted(const GraphicsLayer*, double) OVERRIDE { }
virtual void notifyFlushRequired(const GraphicsLayer*) OVERRIDE { scheduleLayerFlush(); }
virtual void notifyFlushRequired(const GraphicsLayer*) OVERRIDE;
virtual void paintContents(const GraphicsLayer*, GraphicsContext&, GraphicsLayerPaintingPhase, const IntRect&) OVERRIDE;
virtual bool isTrackingRepaints() const OVERRIDE;
......@@ -380,6 +383,11 @@ private:
bool requiresContentShadowLayer() const;
#endif
void scheduleLayerFlushNow();
bool isThrottlingLayerFlushes() const;
void startLayerFlushTimerIfNeeded();
void layerFlushTimerFired(Timer<RenderLayerCompositor>*);
#if !LOG_DISABLED
const char* logReasonsForCompositing(const RenderLayer*);
void logLayerInfo(const RenderLayer*, int depth);
......@@ -441,6 +449,11 @@ private:
OwnPtr<GraphicsLayerUpdater> m_layerUpdater; // Updates tiled layer visible area periodically while animations are running.
Timer<RenderLayerCompositor> m_layerFlushTimer;
bool m_layerFlushThrottlingEnabled;
bool m_layerFlushThrottlingTemporarilyDisabledForInteraction;
bool m_hasPendingLayerFlush;
#if !LOG_DISABLED
int m_rootLayerUpdateCount;
int m_obligateCompositedLayerCount; // count of layer that have to be composited.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment