RESOLVED FIXED 71733
Repaint broken when children of filtered SVG elements are updated
https://bugs.webkit.org/show_bug.cgi?id=71733
Summary Repaint broken when children of filtered SVG elements are updated
Joel Webber
Reported 2011-11-07 14:20:23 PST
When a parent (e.g., <g>) SVG element has a filter applied, and one of its children is updated (in such a way that only a sub-rectangle of the outer element would normally need to be repainted), webkit re-renders the element incorrectly. You can see this in the attached HTML file -- both squares should be green, but the second usually turns up red, because the element doesn't repaint correctly when the child's fill color is updated (on a 1ms timer). This bug manifests in several different ways -- the attached example is the simplest reproduction I could get. Generally speaking, what's happening is as follows: - On the first paint, the SVG filter code allocates an ImageBuffer, paints the results of the filter into it, then caches it. - Subsequent repaints just use the cached ImageBuffer. - When you update the child, it kicks off a repaint for the bounds of the child's RenderObject. - When that paint comes back down the pipeline, the <g> with the filter applied allocates a new ImageBuffer. - The PaintInfo specifies that only the child's bounds needs to be repainted. - The filter's *new* ImageBuffer is only partially painted. - This mostly works, because the part of the filter's ImageBuffer that overlaps the paint rectangle is copied to the screen. - But the *next* time we repaint the <g> from this ImageBuffer cache, it only contains a subset of the pixels it needs. - This causes seemingly-random parts of the <g> to get blown away visually.
Attachments
Missing example (976 bytes, text/html)
2011-11-09 07:28 PST, Joel Webber
no flags
Patch (9.84 KB, patch)
2011-11-09 07:33 PST, Joel Webber
no flags
Patch (9.19 KB, patch)
2011-11-10 10:31 PST, Joel Webber
no flags
Patch (9.85 KB, patch)
2011-11-10 14:08 PST, Joel Webber
no flags
Added the requested '.' (9.21 KB, patch)
2011-11-10 14:14 PST, Joel Webber
no flags
Julien Chaffraix
Comment 1 2011-11-08 10:27:32 PST
(In reply to comment #0) > You can see this in the attached HTML file -- both squares should be green, but the second usually turns up red, because the element doesn't repaint correctly when the child's fill color is updated (on a 1ms timer). Joel, it looks like the attachment did not make it. Could you upload it (again)?
Joel Webber
Comment 2 2011-11-09 07:28:42 PST
Created attachment 114273 [details] Missing example
Joel Webber
Comment 3 2011-11-09 07:33:41 PST
Joel Webber
Comment 4 2011-11-09 07:37:50 PST
My proposed fix for this (in the attached patch) is to modify RenderSVG[Container Root] such that they needsLayout() whenever any of their children needsLayout(). This fixes the immediate problem because the filter's ImageBuffer cache always gets fully repainted whenever a child is modified. I also believe this will fix other probably-as-yet-unreported bugs with SVG filters, because repainting only the child's bounds is insufficient for filters with kernel sizes > 1px. I'm not attached to the approach, but I think it's pretty straightforward -- any suggestions on the details are very much welcome.
Joel Webber
Comment 5 2011-11-09 07:43:51 PST
A couple of notes on the test case: - This test only fails intermittently on trunk. If you load the attached HTML file, you'll see that it occasionally renders correctly (both squares green), but most of the time you'll see that the bottom-right square is red. It always works correctly with the patch applied. - I have another test that more directly models the bug, by changing a child of the <g> element, which causes the rest of the cached ImageBuffer to get blown away (as in the bug description). But for the life of me I can't get it to fail when running in test_shell or as a layout test. I believe this has something to do with differences between the painting logic in test_shell and chrome/safari.
Nikolas Zimmermann
Comment 6 2011-11-10 08:45:37 PST
Comment on attachment 114275 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=114275&action=review First round of review, thanks for tackling this! > Source/WebCore/rendering/svg/RenderSVGContainer.cpp:57 > + SVGRenderSupport::setNeedsLayoutForFilteredContainer(this); I'd rename this to "setContainerNeedsLayoutIfNeeded". > Source/WebCore/rendering/svg/SVGRenderSupport.cpp:310 > + if (object->normalChildNeedsLayout()) { Use early return here. > Source/WebCore/rendering/svg/SVGRenderSupport.cpp:312 > + if (resources && resources->filter()) And here, for any of the cases. > Source/WebCore/rendering/svg/SVGRenderSupport.cpp:313 > + object->setNeedsLayout(true); This is dangerous, you're mutating the setNeedsLayout state of all containing blocks as well, are you sure you want that? Doing that from within layout() that looks suspicious. I'd propose to amend RenderSVGContainers way of laying out the children, where we currently do: SVGRenderSupport::layoutChildren(this, selfNeedsLayout()); This could just read layoutChildren(this, selfNeedsLayout() || filtersForceContainerLayout()) where filtersForceContainerLayout() contains your object->normalChildNeedsLayout() && resources->filter() check. Sounds better, eh? > LayoutTests/svg/repaint/filter-child-repaint.svg:13 > + <feOffset in="SourceGraphic" dx="0" dy="0" result="topCopy"/> > + <feGaussianBlur in="SourceAlpha" stdDeviation="2" result="shadow"/> > + <feOffset in="shadow" dx="3" dy="3" result="movedShadow"/> > + <feMerge> > + <feMergeNode in="movedShadow"/> > + <feMergeNode in="topCopy"/> > + </feMerge> > + </filter> You could use the new feDropShadow primitive, would simplify the test. > LayoutTests/svg/repaint/filter-child-repaint.svg:15 > + Can you add a <title> section, explaining when this tests passes? Currently its hard to tell, without tracing in mind :-) > LayoutTests/svg/repaint/filter-child-repaint.svg:16 > + <g filter='url(#dropShadow)' width='128px' height='128px'> width/height are useless on <g>, you can remove it. > LayoutTests/svg/repaint/filter-child-repaint.svg:18 > + <rect width="64" height="64" style="fill:rgb(0,128,0)"/> > + <rect id='poke' x='32' y='32' width="64" height="64" style="fill:rgb(128,0,0)"/> Also you could just use fill="green" here.
Joel Webber
Comment 7 2011-11-10 10:18:46 PST
Comment on attachment 114275 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=114275&action=review >> Source/WebCore/rendering/svg/RenderSVGContainer.cpp:57 > > I'd rename this to "setContainerNeedsLayoutIfNeeded". Done. >> Source/WebCore/rendering/svg/SVGRenderSupport.cpp:310 >> + if (object->normalChildNeedsLayout()) { > > Use early return here. Done. >> Source/WebCore/rendering/svg/SVGRenderSupport.cpp:312 >> + if (resources && resources->filter()) > > And here, for any of the cases. Done. >> Source/WebCore/rendering/svg/SVGRenderSupport.cpp:313 >> + object->setNeedsLayout(true); > > This is dangerous, you're mutating the setNeedsLayout state of all containing blocks as well, are you sure you want that? Doing that from within layout() that looks suspicious. > I'd propose to amend RenderSVGContainers way of laying out the children, where we currently do: > SVGRenderSupport::layoutChildren(this, selfNeedsLayout()); > > This could just read layoutChildren(this, selfNeedsLayout() || filtersForceContainerLayout()) > where filtersForceContainerLayout() contains your object->normalChildNeedsLayout() && resources->filter() check. > Sounds better, eh? Thanks for clarifying that -- it felt a bit odd to be flipping that bit here, and I failed to notice the hierarchical behavior of calling setNeedsLayout(). I've refactored this code as you suggest, and it does indeed feel better. >> LayoutTests/svg/repaint/filter-child-repaint.svg:13 >> + </filter> > > You could use the new feDropShadow primitive, would simplify the test. Done, thanks. That one wasn't in the SVG 1.1 docs, but I see it in WebKit now. (This awkward implementation, btw, came from a chromium bug I was investigating) >> LayoutTests/svg/repaint/filter-child-repaint.svg:15 >> + > > Can you add a <title> section, explaining when this tests passes? > Currently its hard to tell, without tracing in mind :-) Done. It's a bit hard to tell what the norms are from existing tests :) >> LayoutTests/svg/repaint/filter-child-repaint.svg:16 >> + <g filter='url(#dropShadow)' width='128px' height='128px'> > > width/height are useless on <g>, you can remove it. Done. >> LayoutTests/svg/repaint/filter-child-repaint.svg:18 >> + <rect id='poke' x='32' y='32' width="64" height="64" style="fill:rgb(128,0,0)"/> > > Also you could just use fill="green" here. Done (old habits die hard...).
Joel Webber
Comment 8 2011-11-10 10:31:56 PST
Nikolas Zimmermann
Comment 9 2011-11-10 13:55:25 PST
Comment on attachment 114526 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=114526&action=review Looks good, r=me! > Source/WebCore/rendering/svg/SVGRenderSupport.h:58 > + // Determines whether a container needs to be laid out because it's filtered and a child is being laid out Missed period.
Joel Webber
Comment 10 2011-11-10 14:08:59 PST
Joel Webber
Comment 11 2011-11-10 14:10:05 PST
Please ignore that last patch. There's commit-log diff garbage in there.
Joel Webber
Comment 12 2011-11-10 14:14:19 PST
Created attachment 114569 [details] Added the requested '.'
Joel Webber
Comment 13 2011-11-10 14:19:12 PST
Many thanks for the prompt review, Nikolas. Looks like I've managed to reset the review state by adding that trivial period to the comment and re-uploading. Whenever you have a free moment to R+ it, it would be greatly appreciated.
Nikolas Zimmermann
Comment 14 2011-11-11 07:18:10 PST
Comment on attachment 114569 [details] Added the requested '.' Thanks!
WebKit Review Bot
Comment 15 2011-11-11 15:41:13 PST
Comment on attachment 114569 [details] Added the requested '.' Clearing flags on attachment: 114569 Committed r100036: <http://trac.webkit.org/changeset/100036>
WebKit Review Bot
Comment 16 2011-11-11 15:41:19 PST
All reviewed patches have been landed. Closing bug.
Ryosuke Niwa
Comment 17 2011-11-25 14:36:03 PST
The test added by this patch is failing on Mac (the second box is red).
Note You need to log in before you can comment on or make changes to this bug.