TextureMapper: mask should be applied after filter is applied
https://bugs.webkit.org/show_bug.cgi?id=241772

Reviewed by Don Olmstead.

If an element has both a mask-image and filters, the mask should be
applied after the filters.

BitmapTextureGL::applyFilters didn't actually apply the last filter.
It stored the last filter information in it, and applied the last
filter when blitting onto the target. If the element has a mask,
applyFilters should apply all filters before applying the mask.

* LayoutTests/compositing/masks/mask-and-drop-shadow-expected.html: Added.
* LayoutTests/compositing/masks/mask-and-drop-shadow.html: Added.
* Source/WebCore/platform/graphics/texmap/BitmapTexture.h:
(WebCore::BitmapTexture::applyFilters):
* Source/WebCore/platform/graphics/texmap/BitmapTextureGL.cpp:
(WebCore::BitmapTextureGL::applyFilters):
* Source/WebCore/platform/graphics/texmap/BitmapTextureGL.h:
* Source/WebCore/platform/graphics/texmap/TextureMapperLayer.cpp:
(WebCore::TextureMapperLayer::computeOverlapRegions):
(WebCore::TextureMapperLayer::paintIntoSurface):

Canonical link: https://commits.webkit.org/251722@main


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@295717 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/compositing/masks/mask-and-drop-shadow-expected.html b/LayoutTests/compositing/masks/mask-and-drop-shadow-expected.html
new file mode 100644
index 0000000..21978ee
--- /dev/null
+++ b/LayoutTests/compositing/masks/mask-and-drop-shadow-expected.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<style>
+    div {
+        width: 200px;
+        height: 200px;
+        background: green;
+        will-change: transform;
+        position: absolute;
+        -webkit-mask-image: linear-gradient(45deg, black, transparent);
+    }
+</style>
+
+You should see no red.
+
+<div></div>
diff --git a/LayoutTests/compositing/masks/mask-and-drop-shadow.html b/LayoutTests/compositing/masks/mask-and-drop-shadow.html
new file mode 100644
index 0000000..928eec3
--- /dev/null
+++ b/LayoutTests/compositing/masks/mask-and-drop-shadow.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<style>
+    div {
+        width: 200px;
+        height: 200px;
+        background: green;
+        filter: drop-shadow(50px 50px 0px red);
+        will-change: transform;
+        position: absolute;
+        -webkit-mask-image: linear-gradient(45deg, black, transparent);
+    }
+</style>
+
+You should see no red.
+
+<div></div>
diff --git a/Source/WebCore/platform/graphics/texmap/BitmapTexture.h b/Source/WebCore/platform/graphics/texmap/BitmapTexture.h
index 01d6a6c..36be940 100644
--- a/Source/WebCore/platform/graphics/texmap/BitmapTexture.h
+++ b/Source/WebCore/platform/graphics/texmap/BitmapTexture.h
@@ -78,7 +78,7 @@
     inline int numberOfBytes() const { return size().width() * size().height() * bpp() >> 3; }
     inline bool isOpaque() const { return !(m_flags & SupportsAlpha); }
 
-    virtual RefPtr<BitmapTexture> applyFilters(TextureMapper&, const FilterOperations&) { return this; }
+    virtual RefPtr<BitmapTexture> applyFilters(TextureMapper&, const FilterOperations&, bool) { return this; }
 
 protected:
     IntSize m_contentSize;
diff --git a/Source/WebCore/platform/graphics/texmap/BitmapTextureGL.cpp b/Source/WebCore/platform/graphics/texmap/BitmapTextureGL.cpp
index 91e8ea8..7b69f8a 100644
--- a/Source/WebCore/platform/graphics/texmap/BitmapTextureGL.cpp
+++ b/Source/WebCore/platform/graphics/texmap/BitmapTextureGL.cpp
@@ -202,7 +202,7 @@
     }
 }
 
-RefPtr<BitmapTexture> BitmapTextureGL::applyFilters(TextureMapper& textureMapper, const FilterOperations& filters)
+RefPtr<BitmapTexture> BitmapTextureGL::applyFilters(TextureMapper& textureMapper, const FilterOperations& filters, bool defersLastFilter)
 {
     if (filters.isEmpty())
         return this;
@@ -222,17 +222,14 @@
         int numPasses = getPassesRequiredForFilter(filter->type());
         for (int j = 0; j < numPasses; ++j) {
             bool last = (i == filters.size() - 1) && (j == numPasses - 1);
-            if (!last) {
-                if (!intermediateSurface)
-                    intermediateSurface = texmapGL.acquireTextureFromPool(contentSize(), BitmapTexture::SupportsAlpha);
-                texmapGL.bindSurface(intermediateSurface.get());
-            }
-
-            if (last) {
+            if (defersLastFilter && last) {
                 toBitmapTextureGL(resultSurface.get())->m_filterInfo = BitmapTextureGL::FilterInfo(filter.copyRef(), j, spareSurface.copyRef());
                 break;
             }
 
+            if (!intermediateSurface)
+                intermediateSurface = texmapGL.acquireTextureFromPool(contentSize(), BitmapTexture::SupportsAlpha);
+            texmapGL.bindSurface(intermediateSurface.get());
             texmapGL.drawFiltered(*resultSurface.get(), spareSurface.get(), *filter, j);
             if (!j && filter->type() == FilterOperation::DROP_SHADOW) {
                 spareSurface = resultSurface;
diff --git a/Source/WebCore/platform/graphics/texmap/BitmapTextureGL.h b/Source/WebCore/platform/graphics/texmap/BitmapTextureGL.h
index 5b163f6..b2a9182 100644
--- a/Source/WebCore/platform/graphics/texmap/BitmapTextureGL.h
+++ b/Source/WebCore/platform/graphics/texmap/BitmapTextureGL.h
@@ -60,7 +60,7 @@
     void updateContents(const void*, const IntRect& target, const IntPoint& sourceOffset, int bytesPerLine) override;
     bool isBackedByOpenGL() const override { return true; }
 
-    RefPtr<BitmapTexture> applyFilters(TextureMapper&, const FilterOperations&) override;
+    RefPtr<BitmapTexture> applyFilters(TextureMapper&, const FilterOperations&, bool defersLastFilter) override;
     struct FilterInfo {
         RefPtr<FilterOperation> filter;
         unsigned pass;
diff --git a/Source/WebCore/platform/graphics/texmap/TextureMapperLayer.cpp b/Source/WebCore/platform/graphics/texmap/TextureMapperLayer.cpp
index fd5b2de..98a25da 100644
--- a/Source/WebCore/platform/graphics/texmap/TextureMapperLayer.cpp
+++ b/Source/WebCore/platform/graphics/texmap/TextureMapperLayer.cpp
@@ -343,7 +343,7 @@
     else if (m_contentsLayer || m_state.solidColor.isVisible())
         localBoundingRect = m_state.contentsRect;
 
-    if (m_currentFilters.hasOutsets() && !m_state.backdropLayer) {
+    if (m_currentFilters.hasOutsets() && !m_state.backdropLayer && !m_state.masksToBounds && !m_state.maskLayer) {
         auto outsets = m_currentFilters.outsets();
         localBoundingRect.move(-outsets.left(), -outsets.top());
         localBoundingRect.expand(outsets.left() + outsets.right(), outsets.top() + outsets.bottom());
@@ -485,14 +485,16 @@
         rootLayer().paintSelfAndChildren(options);
     } else
         paintSelfAndChildren(options);
-    if (options.replicaLayer == this) {
-        if (m_state.replicaLayer->m_state.maskLayer)
-            m_state.replicaLayer->m_state.maskLayer->applyMask(options);
-    }
-    if (m_state.maskLayer)
-        m_state.maskLayer->applyMask(options);
-    options.surface = options.surface->applyFilters(options.textureMapper, m_currentFilters);
+
+    bool hasMask = !!m_state.maskLayer;
+    bool hasReplicaMask = options.replicaLayer == this && m_state.replicaLayer->m_state.maskLayer;
+    bool defersLastFilter = !hasMask && !hasReplicaMask;
+    options.surface = options.surface->applyFilters(options.textureMapper, m_currentFilters, defersLastFilter);
     options.textureMapper.bindSurface(options.surface.get());
+    if (hasMask)
+        m_state.maskLayer->applyMask(options);
+    if (hasReplicaMask)
+        m_state.replicaLayer->m_state.maskLayer->applyMask(options);
 }
 
 static void commitSurface(TextureMapperPaintOptions& options, BitmapTexture& surface, const IntRect& rect, float opacity)