[CSS Filters] Drop-shadow and blur can avoid using full source image
authorachicu@adobe.com <achicu@adobe.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 18 Apr 2012 17:37:41 +0000 (17:37 +0000)
committerachicu@adobe.com <achicu@adobe.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 18 Apr 2012 17:37:41 +0000 (17:37 +0000)
https://bugs.webkit.org/show_bug.cgi?id=81263

Reviewed by Dean Jackson.

Instead of using the full bounding box of the RenderLayer we now compute the exact rectangle that is needed to
compute filter inside the dirty rectangle on screen. That's easy to calculate by reversing the filter outsets.
Even if the element is completely offscreen, but its shadow is in the viewport, we can still compute the source
rectangle that needs to be drawn in order to update the shadow correctly.

No new tests, this change doesn't have visible results and the functionality should
already be covered by previous filter tests.

* rendering/FilterEffectRenderer.cpp:
(WebCore::FilterEffectRenderer::FilterEffectRenderer):
(WebCore::FilterEffectRenderer::build): Save the outsets of the filters, so that we can use them in computeSourceImageRectForDirtyRect.
(WebCore::FilterEffectRenderer::updateBackingStoreRect): Only allocate a new source image if the size of the source rectangle changed.
(WebCore::FilterEffectRenderer::allocateBackingStoreIfNeeded): There's no need to call clearIntermediateResults() in this function. We do that after
the filter is computed.
(WebCore::FilterEffectRenderer::computeSourceImageRectForDirtyRect):
(WebCore):
(WebCore::FilterEffectRendererHelper::prepareFilterEffect):
(WebCore::FilterEffectRendererHelper::beginFilterEffect):
* rendering/FilterEffectRenderer.h:
(FilterEffectRendererHelper):
(WebCore::FilterEffectRendererHelper::repaintRect):
(FilterEffectRenderer):
* rendering/RenderLayer.cpp:
(WebCore::RenderLayer::paintLayerContents):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@114529 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/rendering/FilterEffectRenderer.cpp
Source/WebCore/rendering/FilterEffectRenderer.h
Source/WebCore/rendering/RenderLayer.cpp

index 5d13677..7007a5e 100644 (file)
@@ -1,3 +1,35 @@
+2012-04-18  Alexandru Chiculita  <achicu@adobe.com>
+
+        [CSS Filters] Drop-shadow and blur can avoid using full source image
+        https://bugs.webkit.org/show_bug.cgi?id=81263
+
+        Reviewed by Dean Jackson.
+
+        Instead of using the full bounding box of the RenderLayer we now compute the exact rectangle that is needed to 
+        compute filter inside the dirty rectangle on screen. That's easy to calculate by reversing the filter outsets.
+        Even if the element is completely offscreen, but its shadow is in the viewport, we can still compute the source 
+        rectangle that needs to be drawn in order to update the shadow correctly.
+
+        No new tests, this change doesn't have visible results and the functionality should
+        already be covered by previous filter tests.
+
+        * rendering/FilterEffectRenderer.cpp:
+        (WebCore::FilterEffectRenderer::FilterEffectRenderer):
+        (WebCore::FilterEffectRenderer::build): Save the outsets of the filters, so that we can use them in computeSourceImageRectForDirtyRect.
+        (WebCore::FilterEffectRenderer::updateBackingStoreRect): Only allocate a new source image if the size of the source rectangle changed.
+        (WebCore::FilterEffectRenderer::allocateBackingStoreIfNeeded): There's no need to call clearIntermediateResults() in this function. We do that after
+        the filter is computed.
+        (WebCore::FilterEffectRenderer::computeSourceImageRectForDirtyRect):
+        (WebCore):
+        (WebCore::FilterEffectRendererHelper::prepareFilterEffect):
+        (WebCore::FilterEffectRendererHelper::beginFilterEffect):
+        * rendering/FilterEffectRenderer.h:
+        (FilterEffectRendererHelper):
+        (WebCore::FilterEffectRendererHelper::repaintRect):
+        (FilterEffectRenderer):
+        * rendering/RenderLayer.cpp:
+        (WebCore::RenderLayer::paintLayerContents):
+
 2012-04-18  Dominik Röttsches  <dominik.rottsches@linux.intel.com>
 
         [EFL][DRT] @font-face support fails on EFL
index cfedac2..be43904 100644 (file)
@@ -86,6 +86,10 @@ static bool isCSSCustomFilterEnabled(Document* document)
 
 FilterEffectRenderer::FilterEffectRenderer(FilterEffectObserver* observer)
     : m_observer(observer)
+    , m_topOutset(0)
+    , m_rightOutset(0)
+    , m_bottomOutset(0)
+    , m_leftOutset(0)
     , m_graphicsBufferAttached(false)
     , m_hasFilterThatMovesPixels(false)
 {
@@ -114,6 +118,8 @@ bool FilterEffectRenderer::build(Document* document, const FilterOperations& ope
 #endif
 
     m_hasFilterThatMovesPixels = operations.hasFilterThatMovesPixels();
+    if (m_hasFilterThatMovesPixels)
+        operations.getOutsets(m_topOutset, m_rightOutset, m_bottomOutset, m_leftOutset);
     m_effects.clear();
 
     RefPtr<FilterEffect> previousEffect;
@@ -306,7 +312,7 @@ bool FilterEffectRenderer::build(Document* document, const FilterOperations& ope
     return true;
 }
 
-bool FilterEffectRenderer::updateBackingStore(const FloatRect& filterRect)
+bool FilterEffectRenderer::updateBackingStoreRect(const FloatRect& filterRect)
 {
     if (!filterRect.isZero() && isFilterSizeValid(filterRect)) {
         FloatRect currentSourceRect = sourceImageRect();
@@ -331,17 +337,17 @@ void FilterEffectRenderer::removeCustomFilterClients()
 }
 #endif
 
-void FilterEffectRenderer::prepare()
+void FilterEffectRenderer::allocateBackingStoreIfNeeded()
 {
     // At this point the effect chain has been built, and the
     // source image sizes set. We just need to attach the graphic
     // buffer if we have not yet done so.
     if (!m_graphicsBufferAttached) {
         IntSize logicalSize(m_sourceDrawingRegion.width(), m_sourceDrawingRegion.height());
-        setSourceImage(ImageBuffer::create(logicalSize, 1, ColorSpaceDeviceRGB, renderingMode()));
+        if (!sourceImage() || sourceImage()->logicalSize() != logicalSize)
+            setSourceImage(ImageBuffer::create(logicalSize, 1, ColorSpaceDeviceRGB, renderingMode()));
         m_graphicsBufferAttached = true;
     }
-    clearIntermediateResults();
 }
 
 void FilterEffectRenderer::clearIntermediateResults()
@@ -356,30 +362,46 @@ void FilterEffectRenderer::apply()
     lastEffect()->apply();
 }
 
-const LayoutRect& FilterEffectRendererHelper::prepareFilterEffect(RenderLayer* renderLayer, const LayoutRect& filterBoxRect, const LayoutRect& dirtyRect, const LayoutRect& layerRepaintRect)
+LayoutRect FilterEffectRenderer::computeSourceImageRectForDirtyRect(const LayoutRect& filterBoxRect, const LayoutRect& dirtyRect)
+{
+    // The result of this function is the area in the "filterBoxRect" that needs to be repainted, so that we fully cover the "dirtyRect".
+    LayoutRect rectForRepaint = dirtyRect;
+    if (hasFilterThatMovesPixels()) {
+        // Note that the outsets are reversed here because we are going backwards -> we have the dirty rect and
+        // need to find out what is the rectangle that might influence the result inside that dirty rect.
+        rectForRepaint.move(-m_rightOutset, -m_bottomOutset);
+        rectForRepaint.expand(m_leftOutset + m_rightOutset, m_topOutset + m_bottomOutset);
+    }
+    rectForRepaint.intersect(filterBoxRect);
+    return rectForRepaint;
+}
+
+bool FilterEffectRendererHelper::prepareFilterEffect(RenderLayer* renderLayer, const LayoutRect& filterBoxRect, const LayoutRect& dirtyRect, const LayoutRect& layerRepaintRect)
 {
     ASSERT(m_haveFilterEffect && renderLayer->filter());
     m_renderLayer = renderLayer;
-    m_dirtyRect = dirtyRect;
-    m_dirtyRect.intersect(filterBoxRect);
+    m_repaintRect = dirtyRect;
 
     FilterEffectRenderer* filter = renderLayer->filter();
-
-    // Some filters need the whole original area in order to recalculate correctly.
-    // Such filters include blur, drop-shadow and shaders. For that reason,
-    // we keep the whole image buffer in memory and repaint only dirty areas.
-    bool hasFilterThatMovesPixels = filter->hasFilterThatMovesPixels();
-    LayoutRect filterSourceRect = hasFilterThatMovesPixels ? filterBoxRect : m_dirtyRect;
+    LayoutRect filterSourceRect = filter->computeSourceImageRectForDirtyRect(filterBoxRect, dirtyRect);
     m_paintOffset = filterSourceRect.location();
-    filterSourceRect.setLocation(LayoutPoint());
 
-    bool hasUpdatedBackingStore = filter->updateBackingStore(filterSourceRect);
-    if (hasFilterThatMovesPixels)
-        m_dirtyRect.unite(hasUpdatedBackingStore ? filterBoxRect : layerRepaintRect);
-    
-    filter->prepare();
+    if (filterSourceRect.isEmpty()) {
+        // The dirty rect is not in view, just bail out.
+        m_haveFilterEffect = false;
+        return false;
+    }
     
-    return m_dirtyRect;
+    bool hasUpdatedBackingStore = filter->updateBackingStoreRect(filterSourceRect);
+    if (filter->hasFilterThatMovesPixels()) {
+        if (hasUpdatedBackingStore)
+            m_repaintRect = filterSourceRect;
+        else {
+            m_repaintRect.unite(layerRepaintRect);
+            m_repaintRect.intersect(filterSourceRect);
+        }
+    }
+    return true;
 }
    
 GraphicsContext* FilterEffectRendererHelper::beginFilterEffect(GraphicsContext* oldContext)
@@ -387,6 +409,7 @@ GraphicsContext* FilterEffectRendererHelper::beginFilterEffect(GraphicsContext*
     ASSERT(m_renderLayer);
     
     FilterEffectRenderer* filter = m_renderLayer->filter();
+    filter->allocateBackingStoreIfNeeded();
     // Paint into the context that represents the SourceGraphic of the filter.
     GraphicsContext* sourceGraphicsContext = filter->inputContext();
     if (!sourceGraphicsContext || !isFilterSizeValid(filter->filterRegion())) {
@@ -400,8 +423,8 @@ GraphicsContext* FilterEffectRendererHelper::beginFilterEffect(GraphicsContext*
     // Translate the context so that the contents of the layer is captuterd in the offscreen memory buffer.
     sourceGraphicsContext->save();
     sourceGraphicsContext->translate(-m_paintOffset.x(), -m_paintOffset.y());
-    sourceGraphicsContext->clearRect(m_dirtyRect);
-    sourceGraphicsContext->clip(m_dirtyRect);
+    sourceGraphicsContext->clearRect(m_repaintRect);
+    sourceGraphicsContext->clip(m_repaintRect);
     
     return sourceGraphicsContext;
 }
index 5ba289d..2a57575 100644 (file)
@@ -67,15 +67,16 @@ public:
     bool haveFilterEffect() const { return m_haveFilterEffect; }
     bool hasStartedFilterEffect() const { return m_savedGraphicsContext; }
 
-    const LayoutRect& prepareFilterEffect(RenderLayer*, const LayoutRect& filterBoxRect, const LayoutRect& dirtyRect, const LayoutRect& layerRepaintRect);
+    bool prepareFilterEffect(RenderLayer*, const LayoutRect& filterBoxRect, const LayoutRect& dirtyRect, const LayoutRect& layerRepaintRect);
     GraphicsContext* beginFilterEffect(GraphicsContext* oldContext);
     GraphicsContext* applyFilterEffect();
 
+    const LayoutRect& repaintRect() const { return m_repaintRect; }
 private:
     GraphicsContext* m_savedGraphicsContext;
     RenderLayer* m_renderLayer;
     LayoutPoint m_paintOffset;
-    LayoutRect m_dirtyRect;
+    LayoutRect m_repaintRect;
     bool m_haveFilterEffect;
 };
 
@@ -107,14 +108,15 @@ public:
     ImageBuffer* output() const { return lastEffect()->asImageBuffer(); }
 
     bool build(Document*, const FilterOperations&);
-    bool updateBackingStore(const FloatRect& filterRect);
+    bool updateBackingStoreRect(const FloatRect& filterRect);
+    void allocateBackingStoreIfNeeded();
     void clearIntermediateResults();
-    void prepare();
     void apply();
     
     IntRect outputRect() const { return lastEffect()->hasResult() ? lastEffect()->requestedRegionOfInputImageData(IntRect(m_filterRegion)) : IntRect(); }
 
     bool hasFilterThatMovesPixels() const { return m_hasFilterThatMovesPixels; }
+    LayoutRect computeSourceImageRectForDirtyRect(const LayoutRect& filterBoxRect, const LayoutRect& dirtyRect);
 
 private:
 #if ENABLE(CSS_SHADERS)
@@ -153,6 +155,11 @@ private:
     CustomFilterProgramList m_cachedCustomFilterPrograms;
 #endif
     
+    int m_topOutset;
+    int m_rightOutset;
+    int m_bottomOutset;
+    int m_leftOutset;
+    
     bool m_graphicsBufferAttached;
     bool m_hasFilterThatMovesPixels;
 };
index 145ba4f..6681327 100644 (file)
@@ -2983,21 +2983,24 @@ void RenderLayer::paintLayerContents(RenderLayer* rootLayer, GraphicsContext* co
         LayoutPoint rootLayerOffset;
         convertToLayerCoords(rootLayer, rootLayerOffset);
         m_filterRepaintRect.move(rootLayerOffset.x(), rootLayerOffset.y());
-        LayoutRect filterPaintDirtyRect = filterPainter.prepareFilterEffect(this, calculateLayerBounds(this, rootLayer, 0), parentPaintDirtyRect, m_filterRepaintRect);
-        m_filterRepaintRect = IntRect();
-        // Rewire the old context to a memory buffer, so that we can capture the contents of the layer.
-        // NOTE: We saved the old context in the "transparencyLayerContext" local variable, to be able to start a transparency layer
-        // on the original context and avoid duplicating "beginFilterEffect" after each transpareny layer call. Also, note that 
-        // beginTransparencyLayers will only create a single lazy transparency layer, even though it is called twice in this method.
-        context = filterPainter.beginFilterEffect(context);
-        
-        // Check that we didn't fail to allocate the graphics context for the offscreen buffer.
-        if (filterPainter.hasStartedFilterEffect()) {
-            paintDirtyRect = filterPaintDirtyRect;
-            // If the filter needs the full source image, we need to avoid using the clip rectangles.
-            // Otherwise, if for example this layer has overflow:hidden, a drop shadow will not compute correctly.
-            // Note that we will still apply the clipping on the final rendering of the filter.
-            useClipRect = !filter()->hasFilterThatMovesPixels();
+        if (filterPainter.prepareFilterEffect(this, calculateLayerBounds(this, rootLayer, 0), parentPaintDirtyRect, m_filterRepaintRect)) {
+            // Now we know for sure, that the source image will be updated, so we can revert our tracking repaint rect back to zero.
+            m_filterRepaintRect = IntRect();
+
+            // Rewire the old context to a memory buffer, so that we can capture the contents of the layer.
+            // NOTE: We saved the old context in the "transparencyLayerContext" local variable, to be able to start a transparency layer
+            // on the original context and avoid duplicating "beginFilterEffect" after each transpareny layer call. Also, note that 
+            // beginTransparencyLayers will only create a single lazy transparency layer, even though it is called twice in this method.
+            context = filterPainter.beginFilterEffect(context);
+
+            // Check that we didn't fail to allocate the graphics context for the offscreen buffer.
+            if (filterPainter.hasStartedFilterEffect()) {
+                paintDirtyRect = filterPainter.repaintRect();
+                // If the filter needs the full source image, we need to avoid using the clip rectangles.
+                // Otherwise, if for example this layer has overflow:hidden, a drop shadow will not compute correctly.
+                // Note that we will still apply the clipping on the final rendering of the filter.
+                useClipRect = !filter()->hasFilterThatMovesPixels();
+            }
         }
     }
 #endif