Changeset 34210 in webkit


Ignore:
Timestamp:
May 29, 2008 2:05:11 PM (16 years ago)
Author:
hyatt@apple.com
Message:

2008-05-29 David Hyatt <hyatt@apple.com>

Improve the performance of the GUIMark benchmark by 2x in the CoreGraphics code path.

Whenever a foreground image changes size rapidly, we will now dynamically shift into rendering it
using low quality scaling. Once the animation completes, the image will repaint at high quality.
Scaled images will still render at high quality by default, only shifting into low quality if
the scale factor is rapidly changing. This change raises GUIMark from 21fps to 34fps on my machine.

Rewrite the Image draw method to avoid the use of throwaway CG subimages. Instead the entire image is
always drawn (with the appropriate clip and scale set up to make sure the correct subimage portion shows up
in the destination rect). This change raises GUIMark from 34fps to 43fps on my machine.

Reviewed by Darin

  • platform/graphics/GraphicsContext.cpp: (WebCore::GraphicsContext::drawImage):
  • platform/graphics/cg/ImageCG.cpp: (WebCore::BitmapImage::draw):
  • rendering/RenderImage.cpp: (WebCore::RenderImageScaleData::RenderImageScaleData): (WebCore::RenderImageScaleData::~RenderImageScaleData): (WebCore::RenderImageScaleData::size): (WebCore::RenderImageScaleData::time): (WebCore::RenderImageScaleData::useLowQualityScale): (WebCore::RenderImageScaleData::hiqhQualityRepaintTimer): (WebCore::RenderImageScaleData::setSize): (WebCore::RenderImageScaleData::setTime): (WebCore::RenderImageScaleData::setUseLowQualityScale): (WebCore::RenderImageScaleObserver::shouldImagePaintAtLowQuality): (WebCore::RenderImageScaleObserver::imageDestroyed): (WebCore::RenderImageScaleObserver::highQualityRepaintTimerFired): (WebCore::RenderImage::highQualityRepaintTimerFired): (WebCore::RenderImage::~RenderImage): (WebCore::RenderImage::paintReplaced):
  • rendering/RenderImage.h:
Location:
trunk/WebCore
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/WebCore/ChangeLog

    r34209 r34210  
     12008-05-29  David Hyatt  <hyatt@apple.com>
     2
     3        Improve the performance of the GUIMark benchmark by 2x in the CoreGraphics code path.
     4
     5        Whenever a foreground image changes size rapidly, we will now dynamically shift into rendering it
     6        using low quality scaling.  Once the animation completes, the image will repaint at high quality.
     7        Scaled images will still render at high quality by default, only shifting into low quality if
     8        the scale factor is rapidly changing.  This change raises GUIMark from 21fps to 34fps on my machine.
     9
     10        Rewrite the Image draw method to avoid the use of throwaway CG subimages.  Instead the entire image is
     11        always drawn (with the appropriate clip and scale set up to make sure the correct subimage portion shows up
     12        in the destination rect).  This change raises GUIMark from 34fps to 43fps on my machine.
     13
     14        Reviewed by Darin
     15
     16        * platform/graphics/GraphicsContext.cpp:
     17        (WebCore::GraphicsContext::drawImage):
     18        * platform/graphics/cg/ImageCG.cpp:
     19        (WebCore::BitmapImage::draw):
     20        * rendering/RenderImage.cpp:
     21        (WebCore::RenderImageScaleData::RenderImageScaleData):
     22        (WebCore::RenderImageScaleData::~RenderImageScaleData):
     23        (WebCore::RenderImageScaleData::size):
     24        (WebCore::RenderImageScaleData::time):
     25        (WebCore::RenderImageScaleData::useLowQualityScale):
     26        (WebCore::RenderImageScaleData::hiqhQualityRepaintTimer):
     27        (WebCore::RenderImageScaleData::setSize):
     28        (WebCore::RenderImageScaleData::setTime):
     29        (WebCore::RenderImageScaleData::setUseLowQualityScale):
     30        (WebCore::RenderImageScaleObserver::shouldImagePaintAtLowQuality):
     31        (WebCore::RenderImageScaleObserver::imageDestroyed):
     32        (WebCore::RenderImageScaleObserver::highQualityRepaintTimerFired):
     33        (WebCore::RenderImage::highQualityRepaintTimerFired):
     34        (WebCore::RenderImage::~RenderImage):
     35        (WebCore::RenderImage::paintReplaced):
     36        * rendering/RenderImage.h:
     37
    1382008-05-29  Justin Garcia  <justin.garcia@apple.com>
    239
  • trunk/WebCore/platform/graphics/BitmapImage.h

    r32302 r34210  
    100100    ~BitmapImage();
    101101   
     102    virtual bool isBitmapImage() const { return true; }
     103   
    102104    virtual IntSize size() const;
    103105
  • trunk/WebCore/platform/graphics/GraphicsContext.cpp

    r32722 r34210  
    330330}
    331331
    332 static const int cInterpolationCutoff = 800 * 800;
    333 
    334332void GraphicsContext::drawImage(Image* image, const FloatRect& dest, const FloatRect& src, CompositeOperator op, bool useLowQualityScale)
    335333{
     
    352350        th = image->height();
    353351
    354     bool shouldUseLowQualityInterpolation = useLowQualityScale && (tsw != tw || tsh != th) && tsw * tsh > cInterpolationCutoff;
    355     if (shouldUseLowQualityInterpolation) {
     352    if (useLowQualityScale) {
    356353        save();
    357354        setUseLowQualityImageInterpolation(true);
    358355    }
    359356    image->draw(this, FloatRect(dest.location(), FloatSize(tw, th)), FloatRect(src.location(), FloatSize(tsw, tsh)), op);
    360     if (shouldUseLowQualityInterpolation)
     357    if (useLowQualityScale)
    361358        restore();
    362359}
  • trunk/WebCore/platform/graphics/Image.h

    r31830 r34210  
    8080    static bool supportsType(const String&);
    8181
     82    virtual bool isBitmapImage() const { return false; }
     83   
    8284    bool isNull() const;
    8385
  • trunk/WebCore/platform/graphics/cg/ImageCG.cpp

    r31961 r34210  
    127127}
    128128
    129 void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& dstRect, const FloatRect& srcRect, CompositeOperator compositeOp)
    130 {
    131     CGRect fr = ctxt->roundToDevicePixels(srcRect);
    132     CGRect ir = ctxt->roundToDevicePixels(dstRect);
    133 
     129void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator compositeOp)
     130{
    134131    CGImageRef image = frameAtIndex(m_currentFrame);
    135132    if (!image) // If it's too early we won't have an image yet.
     
    137134   
    138135    if (mayFillWithSolidColor()) {
    139         fillWithSolidColor(ctxt, ir, solidColor(), compositeOp);
    140         return;
    141     }
    142 
    143     // Get the height (in adjusted, i.e. scaled, coords) of the portion of the image
    144     // that is currently decoded.  This could be less that the actual height.
    145     CGSize selfSize = size();                          // full image size, in pixels
    146     float curHeight = CGImageGetHeight(image);         // height of loaded portion, in pixels
    147    
    148     CGSize adjustedSize = selfSize;
    149     if (curHeight < selfSize.height) {
    150         adjustedSize.height *= curHeight / selfSize.height;
    151 
    152         // Is the amount of available bands less than what we need to draw?  If so,
    153         // we may have to clip 'fr' if it goes outside the available bounds.
    154         if (CGRectGetMaxY(fr) > adjustedSize.height) {
    155             float frHeight = adjustedSize.height - fr.origin.y; // clip fr to available bounds
    156             if (frHeight <= 0)
    157                 return;                                             // clipped out entirely
    158             ir.size.height *= (frHeight / fr.size.height);    // scale ir proportionally to fr
    159             fr.size.height = frHeight;
    160         }
     136        fillWithSolidColor(ctxt, destRect, solidColor(), compositeOp);
     137        return;
    161138    }
    162139
     
    164141    ctxt->save();
    165142
     143    // If the source rect is a subportion of the image, then we compute an inflated destination rect that will hold the entire image
     144    // and then set a clip to the portion that we want to display.
     145    CGSize selfSize = size();
     146    FloatRect adjustedDestRect = destRect;
     147    if (srcRect.width() != selfSize.width || srcRect.height() != selfSize.height) {
     148        // A subportion of the image is drawing.  Adjust the destination rect to
     149        // account for this.
     150        float xScale = srcRect.width() / destRect.width();
     151        float yScale = srcRect.height() / destRect.height();
     152       
     153        adjustedDestRect.setLocation(FloatPoint(destRect.x() - srcRect.x() / xScale, destRect.y() - srcRect.y() / yScale));
     154        adjustedDestRect.setSize(FloatSize(size().width() / xScale, size().height() / yScale));
     155       
     156        CGContextClipToRect(context, destRect);
     157    }
     158
     159    // If the image is only partially loaded, then shrink the destination rect that we're drawing into accordingly.
     160    float currHeight = CGImageGetHeight(image);
     161    if (currHeight < selfSize.height)
     162        adjustedDestRect.setHeight(adjustedDestRect.height() * currHeight / selfSize.height);
     163
    166164    // Flip the coords.
    167165    ctxt->setCompositeOperation(compositeOp);
    168     CGContextTranslateCTM(context, ir.origin.x, ir.origin.y + ir.size.height);
     166    CGContextTranslateCTM(context, adjustedDestRect.x(), adjustedDestRect.bottom());
    169167    CGContextScaleCTM(context, 1, -1);
    170 
    171     // Translated to origin, now draw at 0,0.
    172     ir.origin.x = ir.origin.y = 0;
    173    
    174     // If we're drawing a sub portion of the image then create
    175     // a image for the sub portion and draw that.
    176     // Test using example site at http://www.meyerweb.com/eric/css/edge/complexspiral/demo.html
    177     if (fr.size.width != adjustedSize.width || fr.size.height != adjustedSize.height) {
    178         // Convert ft to image pixel coords:
    179         float xscale = adjustedSize.width / selfSize.width;
    180         float yscale = adjustedSize.height / curHeight;     // yes, curHeight, not selfSize.height!
    181         fr.origin.x /= xscale;
    182         fr.origin.y /= yscale;
    183         fr.size.width /= xscale;
    184         fr.size.height /= yscale;
    185        
    186         image = CGImageCreateWithImageInRect(image, fr);
    187         if (image) {
    188             CGContextDrawImage(context, ir, image);
    189             CFRelease(image);
    190         }
    191     } else // Draw the whole image.
    192         CGContextDrawImage(context, ir, image);
     168    adjustedDestRect.setLocation(FloatPoint());
     169
     170    // Draw the image.
     171    CGContextDrawImage(context, adjustedDestRect, image);
    193172       
    194173    ctxt->restore();
  • trunk/WebCore/rendering/RenderImage.cpp

    r31981 r34210  
    3737#include "Page.h"
    3838#include "RenderView.h"
     39#include "SystemTime.h"
    3940
    4041using namespace std;
    4142
    4243namespace WebCore {
     44
     45static const double cInterpolationCutoff = 800. * 800.;
     46static const double cLowQualityTimeThreshold = 0.050; // 50 ms
     47
     48class RenderImageScaleData {
     49public:
     50    RenderImageScaleData(RenderImage* image, const IntSize& size, double time, bool lowQualityScale)
     51        : m_size(size)
     52        , m_time(time)
     53        , m_lowQualityScale(lowQualityScale)
     54        , m_highQualityRepaintTimer(image, &RenderImage::highQualityRepaintTimerFired)
     55    {
     56    }
     57
     58    ~RenderImageScaleData()
     59    {
     60        m_highQualityRepaintTimer.stop();
     61    }
     62   
     63    const IntSize& size() const { return m_size; }
     64    double time() const { return m_time; }
     65    bool useLowQualityScale() const { return m_lowQualityScale; }
     66    Timer<RenderImage>& hiqhQualityRepaintTimer() { return m_highQualityRepaintTimer; }
     67
     68    void setSize(const IntSize& s) { m_size = s; }
     69    void setTime(double t) { m_time = t; }
     70    void setUseLowQualityScale(bool b)
     71    {
     72        m_highQualityRepaintTimer.stop();
     73        m_lowQualityScale = b;
     74        if (b)
     75            m_highQualityRepaintTimer.startOneShot(cLowQualityTimeThreshold);
     76    }
     77   
     78private:
     79    IntSize m_size;
     80    double m_time;
     81    bool m_lowQualityScale;
     82    Timer<RenderImage> m_highQualityRepaintTimer;
     83};
     84
     85class RenderImageScaleObserver
     86{
     87public:
     88    static bool shouldImagePaintAtLowQuality(RenderImage*, const IntSize&);
     89
     90    static void imageDestroyed(RenderImage* image)
     91    {
     92        if (gImages) {
     93            RenderImageScaleData* data = gImages->take(image);
     94            delete data;
     95            if (gImages->size() == 0) {
     96                delete gImages;
     97                gImages = 0;
     98            }
     99        }
     100    }
     101   
     102    static void highQualityRepaintTimerFired(RenderImage* image)
     103    {
     104        RenderImageScaleObserver::imageDestroyed(image);
     105        image->repaint();
     106    }
     107   
     108    static HashMap<RenderImage*, RenderImageScaleData*>* gImages;
     109};
     110
     111bool RenderImageScaleObserver::shouldImagePaintAtLowQuality(RenderImage* image, const IntSize& size)
     112{
     113    // If the image is not a bitmap image, then none of this is relevant and we just paint at high
     114    // quality.
     115    if (!image->image() || !image->image()->isBitmapImage())
     116        return false;
     117
     118    // Make sure to use the unzoomed image size, since if a full page zoom is in effect, the image
     119    // is actually being scaled.
     120    IntSize imageSize(image->image()->width(), image->image()->height());
     121
     122    // Look ourselves up in the hashtable.
     123    RenderImageScaleData* data = 0;
     124    if (gImages)
     125        data = gImages->get(image);
     126
     127    if (imageSize == size) {
     128        // There is no scale in effect.  If we had a scale in effect before, we can just delete this data.
     129        if (data) {
     130            gImages->remove(image);
     131            delete data;
     132        }
     133        return false;
     134    }
     135
     136    // There is no need to hash scaled images that always use low quality mode when the page demands it.  This is the iChat case.
     137    if (image->document()->page()->inLowQualityImageInterpolationMode()) {
     138        double totalPixels = static_cast<double>(image->image()->width()) * static_cast<double>(image->image()->height());
     139        if (totalPixels > cInterpolationCutoff)
     140            return true;
     141    }
     142
     143    // If there is no data yet, we will paint the first scale at high quality and record the paint time in case a second scale happens
     144    // very soon.
     145    if (!data) {
     146        data = new RenderImageScaleData(image, size, currentTime(), false);
     147        if (!gImages)
     148            gImages = new HashMap<RenderImage*, RenderImageScaleData*>;
     149        gImages->set(image, data);
     150        return false;
     151    }
     152
     153    // We are scaled, but we painted already at this size, so just keep using whatever mode we last painted with.
     154    if (data->size() == size)
     155        return data->useLowQualityScale();
     156
     157    // We have data and our size just changed.  If this change happened quickly, go into low quality mode and then set a repaint
     158    // timer to paint in high quality mode.  Otherwise it is ok to just paint in high quality mode.
     159    double newTime = currentTime();
     160    data->setUseLowQualityScale(newTime - data->time() < cLowQualityTimeThreshold);
     161    data->setTime(newTime);
     162    data->setSize(size);
     163    return data->useLowQualityScale();
     164}
     165
     166HashMap<RenderImage*, RenderImageScaleData*>* RenderImageScaleObserver::gImages = 0;
     167
     168void RenderImage::highQualityRepaintTimerFired(Timer<RenderImage>* timer)
     169{
     170    RenderImageScaleObserver::highQualityRepaintTimerFired(this);
     171}
    43172
    44173using namespace HTMLNames;
     
    55184    if (m_cachedImage)
    56185        m_cachedImage->removeClient(this);
     186    RenderImageScaleObserver::imageDestroyed(this);
    57187}
    58188
     
    255385#endif
    256386
    257         IntRect rect(IntPoint(tx + leftBorder + leftPad, ty + topBorder + topPad), IntSize(cWidth, cHeight));
    258 
     387        IntSize contentSize(cWidth, cHeight);
     388        bool useLowQualityScaling = RenderImageScaleObserver::shouldImagePaintAtLowQuality(this, contentSize);
     389        IntRect rect(IntPoint(tx + leftBorder + leftPad, ty + topBorder + topPad), contentSize);
    259390        HTMLImageElement* imageElt = (element() && element()->hasTagName(imgTag)) ? static_cast<HTMLImageElement*>(element()) : 0;
    260391        CompositeOperator compositeOperator = imageElt ? imageElt->compositeOperator() : CompositeSourceOver;
    261         context->drawImage(image(cWidth, cHeight), rect, compositeOperator, document()->page()->inLowQualityImageInterpolationMode());
     392        context->drawImage(image(cWidth, cHeight), rect, compositeOperator, useLowQualityScaling);
    262393    }
    263394}
  • trunk/WebCore/rendering/RenderImage.h

    r31981 r34210  
    6969    virtual bool hasImage() const { return m_cachedImage; }
    7070
     71    void highQualityRepaintTimerFired(Timer<RenderImage>*);
     72
    7173protected:
    7274    virtual Image* image(int w = 0, int h = 0) { return m_cachedImage ? m_cachedImage->image() : nullImage(); }
     
    9698
    9799    static Image* nullImage();
     100   
     101    friend class RenderImageScaleObserver;
    98102};
    99103
Note: See TracChangeset for help on using the changeset viewer.