Title: [113918] trunk/Source/WebCore
Revision
113918
Author
[email protected]
Date
2012-04-11 15:48:44 -0700 (Wed, 11 Apr 2012)

Log Message

[chromium] When rendering goes idle, do not count that time against frame rate
https://bugs.webkit.org/show_bug.cgi?id=73454

The FPS counter had a few issues with its reporting. The first
3 swapbuffers are non-blocking and create FPS rates that are
unrealistically high, throwing off the moving averages and
introducing false spikes into the FPS graph. There was also no
way to monitor the smoothness of the animation, or to focus in
on a particular animation or transition.

This patch updates the FPS counter code so that bad data points
are trimmed and not graphed; so that the graph itself is taller
and more legible; so that there is a clear boundary between 40FPS
and better; and to keep statistics on frame rate between
pauses in the activity in the UI so that you can trigger a
transition and read off the average and standard deviation for
that transition to judge it smooth enough or not.

Patch by Alex Nicolaou <[email protected]> on 2012-04-11
Reviewed by James Robinson.

HUD is not testable in webkit.

* platform/graphics/chromium/cc/CCHeadsUpDisplay.cpp:
(WebCore::CCHeadsUpDisplay::CCHeadsUpDisplay):
(WebCore::CCHeadsUpDisplay::onFrameBegin):
(WebCore::CCHeadsUpDisplay::drawHudContents):
(WebCore::CCHeadsUpDisplay::drawFPSCounter):
* platform/graphics/chromium/cc/CCHeadsUpDisplay.h:

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (113917 => 113918)


--- trunk/Source/WebCore/ChangeLog	2012-04-11 22:44:57 UTC (rev 113917)
+++ trunk/Source/WebCore/ChangeLog	2012-04-11 22:48:44 UTC (rev 113918)
@@ -1,3 +1,35 @@
+2012-04-11  Alex Nicolaou  <[email protected]>
+
+        [chromium] When rendering goes idle, do not count that time against frame rate
+        https://bugs.webkit.org/show_bug.cgi?id=73454
+
+        The FPS counter had a few issues with its reporting. The first
+        3 swapbuffers are non-blocking and create FPS rates that are
+        unrealistically high, throwing off the moving averages and
+        introducing false spikes into the FPS graph. There was also no
+        way to monitor the smoothness of the animation, or to focus in
+        on a particular animation or transition.
+
+        This patch updates the FPS counter code so that bad data points
+        are trimmed and not graphed; so that the graph itself is taller
+        and more legible; so that there is a clear boundary between 40FPS
+        and better; and to keep statistics on frame rate between
+        pauses in the activity in the UI so that you can trigger a
+        transition and read off the average and standard deviation for
+        that transition to judge it smooth enough or not.
+
+
+        Reviewed by James Robinson.
+
+        HUD is not testable in webkit.
+
+        * platform/graphics/chromium/cc/CCHeadsUpDisplay.cpp:
+        (WebCore::CCHeadsUpDisplay::CCHeadsUpDisplay):
+        (WebCore::CCHeadsUpDisplay::onFrameBegin):
+        (WebCore::CCHeadsUpDisplay::drawHudContents):
+        (WebCore::CCHeadsUpDisplay::drawFPSCounter):
+        * platform/graphics/chromium/cc/CCHeadsUpDisplay.h:
+
 2012-04-10  Zhenyao Mo  <[email protected]>
 
         WebGLRenderingContext should defer caching program info

Modified: trunk/Source/WebCore/platform/graphics/chromium/cc/CCHeadsUpDisplay.cpp (113917 => 113918)


--- trunk/Source/WebCore/platform/graphics/chromium/cc/CCHeadsUpDisplay.cpp	2012-04-11 22:44:57 UTC (rev 113917)
+++ trunk/Source/WebCore/platform/graphics/chromium/cc/CCHeadsUpDisplay.cpp	2012-04-11 22:48:44 UTC (rev 113918)
@@ -50,7 +50,6 @@
 
 CCHeadsUpDisplay::CCHeadsUpDisplay(LayerRendererChromium* owner)
     : m_currentFrameNumber(1)
-    , m_filteredFrameTime(0)
     , m_layerRenderer(owner)
     , m_useMapSubForUploads(owner->contextSupportsMapSub())
 {
@@ -69,12 +68,15 @@
 {
 }
 
+const double CCHeadsUpDisplay::kIdleSecondsTriggersReset = 0.5;
+const double CCHeadsUpDisplay::kFrameTooFast = 1.0 / 70;
+
 void CCHeadsUpDisplay::initializeFonts()
 {
     ASSERT(!CCProxy::implThread());
     FontDescription mediumFontDesc;
     mediumFontDesc.setGenericFamily(FontDescription::MonospaceFamily);
-    mediumFontDesc.setComputedSize(20);
+    mediumFontDesc.setComputedSize(12);
 
     m_mediumFont = adoptPtr(new Font(mediumFontDesc, 0, 0));
     m_mediumFont->update(0);
@@ -87,9 +89,20 @@
     m_smallFont->update(0);
 }
 
+// safeMod works on -1, returning m-1 in that case.
+static inline int safeMod(int number, int modulus)
+{
+    return (number + modulus) % modulus;
+}
+
+inline int CCHeadsUpDisplay::frameIndex(int frame) const
+{
+    return safeMod(frame, kBeginFrameHistorySize);
+}
+
 void CCHeadsUpDisplay::onFrameBegin(double timestamp)
 {
-    m_beginTimeHistoryInSec[m_currentFrameNumber % kBeginFrameHistorySize] = timestamp;
+    m_beginTimeHistoryInSec[frameIndex(m_currentFrameNumber)] = timestamp;
 }
 
 void CCHeadsUpDisplay::onSwapBuffers()
@@ -174,6 +187,15 @@
     m_hudTexture->unreserve();
 }
 
+bool CCHeadsUpDisplay::isBadFrame(int frameNumber) const
+{
+    double delta = m_beginTimeHistoryInSec[frameIndex(frameNumber)] -
+                   m_beginTimeHistoryInSec[frameIndex(frameNumber - 1)];
+    bool tooSlow = delta > (kNumMissedFramesForReset / 60.0);
+    bool schedulerAllowsDoubleFrames = !CCProxy::hasImplThread();
+    return (schedulerAllowsDoubleFrames && delta < kFrameTooFast) || tooSlow;
+}
+
 void CCHeadsUpDisplay::drawHudContents(GraphicsContext* context, const IntSize& hudSize)
 {
     if (showPlatformLayerTree()) {
@@ -181,11 +203,11 @@
         context->fillRect(FloatRect(0, 0, hudSize.width(), hudSize.height()));
     }
 
-    int fpsCounterHeight = 25;
+    int fpsCounterHeight = m_mediumFont->fontMetrics().floatHeight() + 40;
     int fpsCounterTop = 2;
     int platformLayerTreeTop;
     if (settings().showFPSCounter)
-        platformLayerTreeTop = fpsCounterTop + fpsCounterHeight + 2;
+        platformLayerTreeTop = fpsCounterTop + fpsCounterHeight;
     else
         platformLayerTreeTop = 0;
 
@@ -196,36 +218,69 @@
         drawPlatformLayerTree(context, platformLayerTreeTop);
 }
 
-void CCHeadsUpDisplay::drawFPSCounter(GraphicsContext* context, int top, int height)
+void CCHeadsUpDisplay::getAverageFPSAndStandardDeviation(double *average, double *standardDeviation) const
 {
-    // Note that since we haven't finished the current frame, the FPS counter
-    // actually reports the last frame's time.
-    double secForLastFrame = m_beginTimeHistoryInSec[(m_currentFrameNumber + kBeginFrameHistorySize - 1) % kBeginFrameHistorySize] -
-                             m_beginTimeHistoryInSec[(m_currentFrameNumber + kBeginFrameHistorySize - 2) % kBeginFrameHistorySize];
+    double& averageFPS = *average;
 
-    // Filter the frame times to avoid spikes.
-    const float alpha = 0.1;
-    if (!m_filteredFrameTime) {
-        if (m_currentFrameNumber == 2)
-            m_filteredFrameTime = secForLastFrame;
-    } else
-        m_filteredFrameTime = ((1.0 - alpha) * m_filteredFrameTime) + (alpha * secForLastFrame);
+    int frame = m_currentFrameNumber - 1; 
+    averageFPS = 0;
+    int averageFPSCount = 0;
+    double fpsVarianceNumerator = 0;
 
+    // Walk backwards through the samples looking for a run of good frame
+    // timings from which to compute the mean and standard deviation.
+    //
+    // Slow frames occur just because the user is inactive, and should be
+    // ignored. Fast frames are ignored if the scheduler is in single-thread
+    // mode in order to represent the true frame rate in spite of the fact that
+    // the first few swapbuffers happen instantly which skews the statistics
+    // too much for short lived animations.
+    //
+    // isBadFrame encapsulates the frame too slow/frame too fast logic.
+    while (1) {
+        if (!isBadFrame(frame)) {
+            averageFPSCount++;
+            double secForLastFrame = m_beginTimeHistoryInSec[frameIndex(frame)] -
+                                     m_beginTimeHistoryInSec[frameIndex(frame - 1)];
+            double x = 1.0 / secForLastFrame;
+            double deltaFromAverage = x - averageFPS;
+            // Change with caution - numerics. http://en.wikipedia.org/wiki/Standard_deviation
+            averageFPS = averageFPS + deltaFromAverage / averageFPSCount;
+            fpsVarianceNumerator = fpsVarianceNumerator + deltaFromAverage * (x - averageFPS);
+        }
+        if (averageFPSCount && isBadFrame(frame)) {
+            // We've gathered a run of good samples, so stop.
+            break;
+        }
+        --frame;
+        if (frameIndex(frame) == frameIndex(m_currentFrameNumber) || frame < 0) {
+            // We've gone through all available historical data, so stop.
+            break;
+        }
+    }
+
+    *standardDeviation = sqrt(fpsVarianceNumerator / averageFPSCount);
+}
+
+void CCHeadsUpDisplay::drawFPSCounter(GraphicsContext* context, int top, int height)
+{
     float textWidth = 0;
     if (!CCProxy::implThread())
         textWidth = drawFPSCounterText(context, top, height);
 
     float graphWidth = kBeginFrameHistorySize;
 
-    // Draw background for graph
-    context->setFillColor(Color(0, 0, 0, 255), ColorSpaceDeviceRGB);
-    context->fillRect(FloatRect(2 + textWidth, top, graphWidth, height));
-
     // Draw FPS graph.
-    const double loFPS = 0.0;
-    const double hiFPS = 120.0;
+    const double loFPS = 0;
+    const double hiFPS = 80;
     context->setStrokeStyle(SolidStroke);
+    context->setFillColor(Color(154, 205, 50), ColorSpaceDeviceRGB);
+    context->fillRect(FloatRect(2 + textWidth, top, graphWidth, height / 2));
+    context->setFillColor(Color(255, 250, 205), ColorSpaceDeviceRGB);
+    context->fillRect(FloatRect(2 + textWidth, top + height / 2, graphWidth, height / 2));
     context->setStrokeColor(Color(255, 0, 0), ColorSpaceDeviceRGB);
+    context->setFillColor(Color(255, 0, 0), ColorSpaceDeviceRGB);
+
     int graphLeft = static_cast<int>(textWidth + 3);
     IntPoint prev(-1, 0);
     int x = 0;
@@ -233,6 +288,10 @@
     for (int i = m_currentFrameNumber % kBeginFrameHistorySize; i != (m_currentFrameNumber - 1) % kBeginFrameHistorySize; i = (i + 1) % kBeginFrameHistorySize) {
         int j = (i + 1) % kBeginFrameHistorySize;
         double fps = 1.0 / (m_beginTimeHistoryInSec[j] - m_beginTimeHistoryInSec[i]);
+        if (isBadFrame(j)) {
+            x += 1;
+            continue;
+        }
         double p = 1 - ((fps - loFPS) / (hiFPS - loFPS));
         if (p < 0)
             p = 0;
@@ -251,21 +310,22 @@
     ASSERT(!CCProxy::implThread());
 
     FontCachePurgePreventer fontCachePurgePreventer;
+    double averageFPS, stdDeviation;
 
-    // Create & measure FPS text.
-    String text(String::format("FPS: %5.1f", 1.0 / m_filteredFrameTime));
-    TextRun run(text);
-    float textWidth = m_mediumFont->width(run) + 2.0f;
+    getAverageFPSAndStandardDeviation(&averageFPS, &stdDeviation);
 
+    String fps(String::format("FPS: %4.1f +/-%3.1f", averageFPS, stdDeviation));
+    TextRun text(fps);
+    float textWidth = m_mediumFont->width(text) + 2;
+
     // Draw background.
     context->setFillColor(Color(0, 0, 0, 255), ColorSpaceDeviceRGB);
-    context->fillRect(FloatRect(2, top, textWidth, height));
+    double fontHeight = m_mediumFont->fontMetrics().floatHeight() + 2;
+    context->fillRect(FloatRect(2, top, textWidth, fontHeight));
 
     // Draw FPS text.
-    if (m_filteredFrameTime) {
-        context->setFillColor(Color(255, 0, 0), ColorSpaceDeviceRGB);
-        context->drawText(*m_mediumFont, run, IntPoint(3, top + height - 6));
-    }
+    context->setFillColor(Color(200, 200, 200), ColorSpaceDeviceRGB);
+    context->drawText(*m_mediumFont, text, IntPoint(3, top + fontHeight - 4));
 
     return textWidth;
 }

Modified: trunk/Source/WebCore/platform/graphics/chromium/cc/CCHeadsUpDisplay.h (113917 => 113918)


--- trunk/Source/WebCore/platform/graphics/chromium/cc/CCHeadsUpDisplay.h	2012-04-11 22:44:57 UTC (rev 113917)
+++ trunk/Source/WebCore/platform/graphics/chromium/cc/CCHeadsUpDisplay.h	2012-04-11 22:48:44 UTC (rev 113918)
@@ -67,6 +67,9 @@
     float drawFPSCounterText(GraphicsContext*, int top, int height);
     void drawPlatformLayerTree(GraphicsContext*, int top);
     const CCSettings& settings() const;
+    bool isBadFrame(int frameNumber) const;
+    int frameIndex(int frameNumber) const;
+    void getAverageFPSAndStandardDeviation(double *average, double *standardDeviation) const;
 
     bool showPlatformLayerTree() const;
 
@@ -74,14 +77,17 @@
 
     int m_currentFrameNumber;
 
-    double m_filteredFrameTime;
-
     OwnPtr<ManagedTexture> m_hudTexture;
 
     LayerRendererChromium* m_layerRenderer;
 
-    static const int kBeginFrameHistorySize = 64;
+    static const int kBeginFrameHistorySize = 120;
+    // The number of seconds of no frames makes us decide that the previous
+    // animation is over.
+    static const double kIdleSecondsTriggersReset;
     double m_beginTimeHistoryInSec[kBeginFrameHistorySize];
+    static const double kFrameTooFast;
+    static const int kNumMissedFramesForReset = 5;
 
     OwnPtr<Font> m_smallFont;
     OwnPtr<Font> m_mediumFont;
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to