Repository: WebKit/WebKit
Pull request #42185: [Gtk][WPE][Skia] Use SkPicture(Recorder) instead of DisplayList(Recorder) for paint command recording in multi-threaded rendering mode
Author: login: nikolaszimmermann
Labels: name: WPE WebKit
description: WebKit WPE component
Created: 2025-03-10T10:18:26Z | Merged: 2025-03-25T09:59:49Z

#### fca89822979c51c7dbfe019c6808fb3e8cccbefc
<pre>
[Gtk][WPE][Skia] Use SkPicture(Recorder) instead of DisplayList(Recorder) for paint command recording in multi-threaded rendering mode
<a href="https://bugs.webkit.org/show_bug.cgi?id=289456">https://bugs.webkit.org/show_bug.cgi?id=289456</a>

Reviewed by Carlos Garcia Campos.

For multi-threaded rendering Gtk/WPE ports serialized the painting
commands for a given layer and tile into a DisplayList, and dispatched
a task to a WorkerPool to replay the DisplayList in a secondary thread.

The cross-thread usage of DisplayLists is fragile, and the way we use
DisplayList wouldn&apos;t work on Apple ports, due to timing differences when
a new frame rendering starts. While DisplayList (and its ResourceHeap) protects
the referenced WebCore objects (such as Gradient / Pattern etc.) from
destruction, we&apos;re not protected from mutations between the recording of
a DisplayList and the replay, later on in time.

Furthermore, the Gtk/WPE ports currently depend on specific
synchronization primitives (creating fences in the accelerated rendering
case during recording, and waiting for the fences in the replay, before
attempting to use the fence-protected GPU resource) that were built only
into DisplayList. That was a bad idea, the Skia ports should be able to
properly paint in both CPU and GPU mode, independent of whether
DisplayList is used or not.

This PR moves the Gtk/WPE ports away from DisplayList recording to use a
Skia built-in functionality instead: SkPicture and SkPictureRecorder.
While this doesn&apos;t yet solve the mutation problem in a general way, it
removes a lot of potential problems, when we start to paint the next
frame earlier, then we do right now. Furthermore it resolves some
general design issues Kimmo detected with the current code -- namely
the sprinkling of USE(SKIA) throughout ImageBuffer/ImageBufferBackend
and NativeImage &amp; co.

Follow-up patches will remove the Skia-specific functionality that was
added to ImageBuffer &amp; co to make threaded DisplayList rendering
possible for our ports (albeit only under limited constraints, that we
want to get rid of in the long-term).

Covered by existing tests.

* LayoutTests/fast/backgrounds/background-clip-border-area-alpha.html:
Adjust for slight pixel test differences -- no visible change.
* LayoutTests/fast/multicol/transform-inside-opacity.html:
* LayoutTests/imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-001.html:
* LayoutTests/imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-002.html:
* LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-002.html:
* LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-004.html:
* LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-005.html:
* LayoutTests/svg/as-background-image/svg-transformed-background.html:
* LayoutTests/svg/repaint/buffered-rendering-static-image.html:
* Source/WebCore/platform/Skia.cmake:
* Source/WebCore/platform/SourcesSkia.txt:
* Source/WebCore/platform/graphics/ImageBuffer.cpp:
(WebCore::ImageBuffer::surface const):
* Source/WebCore/platform/graphics/ImageBuffer.h:
* Source/WebCore/platform/graphics/ImageBufferBackend.h:
(WebCore::ImageBufferBackend::surface const):
(WebCore::ImageBufferBackend::waitForAcceleratedRenderingFenceCompletion):
* Source/WebCore/platform/graphics/NativeImage.h:
* Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp:
(WebCore::GraphicsContextSkia::drawNativeImageInternal):
(WebCore::GraphicsContextSkia::setupFillSource):
(WebCore::GraphicsContextSkia::setupStrokeSource):
(WebCore::GraphicsContextSkia::clipToImageBuffer):
(WebCore::createAcceleratedSurface):
(WebCore::GraphicsContextSkia::drawPattern):
(WebCore::GraphicsContextSkia::beginRecording):
(WebCore::GraphicsContextSkia::endRecording):
(WebCore::GraphicsContextSkia::createAcceleratedRenderingFenceIfNeeded):
(WebCore::GraphicsContextSkia::trackAcceleratedRenderingFenceIfNeeded):
(WebCore::GraphicsContextSkia::drawSkiaImage): Deleted.
(WebCore::GraphicsContextSkia::drawFilteredImageBuffer): Deleted.
(WebCore::GraphicsContextSkia::setupFillSource const): Deleted.
(WebCore::GraphicsContextSkia::setupStrokeSource const): Deleted.
(WebCore::GraphicsContextSkia::drawSkiaPattern): Deleted.
* Source/WebCore/platform/graphics/skia/GraphicsContextSkia.h:
* Source/WebCore/platform/graphics/skia/ImageBufferSkiaAcceleratedBackend.h:
* Source/WebCore/platform/graphics/skia/ImageBufferSkiaSurfaceBackend.h:
* Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp:
(WebCore::SkiaPaintingEngine::postPaintingTask):
(WebCore::SkiaPaintingEngine::recordDisplayList const): Deleted.
(WebCore::SkiaPaintingEngine::paintDisplayListIntoBuffer): Deleted.
* Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.h:
* Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp: Added.
(WebCore::SkiaReplayCanvas::SkiaReplayCanvas):
(WebCore::SkiaReplayCanvas::create):
(WebCore::SkiaReplayCanvas::waitForRenderingCompletionAndRewrapImageIfNeeded):
(WebCore::SkiaReplayCanvas::invokeDrawFunctionWithImage):
(WebCore::SkiaReplayCanvas::invokeDrawFunctionWithPaint):
(WebCore::SkiaReplayCanvas::invokeDrawFunctionWithShader):
(WebCore::SkiaReplayCanvas::onClipShader):
(WebCore::SkiaReplayCanvas::onDrawArc):
(WebCore::SkiaReplayCanvas::onDrawAtlas2):
(WebCore::SkiaReplayCanvas::onDrawBehind):
(WebCore::SkiaReplayCanvas::onDrawDRRect):
(WebCore::SkiaReplayCanvas::onDrawEdgeAAImageSet2):
(WebCore::SkiaReplayCanvas::onDrawGlyphRunList):
(WebCore::SkiaReplayCanvas::onDrawImage2):
(WebCore::SkiaReplayCanvas::onDrawImageLattice2):
(WebCore::SkiaReplayCanvas::onDrawImageRect2):
(WebCore::SkiaReplayCanvas::onDrawOval):
(WebCore::SkiaReplayCanvas::onDrawPaint):
(WebCore::SkiaReplayCanvas::onDrawPoints):
(WebCore::SkiaReplayCanvas::onDrawPatch):
(WebCore::SkiaReplayCanvas::onDrawPath):
(WebCore::SkiaReplayCanvas::onDrawRRect):
(WebCore::SkiaReplayCanvas::onDrawRect):
(WebCore::SkiaReplayCanvas::onDrawRegion):
(WebCore::SkiaReplayCanvas::onDrawSlug):
(WebCore::SkiaReplayCanvas::onDrawTextBlob):
(WebCore::SkiaReplayCanvas::onDrawVerticesObject):
* Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.h: Added.

Canonical link: <a href="https://commits.webkit.org/292639@main">https://commits.webkit.org/292639@main</a>
</pre>
<!--EWS-Status-Bubble-Start-->
https://github.com/WebKit/WebKit/commit/eab77ffc5f9861606d8945ebff4ec8c0a99ebc7e

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/96627 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/16241 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/6348 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/101699 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/47147 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/98672 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/16537 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/24675 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/73666 "Found 1 new test failure: fast/multicol/layers-split-across-columns.html (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30860 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/99630 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/12439 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/87372 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53975 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/12198 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/5165 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/46475 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/82299 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/5258 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/103723 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/23694 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/17270 "Passed tests") | [✅ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/82686 "Passed tests") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23945 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/83421 "Passed tests") | [❌ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/82097 "Found 3 new API test failures: /WebKitGTK/TestPrinting:/webkit/WebKitPrintOperation/print-errors, /WebKitGTK/TestPrinting:/webkit/WebKitPrintOperation/print, /WebKitGTK/TestPrinting:/webkit/WebKitPrintOperation/close-after-print (failure)") | 
| [✅ 🛠 🧪 merge](https://ews-build.webkit.org/#/builders/19/builds/20607 "Built successfully and passed tests") | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26718 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/4241 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/17168 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/23657 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/28812 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/23316 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/26796 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/25057 "Built successfully") | | | 
<!--EWS-Status-Bubble-End-->


Diff:
diff --git a/LayoutTests/fast/backgrounds/background-clip-border-area-alpha.html b/LayoutTests/fast/backgrounds/background-clip-border-area-alpha.html
index a42c1631d356..f702847db226 100644
--- a/LayoutTests/fast/backgrounds/background-clip-border-area-alpha.html
+++ b/LayoutTests/fast/backgrounds/background-clip-border-area-alpha.html
@@ -1,6 +1,6 @@
 <!DOCTYPE html>
 <html>
-<meta name="fuzzy" content="maxDifference=0-75; totalPixels=0-520" />
+<meta name="fuzzy" content="maxDifference=0-75; totalPixels=0-22257" />
 <head>
     <style>
         .box {
diff --git a/LayoutTests/fast/multicol/transform-inside-opacity.html b/LayoutTests/fast/multicol/transform-inside-opacity.html
index 7dd5da651ddb..e9896cedc44b 100644
--- a/LayoutTests/fast/multicol/transform-inside-opacity.html
+++ b/LayoutTests/fast/multicol/transform-inside-opacity.html
@@ -1,4 +1,5 @@
 <head>
+<meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-20000" />
 <style>
 .transformed { background-color:green }
 .transformed:hover { background-color:maroon }
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-001.html b/LayoutTests/imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-001.html
index 1f07d60375ea..2251c3c3bbf7 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-001.html
+++ b/LayoutTests/imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-001.html
@@ -7,7 +7,7 @@
     <meta name="assert" content="The background image should have opacity applied to
     it as part of the Root Element Group">
     <link rel="match" href="root-element-background-image-transparency-001-ref.html">
-    <meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-9600" />
+    <meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-244800" />
     <style>
       html {
         background: url(support/transform-triangle-left.svg);
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-002.html b/LayoutTests/imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-002.html
index 0da51986f1ca..fdc5bec95d84 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-002.html
+++ b/LayoutTests/imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-002.html
@@ -7,7 +7,7 @@
     <meta name="assert" content="The background image should have opacity applied to
     it as part of the Root Element Group">
     <link rel="match" href="root-element-background-image-transparency-001-ref.html">
-    <meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-9600" />
+    <meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-244800" />
     <style>
       html {
         background: url(support/transform-triangle-left.svg) fixed;
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-002.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-002.html
index cd8b9a3cbeb6..cec5c7b6e647 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-002.html
+++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-002.html
@@ -4,6 +4,7 @@
 <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1531609">
 <link rel="help" href="https://drafts.csswg.org/css-overflow/#valdef-overflow-clip">
 <link rel="match" href="clip-002-ref.html">
+<meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-5000" />
 <style>
   .outer {
     width: 50px;
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-004.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-004.html
index 30dde4a5e12a..80aecfdf0776 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-004.html
+++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-004.html
@@ -4,6 +4,7 @@
 <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1531609">
 <link rel="help" href="https://drafts.csswg.org/css-overflow/#valdef-overflow-clip">
 <link rel="match" href="clip-004-ref.html">
+<meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-5000" />
 <style>
   .outer {
     width: 30px;
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-005.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-005.html
index e55f4b22fcb7..be8a3a18f1b3 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-005.html
+++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-overflow/clip-005.html
@@ -4,6 +4,7 @@
 <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1531609">
 <link rel="help" href="https://drafts.csswg.org/css-overflow/#valdef-overflow-clip">
 <link rel="match" href="clip-005-ref.html">
+<meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-5000" />
 <style>
   .outer {
     width: 30px;
diff --git a/LayoutTests/svg/as-background-image/svg-transformed-background.html b/LayoutTests/svg/as-background-image/svg-transformed-background.html
index 6d683c3641f9..ca35bfe23305 100644
--- a/LayoutTests/svg/as-background-image/svg-transformed-background.html
+++ b/LayoutTests/svg/as-background-image/svg-transformed-background.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
+<meta name="fuzzy" content="maxDifference=0-34; totalPixels=0-744" />
 <style>
 #circle {
   position: absolute;
diff --git a/LayoutTests/svg/repaint/buffered-rendering-static-image.html b/LayoutTests/svg/repaint/buffered-rendering-static-image.html
index a2167b9cd200..27a6ff19866c 100644
--- a/LayoutTests/svg/repaint/buffered-rendering-static-image.html
+++ b/LayoutTests/svg/repaint/buffered-rendering-static-image.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
-    <meta name="fuzzy" content="maxDifference=1-46; totalPixels=7-512" />
+    <meta name="fuzzy" content="maxDifference=1-46; totalPixels=1-512" />
     <script src="../../fast/repaint/resources/repaint.js"></script>
     <script>
         function repaintTest() {
diff --git a/Source/WebCore/platform/Skia.cmake b/Source/WebCore/platform/Skia.cmake
index 31460a79014b..5b9e94704cbb 100644
--- a/Source/WebCore/platform/Skia.cmake
+++ b/Source/WebCore/platform/Skia.cmake
@@ -16,6 +16,7 @@ list(APPEND WebCore_PRIVATE_FRAMEWORK_HEADERS
     platform/graphics/skia/SkiaHarfBuzzFont.h
     platform/graphics/skia/SkiaHarfBuzzFontCache.h
     platform/graphics/skia/SkiaPaintingEngine.h
+    platform/graphics/skia/SkiaReplayCanvas.h
     platform/graphics/skia/SkiaSpanExtras.h
 )
 
diff --git a/Source/WebCore/platform/SourcesSkia.txt b/Source/WebCore/platform/SourcesSkia.txt
index dee09a7b46ad..f9af229c6b78 100644
--- a/Source/WebCore/platform/SourcesSkia.txt
+++ b/Source/WebCore/platform/SourcesSkia.txt
@@ -62,5 +62,6 @@ platform/graphics/skia/ShareableBitmapSkia.cpp
 platform/graphics/skia/SkiaHarfBuzzFont.cpp
 platform/graphics/skia/SkiaHarfBuzzFontCache.cpp
 platform/graphics/skia/SkiaPaintingEngine.cpp
+platform/graphics/skia/SkiaReplayCanvas.cpp
 
 platform/skia/SharedBufferSkia.cpp
diff --git a/Source/WebCore/platform/graphics/ImageBuffer.cpp b/Source/WebCore/platform/graphics/ImageBuffer.cpp
index 4cad7ad87476..353dcc18e8d3 100644
--- a/Source/WebCore/platform/graphics/ImageBuffer.cpp
+++ b/Source/WebCore/platform/graphics/ImageBuffer.cpp
@@ -431,6 +431,13 @@ IOSurface* ImageBuffer::surface()
 }
 #endif
 
+#if USE(SKIA)
+SkSurface* ImageBuffer::surface() const
+{
+    return m_backend ? m_backend->surface() : nullptr;
+}
+#endif
+
 #if USE(CAIRO)
 RefPtr<cairo_surface_t> ImageBuffer::createCairoSurface()
 {
diff --git a/Source/WebCore/platform/graphics/ImageBuffer.h b/Source/WebCore/platform/graphics/ImageBuffer.h
index c31706b45176..48d0301bcb0c 100644
--- a/Source/WebCore/platform/graphics/ImageBuffer.h
+++ b/Source/WebCore/platform/graphics/ImageBuffer.h
@@ -51,6 +51,7 @@
 
 #if USE(SKIA)
 class GrDirectContext;
+class SkSurface;
 #endif
 
 namespace WTF {
@@ -64,7 +65,6 @@ class DynamicContentScalingDisplayList;
 class Filter;
 class GraphicsClient;
 class ScriptExecutionContext;
-
 class SerializedImageBuffer;
 
 struct ImageBufferCreationContext {
@@ -194,18 +194,12 @@ class ImageBuffer : public ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr<Image
 #endif
 
 #if USE(SKIA)
-    // During DisplayList recording a fence is created, so that we can wait until the SkSurface finished rendering
-    // before we attempt to access the GPU resource from a secondary thread during replay (in threaded GPU painting mode).
+    SkSurface* surface() const;
+
+    // FIXME: Remove the obsolete Skia specific methods below.
     bool finishAcceleratedRenderingAndCreateFence();
     void waitForAcceleratedRenderingFenceCompletion();
-
     const GrDirectContext* skiaGrContext() const;
-
-    // Use to copy an accelerated ImageBuffer, cloning the ImageBufferSkiaAcceleratedBackend, creating
-    // a new SkSurface tied to the current thread (and thus the thread-local GrDirectContext), but re-using
-    // the existing backend render target, of this ImageBuffer. This avoids any GPU->GPU copies and has the
-    // sole purpose to abe able to access an accelerated ImageBuffer from another thread, that is not
-    // the creation thread.
     RefPtr<ImageBuffer> copyAcceleratedImageBufferBorrowingBackendRenderTarget() const;
 #endif
 
diff --git a/Source/WebCore/platform/graphics/ImageBufferBackend.h b/Source/WebCore/platform/graphics/ImageBufferBackend.h
index 57267b922bfc..6b029b98704b 100644
--- a/Source/WebCore/platform/graphics/ImageBufferBackend.h
+++ b/Source/WebCore/platform/graphics/ImageBufferBackend.h
@@ -52,6 +52,7 @@
 
 #if USE(SKIA)
 class GrDirectContext;
+class SkSurface;
 #endif
 
 namespace WTF {
@@ -142,9 +143,11 @@ class ImageBufferBackend {
 #endif
 
 #if USE(SKIA)
+    virtual SkSurface* surface() const { return nullptr; }
+
+    // FIXME: Remove the obsolete Skia specific methods below.
     virtual bool finishAcceleratedRenderingAndCreateFence() { return false; }
     virtual void waitForAcceleratedRenderingFenceCompletion() { }
-
     virtual const GrDirectContext* skiaGrContext() const { return nullptr; }
     WEBCORE_EXPORT virtual RefPtr<ImageBuffer> copyAcceleratedImageBufferBorrowingBackendRenderTarget(const ImageBuffer&) const;
 #endif
diff --git a/Source/WebCore/platform/graphics/NativeImage.h b/Source/WebCore/platform/graphics/NativeImage.h
index 7c3612d61f0f..32734f5f7382 100644
--- a/Source/WebCore/platform/graphics/NativeImage.h
+++ b/Source/WebCore/platform/graphics/NativeImage.h
@@ -119,11 +119,10 @@ class PlatformImageNativeImageBackend final : public NativeImageBackend {
     WEBCORE_EXPORT Headroom headroom() const final;
 
 #if USE(SKIA)
+    // FIXME: Remove the obsolete Skia specific methods and members below.
     bool finishAcceleratedRenderingAndCreateFence() final;
     void waitForAcceleratedRenderingFenceCompletion() final;
-
     const GrDirectContext* skiaGrContext() const final;
-
     RefPtr<NativeImage> copyAcceleratedNativeImageBorrowingBackendTexture() const final;
 #endif
 private:
diff --git a/Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp b/Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
index 133abc789e26..666546883f4b 100644
--- a/Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
+++ b/Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
@@ -31,6 +31,7 @@
 #include "DecomposedGlyphs.h"
 #include "FloatRect.h"
 #include "FloatRoundedRect.h"
+#include "FontRenderOptions.h"
 #include "GLContext.h"
 #include "ImageBuffer.h"
 #include "IntRect.h"
@@ -52,6 +53,8 @@ WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
 #include <skia/core/SkSurface.h>
 #include <skia/core/SkTileMode.h>
 #include <skia/effects/SkImageFilters.h>
+#include <skia/gpu/ganesh/GrBackendSurface.h>
+#include <skia/gpu/ganesh/SkSurfaceGanesh.h>
 WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
 #include <wtf/MathExtras.h>
 
@@ -248,12 +251,13 @@ static SkSamplingOptions toSkSamplingOptions(InterpolationQuality quality)
     return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNearest);
 }
 
-void GraphicsContextSkia::drawSkiaImage(const sk_sp<SkImage>& image, const IntSize& size, const FloatRect& destRect, const FloatRect& srcRect, ImagePaintingOptions options)
+void GraphicsContextSkia::drawNativeImageInternal(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& srcRect, ImagePaintingOptions options)
 {
+    auto image = nativeImage.platformImage();
     if (!image)
         return;
 
-    auto imageSize = size;
+    auto imageSize = nativeImage.size();
     if (options.orientation().usesWidthAsHeight())
         imageSize = imageSize.transposedSize();
     auto imageRect = FloatRect { { }, imageSize };
@@ -293,15 +297,20 @@ void GraphicsContextSkia::drawSkiaImage(const sk_sp<SkImage>& image, const IntSi
 
     sk_sp<SkImage> rasterImage;
     if (hasDropShadow()) {
-        if (image->isTextureBacked() && renderingMode() == RenderingMode::Unaccelerated) {
-            // When drawing GPU-backed image on CPU-backed canvas with filter, we need to convert image to CPU-backed one.
-            rasterImage = image->makeRasterImage();
-            useImage = rasterImage.get();
+        if (image->isTextureBacked()) {
+            if (renderingMode() == RenderingMode::Unaccelerated) {
+                // When drawing GPU-backed image on CPU-backed canvas with filter, we need to convert image to CPU-backed one.
+                rasterImage = image->makeRasterImage();
+                useImage = rasterImage.get();
+            } else
+                trackAcceleratedRenderingFenceIfNeeded(image);
         }
         inExtraTransparencyLayer = drawOutsetShadow(paint, [&](const SkPaint& paint) {
             m_canvas.drawImageRect(useImage, normalizedSrcRect, normalizedDestRect, toSkSamplingOptions(m_state.imageInterpolationQuality()), &paint, clampingConstraint);
         });
-    }
+    } else
+        trackAcceleratedRenderingFenceIfNeeded(image);
+
     m_canvas.drawImageRect(useImage, normalizedSrcRect, normalizedDestRect, toSkSamplingOptions(m_state.imageInterpolationQuality()), &paint, clampingConstraint);
     if (inExtraTransparencyLayer)
         endTransparencyLayer();
@@ -310,52 +319,6 @@ void GraphicsContextSkia::drawSkiaImage(const sk_sp<SkImage>& image, const IntSi
         m_canvas.restore();
 }
 
-void GraphicsContextSkia::drawNativeImageInternal(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& srcRect, ImagePaintingOptions options)
-{
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaImage(newNativeImage->platformImage(), newNativeImage->size(), destRect, srcRect, options);
-        return;
-    }
-
-    drawSkiaImage(nativeImage.platformImage(), nativeImage.size(), destRect, srcRect, options);
-}
-
-void GraphicsContextSkia::drawFilteredImageBuffer(ImageBuffer* sourceImage, const FloatRect& sourceImageRect, Filter& filter, FilterResults& results)
-{
-    auto needsAcceleratedImageBufferCopy = [&]() -> bool {
-        if (m_renderingMode == RenderingMode::Unaccelerated)
-            return false;
-        if (!sourceImage || !sourceImage->skiaGrContext())
-            return false;
-        if (sourceImage->skiaGrContext() == PlatformDisplay::sharedDisplay().skiaGrContext())
-            return false;
-        return true;
-    };
-
-    if (!needsAcceleratedImageBufferCopy()) {
-        GraphicsContext::drawFilteredImageBuffer(sourceImage, sourceImageRect, filter, results);
-        return;
-    }
-
-    // If we encounter an accelerated ImageBuffer (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-    ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-    ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-    // The 'image' was produced on another thread -- to use it here, we need to create a new ImageBuffer, that wraps the existing GPU resource.
-    auto newSourceImage = sourceImage->copyAcceleratedImageBufferBorrowingBackendRenderTarget();
-    GraphicsContext::drawFilteredImageBuffer(newSourceImage.get(), sourceImageRect, filter, results);
-}
-
 // This is only used to draw borders, so we should not draw shadows.
 void GraphicsContextSkia::drawLine(const FloatPoint& point1, const FloatPoint& point2)
 {
@@ -547,11 +510,12 @@ SkPaint GraphicsContextSkia::createFillPaint() const
     return paint;
 }
 
-void GraphicsContextSkia::setupFillSource(SkPaint& paint) const
+void GraphicsContextSkia::setupFillSource(SkPaint& paint)
 {
     if (auto fillPattern = fillBrush().pattern()) {
         paint.setShader(fillPattern->createPlatformPattern({ }, toSkSamplingOptions(imageInterpolationQuality())));
         paint.setAlphaf(alpha());
+        trackAcceleratedRenderingFenceIfNeeded(paint);
     } else if (auto fillGradient = fillBrush().gradient())
         paint.setShader(fillGradient->shader(alpha(), fillBrush().gradientSpaceTransform()));
     else
@@ -572,11 +536,12 @@ SkPaint GraphicsContextSkia::createStrokePaint() const
     return paint;
 }
 
-void GraphicsContextSkia::setupStrokeSource(SkPaint& paint) const
+void GraphicsContextSkia::setupStrokeSource(SkPaint& paint)
 {
-    if (auto strokePattern = strokeBrush().pattern())
+    if (auto strokePattern = strokeBrush().pattern()) {
         paint.setShader(strokePattern->createPlatformPattern({ }, toSkSamplingOptions(imageInterpolationQuality())));
-    else if (auto strokeGradient = strokeBrush().gradient())
+        trackAcceleratedRenderingFenceIfNeeded(paint);
+    } else if (auto strokeGradient = strokeBrush().gradient())
         paint.setShader(strokeGradient->shader(alpha(), strokeBrush().gradientSpaceTransform()));
     else
         paint.setColor(SkColor(strokeBrush().color().colorWithAlphaMultipliedBy(alpha())));
@@ -656,8 +621,11 @@ IntRect GraphicsContextSkia::clipBounds() const
 
 void GraphicsContextSkia::clipToImageBuffer(ImageBuffer& buffer, const FloatRect& destRect)
 {
-    if (auto nativeImage = nativeImageForDrawing(buffer))
-        m_canvas.clipShader(nativeImage->platformImage()->makeShader(SkTileMode::kDecal, SkTileMode::kDecal, { }, SkMatrix::Translate(SkFloatToScalar(destRect.x()), SkFloatToScalar(destRect.y()))));
+    if (auto nativeImage = nativeImageForDrawing(buffer)) {
+        auto image = nativeImage->platformImage();
+        trackAcceleratedRenderingFenceIfNeeded(image);
+        m_canvas.clipShader(image->makeShader(SkTileMode::kDecal, SkTileMode::kDecal, { }, SkMatrix::Translate(SkFloatToScalar(destRect.x()), SkFloatToScalar(destRect.y()))));
+    }
 }
 
 void GraphicsContextSkia::drawFocusRing(const Path& path, float, const Color& color)
@@ -973,11 +941,31 @@ void GraphicsContextSkia::fillRectWithRoundedHole(const FloatRect& outerRect, co
     m_canvas.drawDRRect(SkRRect::MakeRect(outerRect), innerRRect, paint);
 }
 
-void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const IntSize& size, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+// FIXME: Make this a GraphicsContextSkia static function, and use it throughout WebCore.
+static sk_sp<SkSurface> createAcceleratedSurface(const IntSize& size)
+{
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    auto imageInfo = SkImageInfo::Make(size.width(), size.height(), kRGBA_8888_SkColorType, kPremul_SkAlphaType, SkColorSpace::MakeSRGB());
+    SkSurfaceProps properties { 0, FontRenderOptions::singleton().subpixelOrder() };
+    auto surface = SkSurfaces::RenderTarget(grContext, skgpu::Budgeted::kNo, imageInfo, PlatformDisplay::sharedDisplay().msaaSampleCount(), kTopLeft_GrSurfaceOrigin, &properties);
+    if (!surface || !surface->getCanvas())
+        return nullptr;
+
+    return surface;
+}
+
+void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
 {
     if (!patternTransform.isInvertible())
         return;
 
+    auto image = nativeImage.platformImage();
     if (!image)
         return;
 
@@ -995,6 +983,7 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
     SkPaint paint = createFillPaint();
     paint.setBlendMode(toSkiaBlendMode(options.compositeOperator(), options.blendMode()));
 
+    auto size = nativeImage.size();
     if (spacing.isZero() && tileRect.size() == size) {
         // Check whether we're sampling the pattern beyond the image size. If this is the case, we need to set the repeat
         // flag when sampling. Otherwise we use the clamp flag. This is done to avoid a situation where the pattern is scaled
@@ -1010,35 +999,91 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        trackAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                trackAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+void GraphicsContextSkia::beginRecording()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    ASSERT(m_contextMode == ContextMode::PaintingMode);
+    m_contextMode = ContextMode::RecordingMode;
+}
+
+SkiaImageToFenceMap GraphicsContextSkia::endRecording()
+{
+    ASSERT(m_contextMode == ContextMode::RecordingMode);
+    m_contextMode = ContextMode::PaintingMode;
+    return WTFMove(m_imageToFenceMap);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFenceIfNeeded(const sk_sp<SkImage>& image)
+{
+    if (!image || !image->isTextureBacked())
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
     }
 
-    drawSkiaPattern(nativeImage.platformImage(), nativeImage.size(), destRect, tileRect, patternTransform, phase, spacing, options);
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+void GraphicsContextSkia::trackAcceleratedRenderingFenceIfNeeded(const sk_sp<SkImage>& image)
+{
+    if (m_contextMode != ContextMode::RecordingMode)
+        return;
+
+    if (auto fence = createAcceleratedRenderingFenceIfNeeded(image))
+        m_imageToFenceMap.add(image.get(), WTFMove(fence));
+}
+
+void GraphicsContextSkia::trackAcceleratedRenderingFenceIfNeeded(SkPaint& paint)
+{
+    if (m_contextMode != ContextMode::RecordingMode)
+        return;
+
+    auto* shader = paint.getShader();
+    auto* image = shader ? shader->isAImage(nullptr, nullptr) : nullptr;
+    if (auto fence = createAcceleratedRenderingFenceIfNeeded(sk_ref_sp(image)))
+        m_imageToFenceMap.add(image, WTFMove(fence));
 }
 
 void GraphicsContextSkia::drawSkiaText(const sk_sp<SkTextBlob>& blob, SkScalar x, SkScalar y, bool enableAntialias, bool isVertical)
diff --git a/Source/WebCore/platform/graphics/skia/GraphicsContextSkia.h b/Source/WebCore/platform/graphics/skia/GraphicsContextSkia.h
index f8b01f889aac..cd65954e466f 100644
--- a/Source/WebCore/platform/graphics/skia/GraphicsContextSkia.h
+++ b/Source/WebCore/platform/graphics/skia/GraphicsContextSkia.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2024 Igalia S.L.
+ * Copyright (C) 2024, 2025 Igalia S.L.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -27,15 +27,22 @@
 
 #if USE(SKIA)
 
+#include "GLFence.h"
 #include "GraphicsContext.h"
 WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
 #include <skia/core/SkCanvas.h>
+#include <skia/core/SkImage.h>
 #include <skia/effects/SkDashPathEffect.h>
 WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
 #include <wtf/CompletionHandler.h>
+#include <wtf/HashMap.h>
+
+class SkSurface;
 
 namespace WebCore {
 
+using SkiaImageToFenceMap = HashMap<const SkImage*, std::unique_ptr<GLFence>>;
+
 class WEBCORE_EXPORT GraphicsContextSkia final : public GraphicsContext {
 public:
     GraphicsContextSkia(SkCanvas&, RenderingMode, RenderingPurpose, CompletionHandler<void()>&& = nullptr);
@@ -46,6 +53,9 @@ class WEBCORE_EXPORT GraphicsContextSkia final : public GraphicsContext {
 
     const DestinationColorSpace& colorSpace() const final;
 
+    void beginRecording();
+    SkiaImageToFenceMap endRecording();
+
     void didUpdateState(GraphicsContextState&);
 
     void setLineCap(LineCap) final;
@@ -66,8 +76,6 @@ class WEBCORE_EXPORT GraphicsContextSkia final : public GraphicsContext {
 
     void drawNativeImageInternal(NativeImage&, const FloatRect&, const FloatRect&, ImagePaintingOptions) final;
     void drawPattern(NativeImage&, const FloatRect& destRect, const FloatRect& srcRect, const AffineTransform&, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions) final;
-    void drawFilteredImageBuffer(ImageBuffer* sourceImage, const FloatRect& sourceImageRect, Filter&, FilterResults&) final;
-
     void drawRect(const FloatRect&, float) final;
     void drawLine(const FloatPoint&, const FloatPoint&) final;
     void drawLinesForText(const FloatPoint&, float thickness, std::span<const FloatSegment>, bool isPrinting, bool doubleLines, StrokeStyle) final;
@@ -107,11 +115,20 @@ class WEBCORE_EXPORT GraphicsContextSkia final : public GraphicsContext {
 
     void drawSkiaText(const sk_sp<SkTextBlob>&, SkScalar, SkScalar, bool, bool);
 
+    static std::unique_ptr<GLFence> createAcceleratedRenderingFenceIfNeeded(const sk_sp<SkImage>&);
+
 private:
+    enum class ContextMode : bool {
+        PaintingMode,
+        RecordingMode
+    };
+
     bool makeGLContextCurrentIfNeeded() const;
+    void trackAcceleratedRenderingFenceIfNeeded(const sk_sp<SkImage>&);
+    void trackAcceleratedRenderingFenceIfNeeded(SkPaint&);
 
-    void setupFillSource(SkPaint&) const;
-    void setupStrokeSource(SkPaint&) const;
+    void setupFillSource(SkPaint&);
+    void setupStrokeSource(SkPaint&);
 
     enum class ShadowStyle : uint8_t { Outset, Inset };
     sk_sp<SkImageFilter> createDropShadowFilterIfNeeded(ShadowStyle) const;
@@ -119,8 +136,6 @@ class WEBCORE_EXPORT GraphicsContextSkia final : public GraphicsContext {
 
     void drawSkiaRect(const SkRect&, SkPaint&);
     void drawSkiaPath(const SkPath&, SkPaint&);
-    void drawSkiaImage(const sk_sp<SkImage>&, const IntSize&, const FloatRect&, const FloatRect&, ImagePaintingOptions);
-    void drawSkiaPattern(const sk_sp<SkImage>&, const IntSize&, const FloatRect&, const FloatRect&, const AffineTransform&, const FloatPoint&, const FloatSize&, ImagePaintingOptions);
 
     class SkiaState {
     public:
@@ -139,12 +154,14 @@ class WEBCORE_EXPORT GraphicsContextSkia final : public GraphicsContext {
     };
 
     SkCanvas& m_canvas;
+    ContextMode m_contextMode { ContextMode::PaintingMode };
     RenderingMode m_renderingMode { RenderingMode::Accelerated };
     RenderingPurpose m_renderingPurpose { RenderingPurpose::Unspecified };
     CompletionHandler<void()> m_destroyNotify;
     SkiaState m_skiaState;
     Vector<SkiaState, 1> m_skiaStateStack;
     Vector<LayerState, 1> m_layerStateStack;
+    SkiaImageToFenceMap m_imageToFenceMap;
     const DestinationColorSpace m_colorSpace;
 };
 
diff --git a/Source/WebCore/platform/graphics/skia/ImageBufferSkiaAcceleratedBackend.h b/Source/WebCore/platform/graphics/skia/ImageBufferSkiaAcceleratedBackend.h
index 4babde58c484..effde1945986 100644
--- a/Source/WebCore/platform/graphics/skia/ImageBufferSkiaAcceleratedBackend.h
+++ b/Source/WebCore/platform/graphics/skia/ImageBufferSkiaAcceleratedBackend.h
@@ -41,12 +41,12 @@ class ImageBufferSkiaAcceleratedBackend final : public ImageBufferSkiaSurfaceBac
     WTF_MAKE_NONCOPYABLE(ImageBufferSkiaAcceleratedBackend);
 public:
     static std::unique_ptr<ImageBufferSkiaAcceleratedBackend> create(const Parameters&, const ImageBufferCreationContext&);
+    static std::unique_ptr<ImageBufferSkiaAcceleratedBackend> create(const Parameters&, const ImageBufferCreationContext&, sk_sp<SkSurface>&&);
     ~ImageBufferSkiaAcceleratedBackend();
 
     static constexpr RenderingMode renderingMode = RenderingMode::Accelerated;
 
 private:
-    static std::unique_ptr<ImageBufferSkiaAcceleratedBackend> create(const Parameters&, const ImageBufferCreationContext&, sk_sp<SkSurface>&&);
     ImageBufferSkiaAcceleratedBackend(const Parameters&, sk_sp<SkSurface>&&);
 
     void prepareForDisplay() final;
@@ -57,9 +57,9 @@ class ImageBufferSkiaAcceleratedBackend final : public ImageBufferSkiaSurfaceBac
     void getPixelBuffer(const IntRect&, PixelBuffer&) final;
     void putPixelBuffer(const PixelBuffer&, const IntRect& srcRect, const IntPoint& destPoint, AlphaPremultiplication destFormat) final;
 
+    // FIXME: Remove the obsolete Skia specific methods and members below.
     bool finishAcceleratedRenderingAndCreateFence() final;
     void waitForAcceleratedRenderingFenceCompletion() final;
-
     const GrDirectContext* skiaGrContext() const final { return m_skiaGrContext; }
     RefPtr<ImageBuffer> copyAcceleratedImageBufferBorrowingBackendRenderTarget(const ImageBuffer&) const final;
 
diff --git a/Source/WebCore/platform/graphics/skia/ImageBufferSkiaSurfaceBackend.h b/Source/WebCore/platform/graphics/skia/ImageBufferSkiaSurfaceBackend.h
index bcdf63c5c468..5788fee89736 100644
--- a/Source/WebCore/platform/graphics/skia/ImageBufferSkiaSurfaceBackend.h
+++ b/Source/WebCore/platform/graphics/skia/ImageBufferSkiaSurfaceBackend.h
@@ -45,6 +45,8 @@ class ImageBufferSkiaSurfaceBackend : public ImageBufferSkiaBackend {
     static unsigned calculateBytesPerRow(const IntSize&);
     static size_t calculateMemoryCost(const Parameters&);
 
+    SkSurface* surface() const final { return m_surface.get(); }
+
 protected:
     ImageBufferSkiaSurfaceBackend(const Parameters&, sk_sp<SkSurface>&&, RenderingMode);
 
diff --git a/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp b/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
index 8429ad1cbdf1..ed460df05674 100644
--- a/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
+++ b/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
@@ -29,14 +29,21 @@
 #if USE(COORDINATED_GRAPHICS) && USE(SKIA)
 #include "BitmapTexturePool.h"
 #include "CoordinatedTileBuffer.h"
-#include "DisplayListRecorderImpl.h"
-#include "DisplayListReplayer.h"
 #include "GLContext.h"
+#include "GLFence.h"
 #include "GraphicsContextSkia.h"
 #include "GraphicsLayer.h"
 #include "PlatformDisplay.h"
 #include "ProcessCapabilities.h"
 #include "RenderingMode.h"
+#include "SkiaReplayCanvas.h"
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/core/SkPicture.h>
+#include <skia/core/SkPictureRecorder.h>
+#include <skia/gpu/ganesh/GrBackendSurface.h>
+#include <skia/gpu/ganesh/SkImageGanesh.h>
+#include <skia/utils/SkNWayCanvas.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
 #include <wtf/NumberOfCores.h>
 #include <wtf/SystemTracing.h>
 #include <wtf/text/StringToIntegerConversion.h>
@@ -80,30 +87,6 @@ static bool canPerformAcceleratedRendering()
     return ProcessCapabilities::canUseAcceleratedBuffers() && PlatformDisplay::sharedDisplay().skiaGLContext();
 }
 
-std::unique_ptr<DisplayList::DisplayList> SkiaPaintingEngine::recordDisplayList(RenderingMode& renderingMode, const GraphicsLayer& layer, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale) const
-{
-    OptionSet<DisplayList::ReplayOption> options;
-    if (isHybridMode() || renderingMode == RenderingMode::Accelerated)
-        options.add(DisplayList::ReplayOption::FlushAcceleratedImagesAndWaitForCompletion);
-
-    auto displayList = makeUnique<DisplayList::DisplayList>(options);
-    DisplayList::RecorderImpl recordingContext(*displayList, GraphicsContextState(), FloatRect({ }, dirtyRect.size()), AffineTransform());
-    paintIntoGraphicsContext(layer, recordingContext, dirtyRect, contentsOpaque, contentsScale);
-
-    // If we used accelerated ImageBuffers during recording, it's mandatory to replay in a GPU worker thread, with a GL context available.
-    if (recordingContext.usedAcceleratedRendering()) {
-        // Verify that we only used accelerated rendering if we're in either hybrid mode or GPU rendering mode (renderingMode == RenderingMode::Accelerated).
-        // If isHybridMode() == true, we eventually decided to use the CPU before (indicated by renderingMode == RenderingMode::Unaccelerated), but are
-        // forced to switch to use the GPU for replaying, because fences were created due the use of accelerated ImageBuffers, and thus we have to wait for
-        // the fences to be signalled -- that requires a GL context, and thus it's mandatory to replay in a GPU worker thread.
-        ASSERT(isHybridMode() || renderingMode == RenderingMode::Accelerated);
-        ASSERT(canPerformAcceleratedRendering());
-        renderingMode = RenderingMode::Accelerated;
-    }
-
-    return displayList;
-}
-
 void SkiaPaintingEngine::paintIntoGraphicsContext(const GraphicsLayer& layer, GraphicsContext& context, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale) const
 {
     IntRect initialClip(IntPoint::zero(), dirtyRect.size());
@@ -123,26 +106,6 @@ void SkiaPaintingEngine::paintIntoGraphicsContext(const GraphicsLayer& layer, Gr
     layer.paintGraphicsLayerContents(context, clipRect);
 }
 
-bool SkiaPaintingEngine::paintDisplayListIntoBuffer(Ref<CoordinatedTileBuffer>& buffer, DisplayList::DisplayList& displayList)
-{
-    auto* canvas = buffer->canvas();
-    if (!canvas)
-        return false;
-
-    static thread_local RefPtr<ControlFactory> s_controlFactory;
-    if (!s_controlFactory)
-        s_controlFactory = ControlFactory::create();
-
-    canvas->save();
-    canvas->clear(SkColors::kTransparent);
-
-    GraphicsContextSkia context(*canvas, buffer->isBackedByOpenGL() ? RenderingMode::Accelerated : RenderingMode::Unaccelerated, RenderingPurpose::LayerBacking);
-    DisplayList::Replayer(context, displayList.items(), displayList.resourceHeap(), *s_controlFactory, displayList.replayOptions()).replay();
-
-    canvas->restore();
-    return true;
-}
-
 bool SkiaPaintingEngine::paintGraphicsLayerIntoBuffer(Ref<CoordinatedTileBuffer>& buffer, const GraphicsLayer& layer, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale) const
 {
     auto* canvas = buffer->canvas();
@@ -294,27 +257,45 @@ Ref<CoordinatedTileBuffer> SkiaPaintingEngine::paintLayer(const GraphicsLayer& l
 
 Ref<CoordinatedTileBuffer> SkiaPaintingEngine::postPaintingTask(const GraphicsLayer& layer, RenderingMode renderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
 {
-    WTFBeginSignpost(this, RecordTile);
-    auto displayList = recordDisplayList(renderingMode, layer, dirtyRect, contentsOpaque, contentsScale);
-    WTFEndSignpost(this, RecordTile);
-
     auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
     buffer->beginPainting();
 
+    WTFBeginSignpost(this, RecordTile);
+    SkPictureRecorder pictureRecorder;
+    auto* recordingCanvas = pictureRecorder.beginRecording(dirtyRect.width(), dirtyRect.height());
+    GraphicsContextSkia recordingContext(*recordingCanvas, renderingMode, RenderingPurpose::LayerBacking);
+    recordingContext.beginRecording();
+    paintIntoGraphicsContext(layer, recordingContext, dirtyRect, contentsOpaque, contentsScale);
+    auto imageToFenceMap = recordingContext.endRecording();
+    auto picture = pictureRecorder.finishRecordingAsPicture();
+    WTFEndSignpost(this, RecordTile);
+
     auto& workerPool = renderingMode == RenderingMode::Accelerated ? *m_gpuWorkerPool.get() : *m_cpuWorkerPool.get();
-    workerPool.postTask([buffer = Ref { buffer }, displayList = WTFMove(displayList), dirtyRect]() mutable {
-        if (auto* canvas = buffer->canvas()) {
-            WTFBeginSignpost(canvas, PaintTile, "Skia/%s threaded, dirty region %ix%i+%i+%i", buffer->isBackedByOpenGL() ? "GPU" : "CPU", dirtyRect.x(), dirtyRect.y(), dirtyRect.width(), dirtyRect.height());
-            paintDisplayListIntoBuffer(buffer, *displayList.get());
-            WTFEndSignpost(canvas, PaintTile);
+    workerPool.postTask([buffer = Ref { buffer }, picture = sk_sp<SkPicture> { picture }, dirtyRect, imageToFenceMap = WTFMove(imageToFenceMap)]() mutable {
+        auto* canvas = buffer->canvas();
+        if (!canvas) {
+            buffer->completePainting();
+            return;
         }
 
-        buffer->completePainting();
+        auto replayPicture = [&picture](SkCanvas* canvas) {
+            canvas->save();
+            canvas->clear(SkColors::kTransparent);
+            picture->playback(canvas);
+            canvas->restore();
+        };
+
+        WTFBeginSignpost(canvas, PaintTile, "Skia/%s threaded, dirty region %ix%i+%i+%i", buffer->isBackedByOpenGL() ? "GPU" : "CPU", dirtyRect.x(), dirtyRect.y(), dirtyRect.width(), dirtyRect.height());
+        if (!imageToFenceMap.isEmpty()) {
+            auto replayCanvas = SkiaReplayCanvas::create(dirtyRect.size(), WTFMove(imageToFenceMap));
+            replayCanvas->addCanvas(canvas);
+            replayPicture(&replayCanvas.get());
+            replayCanvas->removeCanvas(canvas);
+        } else
+            replayPicture(canvas);
+        WTFEndSignpost(canvas, PaintTile);
 
-        // Destruct display list on main thread.
-        ensureOnMainThread([displayList = WTFMove(displayList)]() mutable {
-            displayList = nullptr;
-        });
+        buffer->completePainting();
     });
 
     return buffer;
diff --git a/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.h b/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.h
index 18768ff930e2..295dc5e3954a 100644
--- a/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.h
+++ b/Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.h
@@ -28,10 +28,12 @@
 #if USE(COORDINATED_GRAPHICS) && USE(SKIA)
 #include <wtf/RefPtr.h>
 #include <wtf/TZoneMalloc.h>
-#include <wtf/Vector.h>
 #include <wtf/WorkerPool.h>
 
+class SkImage;
+
 namespace WebCore {
+
 class BitmapTexturePool;
 class CoordinatedTileBuffer;
 class GraphicsContext;
@@ -40,10 +42,6 @@ class IntRect;
 class IntSize;
 enum class RenderingMode : uint8_t;
 
-namespace DisplayList {
-class DisplayList;
-}
-
 class SkiaPaintingEngine {
     WTF_MAKE_TZONE_ALLOCATED(SkiaPaintingEngine);
     WTF_MAKE_NONCOPYABLE(SkiaPaintingEngine);
@@ -72,10 +70,7 @@ class SkiaPaintingEngine {
 
 private:
     Ref<CoordinatedTileBuffer> createBuffer(RenderingMode, const IntSize&, bool contentsOpaque) const;
-    std::unique_ptr<DisplayList::DisplayList> recordDisplayList(RenderingMode&, const GraphicsLayer&, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale) const;
     void paintIntoGraphicsContext(const GraphicsLayer&, GraphicsContext&, const IntRect&, bool contentsOpaque, float contentsScale) const;
-
-    static bool paintDisplayListIntoBuffer(Ref<CoordinatedTileBuffer>&, DisplayList::DisplayList&);
     bool paintGraphicsLayerIntoBuffer(Ref<CoordinatedTileBuffer>&, const GraphicsLayer&, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale) const;
 
     // Threaded rendering
diff --git a/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp b/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp
new file mode 100644
index 000000000000..63a13333ef7e
--- /dev/null
+++ b/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2025 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "SkiaReplayCanvas.h"
+
+#if USE(COORDINATED_GRAPHICS) && USE(SKIA)
+#include "GLFence.h"
+#include "PlatformDisplay.h"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/gpu/ganesh/GrBackendSurface.h>
+#include <skia/gpu/ganesh/SkImageGanesh.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+namespace WebCore {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(SkiaReplayCanvas);
+
+SkiaReplayCanvas::SkiaReplayCanvas(const IntSize& size, SkiaImageToFenceMap&& imageToFenceMap)
+    : SkNWayCanvas(size.width(), size.height())
+    , m_imageToFenceMap(WTFMove(imageToFenceMap))
+{
+}
+
+SkiaReplayCanvas::~SkiaReplayCanvas() = default;
+
+Ref<SkiaReplayCanvas> SkiaReplayCanvas::create(const IntSize& size, SkiaImageToFenceMap&& imageToFenceMap)
+{
+    return adoptRef(*new SkiaReplayCanvas(size, WTFMove(imageToFenceMap)));
+}
+
+sk_sp<SkImage> SkiaReplayCanvas::waitForRenderingCompletionAndRewrapImageIfNeeded(const SkImage* image)
+{
+    if (!image || !image->isTextureBacked())
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    if (auto fence = m_imageToFenceMap.take(image))
+        fence->serverWait();
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    if (image->isValid(grContext))
+        return nullptr;
+
+    // FIXME: Add error reporting mechanism, a failure from GetBackendTextureFromImage() should be visible / reported.
+    GrBackendTexture backendTexture;
+    if (!SkImages::GetBackendTextureFromImage(image, &backendTexture, false))
+        return nullptr;
+
+    return SkImages::BorrowTextureFrom(grContext, backendTexture, kTopLeft_GrSurfaceOrigin, image->colorType(), image->alphaType(), image->refColorSpace());
+}
+
+void SkiaReplayCanvas::invokeDrawFunctionWithImage(const SkImage* image, Function<void(const SkImage*)>&& drawFunction)
+{
+    if (auto wrappedImage = waitForRenderingCompletionAndRewrapImageIfNeeded(image)) {
+        drawFunction(wrappedImage.get());
+        return;
+    }
+
+    drawFunction(image);
+}
+
+void SkiaReplayCanvas::invokeDrawFunctionWithPaint(const SkPaint& paint, Function<void(const SkPaint&)>&& drawFunction)
+{
+    auto* shader = paint.getShader();
+
+    SkMatrix localMatrix;
+    SkTileMode mode[2];
+    auto* image = shader ? shader->isAImage(&localMatrix, mode) : nullptr;
+    if (auto wrappedImage = waitForRenderingCompletionAndRewrapImageIfNeeded(image)) {
+        // FIXME: There is no way to get the SkSamplingOptions that were used to create the original shader.
+        // Add Skia API? (SkImageShader stores SkSamplingOptions but is private and not installed).
+        auto paintCopy = paint;
+        paintCopy.setShader(wrappedImage->makeShader(mode[0], mode[1], SkSamplingOptions(), &localMatrix));
+        drawFunction(paintCopy);
+        return;
+    }
+
+    drawFunction(paint);
+}
+
+void SkiaReplayCanvas::invokeDrawFunctionWithShader(const SkShader* shader, Function<void(const SkShader*)>&& drawFunction)
+{
+    SkMatrix localMatrix;
+    SkTileMode mode[2];
+    auto* image = shader ? shader->isAImage(&localMatrix, mode) : nullptr;
+    if (auto wrappedImage = waitForRenderingCompletionAndRewrapImageIfNeeded(image)) {
+        // FIXME: There is no way to get the SkSamplingOptions that were used to create the original shader.
+        // Add Skia API? (SkImageShader stores SkSamplingOptions but is private and not installed).
+        auto shaderCopy = wrappedImage->makeShader(mode[0], mode[1], SkSamplingOptions(), &localMatrix);
+        drawFunction(shaderCopy.get());
+        return;
+    }
+
+    drawFunction(shader);
+}
+
+void SkiaReplayCanvas::onClipShader(sk_sp<SkShader> shader, SkClipOp clipOp)
+{
+    invokeDrawFunctionWithShader(shader.get(), [&](const SkShader* shader) {
+        SkNWayCanvas::onClipShader(sk_ref_sp(shader), clipOp);
+    });
+}
+
+void SkiaReplayCanvas::onDrawArc(const SkRect& rect, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawArc(rect, startAngle, sweepAngle, useCenter, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawAtlas2(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[], const SkColor colors[], int count, SkBlendMode mode, const SkSamplingOptions& sampling, const SkRect* cull, const SkPaint* paint)
+{
+    invokeDrawFunctionWithImage(atlas, [&](const SkImage* atlas) {
+        SkNWayCanvas::onDrawAtlas2(atlas, xform, tex, colors, count, mode, sampling, cull, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawBehind(const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawBehind(paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawDRRect(outer, inner, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawEdgeAAImageSet2(const ImageSetEntry set[], int count, const SkPoint dstClips[], const SkMatrix preViewMatrices[], const SkSamplingOptions& sampling, const SkPaint* paint, SrcRectConstraint constraint)
+{
+    if (!paint) {
+        SkNWayCanvas::onDrawEdgeAAImageSet2(set, count, dstClips, preViewMatrices, sampling, nullptr, constraint);
+        return;
+    }
+
+    invokeDrawFunctionWithPaint(*paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawEdgeAAImageSet2(set, count, dstClips, preViewMatrices, sampling, &paint, constraint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawGlyphRunList(const sktext::GlyphRunList& glyphRunList, const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawGlyphRunList(glyphRunList, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawImage2(const SkImage* image, SkScalar x, SkScalar y, const SkSamplingOptions& sampling, const SkPaint* paint)
+{
+    invokeDrawFunctionWithImage(image, [&](const SkImage* image) {
+        SkNWayCanvas::onDrawImage2(image, x, y, sampling, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawImageLattice2(const SkImage* image, const Lattice& lattice, const SkRect& dst, SkFilterMode filter, const SkPaint* paint)
+{
+    invokeDrawFunctionWithImage(image, [&](const SkImage* image) {
+        SkNWayCanvas::onDrawImageLattice2(image, lattice, dst, filter, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawImageRect2(const SkImage* image, const SkRect& src, const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint, SrcRectConstraint constraint)
+{
+    invokeDrawFunctionWithImage(image, [&](const SkImage* image) {
+        SkNWayCanvas::onDrawImageRect2(image, src, dst, sampling, paint, constraint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawOval(const SkRect& oval, const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawOval(oval, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawPaint(const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawPaint(paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawPoints(PointMode mode, size_t count, const SkPoint points[], const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawPoints(mode, count, points, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], const SkPoint texCoords[4], SkBlendMode blendMode, const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawPatch(cubics, colors, texCoords, blendMode, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawPath(const SkPath& path, const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawPath(path, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawRRect(const SkRRect& rrect, const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawRRect(rrect, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawRect(rect, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawRegion(const SkRegion& region, const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawRegion(region, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawSlug(const sktext::gpu::Slug* slug, const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawSlug(slug, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawTextBlob(blob, x, y, paint);
+    });
+}
+
+void SkiaReplayCanvas::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint)
+{
+    invokeDrawFunctionWithPaint(paint, [&](const SkPaint& paint) {
+        SkNWayCanvas::onDrawVerticesObject(vertices, mode, paint);
+    });
+}
+
+} // namespace WebCore
+
+#endif // USE(COORDINATED_GRAPHICS) && USE(SKIA)
diff --git a/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.h b/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.h
new file mode 100644
index 000000000000..060aa45cc2a6
--- /dev/null
+++ b/Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2025 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if USE(COORDINATED_GRAPHICS) && USE(SKIA)
+#include "GraphicsContextSkia.h"
+#include "IntSize.h"
+#include <skia/utils/SkNWayCanvas.h>
+#include <wtf/Assertions.h>
+#include <wtf/Function.h>
+#include <wtf/HashMap.h>
+#include <wtf/RefCounted.h>
+#include <wtf/TZoneMalloc.h>
+
+class SkImage;
+
+namespace WebCore {
+
+class GLFence;
+
+class SkiaReplayCanvas final : public SkNWayCanvas, public RefCounted<SkiaReplayCanvas> {
+    WTF_MAKE_TZONE_ALLOCATED(SkiaReplayCanvas);
+    WTF_MAKE_NONCOPYABLE(SkiaReplayCanvas);
+public:
+    ~SkiaReplayCanvas() override;
+    static Ref<SkiaReplayCanvas> create(const IntSize&, SkiaImageToFenceMap&&);
+
+private:
+    SkiaReplayCanvas(const IntSize&, SkiaImageToFenceMap&&);
+
+    sk_sp<SkImage> waitForRenderingCompletionAndRewrapImageIfNeeded(const SkImage*);
+
+    void invokeDrawFunctionWithImage(const SkImage*, Function<void(const SkImage*)>&&);
+    void invokeDrawFunctionWithPaint(const SkPaint&, Function<void(const SkPaint&)>&&);
+    void invokeDrawFunctionWithShader(const SkShader*, Function<void(const SkShader*)>&&);
+
+    // SkNWayCanvas overrides
+    void onClipShader(sk_sp<SkShader>, SkClipOp) override;
+    void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override;
+    void onDrawAtlas2(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int, SkBlendMode, const SkSamplingOptions&, const SkRect*, const SkPaint*) override;
+    void onDrawBehind(const SkPaint&) override;
+    void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;
+    void onDrawEdgeAAImageSet2(const ImageSetEntry[], int, const SkPoint[], const SkMatrix[], const SkSamplingOptions&, const SkPaint*, SrcRectConstraint) override;
+    void onDrawGlyphRunList(const sktext::GlyphRunList&, const SkPaint&) override;
+    void onDrawImage2(const SkImage*, SkScalar, SkScalar, const SkSamplingOptions&, const SkPaint*) override;
+    void onDrawImageLattice2(const SkImage*, const Lattice&, const SkRect&, SkFilterMode, const SkPaint*) override;
+    void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&, const SkSamplingOptions&, const SkPaint*, SrcRectConstraint) override;
+    void onDrawOval(const SkRect&, const SkPaint&) override;
+    void onDrawPaint(const SkPaint&) override;
+    void onDrawPatch(const SkPoint[12], const SkColor[4], const SkPoint[4], SkBlendMode, const SkPaint&) override;
+    void onDrawPath(const SkPath&, const SkPaint&) override;
+    void onDrawPoints(PointMode, size_t, const SkPoint[], const SkPaint&) override;
+    void onDrawRRect(const SkRRect&, const SkPaint&) override;
+    void onDrawRect(const SkRect&, const SkPaint&) override;
+    void onDrawRegion(const SkRegion&, const SkPaint&) override;
+    void onDrawSlug(const sktext::gpu::Slug*, const SkPaint&) override;
+    void onDrawTextBlob(const SkTextBlob*, SkScalar x, SkScalar y, const SkPaint&) override;
+    void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
+
+    SkiaImageToFenceMap m_imageToFenceMap;
+};
+
+} // namespace WebCore
+
+#endif // USE(COORDINATED_GRAPHICS) && USE(SKIA)


Reviews:
user: login: kkinnunen-apple
body: Looks very great!
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T15:05:37Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T18:34:02Z
user: login: carlosgcampos
body: Great job, this looks really promising. 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T18:20:37Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T22:47:11Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T22:53:44Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T22:54:03Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T22:54:09Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T23:07:41Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T23:08:05Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T23:08:24Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T23:08:35Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T23:09:18Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T23:14:19Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T23:15:55Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T08:03:35Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T08:04:44Z
user: login: nikolaszimmermann
body: Some notes to myself:
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T09:31:35Z
user: login: carlosgcampos
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T12:32:14Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T14:06:43Z
user: login: carlosgcampos
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T09:40:41Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T22:55:17Z
user: login: nikolaszimmermann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T22:56:31Z
user: login: carlosgcampos
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T08:21:05Z

Review comments:
diff_hunk: @@ -27,25 +27,36 @@
 
 #if USE(SKIA)
 
+#include "GLFence.h"
 #include "GraphicsContext.h"
 WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
 #include <skia/core/SkCanvas.h>
+#include <skia/core/SkImage.h>
 #include <skia/effects/SkDashPathEffect.h>
 WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
 #include <wtf/CompletionHandler.h>
+#include <wtf/HashMap.h>
+
+class SkSurface;
 
 namespace WebCore {
 
 class WEBCORE_EXPORT GraphicsContextSkia final : public GraphicsContext {
 public:
-    GraphicsContextSkia(SkCanvas&, RenderingMode, RenderingPurpose, CompletionHandler<void()>&& = nullptr);
+    enum class ContextType {
+        RegularContext,
+        RecordingContext
+    };
+
+    GraphicsContextSkia(SkCanvas&, RenderingMode, RenderingPurpose, CompletionHandler<void()>&& = nullptr, ContextType = ContextType::RegularContext);
     virtual ~GraphicsContextSkia();
 
     bool hasPlatformContext() const final;
     SkCanvas* platformContext() const final;
 
     const DestinationColorSpace& colorSpace() const final;
 
+    HashMap<const SkImage*, std::unique_ptr<GLFence>>&& releaseImageFences();
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.h
user: login: kkinnunen-apple
body: So this doesn't actually release the fences, it just gives the opportunity for the caller to steal the fences.
So you need to have the signature as 
`HashMap<const SkImage*, std::unique_ptr<GLFence>> releaseImageFences();`

The implementation is fine (return move or return exchange).
created_at: 2025-03-19T15:03:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,25 +27,36 @@
 
 #if USE(SKIA)
 
+#include "GLFence.h"
 #include "GraphicsContext.h"
 WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
 #include <skia/core/SkCanvas.h>
+#include <skia/core/SkImage.h>
 #include <skia/effects/SkDashPathEffect.h>
 WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
 #include <wtf/CompletionHandler.h>
+#include <wtf/HashMap.h>
+
+class SkSurface;
 
 namespace WebCore {
 
 class WEBCORE_EXPORT GraphicsContextSkia final : public GraphicsContext {
 public:
-    GraphicsContextSkia(SkCanvas&, RenderingMode, RenderingPurpose, CompletionHandler<void()>&& = nullptr);
+    enum class ContextType {
+        RegularContext,
+        RecordingContext
+    };
+
+    GraphicsContextSkia(SkCanvas&, RenderingMode, RenderingPurpose, CompletionHandler<void()>&& = nullptr, ContextType = ContextType::RegularContext);
     virtual ~GraphicsContextSkia();
 
     bool hasPlatformContext() const final;
     SkCanvas* platformContext() const final;
 
     const DestinationColorSpace& colorSpace() const final;
 
+    HashMap<const SkImage*, std::unique_ptr<GLFence>>&& releaseImageFences();
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.h
user: login: nikolaszimmermann
body: Good catch, will adapt.
created_at: 2025-03-19T18:34:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -356,23 +363,41 @@ RefPtr<ImageBuffer> ImageBuffer::sinkIntoBufferForDifferentThread(RefPtr<ImageBu
 #if USE(SKIA)
 RefPtr<ImageBuffer> ImageBuffer::sinkIntoImageBufferForCrossThreadTransfer(RefPtr<ImageBuffer> buffer)
 {
-    if (!buffer || buffer->renderingMode() != RenderingMode::Accelerated)
-        return buffer;
-    if (buffer->hasOneRef()) {
-        buffer->finishAcceleratedRenderingAndCreateFence();
+    if (!buffer || buffer->hasOneRef() || buffer->renderingMode() != RenderingMode::Accelerated)
         return buffer;
-    }
-    auto bufferCopy = copyImageBuffer(const_cast<ImageBuffer&>(*buffer), PreserveResolution::Yes, RenderingMode::Accelerated);
-    bufferCopy->finishAcceleratedRenderingAndCreateFence();
-    return bufferCopy;
+    return copyImageBuffer(const_cast<ImageBuffer&>(*buffer), PreserveResolution::Yes, RenderingMode::Accelerated);
 }
 
-RefPtr<ImageBuffer> ImageBuffer::sinkIntoImageBufferAfterCrossThreadTransfer(RefPtr<ImageBuffer> buffer)
+RefPtr<ImageBuffer> ImageBuffer::sinkIntoImageBufferAfterCrossThreadTransfer(RefPtr<ImageBuffer> buffer, std::unique_ptr<GLFence>&& fence)
 {
     if (!buffer || buffer->renderingMode() != RenderingMode::Accelerated)
         return buffer;
-    buffer->waitForAcceleratedRenderingFenceCompletion();
-    return buffer->copyAcceleratedImageBufferBorrowingBackendRenderTarget();
+
+    if (fence) {
+        fence->serverWait();
+        fence = nullptr;
+    }
path: Source/WebCore/platform/graphics/ImageBuffer.cpp
user: login: carlosgcampos
body: I think we should wait after making the context current
created_at: 2025-03-20T17:48:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: carlosgcampos
body: release confuses me a bit, I would use takeImageFences() or stealImageFences() instead.
created_at: 2025-03-20T17:57:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    return WTFMove(m_imageFences);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: carlosgcampos
body: I guess we should return early here if the render mode is not accelerated, before trying to get the gl context.
created_at: 2025-03-20T17:59:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    return WTFMove(m_imageFences);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
     }
 
-    drawSkiaPattern(nativeImage.platformImage(), nativeImage.size(), destRect, tileRect, patternTransform, phase, spacing, options);
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(SkSurface* surface)
+{
+    if (!surface)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: carlosgcampos
body: Same here.
created_at: 2025-03-20T18:00:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    return WTFMove(m_imageFences);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: carlosgcampos
body: Shouldn't we check if image is texture based before?
created_at: 2025-03-20T18:01:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    return WTFMove(m_imageFences);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
     }
 
-    drawSkiaPattern(nativeImage.platformImage(), nativeImage.size(), destRect, tileRect, patternTransform, phase, spacing, options);
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(SkSurface* surface)
+{
+    if (!surface)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(surface);
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: carlosgcampos
body: And here. Also the code is the same, I'm not an expert on c++ templates, but I think we could use just one implementation using a template, no?
created_at: 2025-03-20T18:02:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    return WTFMove(m_imageFences);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
     }
 
-    drawSkiaPattern(nativeImage.platformImage(), nativeImage.size(), destRect, tileRect, patternTransform, phase, spacing, options);
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(SkSurface* surface)
+{
+    if (!surface)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(surface);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
+    }
+
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+void GraphicsContextSkia::createAcceleratedRenderingFenceIfNeeded(const sk_sp<SkImage>& image)
+{
+    if (!image || !image->isTextureBacked() || m_contextType != ContextType::RecordingContext)
+        return;
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: carlosgcampos
body: Ah, I see we check it here. Why do we need both functions?
created_at: 2025-03-20T18:04:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,25 +27,36 @@
 
 #if USE(SKIA)
 
+#include "GLFence.h"
 #include "GraphicsContext.h"
 WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
 #include <skia/core/SkCanvas.h>
+#include <skia/core/SkImage.h>
 #include <skia/effects/SkDashPathEffect.h>
 WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
 #include <wtf/CompletionHandler.h>
+#include <wtf/HashMap.h>
+
+class SkSurface;
 
 namespace WebCore {
 
 class WEBCORE_EXPORT GraphicsContextSkia final : public GraphicsContext {
 public:
-    GraphicsContextSkia(SkCanvas&, RenderingMode, RenderingPurpose, CompletionHandler<void()>&& = nullptr);
+    enum class ContextType {
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.h
user: login: carlosgcampos
body: ```suggestion
    enum class ContextType : bool {
```
created_at: 2025-03-20T18:05:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,25 +27,36 @@
 
 #if USE(SKIA)
 
+#include "GLFence.h"
 #include "GraphicsContext.h"
 WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
 #include <skia/core/SkCanvas.h>
+#include <skia/core/SkImage.h>
 #include <skia/effects/SkDashPathEffect.h>
 WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
 #include <wtf/CompletionHandler.h>
+#include <wtf/HashMap.h>
+
+class SkSurface;
 
 namespace WebCore {
 
 class WEBCORE_EXPORT GraphicsContextSkia final : public GraphicsContext {
 public:
-    GraphicsContextSkia(SkCanvas&, RenderingMode, RenderingPurpose, CompletionHandler<void()>&& = nullptr);
+    enum class ContextType {
+        RegularContext,
+        RecordingContext
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.h
user: login: carlosgcampos
body: Maybe we could add a RenderingPurpose so that we don't need to add another parameter
created_at: 2025-03-20T18:06:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,27 +256,34 @@ Ref<CoordinatedTileBuffer> SkiaPaintingEngine::paintLayer(const GraphicsLayer& l
 
 Ref<CoordinatedTileBuffer> SkiaPaintingEngine::postPaintingTask(const GraphicsLayer& layer, RenderingMode renderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
 {
+    auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
+    buffer->beginPainting();
+
     WTFBeginSignpost(this, RecordTile);
-    auto displayList = recordDisplayList(renderingMode, layer, dirtyRect, contentsOpaque, contentsScale);
+    auto* recordingCanvas = m_pictureRecorder.beginRecording(dirtyRect.width(), dirtyRect.height());
+    GraphicsContextSkia recordingContext(*recordingCanvas, renderingMode, RenderingPurpose::LayerBacking, nullptr, GraphicsContextSkia::ContextType::RecordingContext);
+    paintIntoGraphicsContext(layer, recordingContext, dirtyRect, contentsOpaque, contentsScale);
+    auto picture = m_pictureRecorder.finishRecordingAsPicture();
path: Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
user: login: carlosgcampos
body: I have another idea. What if instead of adding a context type construction parameter and stealing the fences layer, we don't add beginRecording/endRecording? In the beginRecording we set an internal flag in the context to indicate we are recording, and endRecording can return the fences.
created_at: 2025-03-20T18:12:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,27 +256,34 @@ Ref<CoordinatedTileBuffer> SkiaPaintingEngine::paintLayer(const GraphicsLayer& l
 
 Ref<CoordinatedTileBuffer> SkiaPaintingEngine::postPaintingTask(const GraphicsLayer& layer, RenderingMode renderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
 {
+    auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
+    buffer->beginPainting();
+
     WTFBeginSignpost(this, RecordTile);
-    auto displayList = recordDisplayList(renderingMode, layer, dirtyRect, contentsOpaque, contentsScale);
+    auto* recordingCanvas = m_pictureRecorder.beginRecording(dirtyRect.width(), dirtyRect.height());
+    GraphicsContextSkia recordingContext(*recordingCanvas, renderingMode, RenderingPurpose::LayerBacking, nullptr, GraphicsContextSkia::ContextType::RecordingContext);
+    paintIntoGraphicsContext(layer, recordingContext, dirtyRect, contentsOpaque, contentsScale);
+    auto picture = m_pictureRecorder.finishRecordingAsPicture();
     WTFEndSignpost(this, RecordTile);
 
-    auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
-    buffer->beginPainting();
+    auto replayCanvas = SkiaReplayCanvas::create(dirtyRect.size(), recordingContext.releaseImageFences());
path: Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
user: login: carlosgcampos
body: I think it would be better to create the replay canvas in the worker thread, since it's only needed for replay, and only when rendering mode is accelerated, for cpu rendering we don't want it
created_at: 2025-03-20T18:14:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,27 +256,34 @@ Ref<CoordinatedTileBuffer> SkiaPaintingEngine::paintLayer(const GraphicsLayer& l
 
 Ref<CoordinatedTileBuffer> SkiaPaintingEngine::postPaintingTask(const GraphicsLayer& layer, RenderingMode renderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
 {
+    auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
+    buffer->beginPainting();
+
     WTFBeginSignpost(this, RecordTile);
-    auto displayList = recordDisplayList(renderingMode, layer, dirtyRect, contentsOpaque, contentsScale);
+    auto* recordingCanvas = m_pictureRecorder.beginRecording(dirtyRect.width(), dirtyRect.height());
+    GraphicsContextSkia recordingContext(*recordingCanvas, renderingMode, RenderingPurpose::LayerBacking, nullptr, GraphicsContextSkia::ContextType::RecordingContext);
+    paintIntoGraphicsContext(layer, recordingContext, dirtyRect, contentsOpaque, contentsScale);
+    auto picture = m_pictureRecorder.finishRecordingAsPicture();
     WTFEndSignpost(this, RecordTile);
 
-    auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
-    buffer->beginPainting();
+    auto replayCanvas = SkiaReplayCanvas::create(dirtyRect.size(), recordingContext.releaseImageFences());
 
     auto& workerPool = renderingMode == RenderingMode::Accelerated ? *m_gpuWorkerPool.get() : *m_cpuWorkerPool.get();
-    workerPool.postTask([buffer = Ref { buffer }, displayList = WTFMove(displayList), dirtyRect]() mutable {
+    workerPool.postTask([buffer = Ref { buffer }, picture = sk_sp<SkPicture> { picture }, dirtyRect, replayCanvas = WTFMove(replayCanvas)]() mutable {
path: Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
user: login: carlosgcampos
body: So we pass the fences to the lambda instead of the canvas
created_at: 2025-03-20T18:15:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2025 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "SkiaReplayCanvas.h"
+
+#if USE(COORDINATED_GRAPHICS) && USE(SKIA)
+#include "GLFence.h"
+#include "PlatformDisplay.h"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/gpu/ganesh/GrBackendSurface.h>
+#include <skia/gpu/ganesh/SkImageGanesh.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+namespace WebCore {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(SkiaReplayCanvas);
+
+SkiaReplayCanvas::SkiaReplayCanvas(const IntSize& size, HashMap<const SkImage*, std::unique_ptr<GLFence>>&& imageFences)
+    : SkNWayCanvas(size.width(), size.height())
+    , m_imageFences(WTFMove(imageFences))
+{
+}
+
+SkiaReplayCanvas::~SkiaReplayCanvas()
+{
+}
path: Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp
user: login: carlosgcampos
body: ```suggestion
SkiaReplayCanvas::~SkiaReplayCanvas() = default;
```
created_at: 2025-03-20T18:17:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2025 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "SkiaReplayCanvas.h"
+
+#if USE(COORDINATED_GRAPHICS) && USE(SKIA)
+#include "GLFence.h"
+#include "PlatformDisplay.h"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/gpu/ganesh/GrBackendSurface.h>
+#include <skia/gpu/ganesh/SkImageGanesh.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+namespace WebCore {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(SkiaReplayCanvas);
+
+SkiaReplayCanvas::SkiaReplayCanvas(const IntSize& size, HashMap<const SkImage*, std::unique_ptr<GLFence>>&& imageFences)
+    : SkNWayCanvas(size.width(), size.height())
+    , m_imageFences(WTFMove(imageFences))
+{
+}
+
+SkiaReplayCanvas::~SkiaReplayCanvas()
+{
+}
+
+Ref<SkiaReplayCanvas> SkiaReplayCanvas::create(const IntSize& size, HashMap<const SkImage*, std::unique_ptr<GLFence>>&& imageFences)
+{
+    return adoptRef(*new SkiaReplayCanvas(size, WTFMove(imageFences)));
+}
+
+sk_sp<SkImage> SkiaReplayCanvas::waitForRenderingCompletionAndRewrapImageIfNeeded(const SkImage* image)
+{
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    if (auto fence = m_imageFences.take(image))
+        fence->serverWait();
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    if (image->isValid(grContext))
+        return nullptr;
+
+    // TODO: Add error reporting mechanism, a failure from GetBackendTextureFromImage() should be visible / reported.
path: Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp
user: login: carlosgcampos
body: ```suggestion
    // FIXME: Add error reporting mechanism, a failure from GetBackendTextureFromImage() should be visible / reported.
```
created_at: 2025-03-20T18:17:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2025 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "SkiaReplayCanvas.h"
+
+#if USE(COORDINATED_GRAPHICS) && USE(SKIA)
+#include "GLFence.h"
+#include "PlatformDisplay.h"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/gpu/ganesh/GrBackendSurface.h>
+#include <skia/gpu/ganesh/SkImageGanesh.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+namespace WebCore {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(SkiaReplayCanvas);
+
+SkiaReplayCanvas::SkiaReplayCanvas(const IntSize& size, HashMap<const SkImage*, std::unique_ptr<GLFence>>&& imageFences)
+    : SkNWayCanvas(size.width(), size.height())
+    , m_imageFences(WTFMove(imageFences))
+{
+}
+
+SkiaReplayCanvas::~SkiaReplayCanvas()
+{
+}
+
+Ref<SkiaReplayCanvas> SkiaReplayCanvas::create(const IntSize& size, HashMap<const SkImage*, std::unique_ptr<GLFence>>&& imageFences)
+{
+    return adoptRef(*new SkiaReplayCanvas(size, WTFMove(imageFences)));
+}
+
+sk_sp<SkImage> SkiaReplayCanvas::waitForRenderingCompletionAndRewrapImageIfNeeded(const SkImage* image)
+{
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    if (auto fence = m_imageFences.take(image))
+        fence->serverWait();
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    if (image->isValid(grContext))
+        return nullptr;
+
+    // TODO: Add error reporting mechanism, a failure from GetBackendTextureFromImage() should be visible / reported.
+    GrBackendTexture backendTexture;
+    if (!SkImages::GetBackendTextureFromImage(image, &backendTexture, false))
+        return nullptr;
+
+    return SkImages::BorrowTextureFrom(grContext, backendTexture, kTopLeft_GrSurfaceOrigin, image->colorType(), image->alphaType(), image->refColorSpace());
+}
+
+void SkiaReplayCanvas::invokeDrawFunctionWithImage(const SkImage* image, Function<void(const SkImage*)>&& drawFunction)
+{
+    if (!image) {
+        drawFunction(image);
+        return;
+    }
path: Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp
user: login: carlosgcampos
body: If waitForRenderingCompletionAndRewrapImageIfNeeded() does the null check we don't need this block
created_at: 2025-03-20T18:19:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2025 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "SkiaReplayCanvas.h"
+
+#if USE(COORDINATED_GRAPHICS) && USE(SKIA)
+#include "GLFence.h"
+#include "PlatformDisplay.h"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/gpu/ganesh/GrBackendSurface.h>
+#include <skia/gpu/ganesh/SkImageGanesh.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+namespace WebCore {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(SkiaReplayCanvas);
+
+SkiaReplayCanvas::SkiaReplayCanvas(const IntSize& size, HashMap<const SkImage*, std::unique_ptr<GLFence>>&& imageFences)
+    : SkNWayCanvas(size.width(), size.height())
+    , m_imageFences(WTFMove(imageFences))
+{
+}
+
+SkiaReplayCanvas::~SkiaReplayCanvas()
+{
+}
+
+Ref<SkiaReplayCanvas> SkiaReplayCanvas::create(const IntSize& size, HashMap<const SkImage*, std::unique_ptr<GLFence>>&& imageFences)
+{
+    return adoptRef(*new SkiaReplayCanvas(size, WTFMove(imageFences)));
+}
+
+sk_sp<SkImage> SkiaReplayCanvas::waitForRenderingCompletionAndRewrapImageIfNeeded(const SkImage* image)
+{
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    if (auto fence = m_imageFences.take(image))
+        fence->serverWait();
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    if (image->isValid(grContext))
+        return nullptr;
+
+    // TODO: Add error reporting mechanism, a failure from GetBackendTextureFromImage() should be visible / reported.
+    GrBackendTexture backendTexture;
+    if (!SkImages::GetBackendTextureFromImage(image, &backendTexture, false))
+        return nullptr;
+
+    return SkImages::BorrowTextureFrom(grContext, backendTexture, kTopLeft_GrSurfaceOrigin, image->colorType(), image->alphaType(), image->refColorSpace());
+}
+
+void SkiaReplayCanvas::invokeDrawFunctionWithImage(const SkImage* image, Function<void(const SkImage*)>&& drawFunction)
+{
+    if (!image) {
+        drawFunction(image);
+        return;
+    }
+
+    if (auto wrappedImage = waitForRenderingCompletionAndRewrapImageIfNeeded(image)) {
+        drawFunction(wrappedImage.get());
+        return;
+    }
+
+    drawFunction(image);
+}
+
+void SkiaReplayCanvas::invokeDrawFunctionWithPaint(const SkPaint& paint, Function<void(const SkPaint&)>&& drawFunction)
+{
+    auto* shader = paint.getShader();
+    if (!shader) {
+        drawFunction(paint);
+        return;
+    }
+
+    SkMatrix localMatrix;
+    SkTileMode mode[2];
+    auto* image = shader->isAImage(&localMatrix, mode);
+    if (!image) {
+        drawFunction(paint);
+        return;
+    }
path: Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp
user: login: carlosgcampos
body: Same here
created_at: 2025-03-20T18:20:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: nikolaszimmermann
body: I'd go for takeImageFences() -- but it's obsoleted by your beginRecording / endRecording proposal, which is even nicer.
created_at: 2025-03-20T22:47:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -356,23 +363,41 @@ RefPtr<ImageBuffer> ImageBuffer::sinkIntoBufferForDifferentThread(RefPtr<ImageBu
 #if USE(SKIA)
 RefPtr<ImageBuffer> ImageBuffer::sinkIntoImageBufferForCrossThreadTransfer(RefPtr<ImageBuffer> buffer)
 {
-    if (!buffer || buffer->renderingMode() != RenderingMode::Accelerated)
-        return buffer;
-    if (buffer->hasOneRef()) {
-        buffer->finishAcceleratedRenderingAndCreateFence();
+    if (!buffer || buffer->hasOneRef() || buffer->renderingMode() != RenderingMode::Accelerated)
         return buffer;
-    }
-    auto bufferCopy = copyImageBuffer(const_cast<ImageBuffer&>(*buffer), PreserveResolution::Yes, RenderingMode::Accelerated);
-    bufferCopy->finishAcceleratedRenderingAndCreateFence();
-    return bufferCopy;
+    return copyImageBuffer(const_cast<ImageBuffer&>(*buffer), PreserveResolution::Yes, RenderingMode::Accelerated);
 }
 
-RefPtr<ImageBuffer> ImageBuffer::sinkIntoImageBufferAfterCrossThreadTransfer(RefPtr<ImageBuffer> buffer)
+RefPtr<ImageBuffer> ImageBuffer::sinkIntoImageBufferAfterCrossThreadTransfer(RefPtr<ImageBuffer> buffer, std::unique_ptr<GLFence>&& fence)
 {
     if (!buffer || buffer->renderingMode() != RenderingMode::Accelerated)
         return buffer;
-    buffer->waitForAcceleratedRenderingFenceCompletion();
-    return buffer->copyAcceleratedImageBufferBorrowingBackendRenderTarget();
+
+    if (fence) {
+        fence->serverWait();
+        fence = nullptr;
+    }
path: Source/WebCore/platform/graphics/ImageBuffer.cpp
user: login: nikolaszimmermann
body: We do that -- this function only exists for the sake of sharing code between the two createAcceleratedRenderingFenceIfNeeded() variants and is not mean to be used without prior checking that the imge is texture backed.
created_at: 2025-03-20T22:53:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    return WTFMove(m_imageFences);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: nikolaszimmermann
body: See above.
created_at: 2025-03-20T22:54:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    return WTFMove(m_imageFences);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
     }
 
-    drawSkiaPattern(nativeImage.platformImage(), nativeImage.size(), destRect, tileRect, patternTransform, phase, spacing, options);
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(SkSurface* surface)
+{
+    if (!surface)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: nikolaszimmermann
body: Ditto :-)
created_at: 2025-03-20T22:54:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,25 +27,36 @@
 
 #if USE(SKIA)
 
+#include "GLFence.h"
 #include "GraphicsContext.h"
 WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
 #include <skia/core/SkCanvas.h>
+#include <skia/core/SkImage.h>
 #include <skia/effects/SkDashPathEffect.h>
 WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
 #include <wtf/CompletionHandler.h>
+#include <wtf/HashMap.h>
+
+class SkSurface;
 
 namespace WebCore {
 
 class WEBCORE_EXPORT GraphicsContextSkia final : public GraphicsContext {
 public:
-    GraphicsContextSkia(SkCanvas&, RenderingMode, RenderingPurpose, CompletionHandler<void()>&& = nullptr);
+    enum class ContextType {
+        RegularContext,
+        RecordingContext
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.h
user: login: nikolaszimmermann
body: No longer a parameter, just a private `enum class ContextMode { PaintingMode, RecordingMode }`. beginRecording() switches to RecordingMode, endRecording() back to PaintingMode. No more consturctor parameter, good suggestion!
created_at: 2025-03-20T23:07:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    return WTFMove(m_imageFences);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
     }
 
-    drawSkiaPattern(nativeImage.platformImage(), nativeImage.size(), destRect, tileRect, patternTransform, phase, spacing, options);
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(SkSurface* surface)
+{
+    if (!surface)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(surface);
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: nikolaszimmermann
body: The SkSurface variant is not used in this patch, but in the one converting ImageBitmap. Need to think about it.
created_at: 2025-03-20T23:08:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    return WTFMove(m_imageFences);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
     }
 
-    drawSkiaPattern(nativeImage.platformImage(), nativeImage.size(), destRect, tileRect, patternTransform, phase, spacing, options);
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(SkSurface* surface)
+{
+    if (!surface)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(surface);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
+    }
+
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+void GraphicsContextSkia::createAcceleratedRenderingFenceIfNeeded(const sk_sp<SkImage>& image)
+{
+    if (!image || !image->isTextureBacked() || m_contextType != ContextType::RecordingContext)
+        return;
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: nikolaszimmermann
body: you mean the SkSurface and SkImage variants?
created_at: 2025-03-20T23:08:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,27 +256,34 @@ Ref<CoordinatedTileBuffer> SkiaPaintingEngine::paintLayer(const GraphicsLayer& l
 
 Ref<CoordinatedTileBuffer> SkiaPaintingEngine::postPaintingTask(const GraphicsLayer& layer, RenderingMode renderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
 {
+    auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
+    buffer->beginPainting();
+
     WTFBeginSignpost(this, RecordTile);
-    auto displayList = recordDisplayList(renderingMode, layer, dirtyRect, contentsOpaque, contentsScale);
+    auto* recordingCanvas = m_pictureRecorder.beginRecording(dirtyRect.width(), dirtyRect.height());
+    GraphicsContextSkia recordingContext(*recordingCanvas, renderingMode, RenderingPurpose::LayerBacking, nullptr, GraphicsContextSkia::ContextType::RecordingContext);
+    paintIntoGraphicsContext(layer, recordingContext, dirtyRect, contentsOpaque, contentsScale);
+    auto picture = m_pictureRecorder.finishRecordingAsPicture();
path: Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
user: login: nikolaszimmermann
body: That is perfect, I did that.
created_at: 2025-03-20T23:08:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,27 +256,34 @@ Ref<CoordinatedTileBuffer> SkiaPaintingEngine::paintLayer(const GraphicsLayer& l
 
 Ref<CoordinatedTileBuffer> SkiaPaintingEngine::postPaintingTask(const GraphicsLayer& layer, RenderingMode renderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
 {
+    auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
+    buffer->beginPainting();
+
     WTFBeginSignpost(this, RecordTile);
-    auto displayList = recordDisplayList(renderingMode, layer, dirtyRect, contentsOpaque, contentsScale);
+    auto* recordingCanvas = m_pictureRecorder.beginRecording(dirtyRect.width(), dirtyRect.height());
+    GraphicsContextSkia recordingContext(*recordingCanvas, renderingMode, RenderingPurpose::LayerBacking, nullptr, GraphicsContextSkia::ContextType::RecordingContext);
+    paintIntoGraphicsContext(layer, recordingContext, dirtyRect, contentsOpaque, contentsScale);
+    auto picture = m_pictureRecorder.finishRecordingAsPicture();
     WTFEndSignpost(this, RecordTile);
 
-    auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
-    buffer->beginPainting();
+    auto replayCanvas = SkiaReplayCanvas::create(dirtyRect.size(), recordingContext.releaseImageFences());
path: Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
user: login: nikolaszimmermann
body: Ack. Already changed it, only think that needs to move cross-threads is the new `ImageFenceMap` (which is just a `using SkiaImageFenceMap = HashMap<const SkImage, std::unique_ptr<GLFence>>`.
created_at: 2025-03-20T23:09:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2025 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "SkiaReplayCanvas.h"
+
+#if USE(COORDINATED_GRAPHICS) && USE(SKIA)
+#include "GLFence.h"
+#include "PlatformDisplay.h"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/gpu/ganesh/GrBackendSurface.h>
+#include <skia/gpu/ganesh/SkImageGanesh.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+namespace WebCore {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(SkiaReplayCanvas);
+
+SkiaReplayCanvas::SkiaReplayCanvas(const IntSize& size, HashMap<const SkImage*, std::unique_ptr<GLFence>>&& imageFences)
+    : SkNWayCanvas(size.width(), size.height())
+    , m_imageFences(WTFMove(imageFences))
+{
+}
+
+SkiaReplayCanvas::~SkiaReplayCanvas()
+{
+}
+
+Ref<SkiaReplayCanvas> SkiaReplayCanvas::create(const IntSize& size, HashMap<const SkImage*, std::unique_ptr<GLFence>>&& imageFences)
+{
+    return adoptRef(*new SkiaReplayCanvas(size, WTFMove(imageFences)));
+}
+
+sk_sp<SkImage> SkiaReplayCanvas::waitForRenderingCompletionAndRewrapImageIfNeeded(const SkImage* image)
+{
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    if (auto fence = m_imageFences.take(image))
+        fence->serverWait();
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    if (image->isValid(grContext))
+        return nullptr;
+
+    // TODO: Add error reporting mechanism, a failure from GetBackendTextureFromImage() should be visible / reported.
+    GrBackendTexture backendTexture;
+    if (!SkImages::GetBackendTextureFromImage(image, &backendTexture, false))
+        return nullptr;
+
+    return SkImages::BorrowTextureFrom(grContext, backendTexture, kTopLeft_GrSurfaceOrigin, image->colorType(), image->alphaType(), image->refColorSpace());
+}
+
+void SkiaReplayCanvas::invokeDrawFunctionWithImage(const SkImage* image, Function<void(const SkImage*)>&& drawFunction)
+{
+    if (!image) {
+        drawFunction(image);
+        return;
+    }
path: Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp
user: login: nikolaszimmermann
body: Good idea, fixed.
created_at: 2025-03-20T23:14:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    return WTFMove(m_imageFences);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: nikolaszimmermann
body: Done elsewhere.
created_at: 2025-03-20T23:15:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    return WTFMove(m_imageFences);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
     }
 
-    drawSkiaPattern(nativeImage.platformImage(), nativeImage.size(), destRect, tileRect, patternTransform, phase, spacing, options);
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(SkSurface* surface)
+{
+    if (!surface)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(surface);
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: nikolaszimmermann
body: I will postpone the introduction of the SkSurface variant for a follow-up PR.
created_at: 2025-03-21T08:03:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +1000,114 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+HashMap<const SkImage*, std::unique_ptr<GLFence>> GraphicsContextSkia::releaseImageFences()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    return WTFMove(m_imageFences);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
     }
 
-    drawSkiaPattern(nativeImage.platformImage(), nativeImage.size(), destRect, tileRect, patternTransform, phase, spacing, options);
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFence(SkSurface* surface)
+{
+    if (!surface)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(surface);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
+    }
+
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+void GraphicsContextSkia::createAcceleratedRenderingFenceIfNeeded(const sk_sp<SkImage>& image)
+{
+    if (!image || !image->isTextureBacked() || m_contextType != ContextType::RecordingContext)
+        return;
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: nikolaszimmermann
body: Ah you mean via createAcceleratedRenderingFence and createAcceleratedRenderingFenceIfNeeded -- yeah it's confusing, I'll get rid of the distinction.
created_at: 2025-03-21T08:04:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +999,97 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+void GraphicsContextSkia::beginRecording()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    ASSERT(m_contextMode == ContextMode::PaintingMode);
+    m_contextMode = ContextMode::RecordingMode;
+}
+
+SkiaImageToFenceMap GraphicsContextSkia::endRecording()
+{
+    ASSERT(m_contextMode == ContextMode::RecordingMode);
+    m_contextMode = ContextMode::PaintingMode;
+    return WTFMove(m_imageToFenceMap);
+}
+
+inline std::unique_ptr<GLFence> createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: nikolaszimmermann
body: This should check for `image->isTextureBacked()` too -- so we can omit the check from `createAcceleratedRenderingFenceIfNeeeded`.
created_at: 2025-03-21T09:29:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +999,97 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+void GraphicsContextSkia::beginRecording()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    ASSERT(m_contextMode == ContextMode::PaintingMode);
+    m_contextMode = ContextMode::RecordingMode;
+}
+
+SkiaImageToFenceMap GraphicsContextSkia::endRecording()
+{
+    ASSERT(m_contextMode == ContextMode::RecordingMode);
+    m_contextMode = ContextMode::PaintingMode;
+    return WTFMove(m_imageToFenceMap);
+}
+
+inline std::unique_ptr<GLFence> createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
     }
 
-    drawSkiaPattern(nativeImage.platformImage(), nativeImage.size(), destRect, tileRect, patternTransform, phase, spacing, options);
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+void GraphicsContextSkia::createAcceleratedRenderingFenceIfNeeded(const sk_sp<SkImage>& image)
+{
+    if (!image || !image->isTextureBacked() || m_contextMode != ContextMode::RecordingMode)
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: nikolaszimmermann
body: The first two conditions should be handled by createAcceleratedRenderingFence.
created_at: 2025-03-21T09:29:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +999,97 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        createAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                createAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+void GraphicsContextSkia::beginRecording()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    ASSERT(m_contextMode == ContextMode::PaintingMode);
+    m_contextMode = ContextMode::RecordingMode;
+}
+
+SkiaImageToFenceMap GraphicsContextSkia::endRecording()
+{
+    ASSERT(m_contextMode == ContextMode::RecordingMode);
+    m_contextMode = ContextMode::PaintingMode;
+    return WTFMove(m_imageToFenceMap);
+}
+
+inline std::unique_ptr<GLFence> createAcceleratedRenderingFence(const sk_sp<SkImage>& image)
+{
+    if (!image)
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
     }
 
-    drawSkiaPattern(nativeImage.platformImage(), nativeImage.size(), destRect, tileRect, patternTransform, phase, spacing, options);
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+void GraphicsContextSkia::createAcceleratedRenderingFenceIfNeeded(const sk_sp<SkImage>& image)
+{
+    if (!image || !image->isTextureBacked() || m_contextMode != ContextMode::RecordingMode)
+        return;
+
+    if (auto fence = createAcceleratedRenderingFence(image))
+        m_imageToFenceMap.add(image.get(), WTFMove(fence));
+}
+
+void GraphicsContextSkia::createAcceleratedRenderingFenceIfNeeded(SkPaint& paint)
+{
+    if (m_contextMode != ContextMode::RecordingMode)
+        return;
+
+    auto* shader = paint.getShader();
+    if (!shader)
+        return;
+
+    auto* image = shader->isAImage(nullptr, nullptr);
+    if (!image || !image->isTextureBacked())
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: nikolaszimmermann
body: Ditto.
created_at: 2025-03-21T09:30:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2025 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "SkiaReplayCanvas.h"
+
+#if USE(COORDINATED_GRAPHICS) && USE(SKIA)
+#include "GLFence.h"
+#include "PlatformDisplay.h"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/gpu/ganesh/GrBackendSurface.h>
+#include <skia/gpu/ganesh/SkImageGanesh.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+namespace WebCore {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(SkiaReplayCanvas);
+
+SkiaReplayCanvas::SkiaReplayCanvas(const IntSize& size, SkiaImageToFenceMap&& imageToFenceMap)
+    : SkNWayCanvas(size.width(), size.height())
+    , m_imageToFenceMap(WTFMove(imageToFenceMap))
+{
+}
+
+SkiaReplayCanvas::~SkiaReplayCanvas() = default;
+
+Ref<SkiaReplayCanvas> SkiaReplayCanvas::create(const IntSize& size, SkiaImageToFenceMap&& imageToFenceMap)
+{
+    return adoptRef(*new SkiaReplayCanvas(size, WTFMove(imageToFenceMap)));
+}
+
+sk_sp<SkImage> SkiaReplayCanvas::waitForRenderingCompletionAndRewrapImageIfNeeded(const SkImage* image)
+{
+    if (!image || !image->isTextureBacked())
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    if (auto fence = m_imageToFenceMap.take(image))
+        fence->serverWait();
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    if (image->isValid(grContext))
+        return nullptr;
+
+    // FIXME: Add error reporting mechanism, a failure from GetBackendTextureFromImage() should be visible / reported.
+    GrBackendTexture backendTexture;
+    if (!SkImages::GetBackendTextureFromImage(image, &backendTexture, false))
+        return nullptr;
+
+    return SkImages::BorrowTextureFrom(grContext, backendTexture, kTopLeft_GrSurfaceOrigin, image->colorType(), image->alphaType(), image->refColorSpace());
+}
+
+void SkiaReplayCanvas::invokeDrawFunctionWithImage(const SkImage* image, Function<void(const SkImage*)>&& drawFunction)
+{
+    if (auto wrappedImage = waitForRenderingCompletionAndRewrapImageIfNeeded(image)) {
+        drawFunction(wrappedImage.get());
+        return;
+    }
+
+    drawFunction(image);
+}
+
+void SkiaReplayCanvas::invokeDrawFunctionWithPaint(const SkPaint& paint, Function<void(const SkPaint&)>&& drawFunction)
+{
+    auto* shader = paint.getShader();
+    if (!shader) {
path: Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp
user: login: nikolaszimmermann
body: We can also save this branch, if we use: `auto *image = shader ? shader->isAImage(&localMatrix, mode) : nullptr` below.

created_at: 2025-03-21T09:31:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2025 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "SkiaReplayCanvas.h"
+
+#if USE(COORDINATED_GRAPHICS) && USE(SKIA)
+#include "GLFence.h"
+#include "PlatformDisplay.h"
+
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_BEGIN
+#include <skia/gpu/ganesh/GrBackendSurface.h>
+#include <skia/gpu/ganesh/SkImageGanesh.h>
+WTF_IGNORE_WARNINGS_IN_THIRD_PARTY_CODE_END
+
+namespace WebCore {
+
+WTF_MAKE_TZONE_ALLOCATED_IMPL(SkiaReplayCanvas);
+
+SkiaReplayCanvas::SkiaReplayCanvas(const IntSize& size, SkiaImageToFenceMap&& imageToFenceMap)
+    : SkNWayCanvas(size.width(), size.height())
+    , m_imageToFenceMap(WTFMove(imageToFenceMap))
+{
+}
+
+SkiaReplayCanvas::~SkiaReplayCanvas() = default;
+
+Ref<SkiaReplayCanvas> SkiaReplayCanvas::create(const IntSize& size, SkiaImageToFenceMap&& imageToFenceMap)
+{
+    return adoptRef(*new SkiaReplayCanvas(size, WTFMove(imageToFenceMap)));
+}
+
+sk_sp<SkImage> SkiaReplayCanvas::waitForRenderingCompletionAndRewrapImageIfNeeded(const SkImage* image)
+{
+    if (!image || !image->isTextureBacked())
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    if (auto fence = m_imageToFenceMap.take(image))
+        fence->serverWait();
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    if (image->isValid(grContext))
+        return nullptr;
+
+    // FIXME: Add error reporting mechanism, a failure from GetBackendTextureFromImage() should be visible / reported.
+    GrBackendTexture backendTexture;
+    if (!SkImages::GetBackendTextureFromImage(image, &backendTexture, false))
+        return nullptr;
+
+    return SkImages::BorrowTextureFrom(grContext, backendTexture, kTopLeft_GrSurfaceOrigin, image->colorType(), image->alphaType(), image->refColorSpace());
+}
+
+void SkiaReplayCanvas::invokeDrawFunctionWithImage(const SkImage* image, Function<void(const SkImage*)>&& drawFunction)
+{
+    if (auto wrappedImage = waitForRenderingCompletionAndRewrapImageIfNeeded(image)) {
+        drawFunction(wrappedImage.get());
+        return;
+    }
+
+    drawFunction(image);
+}
+
+void SkiaReplayCanvas::invokeDrawFunctionWithPaint(const SkPaint& paint, Function<void(const SkPaint&)>&& drawFunction)
+{
+    auto* shader = paint.getShader();
+    if (!shader) {
+        drawFunction(paint);
+        return;
+    }
+
+    SkMatrix localMatrix;
+    SkTileMode mode[2];
+    auto* image = shader->isAImage(&localMatrix, mode);
+
+    if (auto wrappedImage = waitForRenderingCompletionAndRewrapImageIfNeeded(image)) {
+        // FIXME: There is no way to get the SkSamplingOptions that were used to create the original shader.
+        // Add Skia API? (SkImageShader stores SkSamplingOptions but is private and not installed).
+        auto paintCopy = paint;
+        paintCopy.setShader(wrappedImage->makeShader(mode[0], mode[1], SkSamplingOptions(), &localMatrix));
+        drawFunction(paintCopy);
+        return;
+    }
+
+    drawFunction(paint);
+}
+
+void SkiaReplayCanvas::invokeDrawFunctionWithShader(const SkShader* shader, Function<void(const SkShader*)>&& drawFunction)
+{
+    if (!shader) {
path: Source/WebCore/platform/graphics/skia/SkiaReplayCanvas.cpp
user: login: nikolaszimmermann
body: Ditto.
created_at: 2025-03-21T09:31:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +999,91 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        trackAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                trackAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+void GraphicsContextSkia::beginRecording()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    ASSERT(m_contextMode == ContextMode::PaintingMode);
+    m_contextMode = ContextMode::RecordingMode;
+}
+
+SkiaImageToFenceMap GraphicsContextSkia::endRecording()
+{
+    ASSERT(m_contextMode == ContextMode::RecordingMode);
+    m_contextMode = ContextMode::PaintingMode;
+    return WTFMove(m_imageToFenceMap);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFenceIfNeeded(const sk_sp<SkImage>& image)
+{
+    if (!image || !image->isTextureBacked())
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
     }
 
-    drawSkiaPattern(nativeImage.platformImage(), nativeImage.size(), destRect, tileRect, patternTransform, phase, spacing, options);
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+void GraphicsContextSkia::trackAcceleratedRenderingFenceIfNeeded(const sk_sp<SkImage>& image)
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: carlosgcampos
body: I think we can make this const if we mark the fence map as mutable, that way we don't need to remove the const from the setup functions
created_at: 2025-03-21T11:21:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1010,35 +999,91 @@ void GraphicsContextSkia::drawSkiaPattern(const sk_sp<SkImage>& image, const Int
             repeatY = imageSampledRect.y() < 0 || std::trunc(imageSampledRect.bottom()) > size.height();
         }
         paint.setShader(image->makeShader(repeatX ? SkTileMode::kRepeat : SkTileMode::kClamp, repeatY ? SkTileMode::kRepeat : SkTileMode::kClamp, samplingOptions, &shaderMatrix));
+        trackAcceleratedRenderingFenceIfNeeded(image);
     } else {
-        SkPictureRecorder recorder;
-        auto* recordCanvas = recorder.beginRecording(SkRect::MakeWH(tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d()));
-        // The below call effectively extracts a tile from the image thus performing a clipping.
-        recordCanvas->drawImageRect(image, tileRect, SkRect::MakeWH(tileRect.width(), tileRect.height()), samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
-        auto picture = recorder.finishRecordingAsPicture();
-        paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        auto tileFloatRectWithSpacing = FloatRect(0, 0, tileRect.width() + spacing.width() / patternTransform.a(), tileRect.height() + spacing.height() / patternTransform.d());
+        if (image->isTextureBacked()) {
+            auto enclosingTileIntRect = enclosingIntRect(tileRect);
+            auto dstRect = SkRect::MakeWH(enclosingTileIntRect.width(), enclosingTileIntRect.height());
+            auto clipRect = enclosingIntRect(tileFloatRectWithSpacing);
+            if (auto surface = createAcceleratedSurface({ clipRect.width(), clipRect.height() })) {
+                surface->getCanvas()->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+                auto tileImage = surface->makeImageSnapshot();
+                paint.setShader(tileImage->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions, &shaderMatrix));
+                trackAcceleratedRenderingFenceIfNeeded(tileImage);
+            }
+        } else {
+            auto dstRect = SkRect::MakeWH(tileRect.width(), tileRect.height());
+            auto clipRect = SkRect::MakeWH(tileFloatRectWithSpacing.width(), tileFloatRectWithSpacing.height());
+            // For raster images we can save creating a copy alltogether, by using SkPicture recording instead.
+            SkPictureRecorder recorder;
+            auto* recordCanvas = recorder.beginRecording(clipRect);
+            // The below call effectively extracts a tile from the image thus performing a clipping.
+            recordCanvas->drawImageRect(image, tileRect, dstRect, samplingOptions, nullptr, SkCanvas::kStrict_SrcRectConstraint);
+            auto picture = recorder.finishRecordingAsPicture();
+            paint.setShader(picture->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, samplingOptions.filter, &shaderMatrix, nullptr));
+        }
     }
 
     m_canvas.drawRect(destRect, paint);
 }
 
-void GraphicsContextSkia::drawPattern(NativeImage& nativeImage, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, ImagePaintingOptions options)
+void GraphicsContextSkia::beginRecording()
 {
-    auto* nativeImageGrContext = nativeImage.backend().skiaGrContext();
-    if (nativeImageGrContext && nativeImageGrContext != PlatformDisplay::sharedDisplay().skiaGrContext()) {
-        // If we encounter an accelerated NativeImage (skiaGrContext() != nullptr), we are in the threaded GPU rendering painting mode, verify that.
-        ASSERT(m_renderingMode == RenderingMode::Accelerated);
-        ASSERT(ProcessCapabilities::canUseAcceleratedBuffers());
-#if USE(COORDINATED_GRAPHICS)
-        ASSERT(SkiaPaintingEngine::numberOfGPUPaintingThreads() > 0);
-#endif
-        // The 'nativeImage' was produced on another thread -- to use it here, we need to create a new NativeImage, that wraps the existing GPU resource.
-        if (auto newNativeImage = nativeImage.backend().copyAcceleratedNativeImageBorrowingBackendTexture())
-            drawSkiaPattern(newNativeImage->platformImage(), newNativeImage->size(), destRect, tileRect, patternTransform, phase, spacing, options);
-        return;
+    ASSERT(m_contextMode == ContextMode::PaintingMode);
+    m_contextMode = ContextMode::RecordingMode;
+}
+
+SkiaImageToFenceMap GraphicsContextSkia::endRecording()
+{
+    ASSERT(m_contextMode == ContextMode::RecordingMode);
+    m_contextMode = ContextMode::PaintingMode;
+    return WTFMove(m_imageToFenceMap);
+}
+
+std::unique_ptr<GLFence> GraphicsContextSkia::createAcceleratedRenderingFenceIfNeeded(const sk_sp<SkImage>& image)
+{
+    if (!image || !image->isTextureBacked())
+        return nullptr;
+
+    auto* glContext = PlatformDisplay::sharedDisplay().skiaGLContext();
+    if (!glContext || !glContext->makeContextCurrent())
+        return nullptr;
+
+    auto* grContext = PlatformDisplay::sharedDisplay().skiaGrContext();
+    RELEASE_ASSERT(grContext);
+
+    grContext->flush(image);
+
+    if (GLFence::isSupported()) {
+        grContext->submit(GrSyncCpu::kNo);
+
+        if (auto fence = GLFence::create())
+            return fence;
     }
 
-    drawSkiaPattern(nativeImage.platformImage(), nativeImage.size(), destRect, tileRect, patternTransform, phase, spacing, options);
+    grContext->submit(GrSyncCpu::kYes);
+    return nullptr;
+}
+
+void GraphicsContextSkia::trackAcceleratedRenderingFenceIfNeeded(const sk_sp<SkImage>& image)
path: Source/WebCore/platform/graphics/skia/GraphicsContextSkia.cpp
user: login: nikolaszimmermann
body: Well, in the end `setupFillPaint()` needs to alter `m_imageFenceMap` -- marking it as `const` just hides the fact that as side-effect it will mutate member variables. Therefore I think omitting the const is correct - I'd usually use `mutable` as last resort, not on purpose.
created_at: 2025-03-21T14:06:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,27 +256,35 @@ Ref<CoordinatedTileBuffer> SkiaPaintingEngine::paintLayer(const GraphicsLayer& l
 
 Ref<CoordinatedTileBuffer> SkiaPaintingEngine::postPaintingTask(const GraphicsLayer& layer, RenderingMode renderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
 {
-    WTFBeginSignpost(this, RecordTile);
-    auto displayList = recordDisplayList(renderingMode, layer, dirtyRect, contentsOpaque, contentsScale);
-    WTFEndSignpost(this, RecordTile);
-
     auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
     buffer->beginPainting();
 
+    WTFBeginSignpost(this, RecordTile);
+    auto* recordingCanvas = m_pictureRecorder.beginRecording(dirtyRect.width(), dirtyRect.height());
path: Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
user: login: carlosgcampos
body: Why is the SkPictureRecorder a member? We only need it here, and after finishRecordingAsPicture it can be destroyed, no?
created_at: 2025-03-24T09:33:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,27 +256,35 @@ Ref<CoordinatedTileBuffer> SkiaPaintingEngine::paintLayer(const GraphicsLayer& l
 
 Ref<CoordinatedTileBuffer> SkiaPaintingEngine::postPaintingTask(const GraphicsLayer& layer, RenderingMode renderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
 {
-    WTFBeginSignpost(this, RecordTile);
-    auto displayList = recordDisplayList(renderingMode, layer, dirtyRect, contentsOpaque, contentsScale);
-    WTFEndSignpost(this, RecordTile);
-
     auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
     buffer->beginPainting();
 
+    WTFBeginSignpost(this, RecordTile);
+    auto* recordingCanvas = m_pictureRecorder.beginRecording(dirtyRect.width(), dirtyRect.height());
+    GraphicsContextSkia recordingContext(*recordingCanvas, renderingMode, RenderingPurpose::LayerBacking);
+    recordingContext.beginRecording();
+    paintIntoGraphicsContext(layer, recordingContext, dirtyRect, contentsOpaque, contentsScale);
+    auto imageToFenceMap = recordingContext.endRecording();
+    auto picture = m_pictureRecorder.finishRecordingAsPicture();
+    WTFEndSignpost(this, RecordTile);
+
     auto& workerPool = renderingMode == RenderingMode::Accelerated ? *m_gpuWorkerPool.get() : *m_cpuWorkerPool.get();
-    workerPool.postTask([buffer = Ref { buffer }, displayList = WTFMove(displayList), dirtyRect]() mutable {
+    workerPool.postTask([buffer = Ref { buffer }, picture = sk_sp<SkPicture> { picture }, dirtyRect, imageToFenceMap = WTFMove(imageToFenceMap)]() mutable {
         if (auto* canvas = buffer->canvas()) {
+            auto replayCanvas = SkiaReplayCanvas::create(dirtyRect.size(), WTFMove(imageToFenceMap));
+            replayCanvas->addCanvas(canvas);
path: Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
user: login: carlosgcampos
body: I think we should only use the SkiaReplayCanvas when doing GPU rendering. We could even avoid it if the image to fence map is empty, not sure it's worth it, though.
created_at: 2025-03-24T09:36:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,27 +256,35 @@ Ref<CoordinatedTileBuffer> SkiaPaintingEngine::paintLayer(const GraphicsLayer& l
 
 Ref<CoordinatedTileBuffer> SkiaPaintingEngine::postPaintingTask(const GraphicsLayer& layer, RenderingMode renderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
 {
-    WTFBeginSignpost(this, RecordTile);
-    auto displayList = recordDisplayList(renderingMode, layer, dirtyRect, contentsOpaque, contentsScale);
-    WTFEndSignpost(this, RecordTile);
-
     auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
     buffer->beginPainting();
 
+    WTFBeginSignpost(this, RecordTile);
+    auto* recordingCanvas = m_pictureRecorder.beginRecording(dirtyRect.width(), dirtyRect.height());
path: Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
user: login: nikolaszimmermann
body: Agreed. I initially thought it's too expensive to re-create it every time -- especially since it has built-in logic to reuse the fRecorder it allocated in its constructor, across recording operations (see reset()).

We can avoid the member to start with, and profile it once again and eventually optimize this, if it proofs to be expensive.
created_at: 2025-03-24T22:55:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -294,27 +256,35 @@ Ref<CoordinatedTileBuffer> SkiaPaintingEngine::paintLayer(const GraphicsLayer& l
 
 Ref<CoordinatedTileBuffer> SkiaPaintingEngine::postPaintingTask(const GraphicsLayer& layer, RenderingMode renderingMode, const IntRect& dirtyRect, bool contentsOpaque, float contentsScale)
 {
-    WTFBeginSignpost(this, RecordTile);
-    auto displayList = recordDisplayList(renderingMode, layer, dirtyRect, contentsOpaque, contentsScale);
-    WTFEndSignpost(this, RecordTile);
-
     auto buffer = createBuffer(renderingMode, dirtyRect.size(), contentsOpaque);
     buffer->beginPainting();
 
+    WTFBeginSignpost(this, RecordTile);
+    auto* recordingCanvas = m_pictureRecorder.beginRecording(dirtyRect.width(), dirtyRect.height());
+    GraphicsContextSkia recordingContext(*recordingCanvas, renderingMode, RenderingPurpose::LayerBacking);
+    recordingContext.beginRecording();
+    paintIntoGraphicsContext(layer, recordingContext, dirtyRect, contentsOpaque, contentsScale);
+    auto imageToFenceMap = recordingContext.endRecording();
+    auto picture = m_pictureRecorder.finishRecordingAsPicture();
+    WTFEndSignpost(this, RecordTile);
+
     auto& workerPool = renderingMode == RenderingMode::Accelerated ? *m_gpuWorkerPool.get() : *m_cpuWorkerPool.get();
-    workerPool.postTask([buffer = Ref { buffer }, displayList = WTFMove(displayList), dirtyRect]() mutable {
+    workerPool.postTask([buffer = Ref { buffer }, picture = sk_sp<SkPicture> { picture }, dirtyRect, imageToFenceMap = WTFMove(imageToFenceMap)]() mutable {
         if (auto* canvas = buffer->canvas()) {
+            auto replayCanvas = SkiaReplayCanvas::create(dirtyRect.size(), WTFMove(imageToFenceMap));
+            replayCanvas->addCanvas(canvas);
path: Source/WebCore/platform/graphics/skia/SkiaPaintingEngine.cpp
user: login: nikolaszimmermann
body: Good moment. EWS is green as it-is, so we can now optimize and avoid the SkiaReplayCanvas creation altogether whenever imageFencesMap is empty -- in both CPU and GPU drawing scenarios, e.g. a basic fillRect operation won't need a SkiaReplayCanvas in GPU rendering mode either, so I think it's the cleanest approach to decide based on the image to fence map.
created_at: 2025-03-24T22:56:31Z
author_association: CONTRIBUTOR

PR comments:
user: login: webkit-early-warning-system
created_at: 2025-03-10T10:18:35Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/7e4cfbfa7dfe2f5d769e0af1adde777c73952dd8)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/94314 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/13901 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/3680 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/99326 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/44842 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/96364 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/14202 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/22331 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/71953 "Found 7 new test failures: fast/backgrounds/background-clip-border-area-alpha.html fast/multicol/transform-inside-opacity.html imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-001.html imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-002.html imported/w3c/web-platform-tests/css/css-overflow/clip-002.html imported/w3c/web-platform-tests/css/css-overflow/clip-004.html imported/w3c/web-platform-tests/css/css-overflow/clip-005.html (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/29294 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/97316 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/10554 "Passed tests") | [❌ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/85172 "Found 1 new API test failure: TestWebKitAPI.WKWebExtensionAPIDevTools.MessagePassingFromPanelToBackground (failure)") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/52298 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/10243 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/2855 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/44160 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/80475 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/2947 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/101371 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/21366 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/15581 "Passed tests") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/80958 "Found 278 new test failures: compositing/contents-opaque/negative-z-before-html.html css3/blending/background-blend-mode-body-image.html css3/blending/background-blend-mode-body-transparent-color-and-image.html css3/blending/background-blend-mode-crossfade-image.html css3/blending/background-blend-mode-data-uri-svg-image.html css3/blending/background-blend-mode-tiled-layers.html css3/filters/drop-shadow-blur-radius.html css3/filters/drop-shadow-child-clipped.html css3/filters/drop-shadow-filter-overflow.html css3/filters/drop-shadow-in-tiled-backing.html ... (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/21618 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/81185 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/80340 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/24893 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/2276 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/14594 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/21350 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/26530 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/21037 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/24497 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/22778 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: nikolaszimmermann
created_at: 2025-03-10T13:49:56Z
body: This is still lacking the necessary fencing logic, breaking GPU rendering. The purpose of this patch was to check the threaded CPU rendering on WPE using EWS - and that part worked fine. GTK uses threaded GPU rendering and thus fails.

@kkinnunen-apple brought up on Slack that it would be nice to move the fencing logic out of DisplayList for different reasons - I now need the same thing, so I will work on that next, to have a general concept, retaining our threaded rendering, but moving away from DisplayList.
author_association: CONTRIBUTOR
user: login: nikolaszimmermann
created_at: 2025-03-17T20:53:25Z
body: NOTE: This patch needs to be split-up (parts removing the Skia specific ImageBuffer/NativeImage & co changes - the DisplayList changes etc.) But I want to test all of them together to obtain EWS results first.
author_association: CONTRIBUTOR
user: login: webkit-early-warning-system
created_at: 2025-03-17T21:08:58Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/a0a16cd46041a0721c4224a31b31124ad876d3dc)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/95473 "Passed style check") | [❌ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15075 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [❌ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/4954 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [❌ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/100521 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/45978 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/97517 "Passed tests") | [❌ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/15360 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [❌ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/23506 "Failed to compile WebKit") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/5/builds/100521 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30090 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/98476 "Passed tests") | [❌ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/130/builds/15360 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [❌ 🧪 api-mac](https://ews-build.webkit.org/#/builders/138/builds/4954 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [❌ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/5/builds/100521 "Hash a0a16cd4 for PR 42185 does not build (failure)") | 
| | [❌ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/130/builds/15360 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [❌ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/138/builds/4954 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/45314 "Built successfully") | 
| | [❌ 🧪 api-ios](https://ews-build.webkit.org/#/builders/130/builds/15360 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [❌ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/138/builds/4954 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [❌ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/102557 "Hash a0a16cd4 for PR 42185 does not build (failure)") | 
| [![loading](https://user-images.githubusercontent.com/3098702/171232313-daa606f1-8fd6-4b0f-a20b-2cb93c43d19b.png) 🛠 🧪 jsc-arm64](https://ews-build.webkit.org/#/builders/12/builds/94093 "Build is in progress. Recent messages:") | [❌ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/22523 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [❌ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/123/builds/23506 "Failed to compile WebKit") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/2/builds/102557 "Hash a0a16cd4 for PR 42185 does not build (failure)") | 
| [![loading](https://user-images.githubusercontent.com/3098702/171232313-daa606f1-8fd6-4b0f-a20b-2cb93c43d19b.png) 🧪 services](https://ews-build.webkit.org/#/builders/28/builds/95045 "Build is in progress. Recent messages:") | [❌ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/22775 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [❌ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/138/builds/4954 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [❌ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/2/builds/102557 "Hash a0a16cd4 for PR 42185 does not build (failure)") | 
| | [⏳ 🧪 vision-wk2 ](https://ews-build.webkit.org/#/builders/visionOS-2-Simulator-WK2-Tests-EWS "Waiting in queue, processing has not started yet") | [❌ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/138/builds/4954 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/15854 "Built successfully") | 
| | [❌ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/22491 "Hash a0a16cd4 for PR 42185 does not build (failure)") | [❌ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/27645 "Failed to build and analyze WebKit") | | 
| | [❌ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22150 "Hash a0a16cd4 for PR 42185 does not build (failure)") | | | 
| | [❌ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/25626 "Hash a0a16cd4 for PR 42185 does not build (failure)") | | | 
| | [❌ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/23892 "Hash a0a16cd4 for PR 42185 does not build (failure)") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-17T22:40:52Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/da19ffaa45939b7a13a8567fa6438c804f18cc17)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/95502 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15104 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/4987 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/100550 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46007 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/97546 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/15389 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/23535 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/72849 "Found 7 new test failures: fast/backgrounds/background-clip-border-area-alpha.html fast/multicol/transform-inside-opacity.html imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-001.html imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-002.html imported/w3c/web-platform-tests/css/css-overflow/clip-002.html imported/w3c/web-platform-tests/css/css-overflow/clip-004.html imported/w3c/web-platform-tests/css/css-overflow/clip-005.html (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30108 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/98505 "Passed tests") | [❌ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/11531 "Found 1 new test failure: fast/forms/ios/focus-input-via-button.html (failure)") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86248 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53181 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11237 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/3959 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/45343 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/81426 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4078 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/102586 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/22552 "Built successfully") | [❌ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/16468 "Found 1 new test failure: imported/w3c/web-platform-tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-coop-coep.https.any.html (failure)") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/81884 "Found 108 new test failures: css3/masking/mask-repeat-space-border.html fast/backgrounds/scaled-gradient-background.html fast/borders/border-image-repeat-stretch.html fast/transforms/skew-x-135deg-with-gradient.html fast/transforms/skew-y-135deg-with-gradient.html imported/blink/svg/text/obb-paintserver.html imported/mozilla/svg/objectBoundingBox-and-pattern-01a.svg imported/mozilla/svg/objectBoundingBox-and-pattern-01b.svg imported/mozilla/svg/objectBoundingBox-and-pattern-01c.svg imported/mozilla/svg/objectBoundingBox-and-pattern-03.svg ... (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/22804 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82259 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/81236 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/25810 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3272 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/15883 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/22520 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/27677 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22179 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/25655 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/23921 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-18T12:44:27Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/b12a092e68e68275f545c23b1ec4c788e4123554)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/95581 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15183 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5089 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/100630 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46086 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/97625 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/15469 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/23620 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/72900 "Failure limit exceed. At least found 406 new test failures: compositing/masks/reference-clip-path-on-composited-image.html css2.1/20110323/border-conflict-element-001d.htm css2.1/20110323/c541-word-sp-001.htm css2.1/20110323/floats-001.html css2.1/20110323/margin-collapse-clear-012.htm css2.1/20110323/margin-collapse-clear-013.htm css2.1/20110323/margin-collapse-clear-014.htm css2.1/20110323/margin-collapse-clear-015.htm css2.1/20110323/vertical-align-boxes-001.htm css3/filters/backdrop/effect-hw.html ... (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30162 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/98584 "Passed tests") | [❌ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/11596 "Found 1 new test failure: fast/forms/ios/focus-input-via-button.html (failure)") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86331 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53232 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11301 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4044 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/45422 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/81491 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4163 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/102665 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/22631 "Built successfully") | [❌ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/16534 "Found 2 new test failures: html5lib/generated/run-entities01-data.html imported/w3c/web-platform-tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-coop-coep.https.any.html (failure)") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/81941 "Failure limit exceed. At least found 416 new test failures: compositing/images/positioned-image-content-rect.html compositing/masks/reference-clip-path-on-composited-image.html css2.1/20110323/border-conflict-element-001d.htm css2.1/20110323/c541-word-sp-001.htm css2.1/20110323/floats-001.html css2.1/20110323/margin-collapse-clear-012.htm css2.1/20110323/margin-collapse-clear-013.htm css2.1/20110323/margin-collapse-clear-014.htm css2.1/20110323/margin-collapse-clear-015.htm css2.1/20110323/vertical-align-boxes-001.htm ... (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/22883 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82345 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/81292 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/25873 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3344 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/15971 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/22599 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/27756 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22258 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/25734 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24000 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-18T23:10:07Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/0b50c25385529ded2227bbb5897e19d9fbac7085)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/95691 "Passed style check") | [  ~~🛠 ios~~](https://ews-build.webkit.org/#/builders/131/builds/15295 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 mac~~](https://ews-build.webkit.org/#/builders/138/builds/5214 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 wpe~~](https://ews-build.webkit.org/#/builders/5/builds/100742 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 win~~](https://ews-build.webkit.org/#/builders/59/builds/46196 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") 
| [  ~~🧪 bindings~~](https://ews-build.webkit.org/#/builders/9/builds/97735 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 ios-sim~~](https://ews-build.webkit.org/#/builders/130/builds/15583 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 mac-AS-debug~~](https://ews-build.webkit.org/#/builders/123/builds/23732 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 wpe-wk2~~](https://ews-build.webkit.org/#/builders/5/builds/100742 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 win-tests~~](https://ews-build.webkit.org/#/builders/59/builds/46196 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") 
| [  ~~🧪 webkitperl~~](https://ews-build.webkit.org/#/builders/11/builds/98694 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 ios-wk2~~](https://ews-build.webkit.org/#/builders/130/builds/15583 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 api-mac~~](https://ews-build.webkit.org/#/builders/138/builds/5214 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 api-wpe~~](https://ews-build.webkit.org/#/builders/5/builds/100742 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [  ~~🧪 ios-wk2-wpt~~](https://ews-build.webkit.org/#/builders/130/builds/15583 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 mac-wk1~~](https://ews-build.webkit.org/#/builders/138/builds/5214 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 wpe-cairo~~](https://ews-build.webkit.org/#/builders/65/builds/45534 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [  ~~🧪 api-ios~~](https://ews-build.webkit.org/#/builders/130/builds/15583 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 mac-wk2~~](https://ews-build.webkit.org/#/builders/138/builds/5214 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 gtk~~](https://ews-build.webkit.org/#/builders/2/builds/102776 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [  ~~🛠 vision~~](https://ews-build.webkit.org/#/builders/128/builds/22745 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 mac-AS-debug-wk2~~](https://ews-build.webkit.org/#/builders/123/builds/23732 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 gtk-wk2~~](https://ews-build.webkit.org/#/builders/2/builds/102776 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [  ~~🛠 vision-sim~~](https://ews-build.webkit.org/#/builders/121/builds/22997 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 mac-wk2-stress~~](https://ews-build.webkit.org/#/builders/138/builds/5214 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 api-gtk~~](https://ews-build.webkit.org/#/builders/2/builds/102776 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [⏳ 🧪 vision-wk2 ](https://ews-build.webkit.org/#/builders/visionOS-2-Simulator-WK2-Tests-EWS "Waiting in queue, processing has not started yet") | [  ~~🧪 mac-intel-wk2~~](https://ews-build.webkit.org/#/builders/138/builds/5214 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 playstation~~](https://ews-build.webkit.org/#/builders/134/builds/16086 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [  ~~🛠 tv~~](https://ews-build.webkit.org/#/builders/127/builds/22713 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 mac-safer-cpp~~](https://ews-build.webkit.org/#/builders/120/builds/27866 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | 
| | [  ~~🛠 tv-sim~~](https://ews-build.webkit.org/#/builders/125/builds/22372 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | | 
| | [  ~~🛠 watch~~](https://ews-build.webkit.org/#/builders/129/builds/25848 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | | 
| | [  ~~🛠 watch-sim~~](https://ews-build.webkit.org/#/builders/124/builds/24114 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-18T23:11:34Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/21ac970a89beef9805a14c88373ffbc54492ab43)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/95694 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15298 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5217 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/100745 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46199 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/97738 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/15586 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/23735 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/72987 "Failure limit exceed. At least found 407 new test failures: compositing/images/positioned-image-content-rect.html compositing/masks/reference-clip-path-on-composited-image.html compositing/patterns/direct-pattern-compositing-cover.html css2.1/20110323/border-conflict-element-001d.htm css2.1/20110323/c541-word-sp-001.htm css2.1/20110323/floats-001.html css2.1/20110323/margin-collapse-clear-012.htm css2.1/20110323/margin-collapse-clear-013.htm css2.1/20110323/margin-collapse-clear-014.htm css2.1/20110323/margin-collapse-clear-015.htm ... (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30236 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/98697 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/11677 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86437 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53320 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11397 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4156 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/45537 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/81573 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4273 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/102779 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/22748 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/16601 "Passed tests") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/82023 "Failure limit exceed. At least found 413 new test failures: compositing/images/positioned-image-content-rect.html compositing/masks/reference-clip-path-on-composited-image.html css2.1/20110323/border-conflict-element-001d.htm css2.1/20110323/c541-word-sp-001.htm css2.1/20110323/floats-001.html css2.1/20110323/margin-collapse-clear-012.htm css2.1/20110323/margin-collapse-clear-013.htm css2.1/20110323/margin-collapse-clear-014.htm css2.1/20110323/margin-collapse-clear-015.htm css2.1/20110323/vertical-align-boxes-001.htm ... (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23000 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82456 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/81381 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/25958 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3418 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/16089 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/22716 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/27869 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22375 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/25851 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24117 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-19T10:12:37Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/5f001b7d1cf26a994c8a90abecbd1406d4e7188a)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/95792 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15402 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5322 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/100847 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46297 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/97836 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/15689 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/23833 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/73054 "Found 26 new test failures: compositing/patterns/direct-pattern-compositing-contain.html fast/backgrounds/background-clip-border-area-alpha.html fast/multicol/transform-inside-opacity.html imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-001.html imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-002.html imported/w3c/web-platform-tests/css/css-backgrounds/background-size-025.html imported/w3c/web-platform-tests/css/css-backgrounds/background-size-027.html imported/w3c/web-platform-tests/css/css-backgrounds/background-size-028.html imported/w3c/web-platform-tests/css/css-backgrounds/background-size-029.html imported/w3c/web-platform-tests/css/css-backgrounds/background-size-030.html ... (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30295 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/98795 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/11746 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86522 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53386 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11475 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4258 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/45636 "Built successfully") | 
| | [⏳ 🧪 api-ios](https://ews-build.webkit.org/#/builders/API-Tests-iOS-Simulator-EWS "Waiting to run tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4364 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/102877 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/22851 "Built successfully") | [⏳ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/macOS-Sequoia-Debug-WK2-Tests-EWS "Waiting to run tests") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/82085 "Found 26 new test failures: imported/blink/svg/text/obb-paintserver.html imported/w3c/web-platform-tests/css/css-backgrounds/background-repeat/background-repeat-round-roundup.xht imported/w3c/web-platform-tests/css/css-backgrounds/background-size-025.html imported/w3c/web-platform-tests/css/css-backgrounds/background-size-027.html imported/w3c/web-platform-tests/css/css-backgrounds/background-size-028.html imported/w3c/web-platform-tests/css/css-backgrounds/background-size-029.html imported/w3c/web-platform-tests/css/css-backgrounds/background-size-030.html imported/w3c/web-platform-tests/css/css-backgrounds/background-size-031.html imported/w3c/web-platform-tests/css/css-backgrounds/css3-border-image-repeat-repeat.html imported/w3c/web-platform-tests/html/canvas/element/layers/2d.layer.global-states.no-cxt-filter.no-transform.html ... (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23103 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82553 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/81449 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26031 "Passed tests") | [⏳ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/macOS-Sonoma-Release-WK2-Intel-Tests-EWS "Waiting to run tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/16188 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/22818 "Built successfully") | [  ~~🛠 mac-safer-cpp~~](https://ews-build.webkit.org/#/builders/120/builds/27979 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22477 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/25953 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24219 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-19T12:48:35Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/754810926aefaff5692e395ea995ecd1adfe85ac)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/95798 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15408 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5331 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/100853 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46303 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/97842 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/15695 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/23839 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/73058 "Failure limit exceed. At least found 464 new test failures: accessibility/ARIA-reflection.html accessibility/accessibility-crash-focused-element-change.html accessibility/accessibility-node-memory-management.html accessibility/aria-cellspans-with-native-cellspans.html accessibility/aria-checkbox-checked.html accessibility/aria-expanded-supported-roles.html accessibility/aria-hidden-hides-all-elements.html accessibility/aria-liveregions-attributes.html accessibility/aria-modal-multiple-dialogs.html accessibility/aria-modal.html ... (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30297 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/98801 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/11754 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86529 "Passed tests") | [❌ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53390 "Found 16 new API test failures: /WPE/TestWebKitWebView:/webkit/WebKitWebView/is-playing-audio, /WPE/TestUIClient:/webkit/WebKitWebView/usermedia-permission-requests, /WPE/TestUIClient:/webkit/WebKitWebView/display-usermedia-permission-request, /WPE/TestUIClient:/webkit/WebKitWebView/audio-usermedia-permission-request, /WPE/TestWebKitWebView:/webkit/WebKitWebView/frame-displayed, /WPE/TestUIClient:/webkit/WebKitWebView/mediaKeySystem-permission-requests, /TestWebKit:WebKit.FirstMeaningfulPaint, /TestWebKit:WebKit.EnumerateDevicesCrash, /TestWebKit:WebKit.UserMediaBasic, /TestWebKit:WebKit.GeolocationTransitionToHighAccuracy ... (failure)") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11484 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4265 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/45642 "Built successfully") | 
| | [⏳ 🧪 api-ios](https://ews-build.webkit.org/#/builders/API-Tests-iOS-Simulator-EWS "Waiting to run tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4371 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/102883 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/22857 "Built successfully") | [⏳ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/macOS-Sequoia-Debug-WK2-Tests-EWS "Waiting to run tests") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/82095 "Failure limit exceed. At least found 276 new test failures: accessibility/aria-mappings.html animations/3d/animate-to-transform-perpective-to-none.html animations/3d/change-transform-in-end-event.html animations/3d/full-rotation-animation.html animations/3d/matrix-transform-type-animation.html animations/3d/replace-filling-transform.html animations/3d/state-at-end-event-transform.html animations/3d/transform-perspective.html animations/added-while-suspended.html animations/additive-transform-animations.html ... (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23109 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82560 "Passed tests") | [  ~~🧪 api-gtk~~](https://ews-build.webkit.org/#/builders/21/builds/81458 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26038 "Passed tests") | [⏳ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/macOS-Sonoma-Release-WK2-Intel-Tests-EWS "Waiting to run tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/16197 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/22824 "Built successfully") | [  ~~🛠 mac-safer-cpp~~](https://ews-build.webkit.org/#/builders/120/builds/27985 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22483 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/25959 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24225 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-19T14:08:09Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/c5888ead413173194cfa834591718481c1232594)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/95802 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15412 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5346 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/100857 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46307 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/97846 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/15699 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/23843 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/73060 "Failure limit exceed. At least found 330 new test failures: css-custom-properties-api/border-variable-parsing.html css3/blending/background-blend-mode-data-uri-svg-image.html fast/backgrounds/background-clip-border-area-alpha.html fast/backgrounds/background-opaque-clipped-gradients.html fast/block/line-layout/selection-highlight-overlap.html fast/block/transparent-outline-with-and-without-border-radius.html fast/borders/0px-borders.html fast/borders/border-painting-dashed-at-all.html fast/borders/border-painting-dotted-at-all.html fast/borders/dashed-border-on-subpixel-position.html ... (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30301 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/98805 "Passed tests") | [❌ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/11770 "Found 1 new test failure: fast/forms/ios/focus-input-via-button.html (failure)") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86552 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53392 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11494 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4283 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/45646 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/81676 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4402 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/102887 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/22861 "Built successfully") | [⏳ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/macOS-Sequoia-Debug-WK2-Tests-EWS "Waiting to run tests") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/82097 "Failure limit exceed. At least found 290 new test failures: css-custom-properties-api/border-variable-parsing.html css3/blending/background-blend-mode-data-uri-svg-image.html fast/backgrounds/background-opaque-clipped-gradients.html fast/block/line-layout/selection-highlight-overlap.html fast/block/transparent-outline-with-and-without-border-radius.html fast/borders/0px-borders.html fast/borders/border-painting-dashed-at-all.html fast/borders/border-painting-dotted-at-all.html fast/borders/dashed-border-on-subpixel-position.html fast/borders/dotted-border-on-subpixel-position.html ... (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23113 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82570 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/81460 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26040 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3503 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/16202 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/22828 "Built successfully") | [  ~~🛠 mac-safer-cpp~~](https://ews-build.webkit.org/#/builders/120/builds/27989 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22487 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/25970 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24229 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-19T18:21:49Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/08ef5e434d42d6fd0021d68935a0b2cdca13a658)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/95859 "Passed style check") | [  ~~🛠 ios~~](https://ews-build.webkit.org/#/builders/131/builds/15473 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 mac~~](https://ews-build.webkit.org/#/builders/138/builds/5414 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 wpe~~](https://ews-build.webkit.org/#/builders/5/builds/100918 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 win~~](https://ews-build.webkit.org/#/builders/59/builds/46367 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/97904 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/15768 "Built successfully") | [  ~~🛠 mac-AS-debug~~](https://ews-build.webkit.org/#/builders/123/builds/23905 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 wpe-wk2~~](https://ews-build.webkit.org/#/builders/5/builds/100918 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 win-tests~~](https://ews-build.webkit.org/#/builders/59/builds/46367 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/98862 "Passed tests") | [⏳ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/iOS-18-Simulator-WK2-Tests-EWS "Waiting to run tests") | [  ~~🧪 api-mac~~](https://ews-build.webkit.org/#/builders/138/builds/5414 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 api-wpe~~](https://ews-build.webkit.org/#/builders/5/builds/100918 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [⏳ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/iOS-18-Simulator-WPT-WK2-Tests-EWS "Waiting to run tests") | [  ~~🧪 mac-wk1~~](https://ews-build.webkit.org/#/builders/138/builds/5414 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 wpe-cairo~~](https://ews-build.webkit.org/#/builders/65/builds/45703 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [⏳ 🧪 api-ios](https://ews-build.webkit.org/#/builders/API-Tests-iOS-Simulator-EWS "Waiting to run tests") | [  ~~🧪 mac-wk2~~](https://ews-build.webkit.org/#/builders/138/builds/5414 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 gtk~~](https://ews-build.webkit.org/#/builders/2/builds/102947 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [  ~~🛠 vision~~](https://ews-build.webkit.org/#/builders/128/builds/22926 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 mac-AS-debug-wk2~~](https://ews-build.webkit.org/#/builders/123/builds/23905 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 gtk-wk2~~](https://ews-build.webkit.org/#/builders/2/builds/102947 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [  ~~🛠 vision-sim~~](https://ews-build.webkit.org/#/builders/121/builds/23177 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 mac-wk2-stress~~](https://ews-build.webkit.org/#/builders/138/builds/5414 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🧪 api-gtk~~](https://ews-build.webkit.org/#/builders/2/builds/102947 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [⏳ 🧪 vision-wk2 ](https://ews-build.webkit.org/#/builders/visionOS-2-Simulator-WK2-Tests-EWS "Waiting in queue, processing has not started yet") | [  ~~🧪 mac-intel-wk2~~](https://ews-build.webkit.org/#/builders/138/builds/5414 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 playstation~~](https://ews-build.webkit.org/#/builders/134/builds/16263 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | 
| | [  ~~🛠 tv~~](https://ews-build.webkit.org/#/builders/127/builds/22889 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | [  ~~🛠 mac-safer-cpp~~](https://ews-build.webkit.org/#/builders/120/builds/28044 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | 
| | [  ~~🛠 tv-sim~~](https://ews-build.webkit.org/#/builders/125/builds/22548 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | | 
| | [  ~~🛠 watch~~](https://ews-build.webkit.org/#/builders/129/builds/26027 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | | 
| | [  ~~🛠 watch-sim~~](https://ews-build.webkit.org/#/builders/124/builds/24289 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-19T18:34:17Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/45c585a27d61b3af47af0962d1c6d7949839ed87)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/95865 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15479 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5420 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/100924 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46373 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/97910 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/15774 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/23911 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/73108 "Found 18 new test failures: fast/backgrounds/background-clip-border-area-alpha.html fast/multicol/transform-inside-opacity.html imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-001.html imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-002.html imported/w3c/web-platform-tests/css/css-overflow/clip-002.html imported/w3c/web-platform-tests/css/css-overflow/clip-004.html imported/w3c/web-platform-tests/css/css-overflow/clip-005.html imported/w3c/web-platform-tests/html/canvas/element/layers/2d.layer.global-states.no-cxt-filter.no-transform.html imported/w3c/web-platform-tests/html/canvas/element/layers/2d.layer.global-states.no-cxt-filter.rotation.html imported/w3c/web-platform-tests/html/canvas/element/layers/2d.layer.globalCompositeOperation.html ... (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30333 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/98868 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/11812 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86603 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53436 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11543 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4346 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/45709 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/81713 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4453 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/102953 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/22932 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/16724 "Passed tests") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/82152 "Found 12 new test failures: imported/w3c/web-platform-tests/html/canvas/element/layers/2d.layer.global-states.no-cxt-filter.no-transform.html imported/w3c/web-platform-tests/html/canvas/element/layers/2d.layer.globalCompositeOperation.html imported/w3c/web-platform-tests/html/canvas/offscreen/shadows/2d.shadow.image.alpha.html imported/w3c/web-platform-tests/html/canvas/offscreen/shadows/2d.shadow.image.alpha.worker.html imported/w3c/web-platform-tests/html/canvas/offscreen/shadows/2d.shadow.image.basic.html imported/w3c/web-platform-tests/html/canvas/offscreen/shadows/2d.shadow.image.basic.worker.html imported/w3c/web-platform-tests/html/canvas/offscreen/shadows/2d.shadow.image.scale.html imported/w3c/web-platform-tests/html/canvas/offscreen/shadows/2d.shadow.image.scale.worker.html imported/w3c/web-platform-tests/html/canvas/offscreen/shadows/2d.shadow.image.transparent.2.html imported/w3c/web-platform-tests/html/canvas/offscreen/shadows/2d.shadow.image.transparent.2.worker.html ... (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23183 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82622 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/81511 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26091 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3548 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/16269 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/22895 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/28050 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22554 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/26033 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24295 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-20T12:20:23Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/0ec81f48925611b777e6d48eaeed8fc4acc050df)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/96009 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15623 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5569 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/101071 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46518 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/98054 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/15918 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/24055 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/73195 "Found 7 new test failures: fast/backgrounds/background-clip-border-area-alpha.html fast/multicol/transform-inside-opacity.html imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-001.html imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-002.html imported/w3c/web-platform-tests/css/css-overflow/clip-002.html imported/w3c/web-platform-tests/css/css-overflow/clip-004.html imported/w3c/web-platform-tests/css/css-overflow/clip-005.html (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30422 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/99012 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/11918 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86722 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53532 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11661 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4474 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/45853 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/81810 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4575 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/103097 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/23076 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/16811 "Passed tests") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/82237 "Found 2 new test failures: svg/as-background-image/svg-transformed-background.html svg/repaint/buffered-rendering-static-image.html (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23327 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82738 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/81609 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26205 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3642 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/16424 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/23039 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/28194 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22698 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/26178 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24439 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-20T18:44:51Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/36af21442982d155bde688aa10f9097b25593ad2)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/96055 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15669 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5619 "Built successfully") | [❌ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/101118 "Hash 36af2144 for PR 42185 does not build (failure)") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46564 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/98100 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/15964 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/24101 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/5/builds/101118 "Hash 36af2144 for PR 42185 does not build (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30451 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/99058 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/11953 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86768 "Passed tests") | [❌ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/5/builds/101118 "Hash 36af2144 for PR 42185 does not build (failure)") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11697 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4520 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/45899 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/81846 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4622 "Passed tests") | [❌ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/103145 "Hash 36af2144 for PR 42185 does not build (failure)") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/23122 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/16846 "Passed tests") | [❌ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/2/builds/103145 "Hash 36af2144 for PR 42185 does not build (failure)") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23373 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82781 "Passed tests") | [❌ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/2/builds/103145 "Hash 36af2144 for PR 42185 does not build (failure)") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26241 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3682 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/16478 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/23085 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/28240 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22744 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/26224 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24485 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-21T08:06:12Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/aed5066c1d52a186d296f835fa49f99ccbcb0814)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/96166 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15780 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5742 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/101231 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46677 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/98211 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/16075 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/24213 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/73317 "Found 1 new test failure: imported/w3c/web-platform-tests/css/compositing/root-element-background-image-transparency-002.html (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30534 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/99169 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/12047 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86867 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53658 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11798 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4628 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/46010 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/81926 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4725 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/103256 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/23233 "Built successfully") | [⏳ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/macOS-Sequoia-Debug-WK2-Tests-EWS "Waiting to run tests") | [✅ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/82353 "Passed tests") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23484 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82887 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/81729 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26341 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3769 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/16596 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/23196 "Built successfully") | [  ~~🛠 mac-safer-cpp~~](https://ews-build.webkit.org/#/builders/120/builds/28351 "The change is no longer eligible for processing. Commit was outdated when EWS attempted to process it.") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22855 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/26335 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24596 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-early-warning-system
created_at: 2025-03-21T10:56:53Z
body: EWS run on previous version of this PR (hash https://github.com/WebKit/WebKit/commit/a6f0d5df5049c0847399b5fbe3f234864101dd8d)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/96176 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/15790 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/5753 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/101241 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/46695 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/98221 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/16085 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/24223 "Built successfully") | [✅ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/73322 "Passed tests") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30551 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/99179 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/12057 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/86879 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53660 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/11808 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/4640 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/46020 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/81937 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/4737 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/103266 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/23243 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/16927 "Passed tests") | [✅ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/82361 "Passed tests") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23494 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/82898 "Passed tests") | [✅ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/81737 "Passed tests") | 
| | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26351 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/3781 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/16610 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/23206 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/28361 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/22865 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/26345 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/24606 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: nikolaszimmermann
created_at: 2025-03-21T10:59:39Z
body: Should be ready to go.
author_association: CONTRIBUTOR
user: login: webkit-early-warning-system
created_at: 2025-03-24T23:25:21Z
body: EWS run on current version of this PR (hash https://github.com/WebKit/WebKit/commit/eab77ffc5f9861606d8945ebff4ec8c0a99ebc7e)<details>

| Misc | iOS, visionOS, tvOS & watchOS  | macOS  | Linux |  Windows |
| ----- | ---------------------- | ------- |  ----- |  --------- |
| [✅ 🧪 style](https://ews-build.webkit.org/#/builders/38/builds/96627 "Passed style check") | [✅ 🛠 ios](https://ews-build.webkit.org/#/builders/131/builds/16241 "Built successfully") | [✅ 🛠 mac](https://ews-build.webkit.org/#/builders/138/builds/6348 "Built successfully") | [✅ 🛠 wpe](https://ews-build.webkit.org/#/builders/5/builds/101699 "Built successfully") | [✅ 🛠 win](https://ews-build.webkit.org/#/builders/59/builds/47147 "Built successfully") 
| [✅ 🧪 bindings](https://ews-build.webkit.org/#/builders/9/builds/98672 "Passed tests") | [✅ 🛠 ios-sim](https://ews-build.webkit.org/#/builders/130/builds/16537 "Built successfully") | [✅ 🛠 mac-AS-debug](https://ews-build.webkit.org/#/builders/123/builds/24675 "Built successfully") | [❌ 🧪 wpe-wk2](https://ews-build.webkit.org/#/builders/34/builds/73666 "Found 1 new test failure: fast/multicol/layers-split-across-columns.html (failure)") | [✅ 🧪 win-tests](https://ews-build.webkit.org/#/builders/60/builds/30860 "Passed tests") 
| [✅ 🧪 webkitperl](https://ews-build.webkit.org/#/builders/11/builds/99630 "Passed tests") | [✅ 🧪 ios-wk2](https://ews-build.webkit.org/#/builders/132/builds/12439 "Passed tests") | [✅ 🧪 api-mac](https://ews-build.webkit.org/#/builders/18/builds/87372 "Passed tests") | [✅ 🧪 api-wpe](https://ews-build.webkit.org/#/builders/41/builds/53975 "Passed tests") | 
| | [✅ 🧪 ios-wk2-wpt](https://ews-build.webkit.org/#/builders/133/builds/12198 "Passed tests") | [✅ 🧪 mac-wk1](https://ews-build.webkit.org/#/builders/135/builds/5165 "Passed tests") | [✅ 🛠 wpe-cairo](https://ews-build.webkit.org/#/builders/65/builds/46475 "Built successfully") | 
| | [✅ 🧪 api-ios](https://ews-build.webkit.org/#/builders/13/builds/82299 "Passed tests") | [✅ 🧪 mac-wk2](https://ews-build.webkit.org/#/builders/136/builds/5258 "Passed tests") | [✅ 🛠 gtk](https://ews-build.webkit.org/#/builders/2/builds/103723 "Built successfully") | 
| | [✅ 🛠 vision](https://ews-build.webkit.org/#/builders/128/builds/23694 "Built successfully") | [✅ 🧪 mac-AS-debug-wk2](https://ews-build.webkit.org/#/builders/122/builds/17270 "Passed tests") | [✅ 🧪 gtk-wk2](https://ews-build.webkit.org/#/builders/1/builds/82686 "Passed tests") | 
| | [✅ 🛠 vision-sim](https://ews-build.webkit.org/#/builders/121/builds/23945 "Built successfully") | [✅ 🧪 mac-wk2-stress](https://ews-build.webkit.org/#/builders/8/builds/83421 "Passed tests") | [❌ 🧪 api-gtk](https://ews-build.webkit.org/#/builders/21/builds/82097 "Found 3 new API test failures: /WebKitGTK/TestPrinting:/webkit/WebKitPrintOperation/print-errors, /WebKitGTK/TestPrinting:/webkit/WebKitPrintOperation/print, /WebKitGTK/TestPrinting:/webkit/WebKitPrintOperation/close-after-print (failure)") | 
| [✅ 🛠 🧪 merge](https://ews-build.webkit.org/#/builders/19/builds/20607 "Built successfully and passed tests") | [✅ 🧪 vision-wk2](https://ews-build.webkit.org/#/builders/126/builds/26718 "Passed tests") | [✅ 🧪 mac-intel-wk2](https://ews-build.webkit.org/#/builders/137/builds/4241 "Passed tests") | [✅ 🛠 playstation](https://ews-build.webkit.org/#/builders/134/builds/17168 "Built successfully") | 
| | [✅ 🛠 tv](https://ews-build.webkit.org/#/builders/127/builds/23657 "Built successfully") | [✅ 🛠 mac-safer-cpp](https://ews-build.webkit.org/#/builders/120/builds/28812 "Built successfully") | | 
| | [✅ 🛠 tv-sim](https://ews-build.webkit.org/#/builders/125/builds/23316 "Built successfully") | | | 
| | [✅ 🛠 watch](https://ews-build.webkit.org/#/builders/129/builds/26796 "Built successfully") | | | 
| | [✅ 🛠 watch-sim](https://ews-build.webkit.org/#/builders/124/builds/25057 "Built successfully") | | | </details>
author_association: COLLABORATOR
user: login: webkit-commit-queue
created_at: 2025-03-25T09:59:49Z
body: Committed 292639@main (fca89822979c): <https://commits.webkit.org/292639@main>

Reviewed commits have been landed. Closing PR #42185 and removing active labels.
author_association: COLLABORATOR