haikuwebkit/LayoutTests/css3/filters/canvas-with-filter-after-re...

41 lines
940 B
HTML
Raw Permalink Normal View History

REGRESSION (Safari 15): Unable to repaint canvases with software filters when GPU Process is enabled https://bugs.webkit.org/show_bug.cgi?id=228682 rdar://79699997 Reviewed by Simon Fraser. Source/WebCore: In the case where GPU process is enabled for canvas rendering but is not enabled for DOM rendering, canvas elements with software CSS filters fail to repaint as the canvas' backing image buffer changes. In this particular scenario, we first paint the canvas' remotely-backed image buffer into the filter's input buffer, which is backed by an unaccelerated graphics context. The filter is then applied, producing an output image buffer which we then paint into the page. The bug occurs during the initial step of painting the contents of the canvas' remote image buffer into the filter's input context, which involves creating a CGImageRef from the `ImageBufferCGBackend::draw`. When asking for a CGImageRef from the canvas' image buffer (which is backed by an IOSurface that's drawn into in the GPU process), QuartzCore caches the resulting CGImageRef corresponding to the IOSurface handle in the web process. Subsequently, when we try to repaint the canvas, we'll end up using this same initial cached CGImageRef because the graphics context of the IOSurface handle in the web process hasn't been drawn into (because all the actual draw calls are made in the GPU process). As such, regardless of the current state of the canvas' image buffer, the input to the filter will always be the initial native image created from the canvas' image buffer. We avoid this problem when GPU process is disabled because the calls to update the canvas' 2D context in the web process will clear out the cached CGImageRef in QuartzCore. Additionally, we avoid this problem when drawing into accelerated contexts because QuartzCore will draw the cached CGImageRef using a codepath that reads back the up-to-date contents from the IOSurface. However, in the QuartzCore's unaccelerated (ripc) image drawing codepath, we copy the contents of the cached image right away, causing us to end up with stale image data that does not reflect the current state of the IOSurface. To work around this in Safari 15 (in both Monterey as well as downlevel versions of macOS), we deploy a similar technique as we'd previously used in r201334 to invalidate QuartzCore's cached image, but only: 1. If the seed value of the backing IOSurface has changed since the last time we've drawn the image buffer, and 2. The destination context we're painting the image buffer into is unaccelerated. Test: css3/filters/canvas-with-filter-after-repaint.html * platform/graphics/cg/ImageBufferCGBackend.cpp: (WebCore::ImageBufferCGBackend::draw): (WebCore::ImageBufferCGBackend::drawPattern): Call into `prepareToDrawIntoContext` (see below). * platform/graphics/cg/ImageBufferCGBackend.cpp: (WebCore::ImageBufferCGBackend::prepareToDrawIntoContext): * platform/graphics/cg/ImageBufferCGBackend.h: * platform/graphics/cg/ImageBufferIOSurfaceBackend.cpp: (WebCore::ImageBufferIOSurfaceBackend::prepareToDrawIntoContext): Add a new subclassable method on ImageBufferCGBackend that's invoked before drawing the contents of the image buffer into a given destination GraphicsContext. We use this opportunity in ImageBufferIOSurfaceBackend to notice if the IOSurface seed count has updated since we've last drawn the image buffer, and intentionally emit a no-op drawing command (i.e. filling an empty rect) in the graphics context to invalidate QuartzCore's cached CGImageRef. Note that we only deploy this workaround when drawing into unaccelerated contexts (see above for more details). (WebCore::ImageBufferIOSurfaceBackend::invalidateCachedNativeImage const): Pull the no-op drawing command out into a separate helper method, with a comment describing why this exists. (WebCore::ImageBufferIOSurfaceBackend::drawConsuming): (WebCore::ImageBufferIOSurfaceBackend::copyCGImageForEncoding const): * platform/graphics/cg/ImageBufferIOSurfaceBackend.h: * platform/graphics/cocoa/IOSurface.h: * platform/graphics/cocoa/IOSurface.mm: (WebCore::IOSurface::seed const): Add a helper method to grab the seed value of an IOSurface. LayoutTests: * css3/filters/canvas-with-filter-after-repaint-expected.html: Added. * css3/filters/canvas-with-filter-after-repaint.html: Added. Canonical link: https://commits.webkit.org/240170@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@280543 268f45cc-cd09-0410-ab3c-d52691b4dbfc
2021-08-02 17:44:26 +00:00
<!DOCTYPE html>
<html>
<head>
<style>
canvas {
width: 200px;
height: 200px;
filter: drop-shadow(0px 0px 4px black) drop-shadow(0px 0px 4px black);
}
</style>
<script src="../../resources/ui-helper.js"></script>
<script>
if (window.testRunner)
testRunner.waitUntilDone();
addEventListener("load", async () => {
let context = document.querySelector("canvas").getContext("2d");
context.lineWidth = 5;
context.moveTo(150, 150);
context.lineTo(250, 250);
context.moveTo(250, 150);
context.lineTo(150, 250);
context.strokeStyle = "red";
context.stroke();
await UIHelper.renderingUpdate();
context.clearRect(0, 0, 400, 400);
await UIHelper.renderingUpdate();
if (window.testRunner)
testRunner.notifyDone();
});
</script>
</head>
<body>
<canvas width="400" height="400"></canvas>
<p>The above canvas should be completely blank shortly after page load.</p>
</body>
</html>